Skip to content

Commit 3fbf1b4

Browse files
committed
Add derive macro for TypedError
1 parent 61cd326 commit 3fbf1b4

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

core/codegen/src/derive/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod from_form;
33
pub mod from_form_field;
44
pub mod responder;
55
pub mod uri_display;
6+
pub mod typed_error;
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use devise::{*, ext::SpanDiagnosticExt};
2+
use proc_macro2::TokenStream;
3+
use syn::{ConstParam, Index, LifetimeParam, Member, TypeParam};
4+
5+
use crate::exports::{*, Status as _Status};
6+
use crate::http_codegen::Status;
7+
8+
#[derive(Debug, Default, FromMeta)]
9+
struct ItemAttr {
10+
status: Option<SpanWrapped<Status>>,
11+
// TODO: support an option to avoid implementing Transient
12+
// no_transient: bool,
13+
}
14+
15+
#[derive(Default, FromMeta)]
16+
struct FieldAttr {
17+
source: bool,
18+
}
19+
20+
pub fn derive_typed_error(input: proc_macro::TokenStream) -> TokenStream {
21+
let impl_tokens = quote!(impl<'r> #TypedError<'r>);
22+
let typed_error: TokenStream = DeriveGenerator::build_for(input.clone(), impl_tokens)
23+
.support(Support::Struct | Support::Enum | Support::Lifetime | Support::Type)
24+
.replace_generic(0, 0)
25+
.type_bound_mapper(MapperBuild::new()
26+
.input_map(|_, i| {
27+
let bounds = i.generics().type_params().map(|g| &g.ident);
28+
quote! { #(#bounds: 'static,)* }
29+
})
30+
)
31+
.validator(ValidatorBuild::new()
32+
.input_validate(|_, i| match i.generics().lifetimes().count() > 1 {
33+
true => Err(i.generics().span().error("only one lifetime is supported")),
34+
false => Ok(())
35+
})
36+
)
37+
.inner_mapper(MapperBuild::new()
38+
.with_output(|_, output| quote! {
39+
#[allow(unused_variables)]
40+
fn respond_to(&self, request: &'r #Request<'_>) -> #_Result<#Response<'r>, #_Status> {
41+
#output
42+
}
43+
})
44+
.try_fields_map(|_, fields| {
45+
let item = ItemAttr::one_from_attrs("error", fields.parent.attrs())?;
46+
Ok(item.map_or_else(|| quote! {
47+
#_Err(#_Status::InternalServerError)
48+
}, |ItemAttr { status, ..}| quote! {
49+
#_Err(#status)
50+
}))
51+
})
52+
)
53+
.inner_mapper(MapperBuild::new()
54+
.with_output(|_, output| quote! {
55+
fn source(&'r self) -> #_Option<&'r (dyn #TypedError<'r> + 'r)> {
56+
#output
57+
}
58+
})
59+
.try_fields_map(|_, fields| {
60+
let mut source = None;
61+
for field in fields.iter() {
62+
if FieldAttr::one_from_attrs("error", &field.attrs)?.is_some_and(|a| a.source) {
63+
if source.is_some() {
64+
return Err(Diagnostic::spanned(
65+
field.span(),
66+
Level::Error,
67+
"Only one field may be declared as `#[error(source)]`"));
68+
}
69+
if let FieldParent::Variant(_) = field.parent {
70+
let name = field.match_ident();
71+
source = Some(quote! { #_Some(#name as &dyn #TypedError<'r>) })
72+
} else {
73+
let span = field.field.span().into();
74+
let member = match field.ident {
75+
Some(ref ident) => Member::Named(ident.clone()),
76+
None => Member::Unnamed(Index { index: field.index as u32, span })
77+
};
78+
79+
source = Some(quote_spanned!(span => #_Some(&self.#member as &dyn #TypedError<'r>)));
80+
}
81+
}
82+
}
83+
Ok(source.unwrap_or_else(|| quote! { #_None }))
84+
})
85+
)
86+
.inner_mapper(MapperBuild::new()
87+
.with_output(|_, output| quote! {
88+
fn status(&self) -> #_Status { #output }
89+
})
90+
.try_fields_map(|_, fields| {
91+
let item = ItemAttr::one_from_attrs("error", fields.parent.attrs())?;
92+
Ok(item.map_or_else(|| quote! {
93+
#_Status::InternalServerError
94+
}, |ItemAttr { status, ..}| quote! {
95+
#status
96+
}))
97+
})
98+
)
99+
.to_tokens();
100+
let impl_tokens = quote!(unsafe impl #_catcher::Transient);
101+
let transient: TokenStream = DeriveGenerator::build_for(input, impl_tokens)
102+
.support(Support::Struct | Support::Enum | Support::Lifetime | Support::Type)
103+
.replace_generic(1, 0)
104+
.type_bound_mapper(MapperBuild::new()
105+
.input_map(|_, i| {
106+
let bounds = i.generics().type_params().map(|g| &g.ident);
107+
quote! { #(#bounds: 'static,)* }
108+
})
109+
)
110+
.validator(ValidatorBuild::new()
111+
.input_validate(|_, i| match i.generics().lifetimes().count() > 1 {
112+
true => Err(i.generics().span().error("only one lifetime is supported")),
113+
false => Ok(())
114+
})
115+
)
116+
.inner_mapper(MapperBuild::new()
117+
.with_output(|_, output| quote! {
118+
#output
119+
})
120+
.input_map(|_, input| {
121+
let name = input.ident();
122+
let args = input.generics()
123+
.params
124+
.iter()
125+
.map(|g| {
126+
match g {
127+
syn::GenericParam::Lifetime(_) => quote!{ 'static },
128+
syn::GenericParam::Type(TypeParam { ident, .. }) => quote! { #ident },
129+
syn::GenericParam::Const(ConstParam { .. }) => todo!(),
130+
}
131+
});
132+
let trans = input.generics()
133+
.lifetimes()
134+
.map(|LifetimeParam { lifetime, .. }| quote!{#_catcher::Inv<#lifetime>});
135+
quote!{
136+
type Static = #name <#(#args)*>;
137+
type Transience = (#(#trans,)*);
138+
}
139+
})
140+
)
141+
// TODO: hack to generate unsafe impl
142+
.outer_mapper(MapperBuild::new()
143+
.input_map(|_, _| quote!{ unsafe })
144+
)
145+
.to_tokens();
146+
quote!{
147+
#typed_error
148+
#transient
149+
}
150+
}

core/codegen/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,15 @@ pub fn derive_responder(input: TokenStream) -> TokenStream {
10091009
emit!(derive::responder::derive_responder(input))
10101010
}
10111011

1012+
/// Derive for the [`TypedError`] trait.
1013+
///
1014+
/// TODO: Full documentation
1015+
/// [`TypedError`]: ../rocket/catcher/trait.TypedError.html
1016+
#[proc_macro_derive(TypedError, attributes(error))]
1017+
pub fn derive_typed_error(input: TokenStream) -> TokenStream {
1018+
emit!(derive::typed_error::derive_typed_error(input))
1019+
}
1020+
10121021
/// Derive for the [`UriDisplay<Query>`] trait.
10131022
///
10141023
/// The [`UriDisplay<Query>`] derive can be applied to enums and structs. When

0 commit comments

Comments
 (0)