Skip to content

Commit

Permalink
Resolver trait and CompoundResolver macro
Browse files Browse the repository at this point in the history
  • Loading branch information
UMR1352 committed Jun 11, 2024
1 parent c69566c commit b7853fb
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ members = [
"identity_ecdsa_verifier",
"identity_eddsa_verifier",
"examples",
"identity_new_resolver",
"compound_resolver",
]

exclude = ["bindings/wasm", "bindings/grpc"]
Expand Down
20 changes: 20 additions & 0 deletions compound_resolver/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "compound_resolver"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true

[dependencies]
proc-macro2 = "1.0.85"
quote = "1.0.36"
syn = { versin = "2.0.66", features = ["parsing"] }

[lints]
workspace = true

[lib]
proc-macro = true
106 changes: 106 additions & 0 deletions compound_resolver/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::Parse, punctuated::Punctuated, Attribute, Data, DeriveInput, Field, Ident, Token};

#[proc_macro_derive(CompoundResolver, attributes(resolver))]
pub fn derive_macro_compound_resolver(input: TokenStream) -> TokenStream {
let DeriveInput {
ident: struct_ident,
data,
generics,
..
} = syn::parse_macro_input!(input);

let Data::Struct(data) = data else {
panic!("Derive macro \"CompoundResolver\" only works on structs");
};

data
.fields
.into_iter()
// parse all the fields that are annoted with #[resolver(..)]
.filter_map(ResolverField::from_field)
// create an iterator over (field_name, Resolver::I, Resolver::T)
.flat_map(|ResolverField { ident, impls }| {
impls
.into_iter()
.map(move |(input_ty, target_ty)| (ident.clone(), input_ty, target_ty))
})
// generates code that forward the implementation of Resolver<T, I> to field_name.
.map(|(field_name, input_ty, target_ty)| {
quote! {
impl ::identity_new_resolver::Resolver<#target_ty, #input_ty> for #struct_ident #generics {
async fn resolve(&self, input: &#input_ty) -> std::result::Result<#target_ty, ::identity_new_resolver::Error> {
self.#field_name.resolve(input).await
}
}
}
})
.collect::<proc_macro2::TokenStream>()
.into()
}

/// A field annotated with `#[resolver(Input -> Target, ..)]`
struct ResolverField {
ident: Ident,
impls: Vec<(Ident, Ident)>,
}

impl ResolverField {
pub fn from_field(field: Field) -> Option<Self> {
let Field { attrs, ident, .. } = field;
let Some(ident) = ident else {
panic!("Derive macro \"CompoundResolver\" only works on struct with named fields");
};

let impls: Vec<(Ident, Ident)> = attrs
.into_iter()
.flat_map(|attr| parse_resolver_attribute(attr).into_iter())
.collect();

if !impls.is_empty() {
Some(ResolverField { ident, impls })
} else {
None
}
}
}

fn parse_resolver_attribute(attr: Attribute) -> Vec<(Ident, Ident)> {
if attr.path().is_ident("resolver") {
attr
.parse_args_with(Punctuated::<ResolverTy, Token![,]>::parse_terminated)
.expect("invalid resolver annotation")
.into_iter()
.map(Into::into)
.collect()
} else {
vec![]
}
}

struct ResolverTy {
pub input: Ident,
pub target: Ident,
}

impl From<ResolverTy> for (Ident, Ident) {
fn from(value: ResolverTy) -> Self {
(value.input, value.target)
}
}

impl Parse for ResolverTy {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut tys = Punctuated::<Ident, Token![->]>::parse_separated_nonempty(input)?
.into_iter()
.take(2);

Ok({
ResolverTy {
input: tys.next().unwrap(),
target: tys.next().unwrap(),
}
})
}
}
20 changes: 20 additions & 0 deletions identity_new_resolver/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "identity_new_resolver"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true

[dependencies]
anyhow = "1.0.86"
thiserror.workspace = true
compound_resolver = { path = "../compound_resolver" }

[lints]
workspace = true

[dev-dependencies]
tokio = { version = "1.38.0", features = ["macros", "rt"] }
13 changes: 13 additions & 0 deletions identity_new_resolver/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use thiserror::Error;

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Error)]
pub enum Error {
#[error("The requested item \"{0}\" was not found.")]
NotFound(String),
#[error("Failed to parse the provided input into a resolvable type: {0}")]
ParsingFailure(#[source] anyhow::Error),
#[error(transparent)]
Generic(anyhow::Error),
}
7 changes: 7 additions & 0 deletions identity_new_resolver/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod resolver;
mod error;

pub use error::{Result, Error};
pub use resolver::Resolver;

pub use compound_resolver::CompoundResolver;
7 changes: 7 additions & 0 deletions identity_new_resolver/src/resolver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![allow(async_fn_in_trait)]

use crate::Result;

pub trait Resolver<T, I> {
async fn resolve(&self, input: &I) -> Result<T>;
}
47 changes: 47 additions & 0 deletions identity_new_resolver/tests/compound.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use identity_new_resolver::{CompoundResolver, Result, Resolver};

struct DidKey;
struct DidJwk;
struct DidWeb;

struct CoreDoc;

struct DidKeyResolver;
impl Resolver<CoreDoc, DidKey> for DidKeyResolver {
async fn resolve(&self, _input: &DidKey) -> Result<CoreDoc> {
Ok(CoreDoc {})
}
}
struct DidJwkResolver;
impl Resolver<CoreDoc, DidJwk> for DidJwkResolver {
async fn resolve(&self, _input: &DidJwk) -> Result<CoreDoc> {
Ok(CoreDoc {})
}
}
struct DidWebResolver;
impl Resolver<CoreDoc, DidWeb> for DidWebResolver {
async fn resolve(&self, _input: &DidWeb) -> Result<CoreDoc> {
Ok(CoreDoc {})
}
}

#[derive(CompoundResolver)]
struct SuperDidResolver {
#[resolver(DidKey -> CoreDoc)]
did_key: DidKeyResolver,
#[resolver(DidJwk -> CoreDoc)]
did_jwk: DidJwkResolver,
#[resolver(DidWeb -> CoreDoc)]
did_web: DidWebResolver,
}

#[tokio::test]
async fn test_compound_resolver() {
let super_resolver = SuperDidResolver {
did_key: DidKeyResolver {},
did_jwk: DidJwkResolver {},
did_web: DidWebResolver {},
};

assert!(super_resolver.resolve(&DidJwk {}).await.is_ok());
}

0 comments on commit b7853fb

Please sign in to comment.