Skip to main content

salvo_oapi/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
10//! operations**_ will become `server address + relative path`.
11//!
12//! Optionally it also supports parameter substitution with `{variable}` syntax.
13//!
14//! # Examples
15//!
16//! Create new server with relative path.
17//! ```rust
18//! # use salvo_oapi::server::Server;
19//! Server::new("/api/v1");
20//! ```
21//!
22//! Create server with custom url using a builder.
23//! ```rust
24//! # use salvo_oapi::server::Server;
25//! Server::new("https://alternative.api.url.test/api");
26//! ```
27//!
28//! Create server with builder and variable substitution.
29//! ```rust
30//! # use salvo_oapi::server::{Server, ServerVariable};
31//! Server::new("/api/{version}/{username}")
32//!     .add_variable(
33//!         "version",
34//!         ServerVariable::new()
35//!             .enum_values(["v1", "v2"])
36//!             .default_value("v1"),
37//!     )
38//!     .add_variable("username", ServerVariable::new().default_value("the_user"));
39//! ```
40//!
41//! [server]: https://spec.openapis.org/oas/latest.html#server-object
42//! [openapi]: ../struct.OpenApi.html
43use std::cmp::Ordering;
44use std::collections::BTreeSet;
45use std::ops::{Deref, DerefMut};
46
47use serde::{Deserialize, Serialize};
48
49use crate::PropMap;
50
51#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Debug)]
52
53/// Collection for [`Server`] objects.
54pub struct Servers(pub BTreeSet<Server>);
55impl Deref for Servers {
56    type Target = BTreeSet<Server>;
57
58    fn deref(&self) -> &Self::Target {
59        &self.0
60    }
61}
62impl DerefMut for Servers {
63    fn deref_mut(&mut self) -> &mut Self::Target {
64        &mut self.0
65    }
66}
67impl IntoIterator for Servers {
68    type Item = Server;
69    type IntoIter = <BTreeSet<Server> as IntoIterator>::IntoIter;
70
71    fn into_iter(self) -> Self::IntoIter {
72        self.0.into_iter()
73    }
74}
75impl Servers {
76    /// Construct a new empty [`Servers`]. This is effectively same as calling [`Servers::default`].
77    #[must_use]
78    pub fn new() -> Self {
79        Default::default()
80    }
81    /// Returns `true` if instance contains no elements.
82    #[must_use]
83    pub fn is_empty(&self) -> bool {
84        self.0.is_empty()
85    }
86    /// Inserts a server into the instance and returns `self`.
87    #[must_use]
88    pub fn server<S: Into<Server>>(mut self, server: S) -> Self {
89        self.insert(server);
90        self
91    }
92    /// Inserts a server into the instance.
93    pub fn insert<S: Into<Server>>(&mut self, server: S) {
94        let server = server.into();
95        let exist_server = self.0.iter().find(|s| s.url == server.url).cloned();
96        if let Some(mut exist_server) = exist_server {
97            let Server {
98                description,
99                mut variables,
100                extensions,
101                ..
102            } = server;
103            exist_server.variables.append(&mut variables);
104            if description.is_some() {
105                exist_server.description = description;
106            }
107            exist_server.extensions.extend(extensions);
108            self.0.remove(&exist_server);
109            self.0.insert(exist_server);
110        } else {
111            self.0.insert(server);
112        }
113    }
114
115    /// Moves all elements from `other` into `self`, leaving `other` empty.
116    ///
117    /// If a key from `other` is already present in `self`, the respective
118    /// value from `self` will be overwritten with the respective value from `other`.
119    pub fn append(&mut self, other: &mut Self) {
120        let servers = std::mem::take(&mut other.0);
121        for server in servers {
122            self.insert(server);
123        }
124    }
125    /// Extends a collection with the contents of an iterator.
126    pub fn extend<I>(&mut self, iter: I)
127    where
128        I: IntoIterator<Item = Server>,
129    {
130        for server in iter.into_iter() {
131            self.insert(server);
132        }
133    }
134}
135
136/// Represents target server object. It can be used to alter server connection for
137/// _**path operations**_.
138///
139/// By default OpenAPI will implicitly implement [`Server`] with `url = "/"` if no servers is
140/// provided to the [`OpenApi`][openapi].
141///
142/// [openapi]: ../struct.OpenApi.html
143#[non_exhaustive]
144#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
145#[serde(rename_all = "camelCase")]
146pub struct Server {
147    /// Target url of the [`Server`]. It can be valid http url or relative path.
148    ///
149    /// Url also supports variable substitution with `{variable}` syntax. The substitutions
150    /// then can be configured with [`Server::variables`] map.
151    pub url: String,
152
153    /// Optional description describing the target server url. Description supports markdown
154    /// syntax.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub description: Option<String>,
157
158    /// Optional map of variable name and its substitution value used in [`Server::url`].
159    #[serde(skip_serializing_if = "ServerVariables::is_empty")]
160    pub variables: ServerVariables,
161
162    /// Optional extensions "x-something"
163    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
164    pub extensions: PropMap<String, serde_json::Value>,
165}
166
167impl Ord for Server {
168    fn cmp(&self, other: &Self) -> Ordering {
169        self.url.cmp(&other.url)
170    }
171}
172impl PartialOrd for Server {
173    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
174        Some(self.cmp(other))
175    }
176}
177
178impl Server {
179    /// Construct a new [`Server`] with given url. Url can be valid http url or context path of the
180    /// url.
181    ///
182    /// If url is valid http url then all path operation request's will be forwarded to the selected
183    /// [`Server`].
184    ///
185    /// If url is path of url e.g. `/api/v1` then the url will be appended to the servers address
186    /// and the operations will be forwarded to location `server address + url`.
187    ///
188    ///
189    /// # Examples
190    ///
191    /// Create new server with url path.
192    /// ```
193    /// # use salvo_oapi::server::Server;
194    /// Server::new("/api/v1");
195    /// ```
196    ///
197    /// Create new server with alternative server.
198    /// ```
199    /// # use salvo_oapi::server::Server;
200    /// Server::new("https://alternative.pet-api.test/api/v1");
201    /// ```
202    #[must_use]
203    pub fn new<S: Into<String>>(url: S) -> Self {
204        Self {
205            url: url.into(),
206            ..Default::default()
207        }
208    }
209    /// Add url to the target [`Server`].
210    #[must_use]
211    pub fn url<U: Into<String>>(mut self, url: U) -> Self {
212        self.url = url.into();
213        self
214    }
215
216    /// Add or change description of the [`Server`].
217    #[must_use]
218    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
219        self.description = Some(description.into());
220        self
221    }
222
223    /// Add parameter to [`Server`] which is used to substitute values in [`Server::url`] and
224    /// returns `Self`.
225    ///
226    /// * `name` Defines name of the parameter which is being substituted within the url. If url has
227    ///   `{username}` substitution then the name should be `username`.
228    /// * `parameter` Use [`ServerVariable`] to define how the parameter is being substituted within
229    ///   the url.
230    #[must_use]
231    pub fn add_variable<N: Into<String>, V: Into<ServerVariable>>(
232        mut self,
233        name: N,
234        variable: V,
235    ) -> Self {
236        self.variables.insert(name.into(), variable.into());
237        self
238    }
239
240    /// Add openapi extensions (`x-something`) for [`Server`].
241    #[must_use]
242    pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
243        self.extensions = extensions;
244        self
245    }
246}
247
248/// Server Variables information for OpenApi.
249#[derive(Serialize, Deserialize, Default, Clone, PartialEq, Eq, Debug)]
250pub struct ServerVariables(pub PropMap<String, ServerVariable>);
251impl Deref for ServerVariables {
252    type Target = PropMap<String, ServerVariable>;
253
254    fn deref(&self) -> &Self::Target {
255        &self.0
256    }
257}
258impl DerefMut for ServerVariables {
259    fn deref_mut(&mut self) -> &mut Self::Target {
260        &mut self.0
261    }
262}
263impl ServerVariables {
264    /// Construct a new empty [`ServerVariables`]. This is effectively same as calling
265    /// [`ServerVariables::default`].
266    #[must_use]
267    pub fn new() -> Self {
268        Default::default()
269    }
270    /// Returns `true` if instance contains no elements.
271    #[must_use]
272    pub fn is_empty(&self) -> bool {
273        self.0.is_empty()
274    }
275    /// Inserts a key-value pair into the instance and returns `self`.
276    #[must_use]
277    pub fn server_variable<K: Into<String>, V: Into<ServerVariable>>(
278        mut self,
279        key: K,
280        variable: V,
281    ) -> Self {
282        self.insert(key, variable);
283        self
284    }
285    /// Inserts a key-value pair into the instance.
286    pub fn insert<K: Into<String>, V: Into<ServerVariable>>(&mut self, key: K, variable: V) {
287        let key = key.into();
288        let mut variable = variable.into();
289        self.0
290            .entry(key)
291            .and_modify(|item| {
292                if variable.description.is_some() {
293                    item.description = variable.description.take();
294                }
295                item.default_value.clone_from(&variable.default_value);
296                item.enum_values.append(&mut variable.enum_values);
297            })
298            .or_insert(variable);
299    }
300    /// Moves all elements from `other` into `self`, leaving `other` empty.
301    ///
302    /// If a key from `other` is already present in `self`, the respective
303    /// value from `self` will be overwritten with the respective value from `other`.
304    pub fn append(&mut self, other: &mut Self) {
305        let variables = std::mem::take(&mut other.0);
306        for (key, variable) in variables {
307            self.insert(key, variable);
308        }
309    }
310    /// Extends a collection with the contents of an iterator.
311    pub fn extend<I>(&mut self, iter: I)
312    where
313        I: IntoIterator<Item = (String, ServerVariable)>,
314    {
315        for (key, variable) in iter.into_iter() {
316            self.insert(key, variable);
317        }
318    }
319}
320
321/// Implements [OpenAPI Server Variable][server_variable] used to substitute variables in
322/// [`Server::url`].
323///
324/// [server_variable]: https://spec.openapis.org/oas/latest.html#server-variable-object
325#[non_exhaustive]
326#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
327pub struct ServerVariable {
328    /// Default value used to substitute parameter if no other value is being provided.
329    #[serde(rename = "default")]
330    default_value: String,
331
332    /// Optional description describing the variable of substitution. Markdown syntax is supported.
333    #[serde(skip_serializing_if = "Option::is_none")]
334    description: Option<String>,
335
336    /// Enum values can be used to limit possible options for substitution. If enum values is used
337    /// the [`ServerVariable::default_value`] must contain one of the enum values.
338    #[serde(rename = "enum", skip_serializing_if = "BTreeSet::is_empty")]
339    enum_values: BTreeSet<String>,
340
341    /// Optional extensions "x-something"
342    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
343    pub extensions: PropMap<String, serde_json::Value>,
344}
345
346impl ServerVariable {
347    /// Construct a new empty [`ServerVariable`]. This is effectively same as calling
348    /// [`ServerVariable::default`].
349    #[must_use]
350    pub fn new() -> Self {
351        Default::default()
352    }
353    /// Add default value for substitution.
354    #[must_use]
355    pub fn default_value<S: Into<String>>(mut self, default_value: S) -> Self {
356        self.default_value = default_value.into();
357        self
358    }
359
360    /// Add or change description of substituted parameter.
361    #[must_use]
362    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
363        self.description = Some(description.into());
364        self
365    }
366
367    /// Add or change possible values used to substitute parameter.
368    #[must_use]
369    pub fn enum_values<I: IntoIterator<Item = V>, V: Into<String>>(
370        mut self,
371        enum_values: I,
372    ) -> Self {
373        self.enum_values = enum_values.into_iter().map(|value| value.into()).collect();
374        self
375    }
376
377    /// Add openapi extensions (`x-something`) for [`ServerVariable`].
378    #[must_use]
379    pub fn extensions(mut self, extensions: PropMap<String, serde_json::Value>) -> Self {
380        self.extensions = extensions;
381        self
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use assert_json_diff::assert_json_eq;
388    use serde_json::json;
389
390    use super::*;
391
392    macro_rules! test_fn {
393        ($name:ident : $schema:expr; $expected:literal) => {
394            #[test]
395            fn $name() {
396                let value = serde_json::to_value($schema).unwrap();
397                let expected_value: serde_json::Value = serde_json::from_str($expected).unwrap();
398
399                assert_eq!(
400                    value,
401                    expected_value,
402                    "testing serializing \"{}\": \nactual:\n{}\nexpected:\n{}",
403                    stringify!($name),
404                    value,
405                    expected_value
406                );
407
408                println!("{}", &serde_json::to_string_pretty(&$schema).unwrap());
409            }
410        };
411    }
412
413    test_fn! {
414    create_server_with_builder_and_variable_substitution:
415    Server::new("/api/{version}/{username}")
416        .add_variable(
417            "version",
418            ServerVariable::new()
419                .enum_values(["v1", "v2"])
420                .description("api version")
421                .default_value("v1")
422        )
423        .add_variable(
424            "username",
425            ServerVariable::new().default_value("the_user")
426        );
427    r###"{
428  "url": "/api/{version}/{username}",
429  "variables": {
430      "version": {
431          "enum": ["v1", "v2"],
432          "default": "v1",
433          "description": "api version"
434      },
435      "username": {
436          "default": "the_user"
437      }
438  }
439}"###
440    }
441
442    #[test]
443    fn test_servers_is_empty() {
444        let servers = Servers::new();
445        assert!(servers.is_empty());
446    }
447
448    #[test]
449    fn test_servers_server() {
450        let servers = Servers::new();
451        let server = Server::new("/api/v1").description("api v1");
452        let servers = servers.server(server);
453        assert!(servers.len() == 1);
454    }
455
456    #[test]
457    fn test_servers_insert() {
458        let mut servers = Servers::new();
459        let server = Server::new("/api/v1").description("api v1");
460        servers.insert(server);
461        assert!(servers.len() == 1);
462    }
463
464    #[test]
465    fn test_servers_insert_existed_server() {
466        let mut servers = Servers::new();
467        let server1 = Server::new("/api/v1".to_owned())
468            .description("api v1")
469            .add_variable("key1", ServerVariable::new());
470        servers.insert(server1);
471
472        let server2 = Server::new("/api/v1".to_owned())
473            .description("api v1 new description")
474            .add_variable("key2", ServerVariable::new());
475        servers.insert(server2);
476
477        assert!(servers.len() == 1);
478        assert_json_eq!(
479            servers,
480            json!([
481                {
482                    "description": "api v1 new description",
483                    "url": "/api/v1",
484                    "variables": {
485                        "key1": {
486                            "default": ""
487                        },
488                        "key2": {
489                            "default": ""
490                        }
491                    }
492                }
493            ])
494        )
495    }
496
497    #[test]
498    fn test_servers_append() {
499        let mut servers = Servers::new();
500
501        let server = Server::new("/api/v1").description("api v1");
502        let mut other_servers: Servers = Servers::new();
503
504        other_servers.insert(server);
505        assert!(!other_servers.is_empty());
506
507        servers.append(&mut other_servers);
508        assert!(!servers.is_empty());
509    }
510
511    #[test]
512    fn test_servers_extend() {
513        let mut servers = Servers::new();
514
515        let server = Server::new("/api/v1").description("api v1");
516        let mut other_servers: Servers = Servers::new();
517
518        other_servers.insert(server);
519        assert!(!other_servers.is_empty());
520
521        servers.extend(other_servers);
522        assert!(!servers.is_empty());
523    }
524
525    #[test]
526    fn test_servers_deref() {
527        let mut servers = Servers::new();
528        let server = Server::new("/api/v1").description("api v1");
529        servers.insert(server);
530        assert!(servers.len() == 1);
531        assert!(servers.deref().len() == 1);
532
533        servers.deref_mut().clear();
534        assert!(servers.is_empty());
535    }
536
537    #[test]
538    fn test_server_set_url() {
539        let server = Server::new("/api/v1");
540        assert_eq!(server.url, "/api/v1");
541
542        let server = server.url("/new/api/v1");
543        assert_eq!(server.url, "/new/api/v1");
544    }
545
546    #[test]
547    fn test_server_cmp() {
548        let server_a = Server::new("/api/v1");
549        let server_b = Server::new("/api/v2");
550        assert!(server_a < server_b);
551    }
552
553    #[test]
554    fn test_server_variables_is_empty() {
555        let server_variables = ServerVariables::new();
556        assert!(server_variables.is_empty());
557    }
558
559    #[test]
560    fn test_server_variables_server_variable() {
561        let server_variables = ServerVariables::new();
562        let variable = ServerVariable::new();
563        let server_variables = server_variables.server_variable("key", variable);
564
565        assert!(!server_variables.is_empty());
566    }
567
568    #[test]
569    fn test_server_variables_insert() {
570        let mut server_variables = ServerVariables::new();
571        let variable = ServerVariable::new();
572        server_variables.insert("key", variable);
573        assert!(server_variables.len() == 1);
574
575        let new_variable = ServerVariable::new().description("description");
576        server_variables.insert("key", new_variable);
577        assert!(server_variables.len() == 1);
578    }
579
580    #[test]
581    fn test_server_variables_append() {
582        let mut server_variables = ServerVariables::new();
583
584        let mut other_server_variables = ServerVariables::new();
585        let variable = ServerVariable::new();
586        other_server_variables.insert("key", variable);
587
588        server_variables.append(&mut other_server_variables);
589        assert!(server_variables.len() == 1);
590    }
591
592    #[test]
593    fn test_server_variables_extend() {
594        let mut server_variables = ServerVariables::new();
595
596        let mut other_server_variables = ServerVariables::new();
597        let variable = ServerVariable::new();
598        other_server_variables.insert("key", variable);
599
600        server_variables.extend(other_server_variables.0);
601        assert!(server_variables.len() == 1);
602    }
603
604    #[test]
605    fn test_server_variables_deref() {
606        let mut server_variables = ServerVariables::new();
607
608        let variable = ServerVariable::new().default_value("default_value");
609        server_variables.insert("key", variable);
610
611        assert!(!server_variables.is_empty());
612        assert!(server_variables.deref().len() == 1);
613
614        server_variables.deref_mut().clear();
615        assert!(server_variables.is_empty());
616    }
617}