Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Overlapping pathes an path precedence

If there are two routes that match the exact same path and handle the same http method then we’ll get a run-time panic.

#![allow(unused)]
fn main() {
async fn special_page() -> Html<String> {
    Html(String::from("Special"))
}

async fn other_page() -> Html<String> {
    Html(String::from("Other"))
}

fn create_router() -> Router {
    Router::new()
        .route("/special", get(special_page))
        .route("/special", get(other_page))
}
}

This is the panic:

Overlapping method route. Handler for `GET /special` already exists

Though it would be nicer if this was recognized during the compilation already, but the panic happens when we try to create the Router before the server starts, so the simplest test that calls the create_router function will catch this problem.


It is fine to map the same path for different HTTP methods (eg. one of them for GET and the other one for POST).


It can still happen that there are two routes that match a given path if one of them is a capturing variable. In that case the more specific matches. The order of the declaration does not matter.

Cargo.toml

[package]
name = "path-parameters"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
axum = "0.8.8"
tokio = { version = "1.50.0", features = ["full"] }

[dev-dependencies]
http-body-util = "0.1.3"
tower = { version = "0.5.3", features = ["util"] }

The whole example

use axum::{Router, extract::Path, response::Html, routing::get};

async fn main_page() -> Html<&'static str> {
    Html(
        r#"
    <a href="/foo">/foo</a><br>
    <a href="/special">/special</a><br>
    "#,
    )
}

async fn name_page(Path(name): Path<String>) -> Html<String> {
    Html(format!("Hello, {}!", name))
}

async fn special_page() -> Html<String> {
    Html(String::from("Special"))
}

//async fn other_page() -> Html<String> {
//    Html(String::from("Other"))
//}

fn create_router() -> Router {
    Router::new()
        .route("/", get(main_page))
        .route("/{name}", get(name_page))
        .route("/special", get(special_page))
    //.route("/special", get(other_page))
}

#[tokio::main]
async fn main() {
    let app = create_router();

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("listening on http://{}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

#[cfg(test)]
mod tests;

Tests

#![allow(unused)]
fn main() {
use axum::{body::Body, http::Request, http::StatusCode};
use http_body_util::BodyExt;
use tower::ServiceExt;

use super::*;

#[tokio::test]
async fn test_main_page() {
    let response = create_router()
        .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    let content_type = response.headers().get("content-type").unwrap();
    assert_eq!(content_type.to_str().unwrap(), "text/html; charset=utf-8");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();

    assert!(html.contains(r#"<a href="/foo">/foo</a><br>"#));
}

#[tokio::test]
async fn test_regular_page() {
    let response = create_router()
        .oneshot(Request::builder().uri("/foo").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    let content_type = response.headers().get("content-type").unwrap();
    assert_eq!(content_type.to_str().unwrap(), "text/html; charset=utf-8");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();

    assert_eq!(html, "Hello, foo!");
}

#[tokio::test]
async fn test_special_page() {
    let response = create_router()
        .oneshot(
            Request::builder()
                .uri("/special")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    let content_type = response.headers().get("content-type").unwrap();
    assert_eq!(content_type.to_str().unwrap(), "text/html; charset=utf-8");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();

    assert_eq!(html, "Special");
}
}