-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Resolver trait and CompoundResolver macro
- Loading branch information
Showing
8 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |