Skip to main content

ohkami_openapi/
response.rs

1use super::_util::{Content, Map, is_false};
2use super::schema::{RawSchema, Schema, SchemaRef, Type::SchemaType};
3use serde::Serialize;
4
5#[derive(Serialize, Clone)]
6pub struct Responses(Map<Status, Response>);
7
8#[derive(Clone, PartialEq, PartialOrd)]
9pub enum Status {
10    Code(u16),
11    Default,
12}
13impl Serialize for Status {
14    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
15        (match self {
16            Self::Code(code) => std::borrow::Cow::Owned(code.to_string()),
17            Self::Default => std::borrow::Cow::Borrowed("default"),
18        })
19        .serialize(serializer)
20    }
21}
22impl From<u16> for Status {
23    fn from(code: u16) -> Self {
24        Self::Code(code)
25    }
26}
27impl From<&str> for Status {
28    fn from(s: &str) -> Self {
29        match s {
30            "default" => Self::Default,
31            _ => Self::Code(s.parse().expect("invalid status code")),
32        }
33    }
34}
35
36#[derive(Serialize, Clone, PartialEq)]
37pub struct Response {
38    description: &'static str,
39
40    #[serde(skip_serializing_if = "Map::is_empty")]
41    content: Map<&'static str, Content>,
42
43    #[serde(skip_serializing_if = "Map::is_empty")]
44    headers: Map<&'static str, ResponseHeader>,
45}
46
47#[derive(Serialize, Clone, PartialEq)]
48pub struct ResponseHeader {
49    #[serde(skip_serializing_if = "Option::is_none")]
50    description: Option<&'static str>,
51
52    required: bool,
53
54    #[serde(skip_serializing_if = "is_false")]
55    deprecated: bool,
56
57    schema: SchemaRef,
58}
59
60impl Responses {
61    pub fn new<const N: usize>(code_responses: [(u16, Response); N]) -> Self {
62        Self(Map::from_iter(
63            code_responses.map(|(code, res)| (Status::Code(code), res)),
64        ))
65    }
66
67    pub fn or(mut self, code: u16, response: Response) -> Self {
68        self.0.insert(Status::Code(code), response);
69        self
70    }
71
72    pub fn or_default(mut self, default_response: Response) -> Self {
73        self.0.insert(Status::Default, default_response);
74        self
75    }
76
77    pub fn merge(&mut self, another: Self) {
78        for (code, res) in another.0 {
79            self.0.insert(code, res);
80        }
81    }
82
83    pub(crate) fn refize_schemas(&mut self) -> impl Iterator<Item = RawSchema> + '_ {
84        self.0.values_mut().flat_map(Response::refize_schemas)
85    }
86
87    pub(crate) fn override_response_description(
88        &mut self,
89        status: impl Into<Status>,
90        new_description: &'static str,
91    ) {
92        let status = status.into();
93        if let Some(response) = self.0.get_mut(&status) {
94            response.description = new_description;
95        } else {
96            self.0.insert(status, Response::when(new_description));
97        }
98    }
99}
100
101impl Response {
102    pub fn when(description: &'static str) -> Self {
103        Self {
104            description,
105            content: Map::new(),
106            headers: Map::new(),
107        }
108    }
109
110    pub fn content(mut self, media_type: &'static str, schema: impl Into<SchemaRef>) -> Self {
111        if !media_type.is_empty() {
112            self.content
113                .insert(media_type, Content::from(schema.into()));
114        }
115        self
116    }
117
118    pub fn header(mut self, name: &'static str, header: impl Into<ResponseHeader>) -> Self {
119        self.headers.insert(name, header.into());
120        self
121    }
122
123    pub(self) fn refize_schemas(&mut self) -> impl Iterator<Item = RawSchema> {
124        let mut refizeds = vec![];
125        for content in self.content.values_mut() {
126            for refized in content.refize_schema() {
127                refizeds.push(refized);
128            }
129        }
130        refizeds.into_iter()
131    }
132}
133
134impl ResponseHeader {
135    pub fn of(schema: impl Into<SchemaRef>) -> Self {
136        Self {
137            description: None,
138            required: true,
139            deprecated: false,
140            schema: schema.into(),
141        }
142    }
143    pub fn optional(schema: impl Into<SchemaRef>) -> Self {
144        Self {
145            description: None,
146            required: false,
147            deprecated: false,
148            schema: schema.into(),
149        }
150    }
151
152    pub fn description(mut self, description: &'static str) -> Self {
153        self.description = Some(description);
154        self
155    }
156
157    pub fn deprecated(mut self) -> Self {
158        self.deprecated = true;
159        self
160    }
161}
162impl<T: SchemaType> From<Schema<T>> for ResponseHeader {
163    fn from(schema: Schema<T>) -> Self {
164        Self::of(schema)
165    }
166}