restify_openapi/
api_component.rs

1use crate::paths::{MediaType, Parameter, RequestBody, Response, Responses};
2use crate::reference_or::ReferenceOr;
3use crate::security::SecurityScheme;
4use crate::ApiErrorComponent;
5use crate::Schema;
6
7use schemars::schema::{ArrayValidation, InstanceType, SchemaObject, SingleOrVec};
8use std::collections::BTreeMap;
9
10pub trait ApiComponent {
11  fn content_type() -> String {
12    "application/json".to_string()
13  }
14
15  fn required() -> bool {
16    true
17  }
18
19  /// Contains children schemas for this operation
20  /// Each child can also contain child schemas
21  fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)>;
22
23  fn raw_schema() -> Option<ReferenceOr<Schema>> {
24    None
25  }
26
27  fn schema() -> Option<(String, ReferenceOr<Schema>)>;
28
29  fn securities() -> BTreeMap<String, SecurityScheme> {
30    Default::default()
31  }
32
33  fn security_requirement_name() -> Option<String> {
34    None
35  }
36
37  fn request_body() -> Option<RequestBody> {
38    Self::schema().map(|(name, _)| RequestBody {
39      content: BTreeMap::from_iter(vec![(
40        Self::content_type(),
41        MediaType {
42          schema: Some(ReferenceOr::Reference {
43            _ref: format!("#/components/schemas/{}", name),
44          }),
45          ..Default::default()
46        },
47      )]),
48      required: Some(Self::required()),
49      ..Default::default()
50    })
51  }
52
53  fn error_responses() -> Vec<(String, Response)> {
54    vec![]
55  }
56
57  fn error_schemas() -> BTreeMap<String, (String, ReferenceOr<Schema>)> {
58    BTreeMap::default()
59  }
60
61  fn responses(_content_type: Option<String>) -> Option<Responses> {
62    None
63  }
64
65  fn parameters() -> Vec<Parameter> {
66    vec![]
67  }
68}
69
70impl<T> ApiComponent for Option<T>
71where
72  T: ApiComponent,
73{
74  fn required() -> bool {
75    false
76  }
77
78  fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
79    T::child_schemas()
80  }
81
82  fn raw_schema() -> Option<ReferenceOr<Schema>> {
83    T::raw_schema()
84  }
85
86  fn schema() -> Option<(String, ReferenceOr<Schema>)> {
87    T::schema()
88  }
89
90  fn securities() -> BTreeMap<String, SecurityScheme> {
91    T::securities()
92  }
93
94  fn security_requirement_name() -> Option<String> {
95    T::security_requirement_name()
96  }
97}
98
99impl<T> ApiComponent for Vec<T>
100where
101  T: ApiComponent,
102{
103  fn required() -> bool {
104    true
105  }
106
107  fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
108    let mut schemas = T::schema()
109      .into_iter()
110      .collect::<Vec<(String, ReferenceOr<Schema>)>>();
111    schemas.append(&mut T::child_schemas());
112    schemas
113  }
114
115  fn raw_schema() -> Option<ReferenceOr<Schema>> {
116    T::raw_schema()
117  }
118
119  fn schema() -> Option<(String, ReferenceOr<Schema>)> {
120    T::schema().map(|(name, schema)| {
121      let _ref = match schema {
122        ReferenceOr::Reference { _ref } => _ref,
123        ReferenceOr::Object(_) => format!("#/components/schemas/{}", name),
124      };
125
126      (
127        name,
128        ReferenceOr::Object(Schema::Object(SchemaObject {
129          instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
130          array: Some(Box::new(ArrayValidation {
131            items: Some(Schema::new_ref(_ref).into()),
132            ..Default::default()
133          })),
134          ..Default::default()
135        })),
136      )
137    })
138  }
139}
140
141impl<T, E> ApiComponent for Result<T, E>
142where
143  T: ApiComponent,
144  E: ApiErrorComponent,
145{
146  fn required() -> bool {
147    T::required()
148  }
149
150  fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
151    T::child_schemas()
152  }
153
154  fn raw_schema() -> Option<ReferenceOr<Schema>> {
155    T::raw_schema()
156  }
157
158  fn schema() -> Option<(String, ReferenceOr<Schema>)> {
159    T::schema()
160  }
161
162  // We expect error to be present only for response part
163  fn error_responses() -> Vec<(String, Response)> {
164    E::error_responses()
165  }
166
167  // We expect error to be present only for response part
168  fn error_schemas() -> BTreeMap<String, (String, ReferenceOr<Schema>)> {
169    E::schemas_by_status_code()
170  }
171
172  fn responses(content_type: Option<String>) -> Option<Responses> {
173    T::responses(content_type)
174  }
175}
176
177#[cfg(test)]
178mod test {
179  use crate::reference_or::ReferenceOr;
180  use crate::ApiComponent;
181  use assert_json_diff::assert_json_eq;
182  use schemars::schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec};
183  use schemars::{Map, Set};
184  use serde_json::json;
185
186  #[test]
187  #[allow(dead_code)]
188  fn api_component_schema_vec() {
189    struct TestChild {
190      surname: String,
191    }
192
193    impl ApiComponent for TestChild {
194      fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
195        vec![]
196      }
197
198      fn schema() -> Option<(String, ReferenceOr<Schema>)> {
199        Some((
200          "TestChild".to_string(),
201          ReferenceOr::Object(Schema::Object(SchemaObject {
202            object: Some(Box::new(ObjectValidation {
203              required: Set::from_iter(vec!["surname".to_string()]),
204              properties: Map::from_iter(vec![(
205                "surname".to_string(),
206                Schema::Object(SchemaObject {
207                  instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
208                  ..Default::default()
209                }),
210              )]),
211              ..Default::default()
212            })),
213            ..Default::default()
214          })),
215        ))
216      }
217    }
218
219    struct Test {
220      name: String,
221      surname: TestChild,
222    }
223
224    impl ApiComponent for Test {
225      fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
226        <TestChild as ApiComponent>::schema().into_iter().collect()
227      }
228
229      fn schema() -> Option<(String, ReferenceOr<Schema>)> {
230        Some((
231          "Test".to_string(),
232          ReferenceOr::Object(Schema::Object(SchemaObject {
233            object: Some(Box::new(ObjectValidation {
234              required: Set::from_iter(vec!["name".to_string(), "surname".to_string()]),
235              properties: Map::from_iter(vec![
236                (
237                  "name".to_string(),
238                  Schema::Object(SchemaObject {
239                    instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
240                    ..Default::default()
241                  }),
242                ),
243                (
244                  "surname".to_string(),
245                  Schema::new_ref("#/components/schemas/TestChild".to_string()),
246                ),
247              ]),
248              ..Default::default()
249            })),
250            ..Default::default()
251          })),
252        ))
253      }
254    }
255
256    let schema = <Vec<Test> as ApiComponent>::schema();
257    assert!(schema.is_some());
258
259    let json =
260      serde_json::to_value(schema.expect("Missing schema").1).expect("Unable to serialize as Json");
261    assert_json_eq!(
262      json,
263      json!({
264        "type": "array",
265        "items": {
266          "$ref": "#/components/schemas/Test"
267        }
268      })
269    );
270
271    let child_schema = <Vec<Test> as ApiComponent>::child_schemas();
272    assert_eq!(child_schema.len(), 2);
273    let first_child_schema = child_schema.first().cloned();
274    assert!(first_child_schema.is_some());
275
276    let json = serde_json::to_value(first_child_schema.expect("Missing child schema").1)
277      .expect("Unable to serialize as Json");
278    assert_json_eq!(
279      json,
280      json!({
281        "properties": {
282          "name": {
283            "type": "string"
284          },
285          "surname": {
286            "$ref": "#/components/schemas/TestChild"
287          }
288        },
289        "required": [
290          "name",
291          "surname"
292        ]
293      })
294    );
295
296    let last_child_schema = child_schema.last().cloned();
297    assert!(last_child_schema.is_some());
298
299    let json = serde_json::to_value(last_child_schema.expect("Missing child schema").1)
300      .expect("Unable to serialize as Json");
301    assert_json_eq!(
302      json,
303      json!( {
304        "properties": {
305          "surname": {
306            "type": "string"
307          }
308        },
309        "required": [
310          "surname"
311        ]
312      })
313    );
314  }
315}