diff --git a/axum-macros/CHANGELOG.md b/axum-macros/CHANGELOG.md index e91c838e..57b18004 100644 --- a/axum-macros/CHANGELOG.md +++ b/axum-macros/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased -- None. +- **added:** Add `#[from_ref(skip)]` to skip implementing `FromRef` for individual fields ([#1537]) + +[#1537]: https://github.com/tokio-rs/axum/pull/1537 # 0.3.0-rc.2 (8. November, 2022) diff --git a/axum-macros/src/attr_parsing.rs b/axum-macros/src/attr_parsing.rs index 1ee76de9..bfb5d52f 100644 --- a/axum-macros/src/attr_parsing.rs +++ b/axum-macros/src/attr_parsing.rs @@ -82,6 +82,21 @@ where Ok(()) } +pub(crate) fn combine_unary_attribute(a: &mut Option, b: Option) -> syn::Result<()> +where + K: ToTokens, +{ + if let Some(kw) = b { + if a.is_some() { + let kw_name = std::any::type_name::().split("::").last().unwrap(); + let msg = format!("`{}` specified more than once", kw_name); + return Err(syn::Error::new_spanned(kw, msg)); + } + *a = Some(kw); + } + Ok(()) +} + pub(crate) fn second(tuple: (T, K)) -> K { tuple.1 } diff --git a/axum-macros/src/from_ref.rs b/axum-macros/src/from_ref.rs index 3ebb4180..72b9d440 100644 --- a/axum-macros/src/from_ref.rs +++ b/axum-macros/src/from_ref.rs @@ -1,6 +1,12 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote_spanned; -use syn::{spanned::Spanned, Field, ItemStruct}; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned, + Field, ItemStruct, Token, +}; + +use crate::attr_parsing::{combine_unary_attribute, parse_attrs, Combine}; pub(crate) fn expand(item: ItemStruct) -> TokenStream { item.fields @@ -11,6 +17,15 @@ pub(crate) fn expand(item: ItemStruct) -> TokenStream { } fn expand_field(state: &Ident, idx: usize, field: &Field) -> TokenStream { + let FieldAttrs { skip } = match parse_attrs("from_ref", &field.attrs) { + Ok(attrs) => attrs, + Err(err) => return err.into_compile_error(), + }; + + if skip.is_some() { + return TokenStream::default(); + } + let field_ty = &field.ty; let span = field.ty.span(); @@ -33,6 +48,42 @@ fn expand_field(state: &Ident, idx: usize, field: &Field) -> TokenStream { } } +mod kw { + syn::custom_keyword!(skip); +} + +#[derive(Default)] +pub(super) struct FieldAttrs { + pub(super) skip: Option, +} + +impl Parse for FieldAttrs { + fn parse(input: ParseStream) -> syn::Result { + let mut skip = None; + + while !input.is_empty() { + let lh = input.lookahead1(); + if lh.peek(kw::skip) { + skip = Some(input.parse()?); + } else { + return Err(lh.error()); + } + + let _ = input.parse::(); + } + + Ok(Self { skip }) + } +} + +impl Combine for FieldAttrs { + fn combine(mut self, other: Self) -> syn::Result { + let Self { skip } = other; + combine_unary_attribute(&mut self.skip, skip)?; + Ok(self) + } +} + #[test] fn ui() { crate::run_ui_tests("from_ref"); diff --git a/axum-macros/src/lib.rs b/axum-macros/src/lib.rs index 6a2b8ea4..217350f6 100644 --- a/axum-macros/src/lib.rs +++ b/axum-macros/src/lib.rs @@ -589,6 +589,9 @@ pub fn derive_typed_path(input: TokenStream) -> TokenStream { /// struct AppState { /// auth_token: AuthToken, /// database_pool: DatabasePool, +/// // fields can also be skipped +/// #[from_ref(skip)] +/// api_token: String, /// } /// /// // So those types can be extracted via `State` @@ -601,6 +604,7 @@ pub fn derive_typed_path(input: TokenStream) -> TokenStream { /// let state = AppState { /// auth_token, /// database_pool, +/// api_token: "secret".to_owned(), /// }; /// /// let app = Router::new() diff --git a/axum-macros/tests/from_ref/pass/skip.rs b/axum-macros/tests/from_ref/pass/skip.rs new file mode 100644 index 00000000..eb57aba6 --- /dev/null +++ b/axum-macros/tests/from_ref/pass/skip.rs @@ -0,0 +1,10 @@ +use axum_macros::FromRef; + +#[derive(Clone, FromRef)] +struct AppState { + auth_token: String, + #[from_ref(skip)] + also_string: String, +} + +fn main() {}