|
| 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 | +} |
0 commit comments