Add #[derive(FromRef)] (#1430)

* add `#[derive(FromRef)]`

* tests

* don't support skipping fields

probably wouldn't work at all since the whole state likely needs `Clone`

* UI tests

* changelog

* changelog link

* revert hello-world example, used for testing

* Re-export `#[derive(FromRef)]`

* Don't need to return `Result`

* use `collect` instead of quoting the iterator

* Mention it in axum's changelog
This commit is contained in:
David Pedersen
2022-10-10 18:40:14 +00:00
committed by GitHub
parent 1681ecf438
commit 9c0a89cd09
7 changed files with 108 additions and 2 deletions
+39
View File
@@ -0,0 +1,39 @@
use proc_macro2::{Ident, TokenStream};
use quote::quote_spanned;
use syn::{spanned::Spanned, Field, ItemStruct};
pub(crate) fn expand(item: ItemStruct) -> TokenStream {
item.fields
.iter()
.enumerate()
.map(|(idx, field)| expand_field(&item.ident, idx, field))
.collect()
}
fn expand_field(state: &Ident, idx: usize, field: &Field) -> TokenStream {
let field_ty = &field.ty;
let span = field.ty.span();
let body = if let Some(field_ident) = &field.ident {
quote_spanned! {span=> state.#field_ident.clone() }
} else {
let idx = syn::Index {
index: idx as _,
span: field.span(),
};
quote_spanned! {span=> state.#idx.clone() }
};
quote_spanned! {span=>
impl ::axum::extract::FromRef<#state> for #field_ty {
fn from_ref(state: &#state) -> Self {
#body
}
}
}
}
#[test]
fn ui() {
crate::run_ui_tests("from_ref");
}
+42
View File
@@ -49,6 +49,7 @@ use syn::{parse::Parse, Type};
mod attr_parsing;
mod debug_handler;
mod from_ref;
mod from_request;
mod typed_path;
mod with_position;
@@ -573,6 +574,47 @@ pub fn derive_typed_path(input: TokenStream) -> TokenStream {
expand_with(input, typed_path::expand)
}
/// Derive an implementation of [`FromRef`] for each field in a struct.
///
/// # Example
///
/// ```
/// use axum_macros::FromRef;
/// use axum::{Router, routing::get, extract::State};
///
/// #
/// # type AuthToken = String;
/// # type DatabasePool = ();
/// #
/// // This will implement `FromRef` for each field in the struct.
/// #[derive(FromRef, Clone)]
/// struct AppState {
/// auth_token: AuthToken,
/// database_pool: DatabasePool,
/// }
///
/// // So those types can be extracted via `State`
/// async fn handler(State(auth_token): State<AuthToken>) {}
///
/// async fn other_handler(State(database_pool): State<DatabasePool>) {}
///
/// # let auth_token = Default::default();
/// # let database_pool = Default::default();
/// let state = AppState {
/// auth_token,
/// database_pool,
/// };
///
/// let app = Router::with_state(state).route("/", get(handler).post(other_handler));
/// # let _: Router<AppState> = app;
/// ```
///
/// [`FromRef`]: https://docs.rs/axum/latest/axum/extract/trait.FromRef.html
#[proc_macro_derive(FromRef, attributes(from_ref))]
pub fn derive_from_ref(item: TokenStream) -> TokenStream {
expand_with(item, |item| Ok(from_ref::expand(item)))
}
fn expand_with<F, I, K>(input: TokenStream, f: F) -> TokenStream
where
F: FnOnce(I) -> syn::Result<K>,