diff --git a/runtime/pavex_macros/src/error_handler.rs b/runtime/pavex_macros/src/error_handler.rs index f6a8623df..0aa3e4aa1 100644 --- a/runtime/pavex_macros/src/error_handler.rs +++ b/runtime/pavex_macros/src/error_handler.rs @@ -52,7 +52,22 @@ impl CallableAnnotation for ErrorHandlerAnnotation { metadata: Self::InputSchema, item: Callable, ) -> Result { - let error_ref_index = find_error_ref_index(&item).map_err(|e| e.write_errors())?; + let error_ref_index = match find_error_ref_index(&item) { + Ok(index) => index, + Err(e) => { + let properties = Properties::new(metadata, 0); + let result = emit(impl_, item, properties); + + let error_tokens = e.write_errors(); + let handle_def = result.id_def.unwrap_or_default(); + let combined_error = quote::quote! { + #error_tokens + #handle_def + }; + return Err(combined_error.into()); + } + }; + let properties = Properties::new(metadata, error_ref_index); Ok(emit(impl_, item, properties)) } diff --git a/runtime/pavex_macros/src/methods.rs b/runtime/pavex_macros/src/methods.rs index a53d4d2ba..428516360 100644 --- a/runtime/pavex_macros/src/methods.rs +++ b/runtime/pavex_macros/src/methods.rs @@ -17,6 +17,21 @@ use crate::{ }; pub fn methods(_metadata: TokenStream, input: TokenStream) -> Result { + _methods_inner(_metadata, input.clone()).map_err(|error_tokens| { + let error_tokens = proc_macro2::TokenStream::from(error_tokens); + let input_ts: proc_macro2::TokenStream = input.into(); + if let Ok(mut item) = syn::parse2::(input_ts.clone()) { + crate::utils::PxStripper.visit_item_mut(&mut item); + return quote! { #error_tokens #item }.into(); + } + quote! { #error_tokens #input_ts }.into() + }) +} + +fn _methods_inner( + _metadata: TokenStream, + input: TokenStream, +) -> Result { let mut impl_: syn::ItemImpl = syn::parse(input).map_err(|e| e.into_compile_error())?; let mut new_items: Vec = Vec::new(); @@ -78,15 +93,16 @@ pub fn methods(_metadata: TokenStream, input: TokenStream) -> Result( Ok(t) => t.into(), Err(error_tokens) => { let error_tokens = proc_macro2::TokenStream::from(error_tokens); - let mut input_item: syn::Item = syn::parse2(input).expect("Input is not a valid syn::Item"); - crate::utils::PxStripper.visit_item_mut(&mut input_item); - quote! { - #error_tokens - #input_item - }.into() + reemit_with_input(error_tokens, input) } } } @@ -137,3 +132,22 @@ fn parse_metadata(metadata: TokenStream) -> Result proc_macro::TokenStream { + if let Ok(mut item) = syn::parse2::(input.clone()) { + crate::utils::PxStripper.visit_item_mut(&mut item); + return quote! { #error_tokens #item }.into(); + } + if let Ok(mut method) = syn::parse2::(input.clone()) { + crate::utils::PxStripper.visit_impl_item_fn_mut(&mut method); + return quote! { #error_tokens #method }.into(); + } + if let Ok(mut trait_fn) = syn::parse2::(input.clone()) { + crate::utils::PxStripper.visit_trait_item_fn_mut(&mut trait_fn); + return quote! { #error_tokens #trait_fn }.into(); + } + quote! { #error_tokens #input }.into() +} diff --git a/runtime/pavex_macros/src/utils/px_stripper.rs b/runtime/pavex_macros/src/utils/px_stripper.rs index fe84e383b..fb7d03120 100644 --- a/runtime/pavex_macros/src/utils/px_stripper.rs +++ b/runtime/pavex_macros/src/utils/px_stripper.rs @@ -1,6 +1,6 @@ use syn::{ - Attribute, Field, FieldValue, FnArg, ItemEnum, ItemImpl, ItemMod, ItemStruct, ItemTrait, - TraitItem, Variant, + Attribute, Field, FieldValue, FnArg, ImplItemFn, ItemEnum, ItemImpl, ItemMod, + ItemStruct, ItemTrait, TraitItem, TraitItemFn, Variant, visit_mut::{self, VisitMut}, }; @@ -75,6 +75,28 @@ impl VisitMut for PxStripper { attrs.retain(not_px_attr); visit_mut::visit_fn_arg_mut(self, node); } + + fn visit_impl_item_fn_mut(&mut self, node: &mut ImplItemFn) { + strip_pavex_attrs(&mut node.attrs); + visit_mut::visit_impl_item_fn_mut(self, node); + } + + fn visit_trait_item_fn_mut(&mut self, node: &mut TraitItemFn) { + strip_pavex_attrs(&mut node.attrs); + visit_mut::visit_trait_item_fn_mut(self, node); + } +} + +fn strip_pavex_attrs(attrs: &mut Vec) { + attrs.retain(|a| !is_user_pavex_attr(a) && not_px_attr(a)); +} + +fn is_user_pavex_attr(a: &Attribute) -> bool { + a.path() + .segments + .first() + .map(|s| s.ident == "pavex") + .unwrap_or(false) } fn not_px_attr(a: &Attribute) -> bool { diff --git a/runtime/pavex_macros/src/utils/type_like.rs b/runtime/pavex_macros/src/utils/type_like.rs index d7344030f..7452f52c9 100644 --- a/runtime/pavex_macros/src/utils/type_like.rs +++ b/runtime/pavex_macros/src/utils/type_like.rs @@ -2,7 +2,7 @@ //! Pavex components that accept type-like inputs. use convert_case::{Case, Casing as _}; use proc_macro2::TokenStream; -use quote::{quote, ToTokens, format_ident}; +use quote::{ToTokens, format_ident, quote}; use syn::{parse_quote, visit_mut::VisitMut}; use crate::utils::{AnnotationCodegen, deny_unreachable_pub_attr, validation::must_be_public}; @@ -218,12 +218,7 @@ pub fn entrypoint( Ok(t) => t.into(), Err(error_tokens) => { let error_tokens = proc_macro2::TokenStream::from(error_tokens); - let mut input_item: syn::Item = syn::parse2(input).expect("Input is not a valid syn::Item"); - crate::utils::PxStripper.visit_item_mut(&mut input_item); - quote! { - #error_tokens - #input_item - }.into() + reemit_with_input(error_tokens, input) } } } @@ -233,3 +228,14 @@ fn parse_metadata(metadata: TokenStream) -> Result proc_macro::TokenStream { + if let Ok(mut item) = syn::parse2::(input.clone()) { + crate::utils::PxStripper.visit_item_mut(&mut item); + return quote! { #error_tokens #item }.into(); + } + quote! { #error_tokens #input }.into() +} diff --git a/runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.rs b/runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.rs new file mode 100644 index 000000000..92f57a83c --- /dev/null +++ b/runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.rs @@ -0,0 +1,12 @@ +use pavex_macros::error_handler; + +#[error_handler(id = "CUSTOM")] +pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response { + todo!() +} + +fn test_handle_exists() { + let _handle = CUSTOM; +} + +fn main() {} \ No newline at end of file diff --git a/runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.stderr b/runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.stderr new file mode 100644 index 000000000..e5af9a517 --- /dev/null +++ b/runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.stderr @@ -0,0 +1,6 @@ +error: Mark the error reference input with `#[px(error_ref)]`. + Pavex can't automatically identify it if your error handler has two or more input parameters. + --> tests/error_handler/fail/custom_id_generation.rs:4:23 + | +4 | pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response { + | ^^^^^^^^^^^^^^^^^^^ diff --git a/runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.rs b/runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.rs new file mode 100644 index 000000000..14609f01f --- /dev/null +++ b/runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.rs @@ -0,0 +1,12 @@ +use pavex_macros::error_handler; + +#[error_handler] +pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response { + todo!() +} + +fn test_handle_exists() { + let _handle = INVALID_HANDLER; +} + +fn main() {} \ No newline at end of file diff --git a/runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.stderr b/runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.stderr new file mode 100644 index 000000000..509266488 --- /dev/null +++ b/runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.stderr @@ -0,0 +1,6 @@ +error: Mark the error reference input with `#[px(error_ref)]`. + Pavex can't automatically identify it if your error handler has two or more input parameters. + --> tests/error_handler/fail/handle_generation_on_error.rs:4:23 + | +4 | pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response { + | ^^^^^^^^^^^^^^^^^^^