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