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