{"record":{"id":"fe0a890768da6e03","repo":"tokio-rs/axum","slug":"typed-paths-for-unit-structs-cannot-contain-captur","errorCode":null,"errorMessage":"Typed paths for unit structs cannot contain captures","messagePattern":"Typed paths for unit structs cannot contain captures","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"axum-macros/src/typed_path.rs","lineNumber":290,"sourceCode":"}\n\nfn simple_pluralize(count: usize, word: &str) -> String {\n    if count == 1 {\n        format!(\"{count} {word}\")\n    } else {\n        format!(\"{count} {word}s\")\n    }\n}\n\nfn expand_unit_fields(\n    ident: &syn::Ident,\n    path: &LitStr,\n    rejection: Option<&syn::Path>,\n) -> syn::Result<TokenStream> {\n    for segment in parse_path(path)? {\n        match segment {\n            Segment::Capture(_, span) => {\n                return Err(syn::Error::new(\n                    span,\n                    \"Typed paths for unit structs cannot contain captures\",\n                ));\n            }\n            Segment::Static(_) => {}\n        }\n    }\n\n    let typed_path_impl = quote_spanned! {path.span()=>\n        #[automatically_derived]\n        impl ::axum_extra::routing::TypedPath for #ident {\n            const PATH: &'static str = #path;\n        }\n    };\n\n    let display_impl = quote_spanned! {path.span()=>\n        #[automatically_derived]\n        impl ::std::fmt::Display for #ident {","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/tokio-rs/axum/blob/151cd5c12325373b86daf405a6afc0a0086a6706/axum-macros/src/typed_path.rs#L272-L308","documentation":"Thrown by #[derive(TypedPath)] when the struct is a unit struct (struct Foo;) and the route contains a capture (typed_path.rs:290). expand dispatches on Fields::Unit to expand_unit_fields, which iterates the parsed path segments; any Segment::Capture (a {...} or {*...} parameter) is rejected because a unit struct has no fields to store the captured value. Only fully static routes are valid for unit structs.","triggerScenarios":"#[typed_path(\"/users/{id}\")] struct MyPath; — a unit struct whose route has one or more {param} or {*param} captures. Each capture segment hits the Segment::Capture(_, span) arm and returns the error.","commonSituations":"Declaring a typed path for a fixed route but leaving a stray {param} in it. Converting a field-bearing TypedPath struct to a unit struct without simplifying the route. Confusing a unit typed path (route match only) with a parameterized one.","solutions":["Remove all {...} captures from the route so it is fully static, e.g. #[typed_path(\"/health\")] struct HealthRoute;.","If you need the captured value, change the unit struct to a named struct with a field per capture, or a tuple struct with one field per capture (counts must match).","For a catch-all/wildcard route, use a named/tuple struct with the matching field rather than a unit struct."],"exampleFix":"// before\n#[derive(TypedPath, Deserialize)]\n#[typed_path(\"/users/{id}\")]\nstruct MyPath;\n\n// after (option A: drop the capture, keep unit struct)\n#[derive(TypedPath)]\n#[typed_path(\"/users\")]\nstruct UsersIndex;\n\n// after (option B: keep the capture, add a field)\n#[derive(TypedPath, Deserialize)]\n#[typed_path(\"/users/{id}\")]\nstruct MyPath {\n    id: u32,\n}","handlingStrategy":"validation","validationCode":"// Safe invocation pattern: unit structs (struct X;) need a fully static route — no captures.\n#[derive(TypedPath)]\n#[typed_path(\"/health\")] // no {param}\nstruct Health;\n\n// If the route has captures, do NOT use a unit struct; match each capture with a field:\n#[derive(TypedPath, Deserialize)]\n#[typed_path(\"/users/{id}\")]\nstruct UserShow {\n    id: u32,\n}\n\n// Tuple-struct variant (one field per capture, in order):\n#[derive(TypedPath, Deserialize)]\n#[typed_path(\"/users/{id}\")]\nstruct UserShow(u32);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["For unit struct typed paths, keep the route fully static: no {param}, no {*wildcard}.","When a route gains a capture, change the struct from unit to named/tuple with one field per capture (counts must match).","Review routes during refactors: turning a parameterized struct into a unit struct silently breaks if the route still has captures.","Treat unit typed paths as 'match-only' routes (e.g. fixed endpoints like /health, /version)."],"tags":["axum","axum-macros","typed-path","routing","unit-struct","proc-macro","compile-error"],"backgroundTag":null,"analyzedSha":"151cd5c12325373b86daf405a6afc0a0086a6706","analyzedAt":"2026-08-11T16:57:37.422Z","contentChangedAt":"2026-08-11T16:57:37.422Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}