1use crate::observability::ValidationMetrics;
19use crate::validator::ValidateRequest;
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22use std::hash::Hash;
23
24#[derive(Debug, Serialize, Deserialize)]
25pub struct OpenAPI {
26 pub openapi: String,
27 pub info: InfoObject,
28 #[serde(default)]
29 pub servers: Vec<ServerObject>,
30 pub paths: HashMap<String, PathItem>,
31 pub components: Option<ComponentsObject>,
32}
33
34#[derive(Debug, Serialize, Deserialize)]
35pub struct PathItem {
36 pub parameters: Option<Vec<Parameter>>, #[serde(flatten)]
38 pub operations: HashMap<String, PathBase>, #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub servers: Vec<ServerObject>, #[serde(flatten)]
42 pub extra: serde_yaml::Value, }
44
45macro_rules! require_non_empty {
46 ($field:expr, $msg:expr) => {
47 if $field.is_empty() {
48 return Err($msg.to_string());
49 }
50 };
51}
52
53impl OpenAPI {
54 pub fn yaml(contents: &str) -> Result<Self, serde_yaml::Error> {
55 serde_yaml::from_str(contents)
56 }
57
58 pub fn validator(&self, valid: impl ValidateRequest) -> Result<(), String> {
59 let metrics = ValidationMetrics::from_context(&valid.context());
60
61 let result = self.perform_validation(valid);
62
63 match &result {
64 Ok(_) => metrics.record_success(),
65 Err(err) => metrics.record_failure(err.clone()),
66 }
67
68 result
69 }
70
71 fn perform_validation(&self, valid: impl ValidateRequest) -> Result<(), String> {
72 require_non_empty!(self.openapi, "OpenAPI version is required");
73 require_non_empty!(self.info.title, "Title is required");
74 require_non_empty!(self.info.version, "Version is required");
75 require_non_empty!(self.paths, "Paths are required");
76 valid
77 .method(self)
78 .map_err(|e| format!("Method validation failed: {e}"))?;
79 valid
80 .path(self)
81 .map_err(|e| format!("Path validation failed: {e}"))?;
82 valid
83 .query(self)
84 .map_err(|e| format!("Query validation failed: {e}"))?;
85 valid
86 .body(self)
87 .map_err(|e| format!("Body validation failed: {e}"))?;
88 Ok(())
89 }
90}
91
92#[derive(Debug, Serialize, Deserialize)]
93pub struct SecurityRequirementObject {
94 #[serde(rename = "type", default)]
95 pub _type: String,
96 pub scheme: Option<String>,
97 pub description: Option<String>,
98}
99
100#[derive(Debug, Serialize, Deserialize)]
101pub struct InfoObject {
102 pub title: String,
103 pub description: Option<String>,
104 pub version: String,
105}
106
107#[derive(Debug, Serialize, Deserialize)]
108pub struct ServerObject {
109 pub url: String,
110 pub description: Option<String>,
111}
112
113#[derive(Debug, Serialize, Deserialize)]
114pub struct PathBase {
115 pub summary: Option<String>,
116 pub description: Option<String>,
117 #[serde(rename = "operationId")]
118 pub operation_id: Option<String>,
119 pub parameters: Option<Vec<Parameter>>,
120 #[serde(rename = "requestBody")]
121 pub request: Option<Request>,
122 #[serde(default)]
123 pub servers: Vec<ServerObject>,
124}
125
126#[derive(Debug, Serialize, Deserialize)]
127pub struct Parameter {
128 #[serde(rename = "$ref")]
129 pub r#ref: Option<String>,
130 pub name: Option<String>,
131 #[serde(rename = "in")]
132 pub r#in: Option<In>,
133 #[serde(default)]
134 pub required: bool,
135 pub description: Option<String>,
136 pub example: Option<serde_yaml::Value>,
137 #[serde(rename = "type")]
138 pub r#type: Option<TypeOrUnion>,
139 pub r#enum: Option<Vec<serde_yaml::Value>>,
140 pub pattern: Option<String>,
141 pub schema: Option<Box<Schema>>,
142 #[serde(flatten)]
143 pub extra: HashMap<String, serde_yaml::Value>,
144}
145
146#[derive(Debug, Serialize, Deserialize)]
147pub struct Schema {
148 #[serde(rename = "type")]
149 pub r#type: Option<TypeOrUnion>,
150 pub format: Option<Format>,
151 pub title: Option<String>,
152 pub description: Option<String>,
153 pub r#enum: Option<Vec<serde_yaml::Value>>,
154 pub pattern: Option<String>,
155 pub properties: Option<HashMap<String, Properties>>,
156 pub example: Option<serde_yaml::Value>,
157 pub examples: Option<Vec<String>>,
158 #[serde(rename = "$ref")]
159 pub r#ref: Option<String>,
160 #[serde(rename = "allOf")]
161 pub all_of: Option<Vec<ComponentProperties>>,
162 #[serde(rename = "oneOf")]
163 pub one_of: Option<Vec<ComponentProperties>>,
164 pub items: Option<Box<Schema>>,
165 #[serde(default)]
166 pub required: Vec<String>,
167 #[serde(rename = "minItems")]
168 pub min_items: Option<u64>,
169 #[serde(rename = "maxItems")]
170 pub max_items: Option<u64>,
171 #[serde(rename = "minLength")]
172 pub min_length: Option<u64>,
173 #[serde(rename = "maxLength")]
174 pub max_length: Option<u64>,
175 pub minimum: Option<f64>,
176 pub maximum: Option<f64>,
177}
178
179#[derive(Debug, Serialize, Deserialize)]
180pub struct BaseContent {
181 pub schema: Schema,
182}
183
184#[derive(Debug, Serialize, Deserialize)]
185pub struct Request {
186 #[serde(default)]
187 pub required: bool,
188 pub content: HashMap<String, BaseContent>,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
192pub enum SchemaOption {
193 OneOf,
194 AllOf,
195}
196
197#[derive(Debug, Serialize, Deserialize)]
198pub struct ComponentSchemaBase {
199 pub title: Option<String>,
200 pub description: Option<String>,
201 #[serde(rename = "type")]
202 pub r#type: Option<TypeOrUnion>,
203 pub items: Option<Box<ComponentSchemaBase>>,
204 pub properties: Option<HashMap<String, Properties>>,
205 #[serde(default)]
206 pub required: Vec<String>,
207 #[serde(rename = "allOf")]
208 pub all_of: Option<Vec<ComponentProperties>>,
209 #[serde(rename = "oneOf")]
210 pub one_of: Option<Vec<ComponentProperties>>,
211 #[serde(rename = "minItems")]
212 pub min_items: Option<u64>,
213 #[serde(rename = "maxItems")]
214 pub max_items: Option<u64>,
215}
216
217#[derive(Debug, Serialize, Deserialize)]
218pub struct ComponentProperties {
219 #[serde(rename = "type")]
220 pub r#type: Option<TypeOrUnion>,
221 pub description: Option<String>,
222 #[serde(default)]
223 pub properties: HashMap<String, Properties>,
224 #[serde(rename = "$ref")]
225 pub r#ref: Option<String>,
226}
227
228#[derive(Debug, Serialize, Deserialize)]
229pub struct Properties {
230 #[serde(rename = "type")]
231 pub r#type: Option<TypeOrUnion>,
232 pub description: Option<String>,
233 pub format: Option<Format>,
234 pub example: Option<serde_yaml::Value>,
235 pub pattern: Option<String>,
236 #[serde(rename = "minLength")]
237 pub min_length: Option<u64>,
238 #[serde(rename = "maxLength")]
239 pub max_length: Option<u64>,
240 #[serde(rename = "minItems")]
241 pub min_items: Option<u64>,
242 #[serde(rename = "maxItems")]
243 pub max_items: Option<u64>,
244 pub minimum: Option<f64>,
245 pub maximum: Option<f64>,
246 pub items: Option<Box<Properties>>,
247 pub properties: Option<HashMap<String, Properties>>,
248 #[serde(default)]
249 pub required: Vec<String>,
250 pub r#enum: Option<Vec<serde_yaml::Value>>,
251}
252
253#[derive(Debug, Serialize, Deserialize)]
254pub struct ComponentsObject {
255 #[serde(default)]
256 pub schemas: HashMap<String, ComponentSchemaBase>,
257 #[serde(default)]
258 pub parameters: HashMap<String, Parameter>,
259 #[serde(rename = "requestBodies", default)]
260 pub request_bodies: HashMap<String, Request>,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
264#[serde(rename_all(deserialize = "lowercase"))]
265pub enum Type {
266 Object,
267 String,
268 Integer,
269 Number,
270 Array,
271 Boolean,
272 Null,
273 Binary,
274 Base64,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278#[serde(untagged)]
279pub enum TypeOrUnion {
280 Single(Type),
281 Union(Vec<Type>),
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
285#[serde(rename_all(deserialize = "lowercase"))]
286pub enum In {
287 Query,
288 Header,
289 Path,
290 Cookie,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
294#[serde(rename_all(deserialize = "lowercase"))]
295pub enum Format {
296 URI,
297 #[serde(rename = "uri-reference")]
298 URIReference,
299 Regex,
300 Email,
301 Time,
302 Date,
303 #[serde(rename = "date-time")]
304 DateTime,
305 UUID,
306 Hostname,
307 IPV4,
308 IPV6,
309 Password,
310 #[serde(rename = "json-pointer")]
311 JsonPointer,
312 Binary,
313 #[serde(rename = "external-ip")]
314 ExternalIP,
315 #[serde(rename = "int32")]
316 Int32,
317 #[serde(rename = "int64")]
318 Int64,
319 Svg,
320 #[serde(rename = "url")]
321 Url,
322 #[serde(other)]
323 Unknown,
324}