tokio-rs / axum

Ergonomic and modular web framework built with Tokio, Tower, and Hyper

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`AppState::from_ref(...)` inside a `impl<S> FromRequest<S>` breaks trait bounds `axum::serve`

samuela opened this issue · comments

  • I have looked for existing issues (including closed) about this

Bug Report

Version

root@b62e2017d34d:/workspaces/bitbop/cherami# cargo tree | grep axum
├── axum v0.7.5
│   ├── axum-core v0.4.3

Platform

root@b62e2017d34d:/workspaces/bitbop/cherami# uname -a
Linux b62e2017d34d 6.6.22-linuxkit #1 SMP Fri Mar 29 12:21:27 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux

Description

Following this example code and these docs, I have written the following:

use axum::async_trait;
use axum::body::Body;
use axum::extract::FromRef;
use axum::extract::FromRequest;
use axum::extract::Request;
use axum::response::Response;
use axum::routing::post;
use axum::Extension;
use axum::Router;
use std::sync::Arc;

// Clone results in an auto-impl FromRef<AppState> due to https://docs.rs/axum/latest/axum/extract/trait.FromRef.html#impl-FromRef%3CT%3E-for-T.
#[derive(Clone)]
struct AppState {
  stripe_webhook_secret: String,
}

// See https://github.com/arlyon/async-stripe/blob/master/examples/webhook-axum.rs
struct StripeEvent(stripe::Event);

#[async_trait]
impl<S> FromRequest<S> for StripeEvent
where
  String: FromRequest<S>,
  AppState: FromRef<S>,
  S: Send + Sync,
{
  type Rejection = Response;

  async fn from_request(req: Request<Body>, state: &S) -> Result<Self, Self::Rejection> {
    let whsec = AppState::from_ref(state).stripe_webhook_secret;
    todo!()
  }
}

async fn stripe_webhook(Extension(_): Extension<Arc<AppState>>, StripeEvent(_): StripeEvent) -> &'static str {
  todo!()
}

#[tokio::main]
async fn main() {
  let state = {
    let stripe_webhook_secret = std::env::var("STRIPE_WEBHOOK_SECRET").expect("expected STRIPE_WEBHOOK_SECRET");
    Arc::new(AppState { stripe_webhook_secret })
  };
  let app = Router::new()
    .route("/stripe_webhook", post(stripe_webhook))
    .layer(Extension(state));
  let listener = tokio::net::TcpListener::bind("127.0.0.1:8000").await.unwrap();
  axum::serve(listener, app).await.unwrap();
}

However adding where AppState: FromRef<S> seems to break trait bounds necessary for axum::serve:

root@b62e2017d34d:/workspaces/bitbop/cherami# cargo check
    Checking cherami v0.0.0 (/workspaces/bitbop/cherami)
error[E0277]: the trait bound `for<'a> Router<AppState>: tower_service::Service<IncomingStream<'a>>` is not satisfied
  --> src/bin/api.rs:50:25
   |
50 |   axum::serve(listener, app).await.unwrap();
   |   -----------           ^^^ the trait `for<'a> tower_service::Service<IncomingStream<'a>>` is not implemented for `Router<AppState>`
   |   |
   |   required by a bound introduced by this call
   |
   = help: the following other types implement trait `tower_service::Service<Request>`:
             <Router as tower_service::Service<axum::http::Request<B>>>
             <Router as tower_service::Service<IncomingStream<'_>>>
note: required by a bound in `serve`
  --> /root/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.7.5/src/serve.rs:97:8
   |
95 | pub fn serve<M, S>(tcp_listener: TcpListener, make_service: M) -> Serve<M, S>
   |        ----- required by a bound in this function
96 | where
97 |     M: for<'a> Service<IncomingStream<'a>, Error = Infallible, Response = S>,
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `serve`

error[E0277]: the trait bound `Serve<Router<AppState>, _>: IntoFuture` is not satisfied
  --> src/bin/api.rs:50:30
   |
50 |   axum::serve(listener, app).await.unwrap();
   |   ---------------------------^^^^^
   |   |                         ||
   |   |                         |the trait `IntoFuture` is not implemented for `Serve<Router<AppState>, _>`
   |   |                         help: remove the `.await`
   |   this call returns `Serve<Router<AppState>, _>`
   |
   = help: the trait `IntoFuture` is implemented for `Serve<M, S>`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `cherami` (bin "api") due to 2 previous errors

It is not at all clear how this error message relates to the actual code written. Am I doing something wrong here?

Replacing the impl with

#[async_trait]
impl FromRequest<AppState> for StripeEvent
where
  String: FromRequest<AppState>,
{
  type Rejection = Response;

  async fn from_request(req: Request<Body>, state: &AppState) -> Result<Self, Self::Rejection> {
    let whsec = state.stripe_webhook_secret;
    todo!()
  }
}

still results in the same compile error.

Ok I think this is the smallest repro i can cook up:

use axum::async_trait;
use axum::body::Body;
use axum::extract::FromRequest;
use axum::extract::Request;
use axum::response::Response;
use axum::routing::post;
use axum::Extension;
use axum::Router;
use std::sync::Arc;

// Clone results in an auto-impl FromRef<AppState> due to https://docs.rs/axum/latest/axum/extract/trait.FromRef.html#impl-FromRef%3CT%3E-for-T.
#[derive(Clone)]
struct AppState {}

// See https://github.com/arlyon/async-stripe/blob/master/examples/webhook-axum.rs
struct StripeEvent(());

#[async_trait]
impl FromRequest<AppState> for StripeEvent {
  type Rejection = Response;
  async fn from_request(req: Request<Body>, state: &AppState) -> Result<Self, Self::Rejection> {
    Ok(StripeEvent(()))
  }
}

async fn stripe_webhook(StripeEvent(_): StripeEvent) -> &'static str {
  "ok"
}

#[tokio::main]
async fn main() {
  let app = Router::new()
    .route("/stripe_webhook", post(stripe_webhook))
    .layer(Extension(Arc::new(AppState {})));
  let listener = tokio::net::TcpListener::bind("127.0.0.1:8000").await.unwrap();
  axum::serve(listener, app).await.unwrap();
}