Skip to main content

utoipa/openapi/
server.rs

1//! Implements [OpenAPI Server Object][server] types to configure target servers.
2//!
3//! OpenAPI will implicitly add [`Server`] with `url = "/"` to [`OpenApi`][openapi] when no servers
4//! are defined.
5//!
6//! [`Server`] can be used to alter connection url for _**path operations**_. It can be a
7//! relative path e.g `/api/v1` or valid http url e.g. `http://alternative.api.com/api/v1`.
8//!
9//! Relative path will append to the **sever address** so the connection url for _**path operations**_
10//! will become `server address + relative path`.
11//!
12//! Optionally it also supports parameter substitution with `{variable}` syntax.
13//!
14//! See [`Modify`][modify] trait for details how add servers to [`OpenApi`][openapi].
15//!
16//! # Examples
17//!
18//! Create new server with relative path.
19//! ```rust
20//! # use utoipa::openapi::server::Server;
21//! Server::new("/api/v1");
22//! ```
23//!
24//! Create server with custom url using a builder.
25//! ```rust
26//! # use utoipa::openapi::server::ServerBuilder;
27//! ServerBuilder::new().url("https://alternative.api.url.test/api").build();
28//! ```
29//!
30//! Create server with builder and variable substitution.
31//! ```rust
32//! # use utoipa::openapi::server::{ServerBuilder, ServerVariableBuilder};
33//! ServerBuilder::new().url("/api/{version}/{username}")
34//!     .parameter("version", ServerVariableBuilder::new()
35//!         .enum_values(Some(["v1", "v2"]))
36//!         .default_value("v1"))
37//!     .parameter("username", ServerVariableBuilder::new()
38//!         .default_value("the_user")).build();
39//! ```
40//!
41//! [server]: https://spec.openapis.org/oas/latest.html#server-object
42//! [openapi]: ../struct.OpenApi.html
43//! [modify]: ../../trait.Modify.html
44use std::{collections::BTreeMap, iter};
45
46use serde::{Deserialize, Serialize};
47
48use super::extensions::Extensions;
49use super::{builder, set_value};
50
51builder! {
52    ServerBuilder;
53
54    /// Represents target server object. It can be used to alter server connection for
55    /// _**path operations**_.
56    ///
57    /// By default OpenAPI will implicitly implement [`Server`] with `url = "/"` if no servers is provided to
58    /// the [`OpenApi`][openapi].
59    ///
60    /// [openapi]: ../struct.OpenApi.html
61    #[non_exhaustive]
62    #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
63    #[cfg_attr(feature = "debug", derive(Debug))]
64    #[serde(rename_all = "camelCase")]
65    pub struct Server {
66        /// Target url of the [`Server`]. It can be valid http url or relative path.
67        ///
68        /// Url also supports variable substitution with `{variable}` syntax. The substitutions
69        /// then can be configured with [`Server::variables`] map.
70        pub url: String,
71
72        /// Optional description describing the target server url. Description supports markdown syntax.
73        #[serde(skip_serializing_if = "Option::is_none")]
74        pub description: Option<String>,
75
76        /// Optional name for the server.
77        #[serde(skip_serializing_if = "Option::is_none")]
78        pub name: Option<String>,
79
80        /// Optional map of variable name and its substitution value used in [`Server::url`].
81        #[serde(skip_serializing_if = "Option::is_none")]
82        pub variables: Option<BTreeMap<String, ServerVariable>>,
83
84        /// Optional extensions "x-something".
85        #[serde(skip_serializing_if = "Option::is_none", flatten)]
86        pub extensions: Option<Extensions>,
87    }
88}
89
90impl Server {
91    /// Construct a new [`Server`] with given url. Url can be valid http url or context path of the url.
92    ///
93    /// If url is valid http url then all path operation request's will be forwarded to the selected [`Server`].
94    ///
95    /// If url is path of url e.g. `/api/v1` then the url will be appended to the servers address and the
96    /// operations will be forwarded to location `server address + url`.
97    ///
98    ///
99    /// # Examples
100    ///
101    /// Create new server with url path.
102    /// ```rust
103    /// # use utoipa::openapi::server::Server;
104    ///  Server::new("/api/v1");
105    /// ```
106    ///
107    /// Create new server with alternative server.
108    /// ```rust
109    /// # use utoipa::openapi::server::Server;
110    ///  Server::new("https://alternative.pet-api.test/api/v1");
111    /// ```
112    pub fn new<S: Into<String>>(url: S) -> Self {
113        Self {
114            url: url.into(),
115            ..Default::default()
116        }
117    }
118}
119
120impl ServerBuilder {
121    /// Add url to the target [`Server`].
122    pub fn url<U: Into<String>>(mut self, url: U) -> Self {
123        set_value!(self url url.into())
124    }
125
126    /// Add or change description of the [`Server`].
127    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
128        set_value!(self description description.map(|description| description.into()))
129    }
130
131    /// Add or change name of the [`Server`].
132    pub fn name<S: Into<String>>(mut self, name: Option<S>) -> Self {
133        set_value!(self name name.map(|name| name.into()))
134    }
135
136    /// Add parameter to [`Server`] which is used to substitute values in [`Server::url`].
137    ///
138    /// * `name` Defines name of the parameter which is being substituted within the url. If url has
139    ///   `{username}` substitution then the name should be `username`.
140    /// * `parameter` Use [`ServerVariableBuilder`] to define how the parameter is being substituted
141    ///   within the url.
142    pub fn parameter<N: Into<String>, V: Into<ServerVariable>>(
143        mut self,
144        name: N,
145        variable: V,
146    ) -> Self {
147        match self.variables {
148            Some(ref mut variables) => {
149                variables.insert(name.into(), variable.into());
150            }
151            None => {
152                self.variables = Some(BTreeMap::from_iter(iter::once((
153                    name.into(),
154                    variable.into(),
155                ))))
156            }
157        }
158
159        self
160    }
161
162    /// Add openapi extensions (x-something) of the API.
163    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
164        set_value!(self extensions extensions)
165    }
166}
167
168builder! {
169    ServerVariableBuilder;
170
171    /// Implements [OpenAPI Server Variable][server_variable] used to substitute variables in [`Server::url`].
172    ///
173    /// [server_variable]: https://spec.openapis.org/oas/latest.html#server-variable-object
174    #[non_exhaustive]
175    #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
176    #[cfg_attr(feature = "debug", derive(Debug))]
177    pub struct ServerVariable {
178        /// Default value used to substitute parameter if no other value is being provided.
179        #[serde(rename = "default")]
180        pub default_value: String,
181
182        /// Optional description describing the variable of substitution. Markdown syntax is supported.
183        #[serde(skip_serializing_if = "Option::is_none")]
184        pub description: Option<String>,
185
186        /// Enum values can be used to limit possible options for substitution. If enum values is used
187        /// the [`ServerVariable::default_value`] must contain one of the enum values.
188        #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
189        pub enum_values: Option<Vec<String>>,
190
191        /// Optional extensions "x-something".
192        #[serde(skip_serializing_if = "Option::is_none", flatten)]
193        pub extensions: Option<Extensions>,
194    }
195}
196
197impl ServerVariableBuilder {
198    /// Add default value for substitution.
199    pub fn default_value<S: Into<String>>(mut self, default_value: S) -> Self {
200        set_value!(self default_value default_value.into())
201    }
202
203    /// Add or change description of substituted parameter.
204    pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
205        set_value!(self description description.map(|description| description.into()))
206    }
207
208    /// Add or change possible values used to substitute parameter.
209    pub fn enum_values<I: IntoIterator<Item = V>, V: Into<String>>(
210        mut self,
211        enum_values: Option<I>,
212    ) -> Self {
213        set_value!(self enum_values enum_values
214            .map(|enum_values| enum_values.into_iter().map(|value| value.into()).collect()))
215    }
216
217    /// Add openapi extensions (x-something) of the API.
218    pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
219        set_value!(self extensions extensions)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    macro_rules! test_fn {
228        ($name:ident: $schema:expr; $expected:literal) => {
229            #[test]
230            fn $name() {
231                let value = serde_json::to_value($schema).unwrap();
232                let expected_value: serde_json::Value = serde_json::from_str($expected).unwrap();
233
234                assert_eq!(
235                    value,
236                    expected_value,
237                    "testing serializing \"{}\": \nactual:\n{}\nexpected:\n{}",
238                    stringify!($name),
239                    value,
240                    expected_value
241                );
242
243                println!("{}", &serde_json::to_string_pretty(&$schema).unwrap());
244            }
245        };
246    }
247
248    test_fn! {
249    create_server_with_builder_and_variable_substitution:
250    ServerBuilder::new().url("/api/{version}/{username}")
251        .parameter("version", ServerVariableBuilder::new()
252            .enum_values(Some(["v1", "v2"]))
253            .description(Some("api version"))
254            .default_value("v1"))
255        .parameter("username", ServerVariableBuilder::new()
256            .default_value("the_user")).build();
257    r###"{
258  "url": "/api/{version}/{username}",
259  "variables": {
260      "version": {
261          "enum": ["v1", "v2"],
262          "default": "v1",
263          "description": "api version"
264      },
265      "username": {
266          "default": "the_user"
267      }
268  }
269}"###
270    }
271}