Skip to content

Commit fe764db

Browse files
authored
feat: generate component handle type even if the macro input is invalid (#563)
1 parent 54cf661 commit fe764db

File tree

9 files changed

+128
-19
lines changed

9 files changed

+128
-19
lines changed

runtime/pavex_macros/src/error_handler.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,22 @@ impl CallableAnnotation for ErrorHandlerAnnotation {
5252
metadata: Self::InputSchema,
5353
item: Callable,
5454
) -> Result<AnnotationCodegen, proc_macro::TokenStream> {
55-
let error_ref_index = find_error_ref_index(&item).map_err(|e| e.write_errors())?;
55+
let error_ref_index = match find_error_ref_index(&item) {
56+
Ok(index) => index,
57+
Err(e) => {
58+
let properties = Properties::new(metadata, 0);
59+
let result = emit(impl_, item, properties);
60+
61+
let error_tokens = e.write_errors();
62+
let handle_def = result.id_def.unwrap_or_default();
63+
let combined_error = quote::quote! {
64+
#error_tokens
65+
#handle_def
66+
};
67+
return Err(combined_error.into());
68+
}
69+
};
70+
5671
let properties = Properties::new(metadata, error_ref_index);
5772
Ok(emit(impl_, item, properties))
5873
}

runtime/pavex_macros/src/methods.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ use crate::{
1717
};
1818

1919
pub fn methods(_metadata: TokenStream, input: TokenStream) -> Result<TokenStream, TokenStream> {
20+
_methods_inner(_metadata, input.clone()).map_err(|error_tokens| {
21+
let error_tokens = proc_macro2::TokenStream::from(error_tokens);
22+
let input_ts: proc_macro2::TokenStream = input.into();
23+
if let Ok(mut item) = syn::parse2::<syn::Item>(input_ts.clone()) {
24+
crate::utils::PxStripper.visit_item_mut(&mut item);
25+
return quote! { #error_tokens #item }.into();
26+
}
27+
quote! { #error_tokens #input_ts }.into()
28+
})
29+
}
30+
31+
fn _methods_inner(
32+
_metadata: TokenStream,
33+
input: TokenStream,
34+
) -> Result<TokenStream, proc_macro::TokenStream> {
2035
let mut impl_: syn::ItemImpl = syn::parse(input).map_err(|e| e.into_compile_error())?;
2136
let mut new_items: Vec<proc_macro2::TokenStream> = Vec::new();
2237

@@ -78,15 +93,16 @@ pub fn methods(_metadata: TokenStream, input: TokenStream) -> Result<TokenStream
7893
}
7994

8095
if no_pavex_method {
81-
return Err(syn::Error::new(
96+
let error_tokens = syn::Error::new(
8297
proc_macro2::Span::call_site(),
8398
"`#[pavex::methods]` is used to provide context for Pavex attributes on methods \
8499
(e.g., `#[pavex::get]`, `#[pavex::post]`, `#[pavex::error_handler]`, etc.).\n\
85100
This `impl` block contains no methods with Pavex attributes, so `#[pavex::methods]` \
86101
is unnecessary.",
87102
)
88-
.into_compile_error()
89-
.into());
103+
.into_compile_error();
104+
105+
return Err(error_tokens.into());
90106
}
91107

92108
PxStripper.visit_item_impl_mut(&mut impl_);

runtime/pavex_macros/src/utils/fn_like.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,7 @@ pub fn direct_entrypoint<M: CallableAnnotation>(
103103
Ok(t) => t.into(),
104104
Err(error_tokens) => {
105105
let error_tokens = proc_macro2::TokenStream::from(error_tokens);
106-
let mut input_item: syn::Item = syn::parse2(input).expect("Input is not a valid syn::Item");
107-
crate::utils::PxStripper.visit_item_mut(&mut input_item);
108-
quote! {
109-
#error_tokens
110-
#input_item
111-
}.into()
106+
reemit_with_input(error_tokens, input)
112107
}
113108
}
114109
}
@@ -137,3 +132,22 @@ fn parse_metadata<T: darling::FromMeta>(metadata: TokenStream) -> Result<T, Toke
137132
darling::ast::NestedMeta::parse_meta_list(metadata).map_err(|e| e.to_compile_error())?;
138133
T::from_list(&attrs).map_err(|e| e.write_errors())
139134
}
135+
136+
fn reemit_with_input(
137+
error_tokens: proc_macro2::TokenStream,
138+
input: proc_macro2::TokenStream,
139+
) -> proc_macro::TokenStream {
140+
if let Ok(mut item) = syn::parse2::<syn::Item>(input.clone()) {
141+
crate::utils::PxStripper.visit_item_mut(&mut item);
142+
return quote! { #error_tokens #item }.into();
143+
}
144+
if let Ok(mut method) = syn::parse2::<syn::ImplItemFn>(input.clone()) {
145+
crate::utils::PxStripper.visit_impl_item_fn_mut(&mut method);
146+
return quote! { #error_tokens #method }.into();
147+
}
148+
if let Ok(mut trait_fn) = syn::parse2::<syn::TraitItemFn>(input.clone()) {
149+
crate::utils::PxStripper.visit_trait_item_fn_mut(&mut trait_fn);
150+
return quote! { #error_tokens #trait_fn }.into();
151+
}
152+
quote! { #error_tokens #input }.into()
153+
}

runtime/pavex_macros/src/utils/px_stripper.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use syn::{
2-
Attribute, Field, FieldValue, FnArg, ItemEnum, ItemImpl, ItemMod, ItemStruct, ItemTrait,
3-
TraitItem, Variant,
2+
Attribute, Field, FieldValue, FnArg, ImplItemFn, ItemEnum, ItemImpl, ItemMod,
3+
ItemStruct, ItemTrait, TraitItem, TraitItemFn, Variant,
44
visit_mut::{self, VisitMut},
55
};
66

@@ -75,6 +75,28 @@ impl VisitMut for PxStripper {
7575
attrs.retain(not_px_attr);
7676
visit_mut::visit_fn_arg_mut(self, node);
7777
}
78+
79+
fn visit_impl_item_fn_mut(&mut self, node: &mut ImplItemFn) {
80+
strip_pavex_attrs(&mut node.attrs);
81+
visit_mut::visit_impl_item_fn_mut(self, node);
82+
}
83+
84+
fn visit_trait_item_fn_mut(&mut self, node: &mut TraitItemFn) {
85+
strip_pavex_attrs(&mut node.attrs);
86+
visit_mut::visit_trait_item_fn_mut(self, node);
87+
}
88+
}
89+
90+
fn strip_pavex_attrs(attrs: &mut Vec<Attribute>) {
91+
attrs.retain(|a| !is_user_pavex_attr(a) && not_px_attr(a));
92+
}
93+
94+
fn is_user_pavex_attr(a: &Attribute) -> bool {
95+
a.path()
96+
.segments
97+
.first()
98+
.map(|s| s.ident == "pavex")
99+
.unwrap_or(false)
78100
}
79101

80102
fn not_px_attr(a: &Attribute) -> bool {

runtime/pavex_macros/src/utils/type_like.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! Pavex components that accept type-like inputs.
33
use convert_case::{Case, Casing as _};
44
use proc_macro2::TokenStream;
5-
use quote::{quote, ToTokens, format_ident};
5+
use quote::{ToTokens, format_ident, quote};
66
use syn::{parse_quote, visit_mut::VisitMut};
77

88
use crate::utils::{AnnotationCodegen, deny_unreachable_pub_attr, validation::must_be_public};
@@ -218,12 +218,7 @@ pub fn entrypoint<M: TypeAnnotation>(
218218
Ok(t) => t.into(),
219219
Err(error_tokens) => {
220220
let error_tokens = proc_macro2::TokenStream::from(error_tokens);
221-
let mut input_item: syn::Item = syn::parse2(input).expect("Input is not a valid syn::Item");
222-
crate::utils::PxStripper.visit_item_mut(&mut input_item);
223-
quote! {
224-
#error_tokens
225-
#input_item
226-
}.into()
221+
reemit_with_input(error_tokens, input)
227222
}
228223
}
229224
}
@@ -233,3 +228,14 @@ fn parse_metadata<T: darling::FromMeta>(metadata: TokenStream) -> Result<T, Toke
233228
darling::ast::NestedMeta::parse_meta_list(metadata).map_err(|e| e.to_compile_error())?;
234229
T::from_list(&attrs).map_err(|e| e.write_errors())
235230
}
231+
232+
fn reemit_with_input(
233+
error_tokens: proc_macro2::TokenStream,
234+
input: proc_macro2::TokenStream,
235+
) -> proc_macro::TokenStream {
236+
if let Ok(mut item) = syn::parse2::<syn::Item>(input.clone()) {
237+
crate::utils::PxStripper.visit_item_mut(&mut item);
238+
return quote! { #error_tokens #item }.into();
239+
}
240+
quote! { #error_tokens #input }.into()
241+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use pavex_macros::error_handler;
2+
3+
#[error_handler(id = "CUSTOM")]
4+
pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response {
5+
todo!()
6+
}
7+
8+
fn test_handle_exists() {
9+
let _handle = CUSTOM;
10+
}
11+
12+
fn main() {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: Mark the error reference input with `#[px(error_ref)]`.
2+
Pavex can't automatically identify it if your error handler has two or more input parameters.
3+
--> tests/error_handler/fail/custom_id_generation.rs:4:23
4+
|
5+
4 | pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response {
6+
| ^^^^^^^^^^^^^^^^^^^
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use pavex_macros::error_handler;
2+
3+
#[error_handler]
4+
pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response {
5+
todo!()
6+
}
7+
8+
fn test_handle_exists() {
9+
let _handle = INVALID_HANDLER;
10+
}
11+
12+
fn main() {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
error: Mark the error reference input with `#[px(error_ref)]`.
2+
Pavex can't automatically identify it if your error handler has two or more input parameters.
3+
--> tests/error_handler/fail/handle_generation_on_error.rs:4:23
4+
|
5+
4 | pub fn invalid_handler(_a: &str, _b: u64) -> pavex::Response {
6+
| ^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)