{"id":"7381af064cbb1253","repo":"tokio-rs/axum","slug":"missing-from-request-via","errorCode":null,"errorMessage":"missing `#[from_request(via(...))]`","messagePattern":"missing `#\\[from_request\\(via\\(\\.\\.\\.\\)\\)\\]`","errorType":"validation","errorClass":"compile_error","httpStatus":null,"severity":"error","filePath":"axum-macros/src/from_request/mod.rs","lineNumber":220,"sourceCode":"                    state_from_via(&ident, via).map(State::Custom)\n                })()\n                .unwrap_or_else(|| State::Default(syn::parse_quote!(S))),\n            };\n\n            match (via.map(second), rejection) {\n                (Some(via), rejection) => impl_enum_by_extracting_all_at_once(\n                    &ident,\n                    variants,\n                    &via,\n                    rejection.map(second).as_ref(),\n                    &state,\n                    tr,\n                ),\n                (None, Some((rejection_kw, _))) => Err(syn::Error::new_spanned(\n                    rejection_kw,\n                    \"cannot use `rejection` without `via`\",\n                )),\n                (None, _) => Err(syn::Error::new(\n                    Span::call_site(),\n                    \"missing `#[from_request(via(...))]`\",\n                )),\n            }\n        }\n        _ => Err(syn::Error::new_spanned(item, \"expected `struct` or `enum`\")),\n    }\n}\n\nfn parse_single_generic_type_on_struct(\n    generics: syn::Generics,\n    fields: &syn::Fields,\n    tr: Trait,\n) -> syn::Result<Option<Ident>> {\n    if let Some(where_clause) = generics.where_clause {\n        return Err(syn::Error::new_spanned(\n            where_clause,\n            format_args!(\"#[derive({tr})] doesn't support structs with `where` clauses\"),","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/tokio-rs/axum/blob/c9a911b7999de50e9e5023942ca072e9725ae943/axum-macros/src/from_request/mod.rs#L202-L238","documentation":"Thrown by `#[derive(FromRequest)]` / `#[derive(FromRequestParts)]` when applied to an `enum` that is missing the required `#[from_request(via(...))]` container attribute. For enums the macro cannot extract field-by-field (there is no fixed set of fields), so it _requires_ a `via(SomeWrapper)` to delegate extraction through a wrapper type (see the `(None, _)` arm at from_request/mod.rs:220). The same code path also fires when `rejection(...)` is used without `via(...)` (the adjacent arm at line 216 gives a different message). Reproducible with `tests/from_request/fail/enum_no_via.rs`.","triggerScenarios":"Annotated an enum (not a struct) with `#[derive(FromRequest)]` or `#[derive(FromRequestParts)]` and supplied neither `via(...)` nor a valid `rejection(...)` paired with `via(...)`. Specifically the match at line 207 hits the `(None, _)` arm because `via.map(second)` is `None`. Also reachable by writing `#[from_request(rejection = Foo)]` on an enum without `via` — but that hits line 216 and reports \"cannot use `rejection` without `via`\" instead.","commonSituations":"Treating an enum like a struct and assuming the derive will generate field extraction. Using an enum to model several extractor shapes (e.g. an API-error enum or a sum-type extractor) without supplying a wrapper that already implements `FromRequest`. Copy-pasting a struct's `#[derive(FromRequest)]` onto an enum during a refactor. Confusing `FromRequest` with serde's `Deserialize`, where enums work out of the box.","solutions":["Add a `via(...)` attribute naming a wrapper whose generic impl already implements FromRequest/FromRequestParts, e.g. `#[from_request(via(Json))]` if the enum is the body type.","If the enum was meant to be extracted from JSON, switch to `#[derive(serde::Deserialize)]` and accept it as `Json<YourEnum>` in the handler instead of deriving `FromRequest`.","Convert the enum into a struct so the macro can extract each field individually via the default per-field code path (no `via` required for structs).","If you genuinely need a custom enum extractor, implement `FromRequest`/`FromRequestParts` by hand instead of using the derive."],"exampleFix":"// before\n#[derive(FromRequest, Clone)]\nenum Extractor {}\n\n// after\n#[derive(FromRequest, Clone)]\n#[from_request(via(Json))]\nenum Extractor {\n    A(String),\n    B(i32),\n}","handlingStrategy":"type-guard","validationCode":"// There's no runtime check — the macro only accepts enums with `via(...)`.\n// Establish a project convention: enums that are request bodies MUST be\n// wrapped at the call site, e.g. Json<MyEnum>, and never derive FromRequest\n// directly. Add a clippy-style lint comment:\n//\n// // lint: enums require #[from_request(via(...))] when deriving FromRequest\n// #[derive(FromRequest)]\n// #[from_request(via(Json))]\n// enum Body { ... }\n//\n// CI grep to enforce it:\n//   rg --type rust '#\\[derive\\([^)]*FromRequest' --files-with-matches | \\\n//     xargs rg -L 'from_request\\(via' && echo 'enum missing via' || true","typeGuard":"// Type-level marker that documents which enums are safe to derive FromRequest on.\n// Only implement it for enums that carry #[from_request(via(...))].\npub trait DeriveFromRequestSafe {}\n\n// Example: only the Json-backed enum is marked safe.\nimpl DeriveFromRequestSafe for MyJsonEnum {}\n\n// Generic helper that rejects anything else at compile time:\npub fn accept<E: DeriveFromRequestSafe>(e: E) { let _ = e; }","tryCatchPattern":null,"preventionTips":["Prefer deriving `serde::Deserialize` and accepting the enum via `Json<MyEnum>` rather than `#[derive(FromRequest)]`.","If you must derive `FromRequest` on an enum, always pair it with `#[from_request(via(Wrapper))]` from day one.","During review, treat any `#[derive(FromRequest)]` directly above `enum` without a matching `via(...)` as a blocker.","Remember structs do NOT require `via`, so the error is enum-specific — consider converting to a struct if field extraction is what you wanted."],"tags":["rust","axum","proc-macro","from-request","enum","derive","compile-time"],"analyzedSha":"c9a911b7999de50e9e5023942ca072e9725ae943","analyzedAt":"2026-08-06T01:08:56.656Z","schemaVersion":2}