How to pass App State in axum?

Just when I thought that 2 years into Rust, I became rather fluent, I got reminded that new packages and std modules are a lot of pain.

I tried using axum for a simple HTTP server. It should initially do very little: take GET request params and send a JSON back.

fn main() {
	let st2 = AppState2 { some_data };

	let app = Router::new()
        .route("/:area", get(extract_area))
		.with_state(st2);

	let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}


fn extract_area(State(AppState2 { rt }): State<AppState2>, Path (coords): Path<String>) -> &'static str {
	// useful work
}

When the path parameter and GET params are parsed, there may be an error, and the response should be with 4xx status code and meaningful description.

I thought there's some convenient handler for it. Here's the only advice I saw in the docs:

async fn thing_that_might_fail() -> Result<(), anyhow::Error> {
    // ...
}

// this service might fail with `anyhow::Error`
let some_fallible_service = tower::service_fn(|_req| async {
    thing_that_might_fail().await?;
    Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});

async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("Something went wrong: {err}"),
    )
}

That's fine, but how do I pass (a) State (b) path params and (c) GET params into the function?

I tried assembling both, but it fails because of trait boundary:

	let faillible_area_getter = tower::service_fn(|req: Request| async {
		let path = req.extract_parts().await.unwrap();
        let resp_body = extract_area(State(&st2), path)?;
		Ok::<_, SomeError>(body.into_response())
	});

	let app = Router::new()
		.route("/:area", HandleError::new(faillible_area_getter, handle_error),
		.with_state(st2);

async fn handle_error(err: QueryError) -> (StatusCode, String) {
	( StatusCode::BAD_REQUEST, format!("{err}") )
}

But this fails to compile, because st2 is moved and the function is FnOnce rather than FnMut. How can I fix this?

--> src/server.rs:37:51
|
37 |     let some_fallible_service = tower::service_fn(|req: Request| async {
|                                                   ^^^^^^^^^^^^^^ this closure implements `FnOnce`, not `FnMut`
38 |         let path = req.extract_parts().await.unwrap();
39 |         let resp_body = do_route(State(st2), path)?;
|                                        --- closure is `FnOnce` because it moves the variable `st2` out of its environment
...
51 |         .route_service("/route/v1/car/:coords", he)
|          ------------- the requirement to implement `FnMut` derives from here

If some author of axum reads this, I'm very tired of it, because I had to walk some source files to understand what to pass where, and more than that -- it works not in every entry point (like if you look up get function, it's a macro that creates an object, not calls anything.

1 Like

Are you concerned about the extractors erroring? examples/customize-extractor-error is for that. If your actual function can return an error, then you can return a Result<T, E> where both T and E implement the axum::response::IntoResponse trait, which returns a basic HTTP response.

I don't know why this code uses tower. That's probably not at all necessary.

Is this code open-source? If so, I'd love to see the repository, it would really help wrap my head around your use-case.

1 Like

I understood it's too hard to make an extractor proper and just decided do extract data in the handler function, and return Err if it fails.

The problem was to make a handler that can error. The examples you showed have so much boilerplate and deps, it's way too hard.

I found a more compact one here, will try it.

I tried to rewrite with this approach, but it still fails because of some trait boundaries errors.

async fn do_work(State(AppState2 { data }): State<AppState2>, Path (params): Path<String>) -> Result<&'static str, (StatusCode, String)> { ... }

.route_service("/path/:params" get(do_work))

Error:

the trait bound `MethodRouter<AppState2>: tower_service::Service<axum::http::Request<Body>>` is not satisfied
the following other types implement trait `tower_service::Service<Request>`:
  <MethodRouter as tower_service::Service<IncomingStream<'_>>>
  <MethodRouter<(), E> as tower_service::Service<axum::http::Request<B>>>

Wrapping do_work in HandleError or tower::service_fn(HandleError didn't fix this.

I found what caused the problem:

.route_service("/route/v1/car/:coords", get(do_work))

replaced with .route(... and it worked.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.