apple_bloom/v2/
schema.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4// http://json.schemastore.org/swagger-2.0
5
6#[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 Default for Scheme {
16    fn default() -> Self {
17        Scheme::Http
18    }
19}
20
21/// top level document
22#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
23#[serde(rename_all = "camelCase")]
24pub struct Spec {
25    /// The Swagger version of this document.
26    pub swagger: String,
27    pub info: Info,
28    /// The host (name or ip) of the API. Example: 'swagger.io'
29    /// ^[^{}/ :\\\\]+(?::\\d+)?$
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub host: Option<String>,
32    /// The base path to the API. Example: '/api'.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    #[serde(rename = "basePath")]
35    pub base_path: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub schemes: Option<Vec<Scheme>>,
38    /// A list of MIME types accepted by the API.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub consumes: Option<Vec<String>>,
41    /// A list of MIME types the API can produce.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub produces: Option<Vec<String>>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub tags: Option<Vec<Tag>>,
46    /// Relative paths to the individual endpoints. They must be relative
47    /// to the 'basePath'.
48    pub paths: BTreeMap<String, PathItem>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub definitions: Option<BTreeMap<String, Schema>>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub parameters: Option<BTreeMap<String, Parameter>>,
53    /// mappings to http response codes or "default"
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub responses: Option<BTreeMap<String, Response>>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub security_definitions: Option<BTreeMap<String, Security>>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub security: Option<Vec<BTreeMap<String, Vec<String>>>>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub external_docs: Option<Vec<ExternalDoc>>,
62}
63
64#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
65#[serde(rename_all = "lowercase")]
66pub struct Tag {
67    pub name: String,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub description: Option<String>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub external_docs: Option<Vec<ExternalDoc>>,
72}
73
74#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
75pub struct ExternalDoc {
76    pub url: String,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub description: Option<String>,
79}
80
81/// General information about the API.
82///
83/// https://github.com/OAI/OpenAPI-Specification/blob/aa91a19c43f8a12c02efa42d64794e396473f3b1/versions/2.0.md#info-object
84#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
85#[serde(rename_all = "lowercase")]
86pub struct Info {
87    /// A unique and precise title of the API.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub title: Option<String>,
90    /// A semantic version number of the API.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub description: Option<String>,
93    #[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")]
94    pub terms_of_service: Option<String>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub contact: Option<Contact>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub license: Option<License>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub version: Option<String>,
101}
102
103#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
104pub struct Contact {
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub name: Option<String>,
107    // TODO: Make sure the url is a valid URL
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub url: Option<String>,
110    // TODO: Make sure the email is a valid email
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub email: Option<String>,
113}
114
115/// todo x-* properties
116#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
117pub struct License {
118    /// The name of the license type. It's encouraged to use an OSI
119    /// compatible license.
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub name: Option<String>,
122    /// The URL pointing to the license.
123    // TODO: Make sure the url is a valid URL
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub url: Option<String>,
126}
127
128/// todo support x-* properties
129#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
130pub struct PathItem {
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub get: Option<Operation>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub post: Option<Operation>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub put: Option<Operation>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub patch: Option<Operation>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub delete: Option<Operation>,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub options: Option<Operation>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub head: Option<Operation>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub parameters: Option<Vec<ParameterOrRef>>,
147}
148
149/// https://github.com/OAI/OpenAPI-Specification/blob/aa91a19c43f8a12c02efa42d64794e396473f3b1/versions/2.0.md#operation-object
150#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
151#[serde(rename_all = "lowercase")]
152pub struct Operation {
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub summary: Option<String>,
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub description: Option<String>,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub consumes: Option<Vec<String>>,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub produces: Option<Vec<String>>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub schemes: Option<Vec<String>>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub tags: Option<Vec<String>>,
165    #[serde(rename = "operationId", skip_serializing_if = "Option::is_none")]
166    pub operation_id: Option<String>,
167    pub responses: BTreeMap<String, Response>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub parameters: Option<Vec<ParameterOrRef>>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub security: Option<Vec<SecurityRequirement>>,
172}
173
174/// https://github.com/OAI/OpenAPI-Specification/blob/aa91a19c43f8a12c02efa42d64794e396473f3b1/versions/2.0.md#securityRequirementObject
175pub type SecurityRequirement = BTreeMap<String, Vec<String>>;
176
177#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
178#[serde(rename_all = "camelCase")]
179pub struct Parameter {
180    pub name: String,
181    #[serde(rename = "in")]
182    pub location: String,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub required: Option<bool>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub schema: Option<Schema>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub unique_items: Option<bool>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    #[serde(rename = "type")]
191    pub param_type: Option<String>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub format: Option<String>,
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub description: Option<String>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub items: Option<Schema>,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    default: Option<serde_json::Value>,
200}
201
202#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
203pub struct Response {
204    pub description: String,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub schema: Option<Schema>,
207}
208
209// todo: support x-* fields
210/// https://github.com/OAI/OpenAPI-Specification/blob/aa91a19c43f8a12c02efa42d64794e396473f3b1/versions/2.0.md#parameter-object
211#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
212#[serde(untagged)]
213pub enum ParameterOrRef {
214    /// both bodyParameter and nonBodyParameter in one for now
215    Parameter {
216        /// The name of the parameter.
217        name: String,
218        /// values depend on parameter type
219        /// may be `header`, `query`, 'path`, `formData`
220        #[serde(rename = "in")]
221        location: String,
222        #[serde(skip_serializing_if = "Option::is_none")]
223        required: Option<bool>,
224        #[serde(skip_serializing_if = "Option::is_none")]
225        schema: Box<Option<Schema>>,
226        #[serde(skip_serializing_if = "Option::is_none")]
227        #[serde(rename = "uniqueItems")]
228        unique_items: Option<bool>,
229        /// string, number, boolean, integer, array, file ( only for formData )
230        #[serde(skip_serializing_if = "Option::is_none")]
231        #[serde(rename = "type")]
232        param_type: Option<String>,
233        #[serde(skip_serializing_if = "Option::is_none")]
234        format: Option<String>,
235        /// A brief description of the parameter. This could contain examples
236        /// of use.  GitHub Flavored Markdown is allowed.
237        #[serde(skip_serializing_if = "Option::is_none")]
238        description: Option<String>,
239        #[serde(rename = "collectionFormat", skip_serializing_if = "Option::is_none")]
240        collection_format: Option<String>,
241        #[serde(skip_serializing_if = "Option::is_none")]
242        default: Option<serde_json::Value>,
243        // maximum ?
244        // exclusiveMaximum ??
245        // minimum ??
246        // exclusiveMinimum ??
247        // maxLength ??
248        // minLength ??
249        // pattern ??
250        // maxItems ??
251        // minItems ??
252        // enum ??
253        // multipleOf ??
254        // allowEmptyValue ( for query / body params )
255        #[serde(skip_serializing_if = "Option::is_none")]
256        items: Box<Option<Schema>>,
257        #[serde(
258            rename = "additionalProperties",
259            skip_serializing_if = "Option::is_none"
260        )]
261        additional_properties: Box<Option<Schema>>,
262    },
263    Ref {
264        #[serde(rename = "$ref")]
265        ref_path: String,
266    },
267}
268#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
269#[serde(tag = "type")]
270pub enum Security {
271    #[serde(rename = "apiKey")]
272    ApiKey {
273        name: String,
274        #[serde(rename = "in")]
275        location: String,
276        #[serde(skip_serializing_if = "Option::is_none")]
277        description: Option<String>,
278    },
279    #[serde(rename = "oauth2")]
280    Oauth2 {
281        flow: Flow,
282        #[serde(rename = "authorizationUrl")]
283        authorization_url: String,
284        #[serde(rename = "tokenUrl")]
285        #[serde(skip_serializing_if = "Option::is_none")]
286        token_url: Option<String>,
287        scopes: BTreeMap<String, String>,
288        #[serde(skip_serializing_if = "Option::is_none")]
289        description: Option<String>,
290    },
291    #[serde(rename = "basic")]
292    Basic {
293        #[serde(skip_serializing_if = "Option::is_none")]
294        description: Option<String>,
295    },
296}
297
298#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
299#[serde(rename_all = "camelCase")]
300pub enum Flow {
301    Implicit,
302    Password,
303    Application,
304    AccessCode,
305}
306
307/// A [JSON schema](http://json-schema.org/) definition describing
308/// the shape and properties of an object.
309///
310/// This may also contain a `$ref` to another definition
311#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
312pub struct Schema {
313    #[serde(skip_serializing_if = "Option::is_none")]
314    /// [JSON reference](https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03)
315    /// path to another defintion
316    #[serde(rename = "$ref")]
317    pub ref_path: Option<String>,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub description: Option<String>,
320    #[serde(skip_serializing_if = "Option::is_none")]
321    #[serde(rename = "type")]
322    pub schema_type: Option<String>,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub format: Option<String>,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    #[serde(rename = "enum")]
327    pub enum_values: Option<Vec<String>>,
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub required: Option<Vec<String>>,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub items: Option<Box<Schema>>,
332    // implies object
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub properties: Option<BTreeMap<String, Schema>>,
335    // composition
336    #[serde(skip_serializing_if = "Option::is_none")]
337    #[serde(rename = "allOf")]
338    pub all_of: Option<Vec<Box<Schema>>>,
339    // TODO: we need a validation step that we only collect x-* properties here.
340    #[serde(flatten)]
341    pub other: BTreeMap<String, serde_json::Value>,
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347    use serde_json;
348    use serde_yaml;
349    use std::collections::BTreeMap;
350
351    #[test]
352    fn security_api_deserializes() {
353        let json = r#"{"type":"apiKey", "name":"foo", "in": "query"}"#;
354        assert_eq!(
355            serde_yaml::from_str::<Security>(&json).unwrap(),
356            Security::ApiKey {
357                name: "foo".into(),
358                location: "query".into(),
359                description: None,
360            }
361        );
362    }
363
364    #[test]
365    fn security_api_serializes() {
366        let json = r#"{"type":"apiKey","name":"foo","in":"query"}"#;
367        assert_eq!(
368            serde_json::to_string(&Security::ApiKey {
369                name: "foo".into(),
370                location: "query".into(),
371                description: None,
372            })
373            .unwrap(),
374            json
375        );
376    }
377
378    #[test]
379    fn security_basic_deserializes() {
380        let json = r#"{"type":"basic"}"#;
381        assert_eq!(
382            serde_yaml::from_str::<Security>(&json).unwrap(),
383            Security::Basic { description: None }
384        );
385    }
386
387    #[test]
388    fn security_basic_serializes() {
389        let json = r#"{"type":"basic"}"#;
390        assert_eq!(
391            json,
392            serde_json::to_string(&Security::Basic { description: None }).unwrap()
393        );
394    }
395
396    #[test]
397    fn security_oauth_deserializes() {
398        let json = r#"{"type":"oauth2","flow":"implicit","authorizationUrl":"foo/bar","scopes":{"foo":"bar"}}"#;
399        let mut scopes = BTreeMap::new();
400        scopes.insert("foo".into(), "bar".into());
401        assert_eq!(
402            serde_yaml::from_str::<Security>(&json).unwrap(),
403            Security::Oauth2 {
404                flow: Flow::Implicit,
405                authorization_url: "foo/bar".into(),
406                token_url: None,
407                scopes: scopes,
408                description: None,
409            }
410        );
411    }
412
413    #[test]
414    fn security_oauth_serializes() {
415        let json = r#"{"type":"oauth2","flow":"implicit","authorizationUrl":"foo/bar","scopes":{"foo":"bar"}}"#;
416        let mut scopes = BTreeMap::new();
417        scopes.insert("foo".into(), "bar".into());
418        assert_eq!(
419            json,
420            serde_json::to_string(&Security::Oauth2 {
421                flow: Flow::Implicit,
422                authorization_url: "foo/bar".into(),
423                token_url: None,
424                scopes: scopes,
425                description: None,
426            })
427            .unwrap()
428        );
429    }
430
431    #[test]
432    fn parameter_or_ref_deserializes_ref() {
433        let json = r#"{"$ref":"foo/bar"}"#;
434        assert_eq!(
435            serde_yaml::from_str::<ParameterOrRef>(&json).unwrap(),
436            ParameterOrRef::Ref {
437                ref_path: "foo/bar".into()
438            }
439        );
440    }
441
442    #[test]
443    fn parameter_or_ref_serializes_pref() {
444        let json = r#"{"$ref":"foo/bar"}"#;
445        assert_eq!(
446            json,
447            serde_json::to_string(&ParameterOrRef::Ref {
448                ref_path: "foo/bar".into()
449            },)
450            .unwrap()
451        );
452    }
453}