Skip to content

v0.9.9

Latest

Choose a tag to compare

@RomanEmreis RomanEmreis released this 29 Aug 13:04
· 2 commits to main since this release
52e092c

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

  • Validate and Valid<E> - one blanket impl covers every payload extractor, with ValidJson<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<..>> and Result<Valid<..>, Error> compose as around any other extractor.
  • ValidationError - the error implementations accumulate into: push / push_message with into_result() to collect everything, field() / message() to fail fast, merge / merge_at to fold one into another. Answers 400, which with_status overrides. Under problem-details it renders as RFC 9457 with an errors extension per field, picked up by use_problem_details with no extra wiring.
  • Invalid<E> - the newtype that lets a foreign error type satisfy Validate::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-written Validate.
  • #[derive(Validate)] (feature validation-derive) - length(min, max, equal), range(min, max), nested and custom = "path::to::fn", each with an optional message; the container takes schema = "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 under parent.child, collections under parent[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 an f32, format: "double" for an f64. Every float previously published as a bare number.
  • volga::auth::permission is re-exported alongside role, roles, permissions and predicate (#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 suggested permissions, 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