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
//! 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.
//!
//! ## What it does not do yet
//!
//! Response validation, security requirements, `multipart/form-data`
//! bodies, and XML — and exact decimal arithmetic, which would need
//! `serde_json`'s `arbitrary_precision`: numbers are compared as the
//! IEEE-754 doubles they arrive as, and anything that would over-claim
//! on top of one is reported rather than assumed. Anything a check
//! could not judge is reported — split out by
//! [`ValidationReport::unchecked`] from what the request definitely got
//! wrong —
//! [`ErrorKind::Unsupported`] for what is not implemented yet,
//! [`ErrorKind::Unchecked`] for a description this crate can read but
//! cannot apply faithfully — rather than passed over, so a request
//! never looks valid because nothing looked at it.
pub use ;
pub use ;
pub use ;