1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::BTreeMap;
8
9use super::{
10 builder, content::Content, example::Example, extensions::Extensions, path::ParameterStyle,
11 set_value, Deprecated, Object, RefOr, Schema, Type,
12};
13
14builder! {
15 HeaderBuilder;
16
17 #[non_exhaustive]
21 #[derive(Serialize, Deserialize, Clone, PartialEq)]
22 #[cfg_attr(feature = "debug", derive(Debug))]
23 pub struct Header {
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub schema: Option<RefOr<Schema>>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub description: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub deprecated: Option<Deprecated>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub style: Option<ParameterStyle>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub explode: Option<bool>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub example: Option<Value>,
47
48 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
50 pub examples: BTreeMap<String, RefOr<Example>>,
51
52 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
54 pub content: BTreeMap<String, RefOr<Content>>,
55
56 #[serde(skip_serializing_if = "Option::is_none", flatten)]
58 pub extensions: Option<Extensions>,
59 }
60}
61
62impl Header {
63 pub fn new<C: Into<RefOr<Schema>>>(component: C) -> Self {
81 Self {
82 schema: Some(component.into()),
83 ..Default::default()
84 }
85 }
86}
87
88impl Default for Header {
89 fn default() -> Self {
90 Self {
91 schema: Some(Object::with_type(Type::String).into()),
92 description: None,
93 deprecated: None,
94 style: None,
95 explode: None,
96 example: None,
97 examples: BTreeMap::new(),
98 content: BTreeMap::new(),
99 extensions: None,
100 }
101 }
102}
103
104impl HeaderBuilder {
105 pub fn schema<I: Into<RefOr<Schema>>>(mut self, component: Option<I>) -> Self {
107 set_value!(self schema component.map(Into::into))
108 }
109
110 pub fn content<S: Into<String>, C: Into<RefOr<Content>>>(
112 mut self,
113 content_type: S,
114 content: C,
115 ) -> Self {
116 self.content.insert(content_type.into(), content.into());
117 self
118 }
119
120 pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
122 set_value!(self description description.map(|description| description.into()))
123 }
124
125 pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
127 set_value!(self deprecated deprecated)
128 }
129
130 pub fn style(mut self, style: Option<ParameterStyle>) -> Self {
132 set_value!(self style style)
133 }
134
135 pub fn explode(mut self, explode: Option<bool>) -> Self {
137 set_value!(self explode explode)
138 }
139
140 pub fn example(mut self, example: Option<Value>) -> Self {
142 set_value!(self example example)
143 }
144
145 pub fn examples_from_iter<
147 E: IntoIterator<Item = (N, V)>,
148 N: Into<String>,
149 V: Into<RefOr<Example>>,
150 >(
151 mut self,
152 examples: E,
153 ) -> Self {
154 self.examples.extend(
155 examples
156 .into_iter()
157 .map(|(name, example)| (name.into(), example.into())),
158 );
159
160 self
161 }
162
163 pub fn content_from_iter<
165 E: IntoIterator<Item = (N, V)>,
166 N: Into<String>,
167 V: Into<RefOr<Content>>,
168 >(
169 mut self,
170 content: E,
171 ) -> Self {
172 self.content.extend(
173 content
174 .into_iter()
175 .map(|(name, content)| (name.into(), content.into())),
176 );
177
178 self
179 }
180
181 pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
183 set_value!(self extensions extensions)
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use serde_json::json;
191
192 use crate::openapi::content::ContentBuilder;
193 use crate::openapi::example::ExampleBuilder;
194
195 #[test]
196 fn test_header_builder_and_serialization() {
197 let header = HeaderBuilder::new()
198 .description(Some("custom header"))
199 .deprecated(Some(Deprecated::True))
200 .style(Some(ParameterStyle::Simple))
201 .explode(Some(true))
202 .example(Some(json!("example-value")))
203 .build();
204
205 insta::assert_json_snapshot!(&header, @r#"
206 {
207 "schema": {
208 "type": "string"
209 },
210 "description": "custom header",
211 "deprecated": true,
212 "style": "simple",
213 "explode": true,
214 "example": "example-value"
215 }
216 "#);
217 }
218
219 #[test]
220 fn test_header_with_content_and_examples() {
221 let content = ContentBuilder::new()
222 .schema(Some(Object::with_type(Type::Integer)))
223 .build();
224 let example = ExampleBuilder::new().value(Some(json!("test"))).build();
225
226 let header = Header {
227 schema: None,
228 content: BTreeMap::from_iter([("application/json".to_string(), content.into())]),
229 examples: BTreeMap::from_iter([("test_example".to_string(), example.into())]),
230 ..Default::default()
231 };
232
233 insta::assert_json_snapshot!(&header, @r#"
234 {
235 "examples": {
236 "test_example": {
237 "value": "test"
238 }
239 },
240 "content": {
241 "application/json": {
242 "schema": {
243 "type": "integer"
244 }
245 }
246 }
247 }
248 "#);
249 }
250}