1use serde::{Deserialize, Serialize};
2use indexmap::IndexMap;
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}
74
75#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
76#[serde(rename_all = "lowercase")]
77pub struct Tag {
78 pub name: String,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub description: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub external_docs: Option<Vec<ExternalDoc>>,
83}
84
85#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
86pub struct ExternalDoc {
87 pub url: String,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub description: Option<String>,
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
96#[serde(rename_all = "lowercase")]
97pub struct Info {
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub title: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub description: Option<String>,
104 #[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")]
105 pub terms_of_service: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub contact: Option<Contact>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub license: Option<License>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub version: Option<String>,
112}
113
114#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
115pub struct Contact {
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub name: Option<String>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub url: Option<String>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub email: Option<String>,
124}
125
126#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
128pub struct License {
129 #[serde(skip_serializing_if = "Option::is_none")]
132 pub name: Option<String>,
133 #[serde(skip_serializing_if = "Option::is_none")]
136 pub url: Option<String>,
137}
138
139#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
141pub struct PathItem {
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub get: Option<Operation>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub post: Option<Operation>,
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub put: Option<Operation>,
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub patch: Option<Operation>,
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub delete: Option<Operation>,
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub options: Option<Operation>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub head: Option<Operation>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub parameters: Option<Vec<Parameter>>,
158}
159
160#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
162#[serde(rename_all = "lowercase")]
163pub struct Operation {
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub summary: Option<String>,
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub description: Option<String>,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub consumes: Option<Vec<String>>,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 pub produces: Option<Vec<String>>,
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub schemes: Option<Vec<String>>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub tags: Option<Vec<String>>,
176 #[serde(rename = "operationId", skip_serializing_if = "Option::is_none")]
177 pub operation_id: Option<String>,
178 pub responses: IndexMap<String, Response>,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub parameters: Option<Vec<Parameter>>,
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub security: Option<Vec<SecurityRequirement>>,
183}
184
185pub type SecurityRequirement = IndexMap<String, Vec<String>>;
187
188#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
189#[serde(rename_all = "lowercase")]
190pub enum ParameterLocation {
191 Query,
192 Header,
193 Path,
194 FormData,
195 Body,
196}
197
198impl Default for ParameterLocation {
199 fn default() -> Self {
200 ParameterLocation::Query
201 }
202}
203
204impl Parameter {
205 pub fn valid_v3_location(&self) -> bool {
206 use ParameterLocation::*;
207 matches!(self.location, Query | Header | Path)
208 }
209}
210
211#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
212#[serde(rename_all = "camelCase")]
213pub struct Parameter {
214 pub name: String,
215 #[serde(rename = "in")]
216 pub location: ParameterLocation,
217 #[serde(skip_serializing_if = "Option::is_none")]
218 pub required: Option<bool>,
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub schema: Option<ReferenceOrSchema>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub unique_items: Option<bool>,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 #[serde(rename = "type")]
226 pub type_: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub format: Option<String>,
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub description: Option<String>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub items: Option<ReferenceOrSchema>,
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub default: Option<serde_json::Value>,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 #[serde(rename = "collectionFormat")]
238 pub collection_format: Option<String>,
239}
240
241#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
242pub struct Response {
243 pub description: String,
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub schema: Option<ReferenceOrSchema>,
246}
247
248#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
249#[serde(rename_all = "lowercase")]
250pub enum ApiKeyLocation {
251 Query,
252 Header,
253}
254
255#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
256#[serde(tag = "type")]
257pub enum Security {
258 #[serde(rename = "apiKey")]
259 ApiKey {
260 name: String,
261 #[serde(rename = "in")]
262 location: ApiKeyLocation,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 description: Option<String>,
265 },
266 #[serde(rename = "oauth2")]
267 Oauth2 {
268 flow: Flow,
269 #[serde(rename = "authorizationUrl")]
270 authorization_url: String,
271 #[serde(rename = "tokenUrl")]
272 #[serde(skip_serializing_if = "Option::is_none")]
273 token_url: Option<String>,
274 scopes: IndexMap<String, String>,
275 #[serde(skip_serializing_if = "Option::is_none")]
276 description: Option<String>,
277 },
278 #[serde(rename = "basic")]
279 Basic {
280 #[serde(skip_serializing_if = "Option::is_none")]
281 description: Option<String>,
282 },
283}
284
285#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
286#[serde(rename_all = "camelCase")]
287pub enum Flow {
288 Implicit,
289 Password,
290 Application,
291 AccessCode,
292}
293
294#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
299pub struct Schema {
300 #[serde(skip_serializing_if = "Option::is_none")]
301 pub description: Option<String>,
302 #[serde(skip_serializing_if = "Option::is_none")]
303 #[serde(rename = "type")]
304 pub schema_type: Option<String>,
305 #[serde(skip_serializing_if = "Option::is_none")]
306 pub format: Option<String>,
307 #[serde(skip_serializing_if = "Option::is_none")]
308 #[serde(rename = "enum")]
309 pub enum_values: Option<Vec<String>>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 pub required: Option<Vec<String>>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 pub items: Option<Box<ReferenceOrSchema>>,
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub properties: Option<IndexMap<String, ReferenceOrSchema>>,
317 #[serde(skip_serializing_if = "Option::is_none")]
319 #[serde(rename = "allOf")]
320 pub all_of: Option<Vec<ReferenceOrSchema>>,
321 #[serde(flatten)]
323 pub other: IndexMap<String, serde_json::Value>,
324}
325
326#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
327#[serde(untagged)]
328pub enum ReferenceOrSchema {
329 Reference {
330 #[serde(rename = "$ref")]
331 reference: String,
332 },
333 Item(Schema),
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use serde_json;
340 use serde_yaml;
341
342 #[test]
343 fn security_api_deserializes() {
344 let json = r#"{"type":"apiKey", "name":"foo", "in": "query"}"#;
345 assert_eq!(
346 serde_yaml::from_str::<Security>(&json).unwrap(),
347 Security::ApiKey {
348 name: "foo".into(),
349 location: serde_json::from_str("\"query\"").unwrap(),
350 description: None,
351 }
352 );
353 }
354
355 #[test]
356 fn security_api_serializes() {
357 let json = r#"{"type":"apiKey","name":"foo","in":"query"}"#;
358 assert_eq!(
359 serde_json::to_string(&Security::ApiKey {
360 name: "foo".into(),
361 location: serde_json::from_str("\"query\"").unwrap(),
362 description: None,
363 })
364 .unwrap(),
365 json
366 );
367 }
368
369 #[test]
370 fn security_basic_deserializes() {
371 let json = r#"{"type":"basic"}"#;
372 assert_eq!(
373 serde_yaml::from_str::<Security>(&json).unwrap(),
374 Security::Basic { description: None }
375 );
376 }
377
378 #[test]
379 fn security_basic_serializes() {
380 let json = r#"{"type":"basic"}"#;
381 assert_eq!(
382 json,
383 serde_json::to_string(&Security::Basic { description: None }).unwrap()
384 );
385 }
386
387 #[test]
388 fn security_oauth_deserializes() {
389 let json = r#"{"type":"oauth2","flow":"implicit","authorizationUrl":"foo/bar","scopes":{"foo":"bar"}}"#;
390 let mut scopes = IndexMap::new();
391 scopes.insert("foo".into(), "bar".into());
392 assert_eq!(
393 serde_yaml::from_str::<Security>(&json).unwrap(),
394 Security::Oauth2 {
395 flow: Flow::Implicit,
396 authorization_url: "foo/bar".into(),
397 token_url: None,
398 scopes: scopes,
399 description: None,
400 }
401 );
402 }
403
404 #[test]
405 fn security_oauth_serializes() {
406 let json = r#"{"type":"oauth2","flow":"implicit","authorizationUrl":"foo/bar","scopes":{"foo":"bar"}}"#;
407 let mut scopes = IndexMap::new();
408 scopes.insert("foo".into(), "bar".into());
409 assert_eq!(
410 json,
411 serde_json::to_string(&Security::Oauth2 {
412 flow: Flow::Implicit,
413 authorization_url: "foo/bar".into(),
414 token_url: None,
415 scopes,
416 description: None,
417 })
418 .unwrap()
419 );
420 }
421
422 #[test]
423 fn parameter_or_ref_deserializes_ref() {
424 let json = r#"{"$ref":"foo/bar"}"#;
425 assert_eq!(
426 serde_yaml::from_str::<ReferenceOrSchema>(&json).unwrap(),
427 ReferenceOrSchema::Reference {
428 reference: "foo/bar".into()
429 }
430 );
431 }
432
433 #[test]
434 fn parameter_or_ref_serializes_pref() {
435 let json = r#"{"$ref":"foo/bar"}"#;
436 assert_eq!(
437 json,
438 serde_json::to_string(&ReferenceOrSchema::Reference {
439 reference: "foo/bar".into()
440 })
441 .unwrap()
442 );
443 }
444}