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, 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: I) -> Self {
107        set_value!(self schema Some(component.into()))
108    }
109
110    /// Add additional description for header.
111    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
112        set_value!(self description description.map(|description| description.into()))
113    }
114
115    /// Add or change [`Header`] deprecated status.
116    pub fn deprecated(mut self, deprecated: Option<Deprecated>) -> Self {
117        set_value!(self deprecated deprecated)
118    }
119
120    /// Add or change serialization style of [`Header`].
121    pub fn style(mut self, style: Option<ParameterStyle>) -> Self {
122        set_value!(self style style)
123    }
124
125    /// Define whether [`Header`]s are exploded or not.
126    pub fn explode(mut self, explode: Option<bool>) -> Self {
127        set_value!(self explode explode)
128    }
129
130    /// Add or change example of [`Header`]'s potential value.
131    pub fn example(mut self, example: Option<Value>) -> Self {
132        set_value!(self example example)
133    }
134
135    /// Add examples from iterator.
136    pub fn examples_from_iter<
137        E: IntoIterator<Item = (N, V)>,
138        N: Into<String>,
139        V: Into<RefOr<Example>>,
140    >(
141        mut self,
142        examples: E,
143    ) -> Self {
144        self.examples.extend(
145            examples
146                .into_iter()
147                .map(|(name, example)| (name.into(), example.into())),
148        );
149
150        self
151    }
152
153    /// Add media type content representation to [`Header`].
154    pub fn content_from_iter<E: IntoIterator<Item = (N, V)>, N: Into<String>, V: Into<Content>>(
155        mut self,
156        content: E,
157    ) -> Self {
158        self.content.extend(
159            content
160                .into_iter()
161                .map(|(name, content)| (name.into(), content.into())),
162        );
163
164        self
165    }
166
167    /// Add openapi extensions (x-something) to the [`Header`].
168    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
169        set_value!(self extensions extensions)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use serde_json::json;
177
178    use crate::openapi::content::ContentBuilder;
179    use crate::openapi::example::ExampleBuilder;
180
181    #[test]
182    fn test_header_builder_and_serialization() {
183        let header = HeaderBuilder::new()
184            .description(Some("custom header"))
185            .deprecated(Some(Deprecated::True))
186            .style(Some(ParameterStyle::Simple))
187            .explode(Some(true))
188            .example(Some(json!("example-value")))
189            .build();
190
191        insta::assert_json_snapshot!(&header, @r#"
192        {
193          "schema": {
194            "type": "string"
195          },
196          "description": "custom header",
197          "deprecated": true,
198          "style": "simple",
199          "explode": true,
200          "example": "example-value"
201        }
202        "#);
203    }
204
205    #[test]
206    fn test_header_with_content_and_examples() {
207        let content = ContentBuilder::new()
208            .schema(Some(Object::with_type(Type::Integer)))
209            .build();
210        let example = ExampleBuilder::new().value(Some(json!("test"))).build();
211
212        let header = Header {
213            schema: None,
214            content: BTreeMap::from_iter([("application/json".to_string(), content)]),
215            examples: BTreeMap::from_iter([("test_example".to_string(), example.into())]),
216            ..Default::default()
217        };
218
219        insta::assert_json_snapshot!(&header, @r#"
220        {
221          "examples": {
222            "test_example": {
223              "value": "test"
224            }
225          },
226          "content": {
227            "application/json": {
228              "schema": {
229                "type": "integer"
230              }
231            }
232          }
233        }
234        "#);
235    }
236}