Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion runtime/pavex_macros/src/error_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,22 @@ impl CallableAnnotation for ErrorHandlerAnnotation {
metadata: Self::InputSchema,
item: Callable,
) -> Result<AnnotationCodegen, proc_macro::TokenStream> {
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))
}
Expand Down
22 changes: 19 additions & 3 deletions runtime/pavex_macros/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ use crate::{
};

pub fn methods(_metadata: TokenStream, input: TokenStream) -> Result<TokenStream, TokenStream> {
_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::<syn::Item>(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<TokenStream, proc_macro::TokenStream> {
let mut impl_: syn::ItemImpl = syn::parse(input).map_err(|e| e.into_compile_error())?;
let mut new_items: Vec<proc_macro2::TokenStream> = Vec::new();

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

if no_pavex_method {
return Err(syn::Error::new(
let error_tokens = syn::Error::new(
proc_macro2::Span::call_site(),
"`#[pavex::methods]` is used to provide context for Pavex attributes on methods \
(e.g., `#[pavex::get]`, `#[pavex::post]`, `#[pavex::error_handler]`, etc.).\n\
This `impl` block contains no methods with Pavex attributes, so `#[pavex::methods]` \
is unnecessary.",
)
.into_compile_error()
.into());
.into_compile_error();

return Err(error_tokens.into());
}

PxStripper.visit_item_impl_mut(&mut impl_);
Expand Down
26 changes: 20 additions & 6 deletions runtime/pavex_macros/src/utils/fn_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,7 @@ pub fn direct_entrypoint<M: CallableAnnotation>(
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)
}
}
}
Expand Down Expand Up @@ -137,3 +132,22 @@ fn parse_metadata<T: darling::FromMeta>(metadata: TokenStream) -> Result<T, Toke
darling::ast::NestedMeta::parse_meta_list(metadata).map_err(|e| e.to_compile_error())?;
T::from_list(&attrs).map_err(|e| e.write_errors())
}

fn reemit_with_input(
error_tokens: proc_macro2::TokenStream,
input: proc_macro2::TokenStream,
) -> proc_macro::TokenStream {
if let Ok(mut item) = syn::parse2::<syn::Item>(input.clone()) {
crate::utils::PxStripper.visit_item_mut(&mut item);
return quote! { #error_tokens #item }.into();
}
if let Ok(mut method) = syn::parse2::<syn::ImplItemFn>(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::<syn::TraitItemFn>(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()
}
26 changes: 24 additions & 2 deletions runtime/pavex_macros/src/utils/px_stripper.rs
Original file line number Diff line number Diff line change
@@ -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},
};

Expand Down Expand Up @@ -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<Attribute>) {
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 {
Expand Down
20 changes: 13 additions & 7 deletions runtime/pavex_macros/src/utils/type_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -218,12 +218,7 @@ pub fn entrypoint<M: TypeAnnotation>(
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)
}
}
}
Expand All @@ -233,3 +228,14 @@ fn parse_metadata<T: darling::FromMeta>(metadata: TokenStream) -> Result<T, Toke
darling::ast::NestedMeta::parse_meta_list(metadata).map_err(|e| e.to_compile_error())?;
T::from_list(&attrs).map_err(|e| e.write_errors())
}

fn reemit_with_input(
error_tokens: proc_macro2::TokenStream,
input: proc_macro2::TokenStream,
) -> proc_macro::TokenStream {
if let Ok(mut item) = syn::parse2::<syn::Item>(input.clone()) {
crate::utils::PxStripper.visit_item_mut(&mut item);
return quote! { #error_tokens #item }.into();
}
quote! { #error_tokens #input }.into()
}
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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 {
| ^^^^^^^^^^^^^^^^^^^
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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 {
| ^^^^^^^^^^^^^^^^^^^
Loading