Skip to content
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

Merged
merged 15 commits into from Oct 10, 2022
3 changes: 3 additions & 0 deletions axum-core/src/extract/from_ref.rs
Expand Up @@ -5,7 +5,10 @@
///
/// See [`State`] for more details on how library authors should use this trait.
///
/// This trait can be derived using `#[derive(axum_macros::FromRef)]`.
///
/// [`State`]: https://docs.rs/axum/0.6/axum/extract/struct.State.html
/// [`#[derive(axum_macros::FromRef)]`]: https://docs.rs/axum-macros/latest/axum_macros/derive.FromRef.html
Copy link
Member

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.

Copy link
Member Author

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?

Copy link
Member

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.

// NOTE: This trait is defined in axum-core, even though it is mainly used with `State` which is
// defined in axum. That allows crate authors to use it when implementing extractors.
pub trait FromRef<T> {
Expand Down
4 changes: 3 additions & 1 deletion axum-macros/CHANGELOG.md
Expand Up @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# Unreleased

- None
- **added:** Add `#[derive(FromRef)]` ([#1430])

[#1430]: https://github.com/tokio-rs/axum/pull/1430

# 0.3.0-rc.1 (23. August, 2022)

Expand Down
39 changes: 39 additions & 0 deletions axum-macros/src/from_ref.rs
@@ -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 changes: 42 additions & 0 deletions axum-macros/src/lib.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to have #[from_ref(skip)] so you can avoid "weird" impls like FromRef<AppState> for String.

Doesn't have to be in this PR, of course.

Copy link
Member Author

Choose a reason for hiding this comment

The 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>,
Expand Down
19 changes: 19 additions & 0 deletions axum-macros/tests/from_ref/pass/basic.rs
@@ -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));
}
1 change: 1 addition & 0 deletions axum/CHANGELOG.md
Expand Up @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
named `HandlerService` ([#1418])
- **added:** String and binary `From` impls have been added to `extract::ws::Message`
to be more inline with `tungstenite` ([#1421])
- **added:** Add `#[derive(axum::extract::FromRef)]` ([#1430])

[#1368]: https://github.com/tokio-rs/axum/pull/1368
[#1371]: https://github.com/tokio-rs/axum/pull/1371
Expand Down
2 changes: 1 addition & 1 deletion axum/src/extract/mod.rs
Expand Up @@ -19,7 +19,7 @@ mod state;
pub use axum_core::extract::{DefaultBodyLimit, FromRef, FromRequest, FromRequestParts};

#[cfg(feature = "macros")]
pub use axum_macros::{FromRequest, FromRequestParts};
pub use axum_macros::{FromRef, FromRequest, FromRequestParts};

#[doc(inline)]
#[allow(deprecated)]
Expand Down