-
Notifications
You must be signed in to change notification settings - Fork 220
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add openapi Server object types -> Add builders, docs, tests.
- Loading branch information
Showing
4 changed files
with
260 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,244 @@ | ||
use std::collections::HashMap; | ||
//! Implements [OpenAPI Server Object][server] types to configure target servers. | ||
//! | ||
//! OpenAPI will implicitly add [`Server`] with `url = "/"` to [`OpenApi`][openapi] when no servers | ||
//! are defined. | ||
//! | ||
//! [`Server`] can be used to alter connection url for _**path operations**_. It can be a | ||
//! relative path e.g `/api/v1` or valid http url e.g. `http://alternative.api.com/api/v1`. | ||
//! | ||
//! Relative path will append to the **sever address** so the connection url for _**path operations**_ | ||
//! will become `server address + relative path`. | ||
//! | ||
//! Optionally it also supports parmater substitution with `{variable}` syntax. | ||
//! | ||
//! See [`Modify`][modify] trait for details how add servers to [`OpenApi`][openapi]. | ||
//! | ||
//! # Examples | ||
//! | ||
//! Create new server with relative path. | ||
//! ```rust | ||
//! # use utoipa::openapi::server::Server; | ||
//! Server::new("/api/v1"); | ||
//! ``` | ||
//! | ||
//! Create server with custom url using a builder. | ||
//! ```rust | ||
//! # use utoipa::openapi::server::ServerBuilder; | ||
//! ServerBuilder::new().url("https://alternative.api.url.test/api").build(); | ||
//! ``` | ||
//! | ||
//! Create server with builder and variable substitution. | ||
//! ```rust | ||
//! # use utoipa::openapi::server::{ServerBuilder, ServerVariableBuilder}; | ||
//! ServerBuilder::new().url("/api/{version}/{username}") | ||
//! .parameter("version", ServerVariableBuilder::new() | ||
//! .enum_values(Some(["v1", "v2"])) | ||
//! .default_value("v1")) | ||
//! .parameter("username", ServerVariableBuilder::new() | ||
//! .default_value("the_user")).build(); | ||
//! ``` | ||
//! | ||
//! [server]: https://spec.openapis.org/oas/latest.html#server-object | ||
//! [openapi]: ../struct.OpenApi.html | ||
//! [modify]: ../../trait.Modify.html | ||
use std::{collections::HashMap, iter}; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
#[non_exhaustive] | ||
#[derive(Serialize, Deserialize, Default, Clone)] | ||
#[cfg_attr(feature = "debug", derive(Debug))] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Server { | ||
pub url: String, | ||
use super::{build_fn, builder, from, new, set_value}; | ||
|
||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub description: Option<String>, | ||
builder! { | ||
ServerBuilder; | ||
|
||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub variables: Option<HashMap<String, String>>, // TODO check the correct type here | ||
/// Represents target server object. It can be used to alter server connection for | ||
/// _**path operations**_. | ||
/// | ||
/// By default OpenAPI will implicitly implement [`Server`] with `url = "/"` if no servers is provided to | ||
/// the [`OpenApi`][openapi]. | ||
/// | ||
/// [openapi]: ../struct.OpenApi.html | ||
#[non_exhaustive] | ||
#[derive(Serialize, Deserialize, Default, Clone)] | ||
#[cfg_attr(feature = "debug", derive(Debug))] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Server { | ||
/// Target url of the [`Server`]. It can be valid http url or relative path. | ||
/// | ||
/// Url also supports variable substitution with `{variable}` syntax. The substitutions | ||
/// then can be configured with [`Server::variables`] map. | ||
pub url: String, | ||
|
||
/// Optional description describing the target server url. Description supports markdown syntax. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub description: Option<String>, | ||
|
||
/// Optional map of variable name and its substitution value used in [`Server::url`]. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub variables: Option<HashMap<String, ServerVariable>>, | ||
} | ||
} | ||
|
||
impl Server { | ||
pub fn new<S: AsRef<str>>(url: S) -> Self { | ||
/// Construct a new [`Server`] with given url. Url can be valid http url or context path of the url. | ||
/// | ||
/// If url is valid http url then all path operation request's will be forwarded to the selected [`Server`]. | ||
/// | ||
/// If url is path of url e.g. `/api/v1` then the url will be appended to the servers address and the | ||
/// operations will be forwarded to location `server address + url`. | ||
/// | ||
/// | ||
/// # Examples | ||
/// | ||
/// Create new server with url path. | ||
/// ```rust | ||
/// # use utoipa::openapi::server::Server; | ||
/// Server::new("/api/v1"); | ||
/// ``` | ||
/// | ||
/// Create new server with alternative server. | ||
/// ```rust | ||
/// # use utoipa::openapi::server::Server; | ||
/// Server::new("https://alternative.pet-api.test/api/v1"); | ||
/// ``` | ||
pub fn new<S: Into<String>>(url: S) -> Self { | ||
Self { | ||
url: url.as_ref().to_string(), | ||
url: url.into(), | ||
..Default::default() | ||
} | ||
} | ||
} | ||
|
||
impl ServerBuilder { | ||
/// Add url to the target [`Server`]. | ||
pub fn url<U: Into<String>>(mut self, url: U) -> Self { | ||
set_value!(self url url.into()) | ||
} | ||
|
||
pub fn with_description<S: AsRef<str>>(mut self, description: S) -> Self { | ||
self.description = Some(description.as_ref().to_string()); | ||
/// Add or change description of the [`Server`]. | ||
pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self { | ||
set_value!(self description description.map(|description| description.into())) | ||
} | ||
|
||
/// Add parameter to [`Server`] which is used to substitute values in [`Server::url`]. | ||
/// | ||
/// * `name` Defines name of the parmaeter which is being substituted within the url. If url has | ||
/// `{username}` substitution then the name should be `username`. | ||
/// * `parameter` Use [`ServerVariableBuilder`] to define how the parameter is being substituted | ||
/// within the url. | ||
pub fn parameter<N: Into<String>, V: Into<ServerVariable>>( | ||
mut self, | ||
name: N, | ||
variable: V, | ||
) -> Self { | ||
match self.variables { | ||
Some(ref mut variables) => { | ||
variables.insert(name.into(), variable.into()); | ||
} | ||
None => { | ||
self.variables = Some(HashMap::from_iter(iter::once(( | ||
name.into(), | ||
variable.into(), | ||
)))) | ||
} | ||
} | ||
|
||
self | ||
} | ||
} | ||
|
||
builder! { | ||
ServerVariableBuilder; | ||
|
||
/// Implements [OpenAPI Server Variable][server_variable] used to substitute varibles in [`Server::url`]. | ||
/// | ||
/// [server_variable]: https://spec.openapis.org/oas/latest.html#server-variable-object | ||
#[non_exhaustive] | ||
#[derive(Serialize, Deserialize, Default, Clone)] | ||
#[cfg_attr(feature = "debug", derive(Debug))] | ||
pub struct ServerVariable { | ||
/// Default value used to substitute parameter if no other value is being provided. | ||
#[serde(rename = "default")] | ||
default_value: String, | ||
|
||
/// Optional description descringing the variable of substitution. Markdown syntax is supported. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
description: Option<String>, | ||
|
||
/// Enum values can be used to limit possible options for substitution. If enum values is used | ||
/// the [`ServerVariable::default_value`] must contain one of the enum values. | ||
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")] | ||
enum_values: Option<Vec<String>>, | ||
} | ||
} | ||
|
||
impl ServerVariableBuilder { | ||
/// Add default value for substitution. | ||
pub fn default_value<S: Into<String>>(mut self, default_value: S) -> Self { | ||
set_value!(self default_value default_value.into()) | ||
} | ||
|
||
/// Add or change description of substituted parameter. | ||
pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self { | ||
set_value!(self description description.map(|description| description.into())) | ||
} | ||
|
||
/// Add or change possible values used to substitute parameter. | ||
pub fn enum_values<I: IntoIterator<Item = V>, V: Into<String>>( | ||
mut self, | ||
enum_values: Option<I>, | ||
) -> Self { | ||
set_value!(self enum_values enum_values | ||
.map(|enum_values| enum_values.into_iter().map(|value| value.into()).collect())) | ||
} | ||
} | ||
|
||
#[cfg(feature = "json")] | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
macro_rules! test_fn { | ||
($name:ident: $schema:expr; $expected:literal) => { | ||
#[test] | ||
fn $name() { | ||
let value = serde_json::to_value($schema).unwrap(); | ||
let expected_value: serde_json::Value = serde_json::from_str($expected).unwrap(); | ||
|
||
assert_eq!( | ||
value, | ||
expected_value, | ||
"testing serializing \"{}\": \nactual:\n{}\nexpected:\n{}", | ||
stringify!($name), | ||
value, | ||
expected_value | ||
); | ||
|
||
println!("{}", &serde_json::to_string_pretty(&$schema).unwrap()); | ||
} | ||
}; | ||
} | ||
|
||
test_fn! { | ||
create_server_with_builder_and_variable_substitution: | ||
ServerBuilder::new().url("/api/{version}/{username}") | ||
.parameter("version", ServerVariableBuilder::new() | ||
.enum_values(Some(["v1", "v2"])) | ||
.description(Some("api version")) | ||
.default_value("v1")) | ||
.parameter("username", ServerVariableBuilder::new() | ||
.default_value("the_user")).build(); | ||
r###"{ | ||
"url": "/api/{version}/{username}", | ||
"variables": { | ||
"version": { | ||
"enum": ["v1", "v2"], | ||
"default": "v1", | ||
"description": "api version" | ||
}, | ||
"username": { | ||
"default": "the_user" | ||
} | ||
} | ||
}"### | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters