paperclip_core/v2/
actix.rs

1#[cfg(feature = "actix3-validator")]
2extern crate actix_web_validator2 as actix_web_validator;
3#[cfg(feature = "actix4-validator")]
4extern crate actix_web_validator3 as actix_web_validator;
5
6#[cfg(feature = "actix-multipart")]
7use super::schema::TypedData;
8use super::{
9    models::{
10        DefaultOperationRaw, DefaultSchemaRaw, Either, Items, Parameter, ParameterIn, Response,
11        SecurityScheme,
12    },
13    schema::{Apiv2Errors, Apiv2Operation, Apiv2Schema},
14};
15#[cfg(not(feature = "actix4"))]
16use crate::util::{ready, Ready};
17#[cfg(any(feature = "actix3", feature = "actix4"))]
18use actix_web::web::ReqData;
19#[cfg(not(feature = "actix4"))]
20use actix_web::Error;
21#[cfg(feature = "actix4")]
22use actix_web::{body::BoxBody, ResponseError};
23use actix_web::{
24    http::StatusCode,
25    web::{Bytes, Data, Form, Json, Path, Payload, Query},
26    HttpRequest, HttpResponse, Responder,
27};
28
29use pin_project_lite::pin_project;
30
31#[cfg(any(feature = "actix4-validator", feature = "actix3-validator"))]
32use actix_web_validator::{
33    Json as ValidatedJson, Path as ValidatedPath, QsQuery as ValidatedQsQuery,
34    Query as ValidatedQuery,
35};
36use serde::Serialize;
37#[cfg(feature = "serde_qs")]
38use serde_qs::actix::QsQuery;
39
40use std::{
41    collections::BTreeMap,
42    fmt,
43    future::Future,
44    pin::Pin,
45    task::{Context, Poll},
46};
47
48/// Actix-specific trait for indicating that this entity can modify an operation
49/// and/or update the global map of definitions.
50pub trait OperationModifier: Apiv2Schema + Sized {
51    /// Update the parameters list in the given operation (if needed).
52    fn update_parameter(op: &mut DefaultOperationRaw) {
53        update_parameter::<Self>(op);
54    }
55
56    /// Update the responses map in the given operation (if needed).
57    fn update_response(_op: &mut DefaultOperationRaw) {}
58
59    /// Update the definitions map (if needed).
60    fn update_definitions(map: &mut BTreeMap<String, DefaultSchemaRaw>) {
61        update_definitions_from_schema_type::<Self>(map);
62    }
63
64    /// Update the security map in the given operation (if needed).
65    fn update_security(op: &mut DefaultOperationRaw) {
66        update_security::<Self>(op);
67    }
68
69    /// Update the security definition map (if needed).
70    fn update_security_definitions(map: &mut BTreeMap<String, SecurityScheme>) {
71        update_security_definitions::<Self>(map);
72    }
73}
74
75/// All schema types default to updating the definitions map.
76#[cfg(feature = "nightly")]
77impl<T> OperationModifier for T
78where
79    T: Apiv2Schema,
80{
81    default fn update_parameter(op: &mut DefaultOperationRaw) {
82        update_parameter::<Self>(op);
83    }
84
85    default fn update_response(_op: &mut DefaultOperationRaw) {}
86
87    default fn update_definitions(map: &mut BTreeMap<String, DefaultSchemaRaw>) {
88        update_definitions_from_schema_type::<Self>(map);
89    }
90
91    default fn update_security(op: &mut DefaultOperationRaw) {
92        update_security::<Self>(op);
93    }
94
95    default fn update_security_definitions(map: &mut BTreeMap<String, SecurityScheme>) {
96        update_security_definitions::<Self>(map);
97    }
98}
99
100impl<T> OperationModifier for Option<T>
101where
102    T: OperationModifier,
103{
104    fn update_parameter(op: &mut DefaultOperationRaw) {
105        T::update_parameter(op);
106    }
107
108    fn update_response(op: &mut DefaultOperationRaw) {
109        T::update_response(op);
110    }
111
112    fn update_definitions(map: &mut BTreeMap<String, DefaultSchemaRaw>) {
113        T::update_definitions(map);
114    }
115
116    fn update_security(op: &mut DefaultOperationRaw) {
117        T::update_security(op);
118    }
119
120    fn update_security_definitions(map: &mut BTreeMap<String, SecurityScheme>) {
121        T::update_security_definitions(map);
122    }
123}
124
125#[cfg(feature = "nightly")]
126impl<T, E> OperationModifier for Result<T, E>
127where
128    T: OperationModifier,
129{
130    default fn update_parameter(op: &mut DefaultOperationRaw) {
131        T::update_parameter(op);
132    }
133
134    default fn update_response(op: &mut DefaultOperationRaw) {
135        T::update_response(op);
136    }
137
138    default fn update_definitions(map: &mut BTreeMap<String, DefaultSchemaRaw>) {
139        T::update_definitions(map);
140    }
141
142    default fn update_security_definitions(map: &mut BTreeMap<String, SecurityScheme>) {
143        T::update_security_definitions(map);
144    }
145}
146
147impl<T, E> OperationModifier for Result<T, E>
148where
149    T: OperationModifier,
150    E: Apiv2Errors,
151{
152    fn update_parameter(op: &mut DefaultOperationRaw) {
153        T::update_parameter(op);
154    }
155
156    fn update_response(op: &mut DefaultOperationRaw) {
157        T::update_response(op);
158        E::update_error_definitions(op);
159    }
160
161    fn update_definitions(map: &mut BTreeMap<String, DefaultSchemaRaw>) {
162        T::update_definitions(map);
163        E::update_definitions(map);
164    }
165
166    fn update_security_definitions(map: &mut BTreeMap<String, SecurityScheme>) {
167        T::update_security_definitions(map);
168    }
169}
170
171// We don't know what we should do with these abstractions
172// as they could be anything.
173impl<T> Apiv2Schema for Data<T> {}
174#[cfg(not(feature = "nightly"))]
175impl<T> OperationModifier for Data<T> {}
176#[cfg(any(feature = "actix3", feature = "actix4"))]
177impl<T: std::clone::Clone> Apiv2Schema for ReqData<T> {}
178#[cfg(not(feature = "nightly"))]
179#[cfg(any(feature = "actix3", feature = "actix4"))]
180impl<T: std::clone::Clone> OperationModifier for ReqData<T> {}
181
182macro_rules! impl_empty({ $($ty:ty),+ } => {
183    $(
184        impl Apiv2Schema for $ty {}
185        #[cfg(not(feature = "nightly"))]
186        impl OperationModifier for $ty {}
187    )+
188});
189
190#[cfg(feature = "actix4")]
191/// Workaround for possibility to directly return HttpResponse from closure handler.
192///
193/// This is needed after actix removed `impl Future` from `HttpResponse`:
194/// <https://github.com/actix/actix-web/pull/2601>
195///
196/// Example:
197//////
198/// ```ignore
199/// .route(web::get().to(||
200///     async move {
201///         paperclip::actix::HttpResponseWrapper(
202///             HttpResponse::Ok().body("Hi there!")
203///         )
204///     }
205/// ))
206/// ```
207pub struct HttpResponseWrapper(pub HttpResponse);
208
209#[cfg(feature = "actix4")]
210impl Responder for HttpResponseWrapper {
211    type Body = <HttpResponse as Responder>::Body;
212
213    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
214        self.0.respond_to(req)
215    }
216}
217
218#[cfg(feature = "actix4")]
219impl<F> Apiv2Operation for F
220where
221    F: Future<Output = HttpResponseWrapper>,
222{
223    fn operation() -> DefaultOperationRaw {
224        Default::default()
225    }
226
227    fn security_definitions() -> BTreeMap<String, SecurityScheme> {
228        Default::default()
229    }
230
231    fn definitions() -> BTreeMap<String, DefaultSchemaRaw> {
232        Default::default()
233    }
234}
235
236#[cfg(not(feature = "actix4"))]
237impl Apiv2Operation for HttpResponse {
238    fn operation() -> DefaultOperationRaw {
239        Default::default()
240    }
241
242    fn security_definitions() -> BTreeMap<String, SecurityScheme> {
243        Default::default()
244    }
245
246    fn definitions() -> BTreeMap<String, DefaultSchemaRaw> {
247        Default::default()
248    }
249}
250
251impl_empty!(HttpRequest, HttpResponse, Bytes, Payload);
252
253#[cfg(not(feature = "nightly"))]
254mod manual_impl {
255    use super::OperationModifier;
256
257    impl OperationModifier for &str {}
258    impl<T: OperationModifier> OperationModifier for &[T] {}
259
260    macro_rules! impl_simple({ $ty:ty } => {
261        impl OperationModifier for $ty {}
262    });
263
264    impl_simple!(char);
265    impl_simple!(String);
266    impl_simple!(bool);
267    impl_simple!(f32);
268    impl_simple!(f64);
269    impl_simple!(i8);
270    impl_simple!(i16);
271    impl_simple!(i32);
272    impl_simple!(u8);
273    impl_simple!(u16);
274    impl_simple!(u32);
275    impl_simple!(i64);
276    impl_simple!(i128);
277    impl_simple!(isize);
278    impl_simple!(u64);
279    impl_simple!(u128);
280    impl_simple!(usize);
281    #[cfg(feature = "chrono")]
282    impl_simple!(chrono::NaiveDateTime);
283    #[cfg(feature = "jiff01")]
284    impl_simple!(jiff::Timestamp);
285    #[cfg(feature = "jiff01")]
286    impl_simple!(jiff::Zoned);
287    #[cfg(feature = "rust_decimal")]
288    impl_simple!(rust_decimal::Decimal);
289    #[cfg(feature = "url")]
290    impl_simple!(url::Url);
291    #[cfg(feature = "uuid0")]
292    impl_simple!(uuid0_dep::Uuid);
293    #[cfg(feature = "uuid1")]
294    impl_simple!(uuid1_dep::Uuid);
295}
296
297#[cfg(feature = "chrono")]
298impl<T: chrono::TimeZone> OperationModifier for chrono::DateTime<T> {}
299
300// Other extractors
301
302#[cfg(feature = "nightly")]
303impl<T> Apiv2Schema for Json<T> {
304    default fn name() -> Option<String> {
305        None
306    }
307
308    default fn raw_schema() -> DefaultSchemaRaw {
309        Default::default()
310    }
311}
312
313/// JSON needs specialization because it updates the global definitions.
314impl<T: Apiv2Schema> Apiv2Schema for Json<T> {
315    fn name() -> Option<String> {
316        T::name()
317    }
318
319    fn raw_schema() -> DefaultSchemaRaw {
320        T::raw_schema()
321    }
322}
323
324impl<T> OperationModifier for Json<T>
325where
326    T: Apiv2Schema,
327{
328    fn update_parameter(op: &mut DefaultOperationRaw) {
329        op.parameters.push(Either::Right(Parameter {
330            description: None,
331            in_: ParameterIn::Body,
332            name: "body".into(),
333            required: true,
334            schema: Some({
335                let mut def = T::schema_with_ref();
336                def.retain_ref();
337                def
338            }),
339            ..Default::default()
340        }));
341    }
342
343    fn update_response(op: &mut DefaultOperationRaw) {
344        op.responses.insert(
345            "200".into(),
346            Either::Right(Response {
347                // TODO: Support configuring other 2xx codes using macro attribute.
348                description: Some("OK".into()),
349                schema: Some({
350                    let mut def = T::schema_with_ref();
351                    def.retain_ref();
352                    def
353                }),
354                ..Default::default()
355            }),
356        );
357    }
358}
359
360#[cfg(all(
361    any(feature = "actix4-validator", feature = "actix3-validator"),
362    feature = "nightly"
363))]
364impl<T> Apiv2Schema for ValidatedJson<T> {
365    fn name() -> Option<String> {
366        None
367    }
368
369    default fn raw_schema() -> DefaultSchemaRaw {
370        Default::default()
371    }
372}
373
374#[cfg(any(feature = "actix4-validator", feature = "actix3-validator"))]
375impl<T: Apiv2Schema> Apiv2Schema for ValidatedJson<T> {
376    fn name() -> Option<String> {
377        T::name()
378    }
379
380    fn raw_schema() -> DefaultSchemaRaw {
381        T::raw_schema()
382    }
383}
384
385#[cfg(any(feature = "actix4-validator", feature = "actix3-validator"))]
386impl<T> OperationModifier for ValidatedJson<T>
387where
388    T: Apiv2Schema,
389{
390    fn update_parameter(op: &mut DefaultOperationRaw) {
391        Json::<T>::update_parameter(op);
392    }
393
394    fn update_response(op: &mut DefaultOperationRaw) {
395        Json::<T>::update_response(op);
396    }
397}
398
399#[cfg(feature = "actix-multipart")]
400impl OperationModifier for actix_multipart::Multipart {
401    fn update_parameter(op: &mut DefaultOperationRaw) {
402        op.parameters.push(Either::Right(Parameter {
403            description: None,
404            in_: ParameterIn::FormData,
405            name: "file_data".into(),
406            required: true,
407            data_type: Some(<actix_multipart::Multipart as TypedData>::data_type()),
408            format: <actix_multipart::Multipart as TypedData>::format(),
409            ..Default::default()
410        }));
411    }
412}
413
414#[cfg(feature = "actix-session")]
415impl OperationModifier for actix_session::Session {
416    fn update_definitions(_map: &mut BTreeMap<String, DefaultSchemaRaw>) {}
417}
418
419#[cfg(feature = "actix-identity")]
420impl OperationModifier for actix_identity::Identity {
421    fn update_definitions(_map: &mut BTreeMap<String, DefaultSchemaRaw>) {}
422}
423
424#[cfg(feature = "actix-files")]
425impl OperationModifier for actix_files::NamedFile {
426    fn update_definitions(_map: &mut BTreeMap<String, DefaultSchemaRaw>) {}
427}
428
429macro_rules! impl_param_extractor ({ $ty:ty => $container:ident } => {
430    #[cfg(feature = "nightly")]
431    impl<T> Apiv2Schema for $ty {
432        default fn name() -> Option<String> {
433            None
434        }
435
436        default fn raw_schema() -> DefaultSchemaRaw {
437            Default::default()
438        }
439    }
440
441    #[cfg(not(feature = "nightly"))]
442    impl<T: Apiv2Schema> Apiv2Schema for $ty {}
443
444    impl<T: Apiv2Schema> OperationModifier for $ty {
445        fn update_parameter(op: &mut DefaultOperationRaw) {
446            let def = T::raw_schema();
447            // If there aren't any properties and if it's a path parameter,
448            // then add a parameter whose name will be overridden later.
449            if def.properties.is_empty() && ParameterIn::$container == ParameterIn::Path {
450                op.parameters.push(Either::Right(Parameter {
451                    name: String::new(),
452                    in_: ParameterIn::Path,
453                    required: true,
454                    data_type: def.data_type,
455                    format: def.format,
456                    enum_: def.enum_,
457                    description: def.description,
458                    ..Default::default()
459                }));
460            }
461            for (k, v) in def.properties {
462                op.parameters.push(Either::Right(Parameter {
463                    in_: ParameterIn::$container,
464                    required: def.required.contains(&k),
465                    data_type: v.data_type,
466                    format: v.format,
467                    enum_: v.enum_,
468                    description: v.description,
469                    collection_format: None, // this defaults to csv
470                    items: v.items.as_deref().map(map_schema_to_items),
471                    name: k,
472                    ..Default::default()
473                }));
474            }
475        }
476
477        // These don't require updating definitions, as we use them only
478        // to get their properties.
479        fn update_definitions(_map: &mut BTreeMap<String, DefaultSchemaRaw>) {}
480    }
481});
482
483fn map_schema_to_items(schema: &DefaultSchemaRaw) -> Items {
484    Items {
485        data_type: schema.data_type,
486        format: schema.format.clone(),
487        collection_format: None, // this defaults to csv
488        enum_: schema.enum_.clone(),
489        items: schema
490            .items
491            .as_deref()
492            .map(|schema| Box::new(map_schema_to_items(schema))),
493        ..Default::default() // range fields are not emitted
494    }
495}
496
497/// `formData` can refer to the global definitions.
498#[cfg(feature = "nightly")]
499impl<T: Apiv2Schema> Apiv2Schema for Form<T> {
500    fn name() -> Option<String> {
501        T::name()
502    }
503
504    fn raw_schema() -> DefaultSchemaRaw {
505        T::raw_schema()
506    }
507}
508
509impl_param_extractor!(Path<T> => Path);
510impl_param_extractor!(Query<T> => Query);
511impl_param_extractor!(Form<T> => FormData);
512#[cfg(feature = "serde_qs")]
513impl_param_extractor!(QsQuery<T> => Query);
514#[cfg(any(feature = "actix4-validator", feature = "actix3-validator"))]
515impl_param_extractor!(ValidatedPath<T> => Path);
516#[cfg(any(feature = "actix4-validator", feature = "actix3-validator"))]
517impl_param_extractor!(ValidatedQuery<T> => Query);
518#[cfg(any(feature = "actix4-validator", feature = "actix3-validator"))]
519impl_param_extractor!(ValidatedQsQuery<T> => Query);
520
521macro_rules! impl_path_tuple ({ $($ty:ident),+ } => {
522    #[cfg(all(any(feature = "actix4-validator", feature = "actix3-validator"), feature = "nightly"))]
523    impl<$($ty,)+> Apiv2Schema for ValidatedPath<($($ty,)+)> {}
524
525    #[cfg(all(not(feature = "nightly"), any(feature = "actix4-validator", feature = "actix3-validator")))]
526    impl<$($ty: Apiv2Schema,)+> Apiv2Schema for ValidatedPath<($($ty,)+)> {}
527
528    #[cfg(any(feature = "actix4-validator", feature = "actix3-validator"))]
529    impl<$($ty,)+> OperationModifier for ValidatedPath<($($ty,)+)>
530        where $($ty: Apiv2Schema,)+
531    {
532        fn update_parameter(op: &mut DefaultOperationRaw) {
533            $(
534                Path::<$ty>::update_parameter(op);
535            )+
536        }
537    }
538
539    #[cfg(feature = "nightly")]
540    impl<$($ty,)+> Apiv2Schema for Path<($($ty,)+)> {}
541
542    #[cfg(not(feature = "nightly"))]
543    impl<$($ty: Apiv2Schema,)+> Apiv2Schema for Path<($($ty,)+)> {}
544
545    impl<$($ty,)+> OperationModifier for Path<($($ty,)+)>
546        where $($ty: Apiv2Schema,)+
547    {
548        fn update_parameter(op: &mut DefaultOperationRaw) {
549            $(
550                let def = $ty::raw_schema();
551                if def.properties.is_empty() {
552                    op.parameters.push(Either::Right(Parameter {
553                        // NOTE: We're setting empty name, because we don't know
554                        // the name in this context. We'll get it when we add services.
555                        name: String::new(),
556                        in_: ParameterIn::Path,
557                        required: true,
558                        data_type: def.data_type,
559                        format: def.format,
560                        enum_: def.enum_,
561                        description: def.description,
562                        ..Default::default()
563                    }));
564                }
565                for (k, v) in def.properties {
566                    op.parameters.push(Either::Right(Parameter {
567                        in_: ParameterIn::Path,
568                        required: def.required.contains(&k),
569                        data_type: v.data_type,
570                        format: v.format,
571                        enum_: v.enum_,
572                        description: v.description,
573                        collection_format: None, // this defaults to csv
574                        items: v.items.as_deref().map(map_schema_to_items),
575                        name: String::new(),
576                        ..Default::default()
577                    }));
578                }
579            )+
580        }
581    }
582});
583
584impl_path_tuple!(A);
585impl_path_tuple!(A, B);
586impl_path_tuple!(A, B, C);
587impl_path_tuple!(A, B, C, D);
588impl_path_tuple!(A, B, C, D, E);
589impl_path_tuple!(A, B, C, D, E, F);
590impl_path_tuple!(A, B, C, D, E, F, G);
591impl_path_tuple!(A, B, C, D, E, F, G, H);
592impl_path_tuple!(A, B, C, D, E, F, G, H, I);
593impl_path_tuple!(A, B, C, D, E, F, G, H, I, J);
594impl_path_tuple!(A, B, C, D, E, F, G, H, I, J, K);
595impl_path_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
596impl_path_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
597
598/// Wrapper for wrapping over `impl Responder` thingies (to avoid breakage).
599pub struct ResponderWrapper<T>(pub T);
600
601#[cfg(feature = "nightly")]
602impl<T: Responder> Apiv2Schema for ResponderWrapper<T> {
603    default fn name() -> Option<String> {
604        None
605    }
606
607    default fn raw_schema() -> DefaultSchemaRaw {
608        DefaultSchemaRaw::default()
609    }
610}
611
612#[cfg(not(feature = "nightly"))]
613impl<T: Responder> Apiv2Schema for ResponderWrapper<T> {}
614
615#[cfg(not(feature = "nightly"))]
616impl<T: Responder> OperationModifier for ResponderWrapper<T> {}
617
618#[cfg(feature = "actix4")]
619impl Apiv2Schema for actix_web::dev::Response<actix_web::body::BoxBody> {}
620
621#[cfg(feature = "actix4")]
622impl OperationModifier for actix_web::dev::Response<actix_web::body::BoxBody> {}
623
624#[cfg(feature = "actix4")]
625impl<T: Responder> Responder for ResponderWrapper<T> {
626    type Body = T::Body;
627
628    #[inline]
629    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
630        self.0.respond_to(req)
631    }
632}
633
634#[cfg(not(feature = "actix4"))]
635impl<T: Responder> Responder for ResponderWrapper<T> {
636    type Error = T::Error;
637    type Future = T::Future;
638
639    #[inline]
640    fn respond_to(self, req: &HttpRequest) -> Self::Future {
641        self.0.respond_to(req)
642    }
643}
644
645// Wrapper for all response types from handlers. This holds the actual value
646// returned by the handler and a unit struct (autogenerated by the plugin) which
647// is used for generating operation information.
648pin_project! {
649    pub struct ResponseWrapper<T, H> {
650        #[pin]
651        pub responder: T,
652        pub operations: H,
653    }
654}
655
656#[cfg(feature = "actix4")]
657impl<T: Responder, H> Responder for ResponseWrapper<T, H> {
658    type Body = T::Body;
659
660    #[inline]
661    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
662        self.responder.respond_to(req)
663    }
664}
665
666#[cfg(not(feature = "actix4"))]
667impl<T: Responder, H> Responder for ResponseWrapper<T, H> {
668    type Error = <T as Responder>::Error;
669    type Future = <T as Responder>::Future;
670
671    #[inline]
672    fn respond_to(self, req: &HttpRequest) -> Self::Future {
673        self.responder.respond_to(req)
674    }
675}
676
677impl<F, T, H> Future for ResponseWrapper<F, H>
678where
679    F: Future<Output = T>,
680    T: OperationModifier + Responder,
681    H: Apiv2Operation,
682{
683    type Output = T;
684
685    #[inline]
686    fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
687        let this = self.as_mut().project();
688        this.responder.poll(ctx)
689    }
690}
691
692impl<F, T, H> Apiv2Operation for ResponseWrapper<F, H>
693where
694    F: Future<Output = T>,
695    T: OperationModifier + Responder,
696    H: Apiv2Operation,
697{
698    fn operation() -> DefaultOperationRaw {
699        H::operation()
700    }
701
702    fn security_definitions() -> BTreeMap<String, SecurityScheme> {
703        H::security_definitions()
704    }
705
706    fn definitions() -> BTreeMap<String, DefaultSchemaRaw> {
707        H::definitions()
708    }
709
710    fn is_visible() -> bool {
711        H::is_visible()
712    }
713}
714
715/// Given the schema type, recursively update the map of definitions.
716fn update_definitions_from_schema_type<T>(map: &mut BTreeMap<String, DefaultSchemaRaw>)
717where
718    T: Apiv2Schema,
719{
720    let mut schema = T::schema_with_ref();
721    loop {
722        if let Some(s) = schema.items {
723            schema = *s;
724            continue;
725        } else if let Some(Either::Right(s)) = schema.extra_props {
726            schema = *s;
727            continue;
728        } else if let Some(n) = schema.name.take() {
729            schema.remove_refs();
730            map.insert(n, schema);
731        }
732
733        break;
734    }
735}
736
737fn update_parameter<T>(op: &mut DefaultOperationRaw)
738where
739    T: Apiv2Schema,
740{
741    for parameter in T::header_parameter_schema() {
742        op.parameters.push(Either::Right(parameter))
743    }
744}
745
746/// Add security requirements to operation.
747fn update_security<T>(op: &mut DefaultOperationRaw)
748where
749    T: Apiv2Schema,
750{
751    if let (Some(name), Some(scheme)) = (T::name(), T::security_scheme()) {
752        let mut security_map = BTreeMap::new();
753        let scopes = scheme.scopes.keys().map(String::clone).collect();
754        security_map.insert(name, scopes);
755        op.security.push(security_map);
756    }
757}
758
759/// Merge security scheme into existing security definitions or add new.
760fn update_security_definitions<T>(map: &mut BTreeMap<String, SecurityScheme>)
761where
762    T: Apiv2Schema,
763{
764    if let (Some(name), Some(new)) = (T::name(), T::security_scheme()) {
765        new.update_definitions(&name, map);
766    }
767}
768
769macro_rules! json_with_status {
770    ($name:ident => $status:expr) => {
771        pub struct $name<T: Serialize + Apiv2Schema>(pub T);
772
773        impl<T> fmt::Debug for $name<T>
774        where
775            T: fmt::Debug + Serialize + Apiv2Schema,
776        {
777            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
778                let status: StatusCode = $status;
779                let status_str = status.canonical_reason().unwrap_or(status.as_str());
780                write!(f, "{} Json: {:?}", status_str, self.0)
781            }
782        }
783
784        impl<T> fmt::Display for $name<T>
785        where
786            T: fmt::Display + Serialize + Apiv2Schema,
787        {
788            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
789                fmt::Display::fmt(&self.0, f)
790            }
791        }
792
793        #[cfg(feature = "actix4")]
794        impl<T> Responder for $name<T>
795        where
796            T: Serialize + Apiv2Schema,
797        {
798            type Body = BoxBody;
799
800            fn respond_to(self, _: &HttpRequest) -> HttpResponse<BoxBody> {
801                let status: StatusCode = $status;
802                let body = match serde_json::to_string(&self.0) {
803                    Ok(body) => body,
804                    Err(e) => return e.error_response(),
805                };
806
807                HttpResponse::build(status)
808                    .content_type("application/json")
809                    .body(body)
810            }
811        }
812
813        #[cfg(not(feature = "actix4"))]
814        impl<T> Responder for $name<T>
815        where
816            T: Serialize + Apiv2Schema,
817        {
818            type Error = Error;
819            type Future = Ready<Result<HttpResponse, Error>>;
820
821            fn respond_to(self, _: &HttpRequest) -> Self::Future {
822                let status: StatusCode = $status;
823                let body = match serde_json::to_string(&self.0) {
824                    Ok(body) => body,
825                    Err(e) => return ready(Err(e.into())),
826                };
827
828                ready(Ok(HttpResponse::build(status)
829                    .content_type("application/json")
830                    .body(body)))
831            }
832        }
833
834        impl<T> Apiv2Schema for $name<T>
835        where
836            T: Serialize + Apiv2Schema,
837        {
838            fn name() -> Option<String> {
839                T::name()
840            }
841
842            fn raw_schema() -> DefaultSchemaRaw {
843                T::raw_schema()
844            }
845        }
846
847        impl<T> OperationModifier for $name<T>
848        where
849            T: Serialize + Apiv2Schema,
850        {
851            fn update_response(op: &mut DefaultOperationRaw) {
852                let status: StatusCode = $status;
853                op.responses.insert(
854                    status.as_str().into(),
855                    Either::Right(Response {
856                        description: status.canonical_reason().map(ToString::to_string),
857                        schema: Some({
858                            let mut def = T::schema_with_ref();
859                            def.retain_ref();
860                            def
861                        }),
862                        ..Default::default()
863                    }),
864                );
865            }
866        }
867    };
868}
869
870json_with_status!(CreatedJson => StatusCode::CREATED);
871json_with_status!(AcceptedJson => StatusCode::ACCEPTED);
872
873#[derive(Debug)]
874pub struct NoContent;
875
876impl fmt::Display for NoContent {
877    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
878        f.write_str("No Content")
879    }
880}
881
882#[cfg(feature = "actix4")]
883impl Responder for NoContent {
884    type Body = BoxBody;
885
886    fn respond_to(self, _: &HttpRequest) -> HttpResponse<BoxBody> {
887        HttpResponse::build(StatusCode::NO_CONTENT)
888            .content_type("application/json")
889            .finish()
890    }
891}
892
893#[cfg(not(feature = "actix4"))]
894impl Responder for NoContent {
895    type Error = Error;
896    type Future = Ready<Result<HttpResponse, Error>>;
897
898    fn respond_to(self, _: &HttpRequest) -> Self::Future {
899        ready(Ok(HttpResponse::build(StatusCode::NO_CONTENT)
900            .content_type("application/json")
901            .finish()))
902    }
903}
904
905impl Apiv2Schema for NoContent {}
906
907impl OperationModifier for NoContent {
908    fn update_response(op: &mut DefaultOperationRaw) {
909        let status = StatusCode::NO_CONTENT;
910        op.responses.insert(
911            status.as_str().into(),
912            Either::Right(Response {
913                description: status.canonical_reason().map(ToString::to_string),
914                schema: None,
915                ..Default::default()
916            }),
917        );
918    }
919}