Reputation: 771
I'm working on an Axum project and encountering an issue when using the with_state API. My goal is to provide an application state (AppState) to the handlers in my Axum router, but I keep getting the following error:
note: expected struct MethodRouter<()>
found struct MethodRouter<AppState>
Here's the structure of my project:
#[tokio::main]
async fn main() {
if let Ok(db) = init_db().await {
let state = state::app_state::AppState::new(db);
let routes = routes::root::routes();
let router = Router::new().with_state(state).nest("/api", routes);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, router).await.unwrap();
}
}
root.rs
pub fn routes() -> Router {
Router::new().merge(user_routes())
}
routes/user.rs
pub fn routes() -> Router {
Router::new().route("/auth/register", post(register_user))
}
handler/user.rs
pub async fn register_user(Json(payload): Json<User>, State(state): State<AppState>) -> impl IntoResponse {
...
}
#[derive(Clone)]
pub struct AppState {
db_conn: Database
}
impl AppState {
pub fn new(db: Database) -> AppState {
AppState {
db_conn: db
}
}
}
The error suggests a type mismatch between MethodRouter<()> and MethodRouter, and I suspect the issue may lie with how I'm setting up my routes or merging them in root.rs. Any suggestions for resolving this?
What I’ve Tried: I've tried different configurations with with_state and nest, but the error persists. Any insights would be greatly appreciated!
Upvotes: 1
Views: 290
Reputation: 1787
I've bumped into the same problem.
The reason was simple:
As we use with_state(state)
all the routers, merged/nested into your app router, must have type of Router<AppState>
.
That is:
root.rs
pub fn routes() -> Router<AppState> {
Router::new().merge(user_routes())
}
routes/user.rs
pub fn routes() -> Router<AppState> {
Router::new().route("/auth/register", post(register_user))
}
btw you forgot to put parentheses at the function call:
.nest("/api", routes())
Upvotes: 0
Reputation: 141
So I see your code here
#[tokio::main]
async fn main() {
if let Ok(db) = init_db().await {
let state = state::app_state::AppState::new(db);
let routes = routes::root::routes();
let router = Router::new()
.with_state(state)
.nest("/api", routes); //<-- Issue lies here
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, router).await.unwrap();
}
}
In Axum, when a request passes through the layers,
requests
|
v
+----- layer_three -----+
| +---- layer_two ----+ |
| | +-- layer_one --+ | |
| | | | | |
| | | handler | | |
| | | | | |
| | +-- layer_one --+ | |
| +---- layer_two ----+ |
+----- layer_three -----+
|
v
responses
The state values that you declare, if it is meant to pass through the other routes that you have specified, then it must be layered before the route.
Coming back to your code,
#[tokio::main]
async fn main() {
if let Ok(db) = init_db().await {
let state = state::app_state::AppState::new(db);
let routes = routes::root::routes();
let router = Router::new()
.nest("/api", routes)
.with_state(state); // I moved down the state layer
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, router).await.unwrap();
}
}
This should work in your case. If it doesn't let me know.
You can reference this answer to get how we pass state through routes. https://stackoverflow.com/a/79465348/11138368
Reference documentation for Axum: https://docs.rs/axum/latest/axum/middleware/
Upvotes: 0