Skip to content

Commit

Permalink
Add xml schema object (#41)
Browse files Browse the repository at this point in the history
* Add xml schema object. This allows users to define xml styles for properties of objects.
* Add tests
  • Loading branch information
juhaku authored Mar 13, 2022
1 parent 640b8db commit 613301e
Show file tree
Hide file tree
Showing 11 changed files with 422 additions and 33 deletions.
1 change: 1 addition & 0 deletions src/openapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub mod schema;
pub mod security;
pub mod server;
pub mod tag;
pub mod xml;

const OPENAPI_VERSION_3: &str = "3.0.3";

Expand Down
29 changes: 28 additions & 1 deletion src/openapi/schema.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::{security::SecuritySchema, Deprecated};
use super::{security::SecuritySchema, xml::Xml, Deprecated};

#[non_exhaustive]
#[derive(Serialize, Deserialize, Default, Clone)]
Expand Down Expand Up @@ -185,6 +185,9 @@ pub struct Property {

#[serde(skip_serializing_if = "Option::is_none")]
read_only: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
xml: Option<Xml>,
}

impl Property {
Expand Down Expand Up @@ -263,6 +266,12 @@ impl Property {

self
}

pub fn with_xml(mut self, xml: Xml) -> Self {
self.xml = Some(xml);

self
}
}

impl From<Property> for Component {
Expand Down Expand Up @@ -303,6 +312,9 @@ pub struct Object {
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg(not(feature = "serde_json"))]
example: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
xml: Option<Xml>,
}

impl Object {
Expand Down Expand Up @@ -354,6 +366,12 @@ impl Object {

self
}

pub fn with_xml(mut self, xml: Xml) -> Self {
self.xml = Some(xml);

self
}
}

impl From<Object> for Component {
Expand Down Expand Up @@ -413,6 +431,9 @@ pub struct Array {

#[serde(skip_serializing_if = "Option::is_none")]
min_items: Option<usize>,

#[serde(skip_serializing_if = "Option::is_none")]
xml: Option<Xml>,
}

impl Array {
Expand All @@ -435,6 +456,12 @@ impl Array {

self
}

pub fn with_xml(mut self, xml: Xml) -> Self {
self.xml = Some(xml);

self
}
}

impl From<Array> for Component {
Expand Down
60 changes: 60 additions & 0 deletions src/openapi/xml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::borrow::Cow;

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Default, Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct Xml {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<Cow<'static, str>>,

#[serde(skip_serializing_if = "Option::is_none")]
pub namespace: Option<Cow<'static, str>>,

#[serde(skip_serializing_if = "Option::is_none")]
pub prefix: Option<Cow<'static, str>>,

#[serde(skip_serializing_if = "Option::is_none")]
pub attribute: Option<bool>,

#[serde(skip_serializing_if = "Option::is_none")]
pub wrapped: Option<bool>,
}

impl Xml {
pub fn new() -> Self {
Self {
..Default::default()
}
}

pub fn name<S: Into<Cow<'static, str>>>(mut self, name: Option<S>) -> Self {
self.name = name.map(|name| name.into());

self
}

pub fn namespace<S: Into<Cow<'static, str>>>(mut self, namespace: Option<S>) -> Self {
self.namespace = namespace.map(|namespace| namespace.into());

self
}

pub fn prefix<S: Into<Cow<'static, str>>>(mut self, prefix: Option<S>) -> Self {
self.prefix = prefix.map(|prefix| prefix.into());

self
}

pub fn attribute(mut self, attribute: Option<bool>) -> Self {
self.attribute = attribute;

self
}

pub fn wrapped(mut self, wrapped: Option<bool>) -> Self {
self.wrapped = wrapped;

self
}
}
1 change: 1 addition & 0 deletions tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn value_as_string(value: Option<&'_ Value>) -> String {
value.unwrap_or(&Value::Null).to_string()
}

#[allow(unused)]
pub fn assert_json_array_len(value: &Value, len: usize) {
match value {
Value::Array(array) => assert_eq!(
Expand Down
52 changes: 52 additions & 0 deletions tests/component_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,3 +549,55 @@ fn derive_struct_with_read_only_and_write_only() {
"properties.username.writeOnly" = r###"null"###, "User username write only"
}
}

#[test]
fn derive_struct_xml() {
let user = api_doc! {
#[component(xml(name = "user", prefix = "u", namespace = "https://mynamespace.test"))]
struct User {
#[component(xml(attribute, prefix = "u"))]
id: i64,
#[component(xml(name = "user_name", prefix = "u"))]
username: String,
#[component(xml(wrapped(name = "linkList"), name = "link"))]
links: Vec<String>,
#[component(xml(wrapped, name = "photo_url"))]
photos_urls: Vec<String>
}
};

assert_value! {user=>
"xml.attribute" = r###"null"###, "User xml attribute"
"xml.name" = r###""user""###, "User xml name"
"xml.prefix" = r###""u""###, "User xml prefix"
"xml.namespace" = r###""https://mynamespace.test""###, "User xml namespace"
"properties.id.xml.attribute" = r###"true"###, "User id xml attribute"
"properties.id.xml.name" = r###"null"###, "User id xml name"
"properties.id.xml.prefix" = r###""u""###, "User id xml prefix"
"properties.id.xml.namespace" = r###"null"###, "User id xml namespace"
"properties.username.xml.attribute" = r###"null"###, "User username xml attribute"
"properties.username.xml.name" = r###""user_name""###, "User username xml name"
"properties.username.xml.prefix" = r###""u""###, "User username xml prefix"
"properties.username.xml.namespace" = r###"null"###, "User username xml namespace"
"properties.links.xml.attribute" = r###"null"###, "User links xml attribute"
"properties.links.xml.name" = r###""linkList""###, "User links xml name"
"properties.links.xml.prefix" = r###"null"###, "User links xml prefix"
"properties.links.xml.namespace" = r###"null"###, "User links xml namespace"
"properties.links.xml.wrapped" = r###"true"###, "User links xml wrapped"
"properties.links.items.xml.attribute" = r###"null"###, "User links xml items attribute"
"properties.links.items.xml.name" = r###""link""###, "User links xml items name"
"properties.links.items.xml.prefix" = r###"null"###, "User links xml items prefix"
"properties.links.items.xml.namespace" = r###"null"###, "User links xml items namespace"
"properties.links.items.xml.wrapped" = r###"null"###, "User links xml items wrapped"
"properties.photos_urls.xml.attribute" = r###"null"###, "User photos_urls xml attribute"
"properties.photos_urls.xml.name" = r###"null"###, "User photos_urls xml name"
"properties.photos_urls.xml.prefix" = r###"null"###, "User photos_urls xml prefix"
"properties.photos_urls.xml.namespace" = r###"null"###, "User photos_urls xml namespace"
"properties.photos_urls.xml.wrapped" = r###"true"###, "User photos_urls xml wrapped"
"properties.photos_urls.items.xml.attribute" = r###"null"###, "User photos_urls xml items attribute"
"properties.photos_urls.items.xml.name" = r###""photo_url""###, "User photos_urls xml items name"
"properties.photos_urls.items.xml.prefix" = r###"null"###, "User photos_urls xml items prefix"
"properties.photos_urls.items.xml.namespace" = r###"null"###, "User photos_urls xml items namespace"
"properties.photos_urls.items.xml.wrapped" = r###"null"###, "User photos_urls links xml items wrapped"
}
}
46 changes: 41 additions & 5 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ use crate::{
Array, Deprecated,
};

use self::attr::{ComponentAttr, Enum, NamedField, UnnamedFieldStruct};
use self::{
attr::{ComponentAttr, Enum, NamedField, UnnamedFieldStruct},
xml::Xml,
};

mod attr;
mod xml;

pub struct Component<'a> {
ident: &'a Ident,
Expand Down Expand Up @@ -126,15 +130,22 @@ impl ToTokens for NamedStructComponent<'_> {
let field_name = &*field.ident.as_ref().unwrap().to_string();

let component_part = &ComponentPart::from_type(&field.ty);

let deprecated = get_deprecated(&field.attrs);
let attrs = attr::parse_component_attr::<ComponentAttr<NamedField>>(&field.attrs);
let attrs = ComponentAttr::<NamedField>::from_attributes_validated(
&field.attrs,
component_part,
);
let xml_value = attrs
.as_ref()
.and_then(|named_field| named_field.as_ref().xml.as_ref());
let comments = CommentAttributes::from_attributes(&field.attrs);

let component = ComponentProperty::new(
component_part,
Some(&comments),
attrs.as_ref(),
deprecated.as_ref(),
xml_value,
);

tokens.extend(quote! {
Expand All @@ -152,7 +163,7 @@ impl ToTokens for NamedStructComponent<'_> {
tokens.extend(quote! { .with_deprecated(#deprecated) });
}

let attrs = attr::parse_component_attr::<ComponentAttr<attr::Struct>>(self.attributes);
let attrs = ComponentAttr::<attr::Struct>::from_attributes_validated(self.attributes);
if let Some(attrs) = attrs {
tokens.extend(attrs.to_token_stream());
}
Expand Down Expand Up @@ -192,7 +203,7 @@ impl ToTokens for UnnamedStructComponent<'_> {
let deprecated = get_deprecated(self.attributes);
if all_fields_are_same {
tokens.extend(
ComponentProperty::new(first_part, None, attrs.as_ref(), deprecated.as_ref())
ComponentProperty::new(first_part, None, attrs.as_ref(), deprecated.as_ref(), None)
.to_token_stream(),
);
} else {
Expand Down Expand Up @@ -522,6 +533,7 @@ struct ComponentProperty<'a, T> {
comments: Option<&'a CommentAttributes>,
attrs: Option<&'a ComponentAttr<T>>,
deprecated: Option<&'a Deprecated>,
xml: Option<&'a Xml>,
}

impl<'a, T: Sized + ToTokens> ComponentProperty<'a, T> {
Expand All @@ -530,12 +542,14 @@ impl<'a, T: Sized + ToTokens> ComponentProperty<'a, T> {
comments: Option<&'a CommentAttributes>,
attrs: Option<&'a ComponentAttr<T>>,
deprecated: Option<&'a Deprecated>,
xml: Option<&'a Xml>,
) -> Self {
Self {
component_part,
comments,
attrs,
deprecated,
xml,
}
}

Expand Down Expand Up @@ -570,11 +584,21 @@ where
self.comments,
self.attrs,
self.deprecated,
self.xml,
);

tokens.extend(quote! {
#component_property.to_array()
});

if let Some(xml_value) = self.xml {
match xml_value {
Xml::Slice { vec, value: _ } => tokens.extend(quote! {
.with_xml(#vec)
}),
Xml::NonSlice(_) => (),
}
}
}
Some(GenericType::Option)
| Some(GenericType::Cow)
Expand All @@ -585,6 +609,7 @@ where
self.comments,
self.attrs,
self.deprecated,
self.xml,
);

tokens.extend(component_property.into_token_stream())
Expand Down Expand Up @@ -619,6 +644,17 @@ where
if let Some(attributes) = self.attrs {
tokens.extend(attributes.to_token_stream())
}

if let Some(xml_value) = self.xml {
match xml_value {
Xml::Slice { vec: _, value } => tokens.extend(quote! {
.with_xml(#value)
}),
Xml::NonSlice(xml) => tokens.extend(quote! {
.with_xml(#xml)
}),
}
}
}
ValueType::Object => {
let name = &*self.component_part.ident.to_string();
Expand Down
Loading

0 comments on commit 613301e

Please sign in to comment.