okapi_operation/
to_responses.rs

1use okapi::openapi3::{RefOr, Responses};
2
3use crate::Components;
4
5/// Generate [`Responses`] for type.
6pub trait ToResponses {
7    fn generate(components: &mut Components) -> Result<Responses, anyhow::Error>;
8}
9
10/// Generate [`ToResponses`] implementation for newtype.
11///
12/// Inner type should implement `ToMediaTypes`.
13///
14/// # Example
15///
16/// ```rust,compile
17/// # use okapi_operation::*;
18/// # impl_to_media_types_for_wrapper!(JsonWrapper<T>, "application/json");
19/// struct JsonWrapper<T>(T);
20///
21/// impl_to_responses_for_wrapper!(JsonWrapper<T>);
22/// ```
23#[macro_export]
24macro_rules! impl_to_responses_for_wrapper {
25    ($ty:path) => {
26        impl<T: $crate::schemars::JsonSchema> $crate::ToResponses for $ty {
27            fn generate(components: &mut $crate::Components) -> Result<$crate::okapi::openapi3::Responses, $crate::anyhow::Error> {
28                let media_types = <$ty as $crate::ToMediaTypes>::generate(components)?;
29                Ok($crate::okapi::openapi3::Responses {
30                    responses: $crate::okapi::map! {
31                        "200".into() => $crate::okapi::openapi3::RefOr::Object(
32                            $crate::okapi::openapi3::Response { content: media_types, ..Default::default() }
33                        )
34                    },
35                    ..Default::default()
36                })
37            }
38        }
39    };
40}
41
42macro_rules! forward_impl_to_responses {
43    ($ty_for:ty, $ty_base:ty) => {
44        impl $crate::ToResponses for $ty_for {
45            fn generate(
46                components: &mut $crate::Components,
47            ) -> Result<$crate::okapi::openapi3::Responses, $crate::anyhow::Error> {
48                <$ty_base as $crate::ToResponses>::generate(components)
49            }
50        }
51    };
52}
53
54mod impls {
55    use std::borrow::Cow;
56
57    use bytes::{Bytes, BytesMut};
58    use okapi::openapi3::Response;
59
60    use super::*;
61    use crate::ToMediaTypes;
62
63    impl ToResponses for () {
64        fn generate(_components: &mut Components) -> Result<Responses, anyhow::Error> {
65            Ok(Responses {
66                responses: okapi::map! {
67                    "200".into() => RefOr::Object(Default::default())
68                },
69                ..Default::default()
70            })
71        }
72    }
73
74    impl<T, E> ToResponses for Result<T, E>
75    where
76        T: ToResponses,
77        E: ToResponses,
78    {
79        fn generate(components: &mut Components) -> Result<Responses, anyhow::Error> {
80            let overlap_err_fn = |status| {
81                anyhow::anyhow!(
82                    "Type {} produces {} response in both Ok and Err variants",
83                    std::any::type_name::<Self>(),
84                    status
85                )
86            };
87            let mut ok = T::generate(components)?;
88            let err = E::generate(components)?;
89
90            if ok.default.is_some() && err.default.is_some() {
91                return Err(overlap_err_fn("default"));
92            }
93            ok.default = ok.default.or(err.default);
94
95            for (status, response) in err.responses.into_iter() {
96                if ok.responses.contains_key(&status) {
97                    return Err(overlap_err_fn(&status));
98                }
99                let _ = ok.responses.insert(status, response);
100            }
101
102            Ok(ok)
103        }
104    }
105
106    impl ToResponses for String {
107        fn generate(components: &mut Components) -> Result<Responses, anyhow::Error> {
108            Ok(Responses {
109                responses: okapi::map! {
110                    "200".into() => RefOr::Object(Response {
111                        content: <Self as ToMediaTypes>::generate(components)?,
112                        ..Default::default()
113                    })
114                },
115                ..Default::default()
116            })
117        }
118    }
119    forward_impl_to_responses!(&'static str, String);
120    forward_impl_to_responses!(Cow<'static, str>, String);
121
122    impl ToResponses for Vec<u8> {
123        fn generate(components: &mut Components) -> Result<Responses, anyhow::Error> {
124            Ok(Responses {
125                responses: okapi::map! {
126                    "200".into() => RefOr::Object(Response {
127                        content: <Self as ToMediaTypes>::generate(components)?,
128                        ..Default::default()
129                    })
130                },
131                ..Default::default()
132            })
133        }
134    }
135    forward_impl_to_responses!(&'static [u8], Vec<u8>);
136    forward_impl_to_responses!(Cow<'static, [u8]>, Vec<u8>);
137    forward_impl_to_responses!(Bytes, Vec<u8>);
138    forward_impl_to_responses!(BytesMut, Vec<u8>);
139}