1use crate::ApiErrorComponent;
2#[cfg(feature = "actix")]
3use crate::{PathItemDefinition, ResponseWrapper};
4#[cfg(feature = "actix")]
5use actix_web::Either;
6use apistos_models::Schema;
7use apistos_models::paths::{MediaType, Parameter, RequestBody, Response, Responses};
8use apistos_models::reference_or::ReferenceOr;
9use apistos_models::security::SecurityScheme;
10#[cfg(feature = "actix")]
11use schemars::schema::SubschemaValidation;
12use schemars::schema::{ArrayValidation, InstanceType, SchemaObject, SingleOrVec};
13use std::collections::BTreeMap;
14#[cfg(feature = "actix")]
15use std::future::Future;
16
17pub trait ApiComponent {
18 fn content_type() -> String {
19 "application/json".to_string()
20 }
21
22 fn required() -> bool {
23 true
24 }
25
26 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)>;
29
30 fn raw_schema() -> Option<ReferenceOr<Schema>> {
31 None
32 }
33
34 fn schema() -> Option<(String, ReferenceOr<Schema>)>;
35
36 fn securities() -> BTreeMap<String, SecurityScheme> {
37 Default::default()
38 }
39
40 fn security_requirement_name() -> Option<String> {
41 None
42 }
43
44 fn request_body() -> Option<RequestBody> {
45 Self::schema().map(|(name, _)| RequestBody {
46 content: BTreeMap::from_iter(vec![(
47 Self::content_type(),
48 MediaType {
49 schema: Some(ReferenceOr::Reference {
50 _ref: format!("#/components/schemas/{}", name),
51 }),
52 ..Default::default()
53 },
54 )]),
55 required: Some(Self::required()),
56 ..Default::default()
57 })
58 }
59
60 fn error_responses() -> Vec<(String, Response)> {
61 vec![]
62 }
63
64 fn error_schemas() -> BTreeMap<String, (String, ReferenceOr<Schema>)> {
65 BTreeMap::default()
66 }
67
68 fn responses(_content_type: Option<String>) -> Option<Responses> {
69 None
70 }
71
72 fn parameters() -> Vec<Parameter> {
73 vec![]
74 }
75}
76
77impl<T> ApiComponent for Option<T>
78where
79 T: ApiComponent,
80{
81 fn required() -> bool {
82 false
83 }
84
85 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
86 T::child_schemas()
87 }
88
89 fn raw_schema() -> Option<ReferenceOr<Schema>> {
90 T::raw_schema()
91 }
92
93 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
94 T::schema()
95 }
96
97 fn securities() -> BTreeMap<String, SecurityScheme> {
98 T::securities()
99 }
100
101 fn security_requirement_name() -> Option<String> {
102 T::security_requirement_name()
103 }
104}
105
106impl<T> ApiComponent for Vec<T>
107where
108 T: ApiComponent,
109{
110 fn required() -> bool {
111 true
112 }
113
114 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
115 let mut schemas = T::schema().into_iter().collect::<Vec<(String, ReferenceOr<Schema>)>>();
116 schemas.append(&mut T::child_schemas());
117 schemas
118 }
119
120 fn raw_schema() -> Option<ReferenceOr<Schema>> {
121 T::raw_schema()
122 }
123
124 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
125 T::schema().map(|(name, schema)| {
126 let _ref = match schema {
127 ReferenceOr::Reference { _ref } => _ref,
128 ReferenceOr::Object(_) => format!("#/components/schemas/{}", name),
129 };
130
131 (
132 name,
133 ReferenceOr::Object(Schema::Object(SchemaObject {
134 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
135 array: Some(Box::new(ArrayValidation {
136 items: Some(Schema::new_ref(_ref).into()),
137 ..Default::default()
138 })),
139 ..Default::default()
140 })),
141 )
142 })
143 }
144}
145
146impl<T, E> ApiComponent for Result<T, E>
147where
148 T: ApiComponent,
149 E: ApiErrorComponent,
150{
151 fn required() -> bool {
152 T::required()
153 }
154
155 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
156 T::child_schemas()
157 }
158
159 fn raw_schema() -> Option<ReferenceOr<Schema>> {
160 T::raw_schema()
161 }
162
163 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
164 T::schema()
165 }
166
167 fn error_responses() -> Vec<(String, Response)> {
169 E::error_responses()
170 }
171
172 fn error_schemas() -> BTreeMap<String, (String, ReferenceOr<Schema>)> {
174 E::schemas_by_status_code()
175 }
176
177 fn responses(content_type: Option<String>) -> Option<Responses> {
178 T::responses(content_type)
179 }
180}
181
182#[cfg(feature = "actix")]
183impl<T, E> ApiComponent for Either<T, E>
184where
185 T: ApiComponent,
186 E: ApiComponent,
187{
188 fn required() -> bool {
189 T::required() && E::required()
190 }
191
192 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
193 let mut child_schemas = T::child_schemas();
194 child_schemas.append(&mut E::child_schemas());
195 child_schemas
196 }
197
198 fn raw_schema() -> Option<ReferenceOr<Schema>> {
199 match (T::raw_schema(), E::raw_schema()) {
200 (Some(raw_schema1), Some(raw_schema2)) => {
201 let raw_schema1 = match raw_schema1 {
202 ReferenceOr::Object(schema_obj) => schema_obj,
203 ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
204 reference: Some(_ref),
205 ..Default::default()
206 }),
207 };
208 let raw_schema2 = match raw_schema2 {
209 ReferenceOr::Object(schema_obj) => schema_obj,
210 ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
211 reference: Some(_ref),
212 ..Default::default()
213 }),
214 };
215 Some(ReferenceOr::Object(Schema::Object(SchemaObject {
216 subschemas: Some(Box::new(SubschemaValidation {
217 one_of: Some(vec![raw_schema1, raw_schema2]),
218 ..Default::default()
219 })),
220 ..Default::default()
221 })))
222 }
223 (Some(raw_schema1), None) => Some(raw_schema1),
224 (None, Some(raw_schema2)) => Some(raw_schema2),
225 (None, None) => None,
226 }
227 }
228
229 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
230 match (T::schema(), E::schema()) {
231 (Some(schema1), Some(schema2)) => {
232 let (schema_name1, schema1) = schema1;
233 let schema1 = match schema1 {
234 ReferenceOr::Object(schema_obj) => schema_obj,
235 ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
236 reference: Some(_ref),
237 ..Default::default()
238 }),
239 };
240 let (schema_name2, schema2) = schema2;
241 let schema2 = match schema2 {
242 ReferenceOr::Object(schema_obj) => schema_obj,
243 ReferenceOr::Reference { _ref } => Schema::Object(SchemaObject {
244 reference: Some(_ref),
245 ..Default::default()
246 }),
247 };
248 let schema = ReferenceOr::Object(Schema::Object(SchemaObject {
249 subschemas: Some(Box::new(SubschemaValidation {
250 one_of: Some(vec![schema1, schema2]),
251 ..Default::default()
252 })),
253 ..Default::default()
254 }));
255 let schema_name = format!("Either{}Or{}", schema_name1, schema_name2);
256 Some((schema_name, schema))
257 }
258 (Some(schema1), None) => Some(schema1),
259 (None, Some(schema2)) => Some(schema2),
260 (None, None) => None,
261 }
262 }
263
264 fn error_responses() -> Vec<(String, Response)> {
265 let mut error_responses = T::error_responses();
266 error_responses.append(&mut E::error_responses());
267 error_responses
268 }
269
270 fn error_schemas() -> BTreeMap<String, (String, ReferenceOr<Schema>)> {
271 let mut error_schemas = E::error_schemas();
272 error_schemas.append(&mut T::error_schemas());
273 error_schemas
274 }
275
276 fn responses(content_type: Option<String>) -> Option<Responses> {
277 let responses = T::responses(content_type.clone());
278 match responses {
279 None => E::responses(content_type),
280 Some(mut responses) => {
281 responses
282 .responses
283 .append(&mut E::responses(content_type).map(|r| r.responses).unwrap_or_default());
284 Some(responses)
285 }
286 }
287 }
288}
289
290#[cfg(feature = "actix")]
291impl<T> ApiComponent for actix_web::web::Data<T> {
292 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
293 vec![]
294 }
295
296 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
297 None
298 }
299}
300
301#[cfg(feature = "actix")]
302impl<T: Clone> ApiComponent for actix_web::web::ReqData<T> {
303 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
304 vec![]
305 }
306
307 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
308 None
309 }
310}
311
312#[cfg(feature = "actix")]
313impl<F, R, P> ApiComponent for ResponseWrapper<F, P>
314where
315 F: Future<Output = R>,
316 R: actix_web::Responder + ApiComponent,
317 P: PathItemDefinition,
318{
319 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
320 R::child_schemas()
321 }
322
323 fn raw_schema() -> Option<ReferenceOr<Schema>> {
324 R::raw_schema()
325 }
326
327 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
328 R::schema()
329 }
330
331 fn error_responses() -> Vec<(String, Response)> {
332 R::error_responses()
333 }
334
335 fn error_schemas() -> BTreeMap<String, (String, ReferenceOr<Schema>)> {
336 R::error_schemas()
337 }
338
339 fn responses(content_type: Option<String>) -> Option<Responses> {
340 let mut responses = vec![];
341 if let Some(response) = R::responses(content_type.clone()) {
342 responses.append(
343 &mut response
344 .responses
345 .into_iter()
346 .collect::<Vec<(String, ReferenceOr<Response>)>>(),
347 );
348 } else if let Some((name, schema)) = Self::schema() {
349 let ref_or = match schema {
350 r @ ReferenceOr::Reference { .. } => r,
351 ReferenceOr::Object(schema_obj) => {
352 let _ref = ReferenceOr::Reference {
353 _ref: format!("#/components/schemas/{}", name),
354 };
355 match schema_obj {
356 Schema::Object(obj) => {
357 if obj.instance_type == Some(SingleOrVec::Single(Box::new(InstanceType::Array))) {
358 ReferenceOr::Object(Schema::Object(obj))
359 } else {
360 _ref
361 }
362 }
363 Schema::Bool(_) => _ref,
364 }
365 }
366 };
367 responses.push((
368 "200".to_owned(),
369 ReferenceOr::Object(Response {
370 content: BTreeMap::from_iter(vec![(
371 content_type.unwrap_or_else(Self::content_type),
372 MediaType {
373 schema: Some(ref_or),
374 ..Default::default()
375 },
376 )]),
377 ..Default::default()
378 }),
379 ));
380 } else if let Some(schema) = Self::raw_schema() {
381 responses.push((
382 "200".to_owned(),
383 ReferenceOr::Object(Response {
384 content: BTreeMap::from_iter(vec![(
385 content_type.unwrap_or_else(Self::content_type),
386 MediaType {
387 schema: Some(schema),
388 ..Default::default()
389 },
390 )]),
391 ..Default::default()
392 }),
393 ));
394 } else if let Some(content_type) = content_type {
395 responses.push((
396 "200".to_owned(),
397 ReferenceOr::Object(Response {
398 content: BTreeMap::from_iter(vec![(content_type, MediaType::default())]),
399 ..Default::default()
400 }),
401 ));
402 } else {
403 responses.push(("200".to_owned(), ReferenceOr::Object(Response::default())));
404 }
405
406 responses.append(
407 &mut Self::error_responses()
408 .into_iter()
409 .map(|(status, schema)| (status, ReferenceOr::Object(schema)))
410 .collect(),
411 );
412 Some(Responses {
413 responses: BTreeMap::from_iter(responses),
414 ..Default::default()
415 })
416 }
417}
418
419#[cfg(test)]
420mod test {
421 use crate::ApiComponent;
422 use apistos_models::reference_or::ReferenceOr;
423 use assert_json_diff::assert_json_eq;
424 use schemars::schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec};
425 use schemars::{Map, Set};
426 use serde_json::json;
427
428 #[test]
429 #[allow(dead_code)]
430 fn api_component_schema_vec() {
431 struct TestChild {
432 surname: String,
433 }
434
435 impl ApiComponent for TestChild {
436 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
437 vec![]
438 }
439
440 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
441 Some((
442 "TestChild".to_string(),
443 ReferenceOr::Object(Schema::Object(SchemaObject {
444 object: Some(Box::new(ObjectValidation {
445 required: Set::from_iter(vec!["surname".to_string()]),
446 properties: Map::from_iter(vec![(
447 "surname".to_string(),
448 Schema::Object(SchemaObject {
449 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
450 ..Default::default()
451 }),
452 )]),
453 ..Default::default()
454 })),
455 ..Default::default()
456 })),
457 ))
458 }
459 }
460
461 struct Test {
462 name: String,
463 surname: TestChild,
464 }
465
466 impl ApiComponent for Test {
467 fn child_schemas() -> Vec<(String, ReferenceOr<Schema>)> {
468 <TestChild as ApiComponent>::schema().into_iter().collect()
469 }
470
471 fn schema() -> Option<(String, ReferenceOr<Schema>)> {
472 Some((
473 "Test".to_string(),
474 ReferenceOr::Object(Schema::Object(SchemaObject {
475 object: Some(Box::new(ObjectValidation {
476 required: Set::from_iter(vec!["name".to_string(), "surname".to_string()]),
477 properties: Map::from_iter(vec![
478 (
479 "name".to_string(),
480 Schema::Object(SchemaObject {
481 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
482 ..Default::default()
483 }),
484 ),
485 (
486 "surname".to_string(),
487 Schema::new_ref("#/components/schemas/TestChild".to_string()),
488 ),
489 ]),
490 ..Default::default()
491 })),
492 ..Default::default()
493 })),
494 ))
495 }
496 }
497
498 let schema = <Vec<Test> as ApiComponent>::schema();
499 assert!(schema.is_some());
500
501 let json = serde_json::to_value(schema.expect("Missing schema").1).expect("Unable to serialize as Json");
502 assert_json_eq!(
503 json,
504 json!({
505 "type": "array",
506 "items": {
507 "$ref": "#/components/schemas/Test"
508 }
509 })
510 );
511
512 let child_schema = <Vec<Test> as ApiComponent>::child_schemas();
513 assert_eq!(child_schema.len(), 2);
514 let first_child_schema = child_schema.first().cloned();
515 assert!(first_child_schema.is_some());
516
517 let json =
518 serde_json::to_value(first_child_schema.expect("Missing child schema").1).expect("Unable to serialize as Json");
519 assert_json_eq!(
520 json,
521 json!({
522 "properties": {
523 "name": {
524 "type": "string"
525 },
526 "surname": {
527 "$ref": "#/components/schemas/TestChild"
528 }
529 },
530 "required": [
531 "name",
532 "surname"
533 ]
534 })
535 );
536
537 let last_child_schema = child_schema.last().cloned();
538 assert!(last_child_schema.is_some());
539
540 let json =
541 serde_json::to_value(last_child_schema.expect("Missing child schema").1).expect("Unable to serialize as Json");
542 assert_json_eq!(
543 json,
544 json!( {
545 "properties": {
546 "surname": {
547 "type": "string"
548 }
549 },
550 "required": [
551 "surname"
552 ]
553 })
554 );
555 }
556}