From a0167459c706aa03b23b7aaad0e8a0a3d3e12247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABlle=20Huisman?= Date: Sat, 28 Feb 2026 10:51:41 +0100 Subject: [PATCH] feat: generalize hook --- packages/breach-macros/src/http.rs | 13 +++++------ packages/breach-macros/src/http/attribute.rs | 24 ++++++++++++++------ packages/breach-macros/src/http/data.rs | 8 +++++++ packages/breach-macros/src/http/enum.rs | 23 +++++++++++++++++++ packages/breach-macros/src/http/struct.rs | 4 ++++ packages/breach-macros/src/http/union.rs | 4 ++++ packages/breach/src/error.rs | 3 +++ 7 files changed, 65 insertions(+), 14 deletions(-) diff --git a/packages/breach-macros/src/http.rs b/packages/breach-macros/src/http.rs index 0200ac3..709d27e 100644 --- a/packages/breach-macros/src/http.rs +++ b/packages/breach-macros/src/http.rs @@ -32,6 +32,7 @@ impl<'a> ToTokens for HttpError<'a> { let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); let status = self.data.status(); + let hook = self.data.hook(); tokens.append_all(quote! { #[automatically_derived] @@ -39,22 +40,20 @@ impl<'a> ToTokens for HttpError<'a> { fn status(&self) -> ::breach::http::StatusCode { #status } + + fn hook(&self) { + #hook + } } }); if let Some(attribute) = self.data.attribute() { if attribute.axum { - let hook = attribute.axum_hook.as_ref().map(|hook| { - quote! { - #hook(&self); - } - }); - tokens.append_all(quote! { #[automatically_derived] impl #impl_generics ::axum::response::IntoResponse for #ident #type_generics #where_clause { fn into_response(self) -> ::axum::response::Response { - #hook; + self.hook(); (self.status(), ::axum::Json(self)).into_response() } diff --git a/packages/breach-macros/src/http/attribute.rs b/packages/breach-macros/src/http/attribute.rs index aa04525..f5c5942 100644 --- a/packages/breach-macros/src/http/attribute.rs +++ b/packages/breach-macros/src/http/attribute.rs @@ -58,8 +58,8 @@ impl<'a> HttpErrorAttribute { pub struct HttpErrorDataAttribute { pub status: Option, pub base: Option, + pub hook: Option, pub axum: bool, - pub axum_hook: Option, pub utoipa: bool, } @@ -88,8 +88,8 @@ impl<'a> HttpErrorDataAttribute { pub fn parse(attribute: &'a Attribute) -> Result { let mut status = None; let mut base = None; + let mut hook = None; let mut axum = false; - let mut axum_hook = None; let mut utoipa = false; attribute.parse_nested_meta(|meta| { @@ -101,12 +101,12 @@ impl<'a> HttpErrorDataAttribute { base = Some(meta.value()?.parse()?); Ok(()) - } else if meta.path.is_ident("axum") { - axum = true; + } else if meta.path.is_ident("hook") { + hook = Some(meta.value()?.parse()?); Ok(()) - } else if meta.path.is_ident("axum_hook") { - axum_hook = Some(meta.value()?.parse()?); + } else if meta.path.is_ident("axum") { + axum = true; Ok(()) } else if meta.path.is_ident("utoipa") { @@ -121,8 +121,8 @@ impl<'a> HttpErrorDataAttribute { Ok(Self { status, base, + hook, axum, - axum_hook, utoipa, }) } @@ -134,6 +134,16 @@ impl<'a> HttpErrorDataAttribute { pub fn responses(&self, r#type: Option) -> TokenStream { responses(self.status.as_ref(), r#type) } + + pub fn hook(&self) -> TokenStream { + if let Some(hook) = &self.hook { + quote! { + #hook(&self); + } + } else { + TokenStream::new() + } + } } fn status(status: Option<&Status>) -> TokenStream { diff --git a/packages/breach-macros/src/http/data.rs b/packages/breach-macros/src/http/data.rs index cd42486..20cc6ec 100644 --- a/packages/breach-macros/src/http/data.rs +++ b/packages/breach-macros/src/http/data.rs @@ -44,4 +44,12 @@ impl<'a> HttpErrorData<'a> { HttpErrorData::Union(r#union) => r#union.responses(), } } + + pub fn hook(&self) -> TokenStream { + match self { + HttpErrorData::Struct(r#struct) => r#struct.hook(), + HttpErrorData::Enum(r#enum) => r#enum.hook(), + HttpErrorData::Union(r#union) => r#union.hook(), + } + } } diff --git a/packages/breach-macros/src/http/enum.rs b/packages/breach-macros/src/http/enum.rs index 60475df..b920789 100644 --- a/packages/breach-macros/src/http/enum.rs +++ b/packages/breach-macros/src/http/enum.rs @@ -65,6 +65,19 @@ impl<'a> HttpErrorEnum<'a> { } } } + + pub fn hook(&self) -> TokenStream { + let hook = self.attribute.as_ref().map(|attribute| attribute.hook()); + let arms = self.variants.iter().map(|variant| variant.hook()); + + quote! { + #hook + + match &self { + #( #arms ), * + } + } + } } pub struct HttpErrorEnumVariant<'a> { @@ -147,6 +160,16 @@ impl<'a> HttpErrorEnumVariant<'a> { } } + pub fn hook(&self) -> TokenStream { + self.arm(if self.attribute.is_none() && self.field.is_some() { + quote!({ + value.hook(); + }) + } else { + quote!({}) + }) + } + fn arm(&self, tokens: TokenStream) -> TokenStream { let enum_ident = self.enum_ident; let ident = self.ident; diff --git a/packages/breach-macros/src/http/struct.rs b/packages/breach-macros/src/http/struct.rs index 01370e0..667a774 100644 --- a/packages/breach-macros/src/http/struct.rs +++ b/packages/breach-macros/src/http/struct.rs @@ -28,4 +28,8 @@ impl<'a> HttpErrorStruct { pub fn responses(&self) -> TokenStream { self.attribute.responses(Some(quote!(Self))) } + + pub fn hook(&self) -> TokenStream { + self.attribute.hook() + } } diff --git a/packages/breach-macros/src/http/union.rs b/packages/breach-macros/src/http/union.rs index 14cfdf6..6350a20 100644 --- a/packages/breach-macros/src/http/union.rs +++ b/packages/breach-macros/src/http/union.rs @@ -21,4 +21,8 @@ impl HttpErrorUnion { pub fn responses(&self) -> TokenStream { todo!() } + + pub fn hook(&self) -> TokenStream { + todo!() + } } diff --git a/packages/breach/src/error.rs b/packages/breach/src/error.rs index b933394..acfbb24 100644 --- a/packages/breach/src/error.rs +++ b/packages/breach/src/error.rs @@ -4,4 +4,7 @@ use http::StatusCode; pub trait HttpError { /// HTTP status code. fn status(&self) -> StatusCode; + + /// Hook called when the HTTP error is used as response. + fn hook(&self); }