1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! The `Validated<T>` Axum extractor (A5).
//!
//! `Validated<T>` is the high-level request DX type: it combines JSON body
//! extraction, deserialization, and validation into a single Axum
//! [`FromRequest`] extractor. When a controller takes
//! `input: Validated<StoreLinkRequest>`, the payload is extracted and
//! validated before the handler runs — the handler may trust that
//! validation succeeded.
//!
//! ## Validation is the trust boundary
//!
//! At the point a handler receives `Validated<T>`, the `T` has passed
//! [`validator::Validate::validate`]. The handler does not re-validate.
//! This is the "validation is the trust boundary" principle (PROGRAM.md
//! "Request validation").
//!
//! **Validation must not imply authorization.** A validated request is not
//! an authorized request. Authorization is a separate, explicit step (A9
//! Policies). This invariant is permanent (PROGRAM.md "Architecture rules").
//!
//! ## Error responses
//!
//! Extraction and validation failures produce RFC 9457 `Problem` responses
//! (422 for validation failures, 400/415 for extraction failures). This
//! reuses the existing `arcature-api` validation infrastructure — no new
//! API error format (PROGRAM.md "Validation errors").
//!
//! The browser/Inertia validation path (redirect-back-with-errors,
//! preserve field errors and safe old input) is a hook for A6; A5
//! delivers the API 422 Problem path.
//!
//! ## Implementation
//!
//! `Validated<T>` delegates to [`arcature_api::ValidatedJson<T>`], the
//! existing extractor that wraps `axum::Json<T>` + `validator::Validate`
//! and maps rejections/validation failures to `Problem` responses. This
//! satisfies "Do NOT build a homemade validation engine" — the
//! `validator` crate is the engine, `arcature-api` is the integration.
//!
//! ## Example
//!
//! ```ignore
//! use arcature::{Deserialize, Validated, Validate};
//! use arcature::Json;
//!
//! #[request]
//! pub struct StoreLinkRequest {
//! #[validate(url)]
//! pub url: String,
//! #[validate(length(min = 1, max = 120))]
//! pub title: String,
//! }
//!
//! async fn store(input: Validated<StoreLinkRequest>) -> Result<Json<Link>> {
//! let data = input.into_inner();
//! // ... create link from `data` ...
//! Ok(Json(link))
//! }
//! ```
use FromRequest;
use Response;
use DeserializeOwned;
/// A validated JSON request body extractor.
///
/// Wraps [`arcature_api::ValidatedJson<T>`]: extracts and deserializes the
/// JSON body, validates `T` with [`validator::Validate`], and maps
/// rejections/validation failures to RFC 9457 [`Problem`](arcature_api::Problem)
/// responses (`application/problem+json`).
///
/// `T` must implement [`serde::de::DeserializeOwned`] (for the JSON body)
/// and [`validator::Validate`] (for the rules). The payload is validated
/// exactly once.
///
/// Use [`Validated::into_inner`] to extract the validated value in the
/// handler.
;