1use indexmap::IndexMap;
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
7#[serde(rename_all = "lowercase")]
8pub enum Scheme {
9 Http,
10 Https,
11 Ws,
12 Wss,
13}
14
15impl Scheme {
16 pub fn as_str(&self) -> &'static str {
17 match self {
18 Scheme::Http => "http",
19 Scheme::Https => "https",
20 Scheme::Ws => "ws",
21 Scheme::Wss => "wss",
22 }
23 }
24}
25
26impl Default for Scheme {
27 fn default() -> Self {
28 Scheme::Http
29 }
30}
31
32#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
34#[serde(rename_all = "camelCase")]
35pub struct OpenAPI {
36 pub swagger: String,
38 pub info: Info,
39 #[serde(skip_serializing_if = "Option::is_none")]
42 pub host: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 #[serde(rename = "basePath")]
46 pub base_path: Option<String>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub schemes: Option<Vec<Scheme>>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub consumes: Option<Vec<String>>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub produces: Option<Vec<String>>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub tags: Option<Vec<Tag>>,
57 pub paths: IndexMap<String, PathItem>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub definitions: Option<IndexMap<String, Schema>>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub parameters: Option<IndexMap<String, Parameter>>,
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub responses: Option<IndexMap<String, Response>>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub security_definitions: Option<IndexMap<String, Security>>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub security: Option<Vec<IndexMap<String, Vec<String>>>>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub external_docs: Option<Vec<ExternalDoc>>,
73 #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
74 pub extensions: IndexMap<String, serde_json::Value>,
75}
76
77#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
78#[serde(rename_all = "lowercase")]
79pub struct Tag {
80 pub name: String,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub description: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub external_docs: Option<Vec<ExternalDoc>>,
85}
86
87#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
88pub struct ExternalDoc {
89 pub url: String,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub description: Option<String>,
92}
93
94#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
98#[serde(rename_all = "lowercase")]
99pub struct Info {
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub title: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub description: Option<String>,
106 #[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")]
107 pub terms_of_service: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub contact: Option<Contact>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub license: Option<License>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub version: Option<String>,
114}
115
116#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
117pub struct Contact {
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub name: Option<String>,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub url: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub email: Option<String>,
126}
127
128#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
130pub struct License {
131 #[serde(skip_serializing_if = "Option::is_none")]
134 pub name: Option<String>,
135 #[serde(skip_serializing_if = "Option::is_none")]
138 pub url: Option<String>,
139}
140
141#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
143pub struct PathItem {
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub get: Option<Operation>,
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub post: Option<Operation>,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub put: Option<Operation>,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub patch: Option<Operation>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub delete: Option<Operation>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub options: Option<Operation>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub head: Option<Operation>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub parameters: Option<Vec<Parameter>>,
160 #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
161 pub extensions: IndexMap<String, serde_json::Value>,
162}
163
164#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
166#[serde(rename_all = "lowercase")]
167pub struct Operation {
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub summary: Option<String>,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub description: Option<String>,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub consumes: Option<Vec<String>>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub produces: Option<Vec<String>>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub schemes: Option<Vec<String>>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub tags: Option<Vec<String>>,
180 #[serde(rename = "operationId", skip_serializing_if = "Option::is_none")]
181 pub operation_id: Option<String>,
182 pub responses: IndexMap<String, Response>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub parameters: Option<Vec<Parameter>>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub security: Option<Vec<SecurityRequirement>>,
187 #[serde(flatten, deserialize_with = "crate::util::deserialize_extensions")]
188 pub extensions: IndexMap<String, serde_json::Value>,
189}
190
191pub type SecurityRequirement = IndexMap<String, Vec<String>>;
193
194#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
195#[serde(rename_all = "lowercase")]
196pub enum ParameterLocation {
197 Query,
198 Header,
199 Path,
200 FormData,
201 Body,
202}
203
204impl Default for ParameterLocation {
205 fn default() -> Self {
206 ParameterLocation::Query
207 }
208}
209
210impl Parameter {
211 pub fn valid_v3_location(&self) -> bool {
212 use ParameterLocation::*;
213 matches!(self.location, Query | Header | Path)
214 }
215}
216
217#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
218#[serde(rename_all = "camelCase")]
219pub struct Parameter {
220 pub name: String,
221 #[serde(rename = "in")]
222 pub location: ParameterLocation,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub required: Option<bool>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub schema: Option<ReferenceOrSchema>,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub unique_items: Option<bool>,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 #[serde(rename = "type")]
232 pub type_: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub format: Option<String>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub description: Option<String>,
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub items: Option<ReferenceOrSchema>,
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub default: Option<serde_json::Value>,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 #[serde(rename = "collectionFormat")]
244 pub collection_format: Option<String>,
245}
246
247#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
248pub struct Response {
249 pub description: String,
250 #[serde(skip_serializing_if = "Option::is_none")]
251 pub schema: Option<ReferenceOrSchema>,
252}
253
254#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
255#[serde(rename_all = "lowercase")]
256pub enum ApiKeyLocation {
257 Query,
258 Header,
259}
260
261#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
262#[serde(tag = "type")]
263pub enum Security {
264 #[serde(rename = "apiKey")]
265 ApiKey {
266 name: String,
267 #[serde(rename = "in")]
268 location: ApiKeyLocation,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 description: Option<String>,
271 },
272 #[serde(rename = "oauth2")]
273 Oauth2 {
274 flow: Flow,
275 #[serde(rename = "authorizationUrl")]
276 authorization_url: String,
277 #[serde(rename = "tokenUrl")]
278 #[serde(skip_serializing_if = "Option::is_none")]
279 token_url: Option<String>,
280 scopes: IndexMap<String, String>,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 description: Option<String>,
283 },
284 #[serde(rename = "basic")]
285 Basic {
286 #[serde(skip_serializing_if = "Option::is_none")]
287 description: Option<String>,
288 },
289}
290
291#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
292#[serde(rename_all = "camelCase")]
293pub enum Flow {
294 Implicit,
295 Password,
296 Application,
297 AccessCode,
298}
299
300#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
305pub struct Schema {
306 #[serde(skip_serializing_if = "Option::is_none")]
307 pub description: Option<String>,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 #[serde(rename = "type")]
310 pub schema_type: Option<String>,
311 #[serde(skip_serializing_if = "Option::is_none")]
312 pub format: Option<String>,
313 #[serde(skip_serializing_if = "Option::is_none")]
314 #[serde(rename = "enum")]
315 pub enum_values: Option<Vec<String>>,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 pub required: Option<Vec<String>>,
318 #[serde(skip_serializing_if = "Option::is_none")]
319 pub items: Option<Box<ReferenceOrSchema>>,
320 #[serde(skip_serializing_if = "Option::is_none")]
322 pub properties: Option<IndexMap<String, ReferenceOrSchema>>,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 #[serde(rename = "allOf")]
326 pub all_of: Option<Vec<ReferenceOrSchema>>,
327 #[serde(flatten)]
329 pub other: IndexMap<String, serde_json::Value>,
330}
331
332#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
333#[serde(untagged)]
334pub enum ReferenceOrSchema {
335 Reference {
336 #[serde(rename = "$ref")]
337 reference: String,
338 },
339 Item(Schema),
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use serde_json;
346 use serde_yaml;
347
348 #[test]
349 fn security_api_deserializes() {
350 let json = r#"{"type":"apiKey", "name":"foo", "in": "query"}"#;
351 assert_eq!(
352 serde_yaml::from_str::<Security>(&json).unwrap(),
353 Security::ApiKey {
354 name: "foo".into(),
355 location: serde_json::from_str("\"query\"").unwrap(),
356 description: None,
357 }
358 );
359 }
360
361 #[test]
362 fn security_api_serializes() {
363 let json = r#"{"type":"apiKey","name":"foo","in":"query"}"#;
364 assert_eq!(
365 serde_json::to_string(&Security::ApiKey {
366 name: "foo".into(),
367 location: serde_json::from_str("\"query\"").unwrap(),
368 description: None,
369 })
370 .unwrap(),
371 json
372 );
373 }
374
375 #[test]
376 fn security_basic_deserializes() {
377 let json = r#"{"type":"basic"}"#;
378 assert_eq!(
379 serde_yaml::from_str::<Security>(&json).unwrap(),
380 Security::Basic { description: None }
381 );
382 }
383
384 #[test]
385 fn security_basic_serializes() {
386 let json = r#"{"type":"basic"}"#;
387 assert_eq!(
388 json,
389 serde_json::to_string(&Security::Basic { description: None }).unwrap()
390 );
391 }
392
393 #[test]
394 fn security_oauth_deserializes() {
395 let json = r#"{"type":"oauth2","flow":"implicit","authorizationUrl":"foo/bar","scopes":{"foo":"bar"}}"#;
396 let mut scopes = IndexMap::new();
397 scopes.insert("foo".into(), "bar".into());
398 assert_eq!(
399 serde_yaml::from_str::<Security>(&json).unwrap(),
400 Security::Oauth2 {
401 flow: Flow::Implicit,
402 authorization_url: "foo/bar".into(),
403 token_url: None,
404 scopes: scopes,
405 description: None,
406 }
407 );
408 }
409
410 #[test]
411 fn security_oauth_serializes() {
412 let json = r#"{"type":"oauth2","flow":"implicit","authorizationUrl":"foo/bar","scopes":{"foo":"bar"}}"#;
413 let mut scopes = IndexMap::new();
414 scopes.insert("foo".into(), "bar".into());
415 assert_eq!(
416 json,
417 serde_json::to_string(&Security::Oauth2 {
418 flow: Flow::Implicit,
419 authorization_url: "foo/bar".into(),
420 token_url: None,
421 scopes,
422 description: None,
423 })
424 .unwrap()
425 );
426 }
427
428 #[test]
429 fn parameter_or_ref_deserializes_ref() {
430 let json = r#"{"$ref":"foo/bar"}"#;
431 assert_eq!(
432 serde_yaml::from_str::<ReferenceOrSchema>(&json).unwrap(),
433 ReferenceOrSchema::Reference {
434 reference: "foo/bar".into()
435 }
436 );
437 }
438
439 #[test]
440 fn parameter_or_ref_serializes_pref() {
441 let json = r#"{"$ref":"foo/bar"}"#;
442 assert_eq!(
443 json,
444 serde_json::to_string(&ReferenceOrSchema::Reference {
445 reference: "foo/bar".into()
446 })
447 .unwrap()
448 );
449 }
450}