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