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 map of variable name and its substitution value used in [`Server::url`].
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub variables: Option<BTreeMap<String, ServerVariable>>,
79
80 /// Optional extensions "x-something".
81 #[serde(skip_serializing_if = "Option::is_none", flatten)]
82 pub extensions: Option<Extensions>,
83 }
84}
85
86impl Server {
87 /// Construct a new [`Server`] with given url. Url can be valid http url or context path of the url.
88 ///
89 /// If url is valid http url then all path operation request's will be forwarded to the selected [`Server`].
90 ///
91 /// If url is path of url e.g. `/api/v1` then the url will be appended to the servers address and the
92 /// operations will be forwarded to location `server address + url`.
93 ///
94 ///
95 /// # Examples
96 ///
97 /// Create new server with url path.
98 /// ```rust
99 /// # use utoipa::openapi::server::Server;
100 /// Server::new("/api/v1");
101 /// ```
102 ///
103 /// Create new server with alternative server.
104 /// ```rust
105 /// # use utoipa::openapi::server::Server;
106 /// Server::new("https://alternative.pet-api.test/api/v1");
107 /// ```
108 pub fn new<S: Into<String>>(url: S) -> Self {
109 Self {
110 url: url.into(),
111 ..Default::default()
112 }
113 }
114}
115
116impl ServerBuilder {
117 /// Add url to the target [`Server`].
118 pub fn url<U: Into<String>>(mut self, url: U) -> Self {
119 set_value!(self url url.into())
120 }
121
122 /// Add or change description of the [`Server`].
123 pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
124 set_value!(self description description.map(|description| description.into()))
125 }
126
127 /// Add parameter to [`Server`] which is used to substitute values in [`Server::url`].
128 ///
129 /// * `name` Defines name of the parameter which is being substituted within the url. If url has
130 /// `{username}` substitution then the name should be `username`.
131 /// * `parameter` Use [`ServerVariableBuilder`] to define how the parameter is being substituted
132 /// within the url.
133 pub fn parameter<N: Into<String>, V: Into<ServerVariable>>(
134 mut self,
135 name: N,
136 variable: V,
137 ) -> Self {
138 match self.variables {
139 Some(ref mut variables) => {
140 variables.insert(name.into(), variable.into());
141 }
142 None => {
143 self.variables = Some(BTreeMap::from_iter(iter::once((
144 name.into(),
145 variable.into(),
146 ))))
147 }
148 }
149
150 self
151 }
152
153 /// Add openapi extensions (x-something) of the API.
154 pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
155 set_value!(self extensions extensions)
156 }
157}
158
159builder! {
160 ServerVariableBuilder;
161
162 /// Implements [OpenAPI Server Variable][server_variable] used to substitute variables in [`Server::url`].
163 ///
164 /// [server_variable]: https://spec.openapis.org/oas/latest.html#server-variable-object
165 #[non_exhaustive]
166 #[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
167 #[cfg_attr(feature = "debug", derive(Debug))]
168 pub struct ServerVariable {
169 /// Default value used to substitute parameter if no other value is being provided.
170 #[serde(rename = "default")]
171 pub default_value: String,
172
173 /// Optional description describing the variable of substitution. Markdown syntax is supported.
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub description: Option<String>,
176
177 /// Enum values can be used to limit possible options for substitution. If enum values is used
178 /// the [`ServerVariable::default_value`] must contain one of the enum values.
179 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
180 pub enum_values: Option<Vec<String>>,
181
182 /// Optional extensions "x-something".
183 #[serde(skip_serializing_if = "Option::is_none", flatten)]
184 pub extensions: Option<Extensions>,
185 }
186}
187
188impl ServerVariableBuilder {
189 /// Add default value for substitution.
190 pub fn default_value<S: Into<String>>(mut self, default_value: S) -> Self {
191 set_value!(self default_value default_value.into())
192 }
193
194 /// Add or change description of substituted parameter.
195 pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
196 set_value!(self description description.map(|description| description.into()))
197 }
198
199 /// Add or change possible values used to substitute parameter.
200 pub fn enum_values<I: IntoIterator<Item = V>, V: Into<String>>(
201 mut self,
202 enum_values: Option<I>,
203 ) -> Self {
204 set_value!(self enum_values enum_values
205 .map(|enum_values| enum_values.into_iter().map(|value| value.into()).collect()))
206 }
207
208 /// Add openapi extensions (x-something) of the API.
209 pub fn extensions(mut self, extensions: Option<Extensions>) -> Self {
210 set_value!(self extensions extensions)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 macro_rules! test_fn {
219 ($name:ident: $schema:expr; $expected:literal) => {
220 #[test]
221 fn $name() {
222 let value = serde_json::to_value($schema).unwrap();
223 let expected_value: serde_json::Value = serde_json::from_str($expected).unwrap();
224
225 assert_eq!(
226 value,
227 expected_value,
228 "testing serializing \"{}\": \nactual:\n{}\nexpected:\n{}",
229 stringify!($name),
230 value,
231 expected_value
232 );
233
234 println!("{}", &serde_json::to_string_pretty(&$schema).unwrap());
235 }
236 };
237 }
238
239 test_fn! {
240 create_server_with_builder_and_variable_substitution:
241 ServerBuilder::new().url("/api/{version}/{username}")
242 .parameter("version", ServerVariableBuilder::new()
243 .enum_values(Some(["v1", "v2"]))
244 .description(Some("api version"))
245 .default_value("v1"))
246 .parameter("username", ServerVariableBuilder::new()
247 .default_value("the_user")).build();
248 r###"{
249 "url": "/api/{version}/{username}",
250 "variables": {
251 "version": {
252 "enum": ["v1", "v2"],
253 "default": "v1",
254 "description": "api version"
255 },
256 "username": {
257 "default": "the_user"
258 }
259 }
260}"###
261 }
262}