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

Wildcard Path Parameters

We can put a star * in-front of the name we use for the path. That means it will accept slashed as well and it will capture any path.

This can be useful if you’d like to map a filesystem, or if for some reason a / is an acceptable value in the specific field. For example git allows branch-names to contain a slash /.

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="/root/">/root/</a><br>
    <a href="/root/etc">/root/etc</a><br>
    <a href="/root/var/log/nginx.log">/root/var/log/nginx.log</a><br>
    "#,
    )
}

async fn access_path(Path(name): Path<String>) -> Html<String> {
    Html(format!("Path: <b>{}</b>", name))
}

async fn handle_root() -> Html<String> {
    Html(String::from("Root"))
}

fn create_router() -> Router {
    Router::new()
        .route("/", get(main_page))
        .route("/root/{*name}", get(access_path))
        .route("/root/", get(handle_root))
}

#[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="/root/var/log/nginx.log">/root/var/log/nginx.log</a><br>"#));
}

#[tokio::test]
async fn test_root() {
    let response = create_router()
        .oneshot(
            Request::builder()
                .uri("/root/")
                .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, "Root");
}

#[tokio::test]
async fn test_root_etc() {
    let response = create_router()
        .oneshot(
            Request::builder()
                .uri("/root/etc")
                .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, "Path: <b>etc</b>");
}

#[tokio::test]
async fn test_root_file() {
    let response = create_router()
        .oneshot(
            Request::builder()
                .uri("/root/var/log/nginx.log")
                .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, "Path: <b>var/log/nginx.log</b>");
}
}