Skip to content

Commit

Permalink
Chore schema and path builders (#48)
Browse files Browse the repository at this point in the history
* Add schema object and property builders
* Add path builders
* Fix tests and update docs
  • Loading branch information
juhaku authored Mar 27, 2022
1 parent e58b2c7 commit 89ba5a2
Show file tree
Hide file tree
Showing 20 changed files with 985 additions and 579 deletions.
70 changes: 37 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,26 +249,28 @@ pub trait OpenApi {
/// impl utoipa::Component for Pet {
/// fn component() -> utoipa::openapi::schema::Component {
/// use utoipa::openapi::ToArray;
/// utoipa::openapi::Object::new()
/// .with_property(
/// utoipa::openapi::ObjectBuilder::new()
/// .property(
/// "id",
/// utoipa::openapi::Property::new(utoipa::openapi::ComponentType::Integer)
/// .with_format(utoipa::openapi::ComponentFormat::Int64),
/// utoipa::openapi::PropertyBuilder::new()
/// .component_type(utoipa::openapi::ComponentType::Integer)
/// .format(Some(utoipa::openapi::ComponentFormat::Int64)),
/// )
/// .with_required("id")
/// .with_property(
/// .required("id")
/// .property(
/// "name",
/// utoipa::openapi::Property::new(utoipa::openapi::ComponentType::String),
/// )
/// .with_required("name")
/// .with_property(
/// .required("name")
/// .property(
/// "age",
/// utoipa::openapi::Property::new(utoipa::openapi::ComponentType::Integer)
/// .with_format(utoipa::openapi::ComponentFormat::Int32),
/// utoipa::openapi::PropertyBuilder::new()
/// .component_type(utoipa::openapi::ComponentType::Integer)
/// .format(Some(utoipa::openapi::ComponentFormat::Int32)),
/// )
/// .with_example(serde_json::json!({
/// "name":"bob the cat","id":1
/// }))
/// .example(Some(serde_json::json!({
/// "name": "bob the cat", "id": 1
/// })))
/// .into()
/// }
/// }
Expand Down Expand Up @@ -316,12 +318,12 @@ pub trait Component {
///
/// Example of what would manual implementation roughly look like of above `#[utoipa::path(...)]` macro.
/// ```rust
/// utoipa::openapi::Paths::new().append(
/// utoipa::openapi::PathsBuilder::new().path(
/// "/pets/{id}",
/// utoipa::openapi::PathItem::new(
/// utoipa::openapi::PathItemType::Get,
/// utoipa::openapi::path::Operation::new()
/// .with_responses(
/// utoipa::openapi::path::OperationBuilder::new()
/// .responses(
/// utoipa::openapi::ResponsesBuilder::new()
/// .response(
/// "200",
Expand All @@ -331,26 +333,28 @@ pub trait Component {
/// utoipa::openapi::Content::new(
/// utoipa::openapi::Ref::from_component_name("Pet"),
/// ),
/// ).build(),
/// ),
/// )
/// .response("404", utoipa::openapi::Response::new("Pet was not found")).build(),
/// .response("404", utoipa::openapi::Response::new("Pet was not found")),
/// )
/// .with_operation_id("get_pet_by_id")
/// .with_deprecated(utoipa::openapi::Deprecated::False)
/// .with_summary("Get pet by id")
/// .with_description("Get pet by id\n\nGet pet from database by pet database id\n")
/// .with_parameter(
/// utoipa::openapi::path::Parameter::new("id")
/// .with_in(utoipa::openapi::path::ParameterIn::Path)
/// .with_deprecated(utoipa::openapi::Deprecated::False)
/// .with_description("Pet database id to get Pet for")
/// .with_schema(
/// utoipa::openapi::Property::new(utoipa::openapi::ComponentType::Integer)
/// .with_format(utoipa::openapi::ComponentFormat::Int64),
/// )
/// .with_required(utoipa::openapi::Required::True),
/// .operation_id(Some("get_pet_by_id"))
/// .deprecated(Some(utoipa::openapi::Deprecated::False))
/// .summary(Some("Get pet by id"))
/// .description(Some("Get pet by id\n\nGet pet from database by pet database id\n"))
/// .parameter(
/// utoipa::openapi::path::ParameterBuilder::new()
/// .name("id")
/// .parameter_in(utoipa::openapi::path::ParameterIn::Path)
/// .required(utoipa::openapi::Required::True)
/// .deprecated(Some(utoipa::openapi::Deprecated::False))
/// .description(Some("Pet database id to get Pet for"))
/// .schema(
/// Some(utoipa::openapi::PropertyBuilder::new()
/// .component_type(utoipa::openapi::ComponentType::Integer)
/// .format(Some(utoipa::openapi::ComponentFormat::Int64))),
/// ),
/// )
/// .with_tag("pet_api"),
/// .tag("pet_api"),
/// ),
/// );
/// ```
Expand Down
56 changes: 29 additions & 27 deletions src/openapi.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
use std::collections::BTreeMap;

use serde::{de::Visitor, Deserialize, Serialize, Serializer};

pub use self::{
content::{Content, ContentBuilder},
external_docs::ExternalDocs,
header::{Header, HeaderBuilder},
info::{Contact, ContactBuilder, Info, InfoBuilder, License, LicenseBuilder},
path::{PathItem, PathItemType, Paths},
path::{PathItem, PathItemType, Paths, PathsBuilder},
response::{Response, ResponseBuilder, Responses, ResponsesBuilder},
schema::{
Array, Component, ComponentFormat, ComponentType, Components, Object, OneOf, Property, Ref,
ToArray,
Array, ArrayBuilder, Component, ComponentFormat, ComponentType, Components,
ComponentsBuilder, Object, ObjectBuilder, OneOf, OneOfBuilder, Property, PropertyBuilder,
Ref, ToArray,
},
security::SecurityRequirement,
server::Server,
Expand Down Expand Up @@ -79,7 +78,8 @@ builder! {
/// Available paths and operations for the API.
///
/// See more details at <https://spec.openapis.org/oas/latest.html#paths-object>.
pub paths: BTreeMap<String, PathItem>,
#[serde(flatten)]
pub paths: Paths,

/// Holds various reusable schemas for the OpenAPI document.
///
Expand Down Expand Up @@ -124,10 +124,10 @@ impl OpenApi {
/// #
/// let openapi = OpenApi::new(Info::new("pet api", "0.1.0"), Paths::new());
/// ```
pub fn new(info: Info, paths: Paths) -> Self {
pub fn new<P: Into<Paths>>(info: Info, paths: P) -> Self {
Self {
info,
paths: paths.to_map(),
paths: paths.into(),
..Default::default()
}
}
Expand All @@ -152,40 +152,40 @@ impl OpenApi {
impl OpenApiBuilder {
/// Add [`Info`] metadata of the API.
pub fn info(mut self, info: Info) -> Self {
add_value!(self info info)
set_value!(self info info)
}

/// Add iterator of [`Server`]s to configure target servers.
pub fn servers<I: IntoIterator<Item = Server>>(mut self, servers: Option<I>) -> Self {
add_value!(self servers servers.map(|servers| servers.into_iter().collect()))
set_value!(self servers servers.map(|servers| servers.into_iter().collect()))
}

/// Add [`Paths`] to configure operations and endpoints of the API.
pub fn paths(mut self, paths: Paths) -> Self {
add_value!(self paths paths.to_map())
pub fn paths<P: Into<Paths>>(mut self, paths: P) -> Self {
set_value!(self paths paths.into())
}

/// Add [`Components`] to configure reusable schemas.
pub fn components(mut self, components: Option<Components>) -> Self {
add_value!(self components components)
set_value!(self components components)
}

/// Add iterator of [`SecurityRequirement`]s that are globally available for all operations.
pub fn security<I: IntoIterator<Item = SecurityRequirement>>(
mut self,
security: Option<I>,
) -> Self {
add_value!(self security security.map(|security| security.into_iter().collect()))
set_value!(self security security.map(|security| security.into_iter().collect()))
}

/// Add iterator of [`Tag`]s to add additional documentation for **operations** tags.
pub fn tags<I: IntoIterator<Item = Tag>>(mut self, tags: Option<I>) -> Self {
add_value!(self tags tags.map(|tags| tags.into_iter().collect()))
set_value!(self tags tags.map(|tags| tags.into_iter().collect()))
}

/// Add [`ExternalDocs`] for refering additional documentation.
pub fn external_docs(mut self, external_docs: Option<ExternalDocs>) -> Self {
add_value!(self external_docs external_docs)
set_value!(self external_docs external_docs)
}
}

Expand Down Expand Up @@ -318,21 +318,20 @@ macro_rules! build_fn {
$(
$field: self.$field,
)*
..Default::default()
}
}
};
}
pub(crate) use build_fn;

macro_rules! add_value {
macro_rules! set_value {
( $self:ident $field:ident $value:expr ) => {{
$self.$field = $value;

$self
}};
}
pub(crate) use add_value;
pub(crate) use set_value;

macro_rules! new {
( $vis:vis $name:ident ) => {
Expand Down Expand Up @@ -406,7 +405,10 @@ pub(crate) use builder;
#[cfg(test)]
#[cfg(feature = "serde_json")]
mod tests {
use crate::openapi::info::InfoBuilder;
use crate::openapi::{
info::InfoBuilder,
path::{OperationBuilder, PathsBuilder},
};

use super::{path::Operation, response::Response, *};

Expand Down Expand Up @@ -447,26 +449,26 @@ mod tests {
fn serialize_openapi_json_with_paths_success() -> Result<(), serde_json::Error> {
let openapi = OpenApi::new(
Info::new("My big api", "1.1.0"),
Paths::new()
.append(
PathsBuilder::new()
.path(
"/api/v1/users",
PathItem::new(
PathItemType::Get,
Operation::new().with_response("200", Response::new("Get users list")),
OperationBuilder::new().response("200", Response::new("Get users list")),
),
)
.append(
.path(
"/api/v1/users",
PathItem::new(
PathItemType::Post,
Operation::new().with_response("200", Response::new("Post new user")),
OperationBuilder::new().response("200", Response::new("Post new user")),
),
)
.append(
.path(
"/api/v1/users/{id}",
PathItem::new(
PathItemType::Get,
Operation::new().with_response("200", Response::new("Get user by id")),
OperationBuilder::new().response("200", Response::new("Get user by id")),
),
),
);
Expand Down
8 changes: 4 additions & 4 deletions src/openapi/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "serde_json")]
use serde_json::Value;

use super::{add_value, build_fn, from, new, Component};
use super::{build_fn, from, new, set_value, Component};

/// Content holds request body content or response content.
#[derive(Serialize, Deserialize, Default, Clone)]
Expand Down Expand Up @@ -53,19 +53,19 @@ impl ContentBuilder {

/// Add schema.
pub fn schema<I: Into<Component>>(mut self, component: I) -> Self {
add_value!(self schema component.into())
set_value!(self schema component.into())
}

/// Add example of schema.
#[cfg(feature = "serde_json")]
pub fn example(mut self, example: Option<Value>) -> Self {
add_value!(self example example)
set_value!(self example example)
}

/// Add example of schema.
#[cfg(not(feature = "serde_json"))]
pub fn example<S: Into<String>>(mut self, example: Option<S>) -> Self {
add_value!(self example example.map(|example| example.into()))
set_value!(self example example.map(|example| example.into()))
}

build_fn!(pub Content schema, example);
Expand Down
6 changes: 3 additions & 3 deletions src/openapi/external_docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! [external_docs]: https://spec.openapis.org/oas/latest.html#xml-object
use serde::{Deserialize, Serialize};

use super::{add_value, build_fn, builder, from, new};
use super::{build_fn, builder, from, new, set_value};

builder! {
ExternalDocsBuilder;
Expand Down Expand Up @@ -43,11 +43,11 @@ impl ExternalDocs {
impl ExternalDocsBuilder {
/// Add target url for external documentation location.
pub fn url<I: Into<String>>(mut self, url: I) -> Self {
add_value!(self url url.into())
set_value!(self url url.into())
}

/// Add additional description of external documentation.
pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
add_value!(self description description.map(|description| description.into()))
set_value!(self description description.map(|description| description.into()))
}
}
6 changes: 3 additions & 3 deletions src/openapi/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize};

use super::{add_value, build_fn, builder, from, new, Component, ComponentType, Property};
use super::{build_fn, builder, from, new, set_value, Component, ComponentType, Property};

builder! {
HeaderBuilder;
Expand Down Expand Up @@ -64,11 +64,11 @@ impl Default for Header {
impl HeaderBuilder {
/// Add schema of header.
pub fn schema<I: Into<Component>>(mut self, component: I) -> Self {
add_value!(self schema component.into())
set_value!(self schema component.into())
}

/// Add additional description for header.
pub fn description<S: Into<String>>(mut self, description: Option<S>) -> Self {
add_value!(self description description.map(|description| description.into()))
set_value!(self description description.map(|description| description.into()))
}
}
Loading

0 comments on commit 89ba5a2

Please sign in to comment.