openapiv3_1/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 openapiv3_1::server::Server;
21//! Server::new("/api/v1");
22//! ```
23//!
24//! Create server with custom url using a builder.
25//! ```rust
26//! # use openapiv3_1::server::Server;
27//! Server::builder().url("https://alternative.api.url.test/api").build();
28//! ```
29//!
30//! Create server with builder and variable substitution.
31//! ```rust
32//! # use openapiv3_1::server::{Server, ServerVariable};
33//! Server::builder().url("/api/{version}/{username}")
34//! .parameter("version", ServerVariable::builder()
35//! .enum_values(["v1".into(), "v2".into()])
36//! .default_value("v1"))
37//! .parameter("username", ServerVariable::builder()
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 indexmap::IndexMap;
45
46use super::extensions::Extensions;
47
48/// Represents target server object. It can be used to alter server connection for
49/// _**path operations**_.
50///
51/// By default OpenAPI will implicitly implement [`Server`] with `url = "/"` if no servers is provided to
52/// the [`OpenApi`][openapi].
53///
54/// [openapi]: ../struct.OpenApi.html
55#[non_exhaustive]
56#[derive(serde_derive::Serialize, serde_derive::Deserialize, Default, Clone, PartialEq, Eq, bon::Builder)]
57#[cfg_attr(feature = "debug", derive(Debug))]
58#[serde(rename_all = "camelCase")]
59#[builder(on(_, into))]
60pub struct Server {
61 /// Optional map of variable name and its substitution value used in [`Server::url`].
62 #[serde(skip_serializing_if = "Option::is_none")]
63 #[builder(field)]
64 pub variables: Option<IndexMap<String, ServerVariable>>,
65
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 extensions "x-something".
77 #[serde(skip_serializing_if = "Option::is_none", flatten)]
78 pub extensions: Option<Extensions>,
79}
80
81impl Server {
82 /// Construct a new [`Server`] with given url. Url can be valid http url or context path of the url.
83 ///
84 /// If url is valid http url then all path operation request's will be forwarded to the selected [`Server`].
85 ///
86 /// If url is path of url e.g. `/api/v1` then the url will be appended to the servers address and the
87 /// operations will be forwarded to location `server address + url`.
88 ///
89 ///
90 /// # Examples
91 ///
92 /// Create new server with url path.
93 /// ```rust
94 /// # use openapiv3_1::server::Server;
95 /// Server::new("/api/v1");
96 /// ```
97 ///
98 /// Create new server with alternative server.
99 /// ```rust
100 /// # use openapiv3_1::server::Server;
101 /// Server::new("https://alternative.pet-api.test/api/v1");
102 /// ```
103 pub fn new<S: Into<String>>(url: S) -> Self {
104 Self {
105 url: url.into(),
106 ..Default::default()
107 }
108 }
109}
110
111impl<S: server_builder::State> ServerBuilder<S> {
112 /// Add parameter to [`Server`] which is used to substitute values in [`Server::url`].
113 ///
114 /// * `name` Defines name of the parameter which is being substituted within the url. If url has
115 /// `{username}` substitution then the name should be `username`.
116 /// * `parameter` Use [`ServerVariableBuilder`] to define how the parameter is being substituted
117 /// within the url.
118 pub fn parameter(mut self, name: impl Into<String>, variable: impl Into<ServerVariable>) -> Self {
119 self.variables.get_or_insert_default().insert(name.into(), variable.into());
120 self
121 }
122}
123
124/// Implements [OpenAPI Server Variable][server_variable] used to substitute variables in [`Server::url`].
125///
126/// [server_variable]: https://spec.openapis.org/oas/latest.html#server-variable-object
127#[non_exhaustive]
128#[derive(serde_derive::Serialize, serde_derive::Deserialize, Default, Clone, PartialEq, Eq, bon::Builder)]
129#[cfg_attr(feature = "debug", derive(Debug))]
130#[builder(on(_, into))]
131pub struct ServerVariable {
132 /// Default value used to substitute parameter if no other value is being provided.
133 #[serde(rename = "default")]
134 pub default_value: String,
135
136 /// Optional description describing the variable of substitution. Markdown syntax is supported.
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub description: Option<String>,
139
140 /// Enum values can be used to limit possible options for substitution. If enum values is used
141 /// the [`ServerVariable::default_value`] must contain one of the enum values.
142 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
143 pub enum_values: Option<Vec<String>>,
144
145 /// Optional extensions "x-something".
146 #[serde(skip_serializing_if = "Option::is_none", flatten)]
147 pub extensions: Option<Extensions>,
148}
149
150impl<S: server_variable_builder::IsComplete> From<ServerVariableBuilder<S>> for ServerVariable {
151 fn from(value: ServerVariableBuilder<S>) -> Self {
152 value.build()
153 }
154}
155
156#[cfg(test)]
157#[cfg_attr(coverage_nightly, coverage(off))]
158mod tests {
159 use super::*;
160
161 macro_rules! test_fn {
162 ($name:ident : $schema:expr; $expected:literal) => {
163 #[test]
164 fn $name() {
165 let value = serde_json::to_value($schema).unwrap();
166 let expected_value: serde_json::Value = serde_json::from_str($expected).unwrap();
167
168 assert_eq!(
169 value,
170 expected_value,
171 "testing serializing \"{}\": \nactual:\n{}\nexpected:\n{}",
172 stringify!($name),
173 value,
174 expected_value
175 );
176
177 println!("{}", &serde_json::to_string_pretty(&$schema).unwrap());
178 }
179 };
180 }
181
182 test_fn! {
183 create_server_with_builder_and_variable_substitution:
184 Server::builder().url("/api/{version}/{username}")
185 .parameter("version", ServerVariable::builder()
186 .enum_values(["v1".into(), "v2".into()])
187 .description("api version")
188 .default_value("v1"))
189 .parameter("username", ServerVariable::builder()
190 .default_value("the_user")).build();
191 r###"{
192 "url": "/api/{version}/{username}",
193 "variables": {
194 "version": {
195 "enum": ["v1", "v2"],
196 "default": "v1",
197 "description": "api version"
198 },
199 "username": {
200 "default": "the_user"
201 }
202 }
203}"###
204 }
205}