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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! Validates HTTP requests against an OpenAPI description.
//!
//! [`roas`](https://crates.io/crates/roas) parses a description and
//! checks that the *description* is well formed. This checks that a
//! *request* is what the description says it should be: the path is one
//! the description names, the method is one that path offers, every
//! required parameter arrived, each one is the type its Schema Object
//! declares, and the body is what the Request Body Object describes.
//!
//! ```
//! use roas_http_validator::{RequestView, Validator};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # let spec = serde_json::from_str(r#"{
//! # "openapi": "3.2.0",
//! # "info": { "title": "Pets", "version": "1.0.0" },
//! # "paths": { "/pets": { "get": { "operationId": "listPets", "parameters": [
//! # { "name": "limit", "in": "query", "schema": { "type": "integer", "maximum": 100 } }
//! # ] } } }
//! # }"#)?;
//! let validator = Validator::new(spec);
//!
//! let request = RequestView::new("GET", "/pets").with_query("limit=1000");
//! let report = validator.validate(&request)?;
//!
//! assert!(!report.is_valid());
//! assert_eq!(
//! report.errors[0].to_string(),
//! "query parameter \"limit\": 1000 is above maximum 100",
//! );
//! # Ok(()) }
//! ```
//!
//! ## Examples
//!
//! The repository carries three runnable ones: `validate` for the shape
//! of the whole crate, `axum_layer` for the same thing as middleware
//! (and for what buffering a body actually looks like), and
//! `client_check` for asking whether a call you are about to *make*
//! matches the description.
//!
//! ## Which request type
//!
//! None of them, and all of them. Rust has no single HTTP request type
//! to validate: `http::Request` comes closest, but it is generic over a
//! body that is usually a stream, and it is version-split — actix-web 4
//! is on `http` 0.2 while hyper 1, axum 0.8 and reqwest are on 1.x, so
//! their `HeaderMap`s are different types. Taking either one would shut
//! out half the ecosystem.
//!
//! So this crate takes [`RequestView`], the small set of things an
//! OpenAPI description actually talks about, and each framework gets a
//! [`ToRequestView`] impl behind its own feature:
//!
//! | Feature | Covers |
//! | --- | --- |
//! | `http` | `http::Request`, `http::request::Parts` — and so axum, warp, tonic, hyper |
//! | `actix-web` | `actix_web::HttpRequest` |
//! | `poem` | `poem::Request` |
//! | `salvo` | `salvo_core::http::Request` |
//! | `rocket` | `rocket::Request` |
//! | `reqwest` | `reqwest::Request` and its blocking twin — the client's side, for checking an outgoing call |
//!
//! The body is not part of that conversion. A framework body is a
//! stream, and validating one means buffering it — how much, and
//! whether at all, is the caller's decision, so the adapters convert
//! the head and [`RequestView::with_body`] takes the bytes. The one
//! exception is `reqwest`, where a non-streaming body is already bytes
//! in memory and there is nothing to buffer.
//!
//! ## Versions
//!
//! The interpreter is v3.2. Enable `v3_1`, `v3_0` or `v2` to accept a
//! description written to an older version: it is upconverted through
//! `roas`'s own migrations first, so there is one interpreter rather
//! than four.
//!
//! Numbers are compared as the decimals they were written as, on both
//! sides. The one limit is the format a
//! *description* is parsed from: JSON is exact throughout, while YAML
//! reads scalars through an `f64` before `serde_json` is involved, so a
//! fractional bound carrying more precision than a double is already
//! rounded when it arrives. Every integer survives either way.
//!
//! ## Media types it does not read itself
//!
//! JSON, `application/x-www-form-urlencoded` and `text/*` are built in.
//! Anything else — `multipart/form-data`, XML — is reported rather than
//! guessed at, and [`Options::decoder`] is the way in: the bytes become
//! a value and the Schema Object judges it like any other.
//!
//! Those two are a hook rather than more built-ins on purpose.
//! Multipart would mean owning a boundary parser and buffering file
//! uploads, which is exactly where this crate leaves buffering to the
//! caller. XML has no specified mapping onto a schema instance at all —
//! OpenAPI's XML Object is serialization metadata for code generators —
//! so any translation is a choice, and taking the caller's beats
//! inventing one.
//!
//! ## What it does not check yet
//!
//! Response validation and security requirements.
//!
//! Everything a check could not judge is reported rather than passed
//! over, so a request never looks valid because nothing looked at it:
//! [`ErrorKind::Unsupported`] for what is not implemented,
//! [`ErrorKind::Unchecked`] for a description this crate can read but
//! cannot apply faithfully. [`ValidationReport::unchecked`] separates
//! both from what the request definitely got wrong.
pub use Decoder;
pub use ;
pub use ;
pub use ;