From f6044ddb618dae849c85426a81c1e22943808a67 Mon Sep 17 00:00:00 2001 From: Paras Sharma Date: Thu, 30 Oct 2025 04:30:02 +0530 Subject: [PATCH 1/4] feat: err handle gen on validation error --- runtime/pavex_macros/src/error_handler.rs | 17 +++++++++- runtime/pavex_macros/src/methods.rs | 22 +++++++++++-- runtime/pavex_macros/src/utils/fn_like.rs | 30 ++++++++++++++---- runtime/pavex_macros/src/utils/px_stripper.rs | 31 +++++++++++++++++-- runtime/pavex_macros/src/utils/type_like.rs | 20 +++++++----- .../fail/handle_generation_on_error.rs | 12 +++++++ .../fail/handle_generation_on_error.stderr | 6 ++++ 7 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.rs create mode 100644 runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.stderr 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,26 @@ 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(); + } + if let Ok(mut foreign_fn) = syn::parse2::(input.clone()) { + crate::utils::PxStripper.visit_foreign_item_fn_mut(&mut foreign_fn); + return quote! { #error_tokens #foreign_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..95e9f01f1 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, ForeignItemFn, ImplItemFn, ItemEnum, ItemImpl, ItemMod, + ItemStruct, ItemTrait, TraitItem, TraitItemFn, Variant, visit_mut::{self, VisitMut}, }; @@ -75,6 +75,33 @@ 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 visit_foreign_item_fn_mut(&mut self, node: &mut ForeignItemFn) { + strip_pavex_attrs(&mut node.attrs); + visit_mut::visit_foreign_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/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..af26928c3 --- /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:5:23 + | +5 | pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response { + | ^^^^^^^^^^^^^^^^^^^ From 88f34233d37deb22c84aa4ccfb4e2624d5f9e736 Mon Sep 17 00:00:00 2001 From: Paras Sharma Date: Thu, 13 Nov 2025 03:50:37 +0530 Subject: [PATCH 2/4] added custom id test --- .../tests/error_handler/fail/custom_id_generation.rs | 12 ++++++++++++ .../error_handler/fail/custom_id_generation.stderr | 6 ++++++ .../fail/handle_generation_on_error.stderr | 4 ++-- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.rs create mode 100644 runtime/pavex_macros/tests/error_handler/fail/custom_id_generation.stderr 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.stderr b/runtime/pavex_macros/tests/error_handler/fail/handle_generation_on_error.stderr index af26928c3..509266488 100644 --- 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 @@ -1,6 +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:5:23 + --> tests/error_handler/fail/handle_generation_on_error.rs:4:23 | -5 | pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response { +4 | pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response { | ^^^^^^^^^^^^^^^^^^^ From 6545b2539a85f3d98e9d414f79461a3deb787c2a Mon Sep 17 00:00:00 2001 From: Paras Sharma Date: Mon, 17 Nov 2025 13:57:15 +0530 Subject: [PATCH 3/4] removed foreign fn --- runtime/pavex_macros/src/utils/fn_like.rs | 4 ---- runtime/pavex_macros/src/utils/px_stripper.rs | 5 ----- 2 files changed, 9 deletions(-) diff --git a/runtime/pavex_macros/src/utils/fn_like.rs b/runtime/pavex_macros/src/utils/fn_like.rs index afa467a75..59a7dacbe 100644 --- a/runtime/pavex_macros/src/utils/fn_like.rs +++ b/runtime/pavex_macros/src/utils/fn_like.rs @@ -149,9 +149,5 @@ fn reemit_with_input( crate::utils::PxStripper.visit_trait_item_fn_mut(&mut trait_fn); return quote! { #error_tokens #trait_fn }.into(); } - if let Ok(mut foreign_fn) = syn::parse2::(input.clone()) { - crate::utils::PxStripper.visit_foreign_item_fn_mut(&mut foreign_fn); - return quote! { #error_tokens #foreign_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 95e9f01f1..a8451bcf1 100644 --- a/runtime/pavex_macros/src/utils/px_stripper.rs +++ b/runtime/pavex_macros/src/utils/px_stripper.rs @@ -85,11 +85,6 @@ impl VisitMut for PxStripper { strip_pavex_attrs(&mut node.attrs); visit_mut::visit_trait_item_fn_mut(self, node); } - - fn visit_foreign_item_fn_mut(&mut self, node: &mut ForeignItemFn) { - strip_pavex_attrs(&mut node.attrs); - visit_mut::visit_foreign_item_fn_mut(self, node); - } } fn strip_pavex_attrs(attrs: &mut Vec) { From 210258d363b3b80e60e663952fb06e09139be67a Mon Sep 17 00:00:00 2001 From: Paras Sharma Date: Mon, 17 Nov 2025 16:22:02 +0530 Subject: [PATCH 4/4] unused import --- runtime/pavex_macros/src/utils/px_stripper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/pavex_macros/src/utils/px_stripper.rs b/runtime/pavex_macros/src/utils/px_stripper.rs index a8451bcf1..fb7d03120 100644 --- a/runtime/pavex_macros/src/utils/px_stripper.rs +++ b/runtime/pavex_macros/src/utils/px_stripper.rs @@ -1,5 +1,5 @@ use syn::{ - Attribute, Field, FieldValue, FnArg, ForeignItemFn, ImplItemFn, ItemEnum, ItemImpl, ItemMod, + Attribute, Field, FieldValue, FnArg, ImplItemFn, ItemEnum, ItemImpl, ItemMod, ItemStruct, ItemTrait, TraitItem, TraitItemFn, Variant, visit_mut::{self, VisitMut}, };