1use serde::{Deserialize, Serialize};
2use std::fmt::Debug;
3
4use indexmap::IndexMap;
5use serde_json::Value;
6
7use crate::core::{DiffResult, Either, MayBeRefCore, ReferenceDescriptor};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct OpenApi303Ref {
11 #[serde(rename = "$ref")]
12 pub reference: String,
13}
14
15impl ReferenceDescriptor for OpenApi303Ref {
16 fn reference(&self) -> &str {
17 &self.reference
18 }
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct OpenApi303RefDiff {
23 #[serde(rename = "$ref")]
24 pub reference: DiffResult<String>,
25}
26
27impl ReferenceDescriptor for OpenApi303RefDiff {
28 fn reference(&self) -> &str {
29 self.reference.get().expect("Reference diff cannot be null")
30 }
31}
32
33pub type MayBeRef303<T> = MayBeRefCore<T, OpenApi303Ref>;
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct OpenApi303 {
38 pub openapi: String,
39 pub info: Option<Info>,
40 pub servers: Option<Vec<Server>>,
41 pub paths: Option<IndexMap<String, MayBeRef303<Path>>>,
42 pub components: Option<Components>,
43 pub tags: Option<Vec<Tag>>,
46 pub external_docs: Option<ExternalDoc>,
47}
48
49impl OpenApi303 {
50 pub const fn id() -> &'static str {
51 "OpenApi303"
52 }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct Info {
58 pub title: Option<String>,
59 pub description: Option<String>,
60 pub terms_of_service: Option<String>,
61
62 pub contact: Option<Contact>,
63 pub license: Option<License>,
64
65 pub version: Option<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct Contact {
70 pub name: Option<String>,
71 pub url: Option<String>,
72 pub email: Option<String>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct License {
77 pub name: Option<String>,
78 pub url: Option<String>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Server {
83 pub url: Option<String>,
84 pub description: Option<String>,
85 pub variables: Option<IndexMap<String, ServerVariable>>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ServerVariable {
90 pub r#enum: Option<Vec<String>>,
91 pub default: Option<Value>,
92 pub description: Option<String>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct Components {
98 pub schemas: Option<IndexMap<String, MayBeRef303<Schema>>>,
99 pub responses: Option<IndexMap<String, MayBeRef303<Response>>>,
100 pub parameters: Option<IndexMap<String, MayBeRef303<Parameter>>>,
101 pub examples: Option<IndexMap<String, MayBeRef303<Example>>>,
102 pub request_bodies: Option<IndexMap<String, MayBeRef303<RequestBody>>>,
103 pub headers: Option<IndexMap<String, MayBeRef303<Header>>>,
104 pub security_schemes:
105 Option<IndexMap<String, MayBeRef303<SecurityScheme>>>,
106 pub links: Option<IndexMap<String, MayBeRef303<Link>>>,
107 }
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ExternalDoc {
112 pub url: Option<String>,
113 pub description: Option<String>,
114}
115
116#[derive(Serialize, Deserialize, Debug, Clone, Default)]
117#[serde(rename_all = "camelCase")]
118pub struct Parameter {
119 pub name: String,
120 pub r#in: String,
121
122 pub description: Option<String>,
123
124 pub required: Option<bool>,
125 pub deprecated: Option<bool>,
126 pub allow_empty_value: Option<bool>,
127
128 pub style: Option<String>,
129 pub explode: Option<bool>,
130 pub allow_reserved: Option<bool>,
131
132 pub schema: Option<MayBeRef303<Schema>>,
133
134 pub example: Option<Value>,
135 pub examples: Option<IndexMap<String, MayBeRef303<Value>>>,
136
137 pub content: Option<IndexMap<String, MediaType>>,
138
139 #[serde(flatten)]
140 pub custom_fields: IndexMap<String, Value>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct RequestBody {
145 pub description: Option<String>,
146 pub content: Option<IndexMap<String, MediaType>>,
147 pub required: Option<bool>,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct MediaType {
152 pub schema: Option<MayBeRef303<Schema>>,
153 pub example: Option<Value>,
154
155 pub examples: Option<IndexMap<String, MayBeRef303<Example>>>,
156 pub encoding: Option<IndexMap<String, Encoding>>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct Encoding {
162 pub content_type: Option<String>,
163 pub headers: Option<IndexMap<String, MayBeRef303<Header>>>,
164 pub style: Option<String>,
165 pub explode: Option<bool>,
166 pub allow_reserved: Option<bool>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct Link {
172 pub operation_ref: Option<String>,
173 pub operation_id: Option<String>,
174 pub parameters: Option<IndexMap<String, Value>>,
175 pub request_body: Option<Value>,
176 pub description: Option<String>,
177 pub server: Option<Server>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct Response {
182 pub description: Option<String>,
183 pub headers: Option<IndexMap<String, MayBeRef303<Header>>>,
184 pub content: Option<IndexMap<String, MediaType>>,
185
186 pub links: Option<IndexMap<String, MayBeRef303<Link>>>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct Example {
192 pub summary: Option<String>,
193 pub description: Option<String>,
194 pub value: Option<Value>,
195 pub external_value: Option<String>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct Discriminator {
201 pub property_name: Option<String>,
202 pub mapping: Option<IndexMap<String, String>>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct Xml {
207 pub name: Option<String>,
208 pub namespace: Option<String>,
209 pub prefix: Option<String>,
210 pub attribute: Option<bool>,
211 pub wrapped: Option<bool>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct SecurityScheme {
217 pub r#type: Option<String>,
218 pub description: Option<String>,
219 pub name: Option<String>,
220 pub r#in: Option<String>,
221 pub scheme: Option<String>,
222 pub bearer_format: Option<String>,
223 pub flows: Option<OAuthFlows>,
224 pub open_id_connect_url: Option<String>,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
228#[serde(rename_all = "camelCase")]
229pub struct OAuthFlows {
230 pub implicit: Option<OAuthFlow>,
231 pub password: Option<OAuthFlow>,
232 pub client_credentials: Option<OAuthFlow>,
233 pub authorization_code: Option<OAuthFlow>,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct OAuthFlow {
239 pub authorization_url: Option<String>,
240 pub token_url: Option<String>,
241 pub refresh_url: Option<String>,
242 pub scopes: Option<IndexMap<String, String>>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct Tag {
248 pub name: Option<String>,
249 pub description: Option<String>,
250 pub external_doc: Option<ExternalDoc>,
251}
252
253#[derive(Debug, Clone, Default, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct Schema {
256 pub title: Option<String>,
257 pub multiple_of: Option<f32>,
258 pub maximum: Option<f32>,
259 pub exclusive_maximum: Option<bool>,
260 pub minimum: Option<f32>,
261 pub exclusive_minimum: Option<bool>,
262 pub max_length: Option<usize>,
263 pub min_length: Option<usize>,
264 pub pattern: Option<String>,
265 pub max_items: Option<usize>,
266 pub min_items: Option<usize>,
267 pub unique_items: Option<bool>,
268 pub max_properties: Option<usize>,
269 pub min_properties: Option<usize>,
270 pub required: Option<Vec<String>>,
271 pub r#enum: Option<Vec<Value>>,
272
273 pub r#type: Option<Either<String, Vec<String>>>,
275 pub all_of: Option<Vec<MayBeRef303<Schema>>>,
276 pub one_of: Option<Vec<MayBeRef303<Schema>>>,
277 pub any_of: Option<Vec<MayBeRef303<Schema>>>,
278 pub not: Option<Vec<MayBeRef303<Schema>>>,
279
280 pub items: Box<Option<MayBeRef303<Schema>>>,
281 pub properties: Option<IndexMap<String, MayBeRef303<Schema>>>,
282 pub additional_properties: Option<Either<bool, MayBeRef303<Schema>>>,
283 pub description: Option<String>,
284 pub format: Option<String>,
285 pub default: Option<Value>,
286
287 pub nullable: Option<bool>,
288 pub discriminator: Option<Discriminator>,
289 pub read_only: Option<bool>,
290 pub write_only: Option<bool>,
291 pub xml: Option<Xml>,
292 pub external_docs: Option<ExternalDoc>,
293 pub example: Option<Value>,
294 pub deprecated: Option<bool>,
295
296 #[serde(flatten)]
297 pub custom_fields: IndexMap<String, Value>,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct Header {
303 pub description: Option<String>,
304
305 pub required: Option<bool>,
306 pub deprecated: Option<bool>,
307 pub allow_empty_value: Option<bool>,
308
309 pub style: Option<String>,
310 pub explode: Option<bool>,
311 pub allow_reserved: Option<bool>,
312
313 pub schema: Option<MayBeRef303<Schema>>,
314
315 pub example: Option<Value>,
316 pub examples: Option<IndexMap<String, MayBeRef303<Value>>>,
317
318 pub content: Option<IndexMap<String, MediaType>>,
319
320 #[serde(flatten)]
321 pub custom_fields: IndexMap<String, Value>,
322}
323
324#[derive(Debug, Clone, Default, Serialize, Deserialize)]
325#[serde(rename_all = "camelCase")]
326pub struct Operation {
327 pub tags: Option<Vec<String>>,
328 pub summary: Option<String>,
329 pub description: Option<String>,
330
331 pub external_docs: Option<ExternalDoc>,
332
333 pub operation_id: Option<String>,
334
335 pub parameters: Option<Vec<MayBeRef303<Parameter>>>,
336
337 pub responses: Option<IndexMap<String, MayBeRef303<Response>>>,
338
339 pub request_body: Option<MayBeRef303<RequestBody>>,
340 pub servers: Option<Vec<Server>>,
341
342 pub security: Option<Vec<IndexMap<String, Vec<String>>>>,
343
344 pub deprecated: Option<bool>,
347}
348
349#[derive(Debug, Clone, Default, Serialize, Deserialize)]
350pub struct Path {
351 pub get: Option<Operation>,
352 pub put: Option<Operation>,
353 pub post: Option<Operation>,
354 pub delete: Option<Operation>,
355 pub options: Option<Operation>,
356 pub head: Option<Operation>,
357 pub patch: Option<Operation>,
358 pub trace: Option<Operation>,
359
360 pub servers: Option<Vec<Server>>,
361 pub parameters: Option<Vec<MayBeRef303<Parameter>>>,
362
363 pub summary: Option<String>,
364 pub description: Option<String>,
365}
366
367#[cfg(test)]
368mod tests {
369 use crate::core::Either;
370 use crate::schemas::openapi310::schema::*;
371
372 #[test]
373 fn check_operation() {
374 let op_def = r#"{
375 "post": {
376 "tags": ["Nodes"],
377 "summary": "Export Xlsx Template",
378 "description": "Generate XLSX-template for aggregated node data editing",
379 "operationId": "export_xlsx_template_api_v2_nodes__path__template_generate__post",
380 "parameters": [
381 {
382 "required": true,
383 "schema": { "title": "Path", "type": "string" },
384 "name": "path",
385 "in": "path"
386 },
387 {
388 "required": false,
389 "schema": { "title": "Update Sender", "type": "string" },
390 "name": "update_sender",
391 "in": "query"
392 },
393 {
394 "required": false,
395 "schema": { "title": "Force", "type": "boolean", "default": false },
396 "name": "force",
397 "in": "query"
398 },
399 {
400 "required": false,
401 "schema": { "title": "Compound Amount", "type": "integer" },
402 "name": "compound_amount",
403 "in": "query"
404 },
405 {
406 "required": false,
407 "schema": {
408 "allOf": [{ "$ref": "/components/schemas/ExportFmt" }],
409 "default": "xlsx"
410 },
411 "name": "export_format",
412 "in": "query"
413 }
414 ],
415 "requestBody": {
416 "content": {
417 "application/json": {
418 "schema": {
419 "$ref": "/components/schemas/Body_export_xlsx_template_api_v2_nodes__path__template_generate__post"
420 }
421 }
422 }
423 },
424 "responses": {
425 "200": {
426 "description": "Successful Response",
427 "content": {
428 "application/json": { "schema": {} },
429 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {}
430 }
431 },
432 "422": {
433 "description": "Validation Error",
434 "content": {
435 "application/json": {
436 "schema": { "$ref": "/components/schemas/HTTPValidationError" }
437 }
438 }
439 }
440 },
441 "security": [{ "OAuth2PasswordBearer": [] }]
442 }
443 }"#;
444
445 let _: Path = serde_json::from_str(op_def).unwrap();
446 }
447
448 #[test]
449 fn check_schema_additional_properties() {
450 let op_def = r#"{
451 "title": "AdditionalProperties",
452 "type": "object",
453 "additionalProperties": {
454 "$ref": "/components/schemas/AdditionalProperties"
455 }
456 }"#;
457
458 let op: Schema = serde_json::from_str(op_def).unwrap();
459 assert!(matches!(op.additional_properties, Some(Either::Right(_))));
460
461 let op_def = r#"{
462 "title": "AdditionalProperties",
463 "type": "object",
464 "additionalProperties": false
465 }"#;
466
467 let op: Schema = serde_json::from_str(op_def).unwrap();
468 assert!(matches!(op.additional_properties, Some(Either::Left(_))));
469
470 let sc_def = r#"
471 {
472 "type": "object",
473 "discriminator": { "propertyName": "type" },
474 "properties": {
475 "type": {
476 "type": "string",
477 "description": "The type of context being attached to the entity.",
478 "enum": ["link", "image"]
479 }
480 },
481 "required": ["type"]
482 }
483 "#;
484 let op: Schema = serde_json::from_str(sc_def).unwrap();
485 assert!(matches!(op.discriminator, Some(_)))
486 }
487
488 #[test]
489 fn check_strange_thing() {
490 let op_def = r#"
491 {
492 "description": "Response",
493 "content": {
494 "application/json": {
495 "schema": {
496 "allOf": [
497 {
498 "type": "object",
499 "properties": {
500 "client_id": {
501 "type": "string"
502 },
503 "client_secret": {
504 "type": "string"
505 },
506 "webhook_secret": {
507 "type": [
508 "string",
509 "null"
510 ]
511 },
512 "pem": {
513 "type": "string"
514 }
515 },
516 "required": [
517 "client_id",
518 "client_secret",
519 "webhook_secret",
520 "pem"
521 ],
522 "additionalProperties": true
523 }
524 ]
525 },
526 "examples": {
527 }
528 }
529 }
530 }
531 "#;
532
533 let source_de = &mut serde_json::Deserializer::from_str(op_def);
534 let result: Result<Response, _> =
535 serde_path_to_error::deserialize(source_de);
536 let _ = result.map_err(|err| {
537 let path = err.path().to_string();
538 dbg!(path, err)
539 });
540 }
541
542 #[test]
543 fn check_additional_props() {
544 let schema_def = r#"
545 {
546 "type": "array",
547 "items": {
548 "maxItems": 4,
549 "minItems": 4,
550 "type": "array",
551 "items": {
552 "type": "string"
553 }
554 }
555 }
556 "#;
557
558 let _: Schema = serde_json::from_str(schema_def).unwrap();
559 }
560}