New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add #[derive(FromRef)]
#1430
Add #[derive(FromRef)]
#1430
Changes from 11 commits
3ca5d87
29fd8aa
540f2a7
c871de2
37a7f2c
9c43baa
215349c
280f483
06f2de8
48dd51a
5277647
05c7be6
1b01d97
7901707
33e195a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,7 @@ use syn::{parse::Parse, Type}; | |
|
||
mod attr_parsing; | ||
mod debug_handler; | ||
mod from_ref; | ||
mod from_request; | ||
mod typed_path; | ||
mod with_position; | ||
|
@@ -575,6 +576,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)] | ||
/// struct AppState { | ||
/// auth_token: AuthToken, | ||
/// database_pool: DatabasePool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be nice to have Doesn't have to be in this PR, of course. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I've gone back and forth on this. We can add that later if someone requests it. |
||
/// } | ||
/// | ||
/// // 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>, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
use axum_macros::FromRef; | ||
use axum::{Router, routing::get, extract::State}; | ||
|
||
// This will implement `FromRef` for each field in the struct. | ||
#[derive(FromRef)] | ||
struct AppState { | ||
auth_token: String, | ||
} | ||
|
||
// So those types can be extracted via `State` | ||
async fn handler(_: State<String>) {} | ||
|
||
fn main() { | ||
let state = AppState { | ||
auth_token: Default::default(), | ||
}; | ||
|
||
let _: Router<AppState> = Router::with_state(state).route("/", get(handler)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could add axum-macros as a dev-dependency so we can use an intra-doc link here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weren't there something with docsrs not building dev dependencies, and that is why we have the
__private_docs
feature in axum?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm yeah, that might be right.