Highlights
Input validation, with volga knowing none of the rules. Anything past serde - a non-empty key, a bounded page size, a range - used to be re-written at the top of every handler, and every service invented its own shape for the failure. The Validate trait is where those rules go now, and Valid<E> is the extractor that runs them: it wraps Json, Query, Form or NamedPath, calls validate() on what came out, and either hands it to the handler or answers the request. The handler is never entered with a payload that failed. Both are unconditional - no feature flag, no new dependency (#218).
The rules are published as well as enforced. #[derive(Validate)] (feature validation-derive) writes out the same validate() you would have written, and the bounds it reads reach the OpenAPI schema as minLength, minItems, minProperties, minimum and maximum - each landing on the property or parameter the declaring extractor described. What cannot be published exactly is left out rather than rounded, so a documented constraint and an enforced one cannot drift apart.
Added
ValidateandValid<E>- one blanket impl covers every payload extractor, withValidJson<T>/ValidQuery<T>/ValidForm<T>/ValidPath<T>as the shorthand. The wrapper forwards the inner extractor's payload source and its OpenAPI description, so a body and a query struct can both be validated in one handler and neither disappears from the spec.Option<Valid<..>>andResult<Valid<..>, Error>compose as around any other extractor.ValidationError- the error implementations accumulate into:push/push_messagewithinto_result()to collect everything,field()/message()to fail fast,merge/merge_atto fold one into another. Answers400, whichwith_statusoverrides. Underproblem-detailsit renders as RFC 9457 with anerrorsextension per field, picked up byuse_problem_detailswith no extra wiring.Invalid<E>- the newtype that lets a foreign error type satisfyValidate::Error. This is the whole of volga's relationship with validation crates: no dependency, no feature flag, no blanket impl over anyone else's trait - just room to put one behind a hand-writtenValidate.#[derive(Validate)](featurevalidation-derive) -length(min, max, equal),range(min, max),nestedandcustom = "path::to::fn", each with an optionalmessage; the container takesschema = "path::to::fn"for a rule spanning two fields, which is why the derive stays sugar over the trait rather than a replacement. All failures are collected, and every bound and message is rendered at expansion time, so a failing check allocates nothing. An attribute the derive could only half-honour is a compile error rather than a half-applied rule.- Failures name what the client sent -
#[serde(rename)]and#[serde(rename_all)]are read off the type,#[validate(rename)]overrides them for the message, and a#[serde(flatten)]field reports at the level it arrived on. Nested failures merge underparent.child, collections underparent[0].child.
Fixed
- Query parameters vanished from the OpenAPI spec for any handler whose query struct had a typed optional field (
Option<String>,Option<u32>, ...) - one field the schema probe could not read failed the probe for the whole struct, so the operation was published with no parameters at all rather than with one missing.Option<()>happened to work, which is why the tests did not catch it. - Number schemas record the width they were inferred from:
format: "float"for anf32,format: "double"for anf64. Every float previously published as a barenumber. volga::auth::permissionis re-exported alongsiderole,roles,permissionsandpredicate(#216). The singular form was the only one missing, so copying the list of built-in authorizers out of the documentation did not compile - and the compiler suggestedpermissions, which is a different function.
Upgrading
Nothing to do. Every addition is additive, no public item changed shape, and validation-derive is off by default (it is the only part that pulls a dependency - volga-macros).
The one visible change to existing output is the format on number schemas: a float field that published as {"type": "number"} now publishes {"type": "number", "format": "float"} or "double". Both are registered OpenAPI formats, and clients generated from the old spec keep working.
Full Changelog: 0.9.8...0.9.9