Skip to main content

utoipa/openapi/
header.rs

1//! Implements [OpenAPI Header Object][header] types.
2//!
3//! [header]: https://spec.openapis.org/oas/latest.html#header-object
4
5use 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    /// Implements [OpenAPI Header Object][header] for response headers.
18    ///
19    /// [header]: https://spec.openapis.org/oas/latest.html#header-object
20    #[non_exhaustive]
21    #[derive(Serialize, Deserialize, Clone, PartialEq)]
22    #[cfg_attr(feature = "debug", derive(Debug))]
23    pub struct Header {
24        /// Schema of header type.
25        #[serde(skip_serializing_if = "Option::is_none")]
26        pub schema: Option<RefOr<Schema>>,
27
28        /// Additional description of the header value.
29        #[serde(skip_serializing_if = "Option::is_none")]
30        pub description: Option<String>,
31
32        /// Declares the header deprecated status.
33        #[serde(skip_serializing_if = "Option::is_none")]
34        pub deprecated: Option<Deprecated>,
35
36        /// Describes how the header value will be serialized.
37        #[serde(skip_serializing_if = "Option::is_none")]
38        pub style: Option<ParameterStyle>,
39
40        /// When _`true`_ it will generate separate header value for each value with _`array`_ and _`object`_ type.
41        #[serde(skip_serializing_if = "Option::is_none")]
42        pub explode: Option<bool>,
43
44        /// Example of the header potential value.
45        #[serde(skip_serializing_if = "Option::is_none")]
46        pub example: Option<Value>,
47
48        /// Examples of the header potential values.
49        #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
50        pub examples: BTreeMap<String, RefOr<Example>>,
51
52        /// A map containing the representations for the header.
53        #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
54        pub content: BTreeMap<String, RefOr<Content>>,
55
56        /// Optional extensions "x-something".
57        #[serde(skip_serializing_if = "Option::is_none", flatten)]
58        pub extensions: Option<Extensions>,
59    }
60}
61
62impl Header {
63    /// Construct a new [`Header`] with custom schema. If you wish to construct a default
64    /// header with `String` type you can use [`Header::default`] function.
65    ///
66    /// # Examples
67    ///
68    /// Create new [`Header`] with integer type.
69    /// ```rust
70    /// # use utoipa::openapi::header::Header;
71    /// # use utoipa::openapi::{Object, Type};
72    /// let header = Header::new(Object::with_type(Type::Integer));
73    /// ```
74    ///
75    /// Create a new [`Header`] with default type `String`
76    /// ```rust
77    /// # use utoipa::openapi::header::Header;
78    /// let header = Header::default();
79    /// ```
80    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    /// Add schema of header.
106    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    /// Add media type representation for the header.
111    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    /// Add additional description for header.
121    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    /// Add or change [`Header`] deprecated status.
126    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
127        set_value!(self deprecated deprecated)
128    }
129
130    /// Add or change serialization style of [`Header`].
131    pub fn style(mut self, style: Option<ParameterStyle>) -> Self {
132        set_value!(self style style)
133    }
134
135    /// Define whether [`Header`]s are exploded or not.
136    pub fn explode(mut self, explode: Option<bool>) -> Self {
137        set_value!(self explode explode)
138    }
139
140    /// Add or change example of [`Header`]'s potential value.
141    pub fn example(mut self, example: Option<Value>) -> Self {
142        set_value!(self example example)
143    }
144
145    /// Add examples from iterator.
146    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    /// Add media type content representation to [`Header`].
164    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    /// Add openapi extensions (x-something) to the [`Header`].
182    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}