Skip to main content

okapi_operation/
to_media_types.rs

1use okapi::{Map, openapi3::MediaType};
2
3use crate::Components;
4
5/// Generate [`MediaType`] for type.
6pub trait ToMediaTypes {
7    fn generate(components: &mut Components) -> Result<Map<String, MediaType>, anyhow::Error>;
8}
9
10/// Generate [`ToMediaTypes`] implementation for newtype.
11///
12/// Inner type should implement `schemars::JsonSchema`.
13///
14/// # Example
15///
16/// ```rust,compile
17/// # use okapi_operation::*;
18/// struct JsonWrapper<T>(T);
19///
20/// impl_to_media_types_for_wrapper!(JsonWrapper<T>, "application/json");
21/// ```
22#[macro_export]
23macro_rules! impl_to_media_types_for_wrapper {
24    ($ty:path, $mime:expr) => {
25        impl<T: $crate::schemars::JsonSchema> $crate::ToMediaTypes for $ty {
26            fn generate(
27                components: &mut $crate::Components,
28            ) -> Result<
29                    $crate::okapi::Map<String, $crate::okapi::openapi3::MediaType>,
30                    $crate::anyhow::Error
31                >
32            {
33                let schema = components.schema_for::<T>();
34                Ok($crate::okapi::map! {
35                    $mime.into() => {
36                        $crate::okapi::openapi3::MediaType { schema: Some(schema), ..Default::default() }
37                    }
38                })
39            }
40        }
41    };
42}
43
44macro_rules! forward_impl_to_media_types {
45    ($ty_for:ty, $ty_base:ty) => {
46        impl $crate::ToMediaTypes for $ty_for {
47            fn generate(
48                components: &mut $crate::Components,
49            ) -> Result<
50                $crate::okapi::Map<String, $crate::okapi::openapi3::MediaType>,
51                $crate::anyhow::Error,
52            > {
53                <$ty_base as $crate::ToMediaTypes>::generate(components)
54            }
55        }
56    };
57}
58
59mod impls {
60    use std::borrow::Cow;
61
62    use bytes::{Bytes, BytesMut};
63    use mime::{APPLICATION_OCTET_STREAM, TEXT_PLAIN};
64    use okapi::{
65        map,
66        openapi3::SchemaObject,
67        schemars::schema::{InstanceType, SingleOrVec},
68    };
69
70    use super::*;
71
72    impl ToMediaTypes for () {
73        fn generate(_components: &mut Components) -> Result<Map<String, MediaType>, anyhow::Error> {
74            Ok(map! {})
75        }
76    }
77
78    impl ToMediaTypes for String {
79        fn generate(_components: &mut Components) -> Result<Map<String, MediaType>, anyhow::Error> {
80            Ok(map! {
81                TEXT_PLAIN.to_string() => MediaType::default()
82            })
83        }
84    }
85    forward_impl_to_media_types!(&'static str, String);
86    forward_impl_to_media_types!(Cow<'static, str>, String);
87
88    impl ToMediaTypes for Vec<u8> {
89        fn generate(_components: &mut Components) -> Result<Map<String, MediaType>, anyhow::Error> {
90            // In schemars Bytes defined as array of integers, but OpenAPI recommend
91            // use string type with binary format
92            // https://swagger.io/docs/specification/describing-request-body/file-upload/
93            let schema = SchemaObject {
94                instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
95                format: Some("binary".into()),
96                ..SchemaObject::default()
97            };
98
99            Ok(map! {
100                APPLICATION_OCTET_STREAM.to_string() => MediaType {
101                    schema: Some(schema),
102                    ..MediaType::default()
103                },
104            })
105        }
106    }
107    forward_impl_to_media_types!(&'static [u8], Vec<u8>);
108    forward_impl_to_media_types!(Cow<'static, [u8]>, Vec<u8>);
109    forward_impl_to_media_types!(Bytes, Vec<u8>);
110    forward_impl_to_media_types!(BytesMut, Vec<u8>);
111}