salvo_oapi/openapi/
header.rs1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use super::parameter::ParameterStyle;
9use super::{BasicType, Content, Deprecated, Example, Object, PropMap, RefOr, Schema};
10
11#[non_exhaustive]
20#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
21#[serde(rename_all = "camelCase")]
22pub struct Header {
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub description: Option<String>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub required: Option<bool>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub deprecated: Option<Deprecated>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub schema: Option<RefOr<Schema>>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
42 pub style: Option<ParameterStyle>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
47 pub explode: Option<bool>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub example: Option<Value>,
52
53 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
56 pub examples: PropMap<String, RefOr<Example>>,
57
58 #[serde(skip_serializing_if = "PropMap::is_empty", default)]
61 pub content: PropMap<String, Content>,
62
63 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
65 pub extensions: PropMap<String, serde_json::Value>,
66}
67
68impl Header {
69 #[must_use]
86 pub fn new<C: Into<RefOr<Schema>>>(component: C) -> Self {
87 Self {
88 schema: Some(component.into()),
89 ..Default::default()
90 }
91 }
92
93 #[must_use]
103 pub fn with_content<S: Into<String>, C: Into<Content>>(media_type: S, content: C) -> Self {
104 let mut header = Self {
105 schema: None,
106 ..Default::default()
107 };
108 header.content.insert(media_type.into(), content.into());
109 header
110 }
111
112 #[must_use]
114 pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: I) -> Self {
115 self.schema = Some(component.into());
116 self
117 }
118
119 #[must_use]
121 pub fn description<S: Into<String>>(mut self, description: S) -> Self {
122 self.description = Some(description.into());
123 self
124 }
125
126 #[must_use]
128 pub fn required(mut self, required: bool) -> Self {
129 self.required = Some(required);
130 self
131 }
132
133 #[must_use]
135 pub fn deprecated<D: Into<Deprecated>>(mut self, deprecated: D) -> Self {
136 self.deprecated = Some(deprecated.into());
137 self
138 }
139
140 #[must_use]
142 pub fn style(mut self, style: ParameterStyle) -> Self {
143 self.style = Some(style);
144 self
145 }
146
147 #[must_use]
149 pub fn explode(mut self, explode: bool) -> Self {
150 self.explode = Some(explode);
151 self
152 }
153
154 #[must_use]
156 pub fn example(mut self, example: Value) -> Self {
157 self.example = Some(example);
158 self
159 }
160
161 #[must_use]
163 pub fn add_example<N: Into<String>, E: Into<RefOr<Example>>>(
164 mut self,
165 name: N,
166 example: E,
167 ) -> Self {
168 self.examples.insert(name.into(), example.into());
169 self
170 }
171
172 #[must_use]
177 pub fn content<S: Into<String>, C: Into<Content>>(mut self, media_type: S, content: C) -> Self {
178 self.content.insert(media_type.into(), content.into());
179 self
180 }
181
182 #[must_use]
184 pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
185 self.extensions.insert(key.into(), value);
186 self
187 }
188}
189
190impl Default for Header {
191 fn default() -> Self {
192 Self {
193 description: Default::default(),
194 required: Default::default(),
195 deprecated: Default::default(),
196 schema: Some(Object::with_type(BasicType::String).into()),
197 style: Default::default(),
198 explode: Default::default(),
199 example: Default::default(),
200 examples: Default::default(),
201 content: Default::default(),
202 extensions: Default::default(),
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use assert_json_diff::assert_json_eq;
210 use serde_json::json;
211
212 use super::*;
213
214 #[test]
215 fn test_build_header() {
216 let header = Header::new(Object::with_type(BasicType::String));
217 assert_json_eq!(
218 header,
219 json!({
220 "schema": {
221 "type": "string"
222 }
223 })
224 );
225
226 let header = header
227 .description("test description")
228 .schema(Object::with_type(BasicType::Number));
229 assert_json_eq!(
230 header,
231 json!({
232 "description": "test description",
233 "schema": {
234 "type": "number"
235 }
236 })
237 );
238 }
239
240 #[test]
241 fn header_full_surface_round_trips() {
242 let header = Header::new(Object::with_type(BasicType::String))
243 .description("rate limit")
244 .required(true)
245 .deprecated(crate::Deprecated::False)
246 .style(ParameterStyle::Simple)
247 .explode(false)
248 .example(json!("100"))
249 .add_extension("x-vendor", json!("acme"));
250
251 let value = serde_json::to_value(&header).expect("serialize");
252 assert_json_eq!(
253 &value,
254 json!({
255 "description": "rate limit",
256 "required": true,
257 "deprecated": false,
258 "schema": { "type": "string" },
259 "style": "simple",
260 "explode": false,
261 "example": "100",
262 "x-vendor": "acme"
263 })
264 );
265
266 let parsed: Header = serde_json::from_value(value).expect("deserialize");
267 assert_eq!(parsed, header);
268 }
269
270 #[test]
271 fn header_with_content_omits_schema() {
272 let header = Header::with_content(
273 "application/linkset",
274 Content::new(Object::with_type(BasicType::String)),
275 );
276
277 let value = serde_json::to_value(&header).expect("serialize");
278 assert_json_eq!(
279 &value,
280 json!({
281 "content": {
282 "application/linkset": { "schema": { "type": "string" } }
283 }
284 })
285 );
286
287 let parsed: Header = serde_json::from_value(value).expect("deserialize");
288 assert_eq!(parsed, header);
289 }
290}