B. Common problems
cannot find garde in the crate root / use of unresolved module or unlinked crate schemars``
The plain derives (#[derive(Validate)], #[derive(JsonSchema)], #[derive(Serialize)])
generate code that refers to ::garde, ::schemars and ::serde, which only exist when your
crate depends on them. Use #[lesto::model] instead: it derives the same traits through lesto’s
re-exports. If you do want the plain derives, add the three crates to your manifest, at the
versions lesto uses (garde 0.23, schemars 1, serde 1).
warning: axum::Json<T> / axum::extract::Query<T> does not run T’s garde rules
The handler takes axum’s Json or Query, not lesto’s, and T has garde rules: the payload
reaches the handler without being validated. Import lesto::Json / lesto::Query (they are in
lesto::prelude). If skipping validation is what you want, put #[allow(deprecated)] on the
handler: lesto reports this as a deprecation because that is the one warning a macro can raise.
field x has no validation rule
garde requires an attribute on every field. Add #[garde(skip)] to the field or
#[garde(allow_unvalidated)] to the struct.
the trait bound Foo: Validate is not satisfied
You are using Json<Foo> or Query<Foo> with a type lacking #[derive(Validate)]. Add it, or if
Foo is an external type use lesto::axum::Json<Foo> (documented but without garde validation).
the trait bound Foo: JsonSchema is not satisfied
#[derive(JsonSchema)] is missing on a type that goes through the API (body, response, query,
path). For external types that do not implement it, wrap them in a newtype or use
#[schemars(with = "...")].
X cannot be a handler argument in this position
A body-consuming extractor (Json<T>, String, Bytes, Request) is not the last argument.
Move it to the end. The error points at the offending argument.
X is not an extractor (state type S)
The last argument is not usable as an extractor with that state. Typical causes:
- The payload type lacks a derive:
DeserializeandJsonSchemaforPath/Query/Json, plusValidateforJson/Query. - The extractor only works with a specific state and the handler is registered on an
App<S>with another one. The checks take the state from aState<T>argument if there is one, else from theApp<S>theroutes![]set is added to.#[lesto::get("/path", state = AppState)]pins it explicitly, which moves the error onto the offending argument.
lesto cannot document X as a handler argument / response
The type works at runtime but lesto does not know how to describe it in OpenAPI. For a response
type this is always an error; for an argument only with lesto’s strict-docs feature on (or
when registering through RouteSet::add instead of routes![]). For payloads, derive
JsonSchema (#[lesto::model]) and use Json<T>/Query<T>/Path<T>; for a custom extractor
or response type, add impl lesto::OperationInput for X {} / impl lesto::OperationOutput for X {}
(an empty impl documents nothing) or delegate to the wrapped extractor (chapter 8).
the trait bound fn(…) {handler}: Handler<_, _> is not satisfied
axum’s own error; lesto’s per-argument checks usually report the real cause just above it. If
it appears alone, check: the state in State<A> matches with_state(B); every handler in the
same routes![] uses the same state; the handler is async and Send (no std MutexGuard
held across an .await).
type annotations needed for App<_>``
The state type cannot be inferred (for instance an App with no State handlers, on which
.openapi() is called before with_state). Write App::<()>::new() or App::<AppState>::new().
routes![list]: cannot find type list in this scope
Older compilers say “expected type, found function list”. The handler is missing its #[lesto::get(...)] attribute: routes! looks for the metadata
type the attribute generates, and finds only the function. Add the attribute.
routes![users::list] cannot find the function
The handler must be pub (the generated type has the same visibility as the function) and the
module must expose it.
Swagger UI says “Could not render responses”
A schema is not an object. Recent lesto versions convert boolean schemas; if it happens with a
type of yours that implements JsonSchema by hand, return an object ({} instead of true).
The port is already in use
Address already in use: another process is listening on the same port. Change port or find the
process with lsof -i :8000.
Fields in the documentation are not in the struct’s order
They are: lesto guarantees declaration order for JSON keys, in the OpenAPI document and in
response bodies (struct fields, problem extensions, a serde_json::Map filled in order). It does
so by enabling serde_json and schemars’ preserve_order feature. Cargo unifies features
across a build, so every serde_json::Map in your application is insertion-ordered too, not
sorted by key. If a field is out of order, it is almost always a HashMap, which has no order to
keep: use indexmap::IndexMap (or BTreeMap for sorted keys).
/docs is blank behind a firewall
The documentation pages load Scalar and Swagger UI from jsDelivr (pinned versions, integrity
hashes). Where the CDN is unreachable, host the files yourself and point the pages at them:
App::scalar_script_url("https://cdn.internal/scalar.js") and
App::swagger_ui_base_url("https://cdn.internal/swagger-ui-dist") (the directory that holds
swagger-ui.css and swagger-ui-bundle.js).
Client generators complain about duplicate operationIds
Every operation gets {function}_{path}_{method} by default, which is unique per route. If you
set operation_id = "..." by hand, the values must be unique across the whole document, nested
apps included.
A Jwt route answers 500
The log says a `Jwt` argument needs a verifier: the app serving the route has no
App::oidc(..) (or App::protect(..)). Call .oidc(auth) on the app you serve; it covers the
nested apps too (chapter 9).
Every token answers 401 “The token is meant for another audience” (or “another issuer”)
The provider puts in aud (or iss) something other than what lesto expects. Decode one token
(the middle segment is base64 JSON) and compare: audiences([..]) must list a value of its aud;
iss must equal the issuer of the discovery document, which is why the discovery URL has to be
the provider’s own (behind a proxy, the provider must announce the public URL). The reason is
logged at debug by lesto: RUST_LOG=info,lesto=debug shows it (chapter 11).
an MCP resource must be a GET route (or prompt)
Resources and prompts are read, so only a GET route can be one. An operation that creates or
changes something is a tool: mcp = "tool" (chapter 16).
`X` cannot be the response of an MCP prompt
A route marked mcp = "prompt" builds the prompt’s messages: it returns lesto::mcp::Prompt (or
Result<Prompt, E>), for example Prompt::new().user(".."). A route that returns data for the
model to read is a resource: mcp = "resource".
panicked at 'two routes are exposed as the MCP tool x' (or resource, prompt)
Two routes of the same kind carry the same name = "x". Names must be unique per kind across
the app, nested apps included: rename one. Default names (the handler’s) never collide: when two
exposed handlers of a kind share a name, both use their operationId.
panicked at 'GET /path cannot be an MCP resource: its query parameter q is required'
A resource is read by its URI alone, so a required query parameter could never be filled. Make
it optional (Option<T>), or expose the route as a tool, whose arguments can carry it.
An MCP client says the server rejected the request with 403
The request came from a browser page whose origin is not on this machine, possibly your own
public domain. The endpoint refuses such Origins against DNS rebinding; list the page’s origin
in Mcp::allowed_origins([..]).