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