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

axum_garde and State #47

Closed
weaversa opened this issue Jun 4, 2023 · 2 comments
Closed

axum_garde and State #47

weaversa opened this issue Jun 4, 2023 · 2 comments

Comments

@weaversa
Copy link

weaversa commented Jun 4, 2023

To help demonstrate this issue I'll use the json example in this repository.

I've been successful in using garde and axum together to pass state and validate parameters, so thank you! Your project is quite helpful. The way I've been successful is to use axum's older Extension method for passing state, see here:

//! Simple usage of using `axum_garde` for a REST API
//!
//! Run the example using
//!
//! ```sh
//! cargo run --example json --features=json
//! ```
use axum::response::IntoResponse;
use axum::routing::post;
use axum::extract::Extension;
use axum::{Json, Router, Server};
use axum_garde::WithValidation;
use garde::Validate;
use serde::{Deserialize, Serialize};

// Define your valid scheme
#[derive(Debug, Serialize, Deserialize, Validate)]
struct Person {
    #[garde(ascii, length(min = 3, max = 25))]
    username: String,
    #[garde(length(min = 15))]
    password: String,
}

#[derive(Clone)]
struct AppState { }

async fn insert_valid_person(
    Extension(state): Extension<AppState>,
    // Perform validation on the request payload
    WithValidation(person): WithValidation<Json<Person>>,
) -> impl IntoResponse {
    println!("Inserted person on database: {person:?}");
    Json(person.into_inner())
}

#[tokio::main]
async fn main() {
    let state = AppState { };
    let app = Router::new()
        .route("/person", post(insert_valid_person))
        // Create the application state
        .layer(Extension(state));
    println!("See example: http://127.0.0.1:8080/person");
    Server::bind(&([127, 0, 0, 1], 8080).into())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

That seems to work just fine. That said, there is a relatively new feature in axum called State - https://tokio.rs/blog/2022-08-whats-new-in-axum-0-6-0-rc1. This is now the preferred way to pass state as it is type safe, whereas Extension is not. The article also claims that State is faster than Extension.

When I rework the example above to use State rather than Extension I get an error:

//! Simple usage of using `axum_garde` for a REST API
//!
//! Run the example using
//!
//! ```sh
//! cargo run --example json --features=json
//! ```
use axum::response::IntoResponse;
use axum::routing::post;
use axum::extract::State;
use axum::{Json, Router, Server};
use axum_garde::WithValidation;
use garde::Validate;
use serde::{Deserialize, Serialize};

// Define your valid scheme
#[derive(Debug, Serialize, Deserialize, Validate)]
struct Person {
    #[garde(ascii, length(min = 3, max = 25))]
    username: String,
    #[garde(length(min = 15))]
    password: String,
}

#[derive(Clone)]
struct AppState { }

async fn insert_valid_person(
    State(state): State<AppState>,
    // Perform validation on the request payload
    WithValidation(person): WithValidation<Json<Person>>,
) -> impl IntoResponse {
    println!("Inserted person on database: {person:?}");
    Json(person.into_inner())
}

#[tokio::main]
async fn main() {
    let state = AppState { };
    let app = Router::new()
        .route("/person", post(insert_valid_person))
        // Create the application state
        .with_state(state);
    println!("See example: http://127.0.0.1:8080/person");
    Server::bind(&([127, 0, 0, 1], 8080).into())
        .serve(app.into_make_service())
        .await
        .unwrap();
}
$ cargo run --example json --features=json
warning: garde/garde/Cargo.toml: version requirement `0.3.2+8.13.9` for dependency `phonenumber` includes semver metadata which will be ignored, removing the metadata is recommended to avoid confusion
   Compiling axum_garde v0.11.2 (garde/integrations/axum_garde)
error[E0277]: the trait bound `(): FromRef<AppState>` is not satisfied
   --> integrations/axum_garde/examples/json.rs:41:32
    |
41  |         .route("/person", post(insert_valid_person))
    |                           ---- ^^^^^^^^^^^^^^^^^^^ the trait `FromRef<AppState>` is not implemented for `()`
    |                           |
    |                           required by a bound introduced by this call
    |
    = help: the following other types implement trait `Handler<T, S, B>`:
              <Layered<L, H, T, S, B, B2> as Handler<T, S, B2>>
              <MethodRouter<S, B> as Handler<(), S, B>>
    = note: required for `WithValidation<Json<Person>>` to implement `FromRequest<AppState, _>`
    = note: required for `fn(State<AppState>, WithValidation<Json<Person>>) -> impl Future<Output = impl IntoResponse> {insert_valid_person}` to implement `Handler<(axum_core::extract::private::ViaRequest, State<AppState>, WithValidation<Json<Person>>), AppState, _>`
note: required by a bound in `post`
   --> .cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.6.18/src/routing/method_routing.rs:407:1
    |
407 | top_level_handler_fn!(post, POST);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `post`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `axum_garde` due to previous error

This error message is currently beyond my ability to comprehend. If I remove the garde validation, the example works fine, so I guess there is some friction between garde and this newish feature in axum that is causing the issue. Any help would be appreciated.

@jprochazk
Copy link
Owner

jprochazk commented Jun 4, 2023

garde supports custom validation with custom context types, and if one isn't provided via the #[garde(context(T))] attribute, then it defaults to (). What you're seeing is WithValidation attempting to extract () from the struct passed to with_state. axum supports substates, so the easiest way out is to implement FromRef<AppState> for ():

impl FromRef<AppState> for () {
    fn from_ref(_: &AppState) -> Self {}
}
Full example
//! Simple usage of using `axum_garde` for a REST API
//!
//! Run the example using
//!
//! ```sh
//! cargo run --example json --features=json
//! ```
use axum::extract::FromRef;
use axum::response::IntoResponse;
use axum::routing::post;
use axum::{Json, Router, Server};
use axum_garde::WithValidation;
use garde::Validate;
use serde::{Deserialize, Serialize};

// Define your valid scheme
#[derive(Debug, Serialize, Deserialize, Validate)]
struct Person {
    #[garde(ascii, length(min = 3, max = 25))]
    username: String,
    #[garde(length(min = 15))]
    password: String,
}

async fn insert_valid_person(
    State(state): State<AppState>,
    // Perform validation on the request payload
    WithValidation(person): WithValidation<Json<Person>>,
) -> impl IntoResponse {
    println!("Inserted person on database: {person:?}");
    Json(person.into_inner())
}

#[derive(Clone)]
struct AppState {}

impl FromRef<AppState> for () {
    fn from_ref(_: &AppState) -> Self {}
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/person", post(insert_valid_person))
        // Create the application state
        .with_state(AppState {});
    println!("See example: http://127.0.0.1:8080/person");
    Server::bind(&([127, 0, 0, 1], 8080).into())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

@weaversa
Copy link
Author

weaversa commented Jun 4, 2023

Thank you @jprochazk. With this example I was able to get my application up and running w/ State and your garde.

@weaversa weaversa closed this as completed Jun 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants