Skip to content

Commit

Permalink
Refactor ComponentFormat (#221)
Browse files Browse the repository at this point in the history
ComponentFormat parsing and rust type resolving logic was scattered two
places. This PR will refactor this logic under same type.
  • Loading branch information
juhaku authored Jul 14, 2022
1 parent 07407db commit a42843e
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 105 deletions.
132 changes: 129 additions & 3 deletions utoipa-gen/src/component_type.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use proc_macro2::TokenStream;
use proc_macro_error::abort_call_site;
use quote::{quote, ToTokens};
use syn::{parse::Parse, Error, Ident, TypePath};

/// Tokenizes OpenAPI data type correctly according to the Rust type
pub struct ComponentType<'a>(pub &'a syn::TypePath);
Expand Down Expand Up @@ -126,10 +128,50 @@ impl ToTokens for ComponentType<'_> {
}
}

/// Tokenizes OpenAPI data type format correctly by given Rust type.
pub struct ComponentFormat<'a>(pub(crate) &'a syn::TypePath);
/// Either Rust type component variant or enum variant component variant.
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum ComponentFormat<'c> {
/// [`utoipa::openapi::shcema::ComponentFormat`] enum variant component format.
Variant(Variant),
/// Rust type component format.
Type(Type<'c>),
}

impl ComponentFormat<'_> {
pub fn is_known_format(&self) -> bool {
match self {
Self::Type(ty) => ty.is_known_format(),
Self::Variant(_) => true,
}
}
}

impl<'a> From<&'a TypePath> for ComponentFormat<'a> {
fn from(type_path: &'a TypePath) -> Self {
Self::Type(Type(type_path))
}
}

impl Parse for ComponentFormat<'_> {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self::Variant(input.parse()?))
}
}

impl ToTokens for ComponentFormat<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Type(ty) => ty.to_tokens(tokens),
Self::Variant(variant) => variant.to_tokens(tokens),
}
}
}

/// Tokenizes OpenAPI data type format correctly by given Rust type.
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct Type<'a>(&'a syn::TypePath);

impl Type<'_> {
/// Check is the format know format. Known formats can be used within `quote! {...}` statements.
pub fn is_known_format(&self) -> bool {
let last_segment = match self.0.path.segments.last() {
Expand Down Expand Up @@ -170,7 +212,7 @@ fn is_known_format(name: &str) -> bool {
)
}

impl ToTokens for ComponentFormat<'_> {
impl ToTokens for Type<'_> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let last_segment = self.0.path.segments.last().unwrap_or_else(|| {
abort_call_site!("expected there to be at least one segment in the path")
Expand All @@ -193,3 +235,87 @@ impl ToTokens for ComponentFormat<'_> {
}
}
}

/// [`Parse`] and [`ToTokens`] implementation for [`utoipa::openapi::schema::ComponentFormat`].
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum Variant {
Int32,
Int64,
Float,
Double,
Byte,
Binary,
Date,
DateTime,
Password,
#[cfg(feature = "uuid")]
Uuid,
}

impl Parse for Variant {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
const FORMATS: [&str; 10] = [
"Int32", "Int64", "Float", "Double", "Byte", "Binary", "Date", "DateTime", "Password",
"Uuid",
];
let allowed_formats = FORMATS
.into_iter()
.filter(|_format| {
#[cfg(feature = "uuid")]
{
true
}
#[cfg(not(feature = "uuid"))]
{
_format != &"Uuid"
}
})
.collect::<Vec<_>>();
let expected_formats = format!(
"unexpected format, expected one of: {}",
allowed_formats.join(", ")
);
let format = input.parse::<Ident>()?;
let name = &*format.to_string();

match name {
"Int32" => Ok(Self::Int32),
"Int64" => Ok(Self::Int64),
"Float" => Ok(Self::Float),
"Double" => Ok(Self::Double),
"Byte" => Ok(Self::Byte),
"Binary" => Ok(Self::Binary),
"Date" => Ok(Self::Date),
"DateTime" => Ok(Self::DateTime),
"Password" => Ok(Self::Password),
#[cfg(feature = "uuid")]
"Uuid" => Ok(Self::Uuid),
_ => Err(Error::new(
format.span(),
format!("unexpected format: {name}, expected one of: {expected_formats}"),
)),
}
}
}

impl ToTokens for Variant {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Int32 => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Int32)),
Self::Int64 => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Int64)),
Self::Float => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Float)),
Self::Double => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Double)),
Self::Byte => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Byte)),
Self::Binary => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Binary)),
Self::Date => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Date)),
Self::DateTime => {
tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::DateTime))
}
Self::Password => {
tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Password))
}
#[cfg(feature = "uuid")]
Self::Uuid => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Uuid)),
};
}
}
2 changes: 1 addition & 1 deletion utoipa-gen/src/path/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl ToTokens for Property<'_> {
utoipa::openapi::PropertyBuilder::new().component_type(#component_type)
};

let format = ComponentFormat(component_type.0);
let format: ComponentFormat = (&*component_type.0).into();
if format.is_known_format() {
component.extend(quote! {
.format(Some(#format))
Expand Down
12 changes: 8 additions & 4 deletions utoipa-gen/src/schema/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,13 @@ impl ToTokens for NamedStructComponent<'_> {
component_part,
);

let override_component_part = attrs
.as_ref()
.and_then(|field| field.as_ref().value_type.as_ref().map(ComponentPart::from_type_path));
let override_component_part = attrs.as_ref().and_then(|field| {
field
.as_ref()
.value_type
.as_ref()
.map(ComponentPart::from_type_path)
});

let xml_value = attrs
.as_ref()
Expand Down Expand Up @@ -753,7 +757,7 @@ where
utoipa::openapi::PropertyBuilder::new().component_type(#component_type)
});

let format = ComponentFormat(&*component_part.path);
let format: ComponentFormat = (&*component_part.path).into();
if format.is_known_format() {
tokens.extend(quote! {
.format(Some(#format))
Expand Down
109 changes: 13 additions & 96 deletions utoipa-gen/src/schema/component/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use quote::{quote, ToTokens};
use syn::{parenthesized, parse::Parse, Attribute, Error, Token, TypePath};

use crate::{
component_type::ComponentFormat,
parse_utils,
schema::{ComponentPart, GenericType},
AnyValue,
Expand Down Expand Up @@ -73,25 +74,25 @@ impl IsInline for Struct {

#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct UnnamedFieldStruct {
pub struct UnnamedFieldStruct<'c> {
pub(super) value_type: Option<TypePath>,
format: Option<ComponentFormat>,
format: Option<ComponentFormat<'c>>,
default: Option<AnyValue>,
example: Option<AnyValue>,
}

impl IsInline for UnnamedFieldStruct {
impl IsInline for UnnamedFieldStruct<'_> {
fn is_inline(&self) -> bool {
false
}
}

#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct NamedField {
pub struct NamedField<'c> {
example: Option<AnyValue>,
pub(super) value_type: Option<TypePath>,
format: Option<ComponentFormat>,
format: Option<ComponentFormat<'c>>,
default: Option<AnyValue>,
write_only: Option<bool>,
read_only: Option<bool>,
Expand All @@ -100,7 +101,7 @@ pub struct NamedField {
inline: bool,
}

impl IsInline for NamedField {
impl IsInline for NamedField<'_> {
fn is_inline(&self) -> bool {
self.inline
}
Expand Down Expand Up @@ -200,7 +201,7 @@ impl Parse for ComponentAttr<Struct> {
}
}

impl Parse for ComponentAttr<UnnamedFieldStruct> {
impl<'c> Parse for ComponentAttr<UnnamedFieldStruct<'c>> {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
const EXPECTED_ATTRIBUTE_MESSAGE: &str =
"unexpected attribute, expected any of: default, example, format, value_type";
Expand Down Expand Up @@ -228,7 +229,7 @@ impl Parse for ComponentAttr<UnnamedFieldStruct> {
}
"format" => {
unnamed_struct.format = Some(parse_utils::parse_next(input, || {
input.parse::<ComponentFormat>()
input.parse::<ComponentFormat<'c>>()
})?)
}
"value_type" => {
Expand All @@ -250,7 +251,7 @@ impl Parse for ComponentAttr<UnnamedFieldStruct> {
}
}

impl ComponentAttr<NamedField> {
impl<'c> ComponentAttr<NamedField<'c>> {
pub(super) fn from_attributes_validated(
attributes: &[Attribute],
component_part: &ComponentPart,
Expand Down Expand Up @@ -303,7 +304,7 @@ fn is_valid_xml_attr(attrs: &ComponentAttr<NamedField>, component_part: &Compone
}
}

impl Parse for ComponentAttr<NamedField> {
impl<'c> Parse for ComponentAttr<NamedField<'c>> {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
const EXPECTED_ATTRIBUTE_MESSAGE: &str = "unexpected attribute, expected any of: example, format, default, write_only, read_only, xml, value_type, inline";
let mut field = NamedField::default();
Expand Down Expand Up @@ -358,90 +359,6 @@ impl Parse for ComponentAttr<NamedField> {
}
}

/// [`Parse`] and [`ToTokens`] implementation for [`utoipa::openapi::schema::ComponentFormat`].
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum ComponentFormat {
Int32,
Int64,
Float,
Double,
Byte,
Binary,
Date,
DateTime,
Password,
#[cfg(feature = "uuid")]
Uuid,
}

impl Parse for ComponentFormat {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
const FORMATS: [&str; 10] = [
"Int32", "Int64", "Float", "Double", "Byte", "Binary", "Date", "DateTime", "Password",
"Uuid",
];
let allowed_formats = FORMATS
.into_iter()
.filter(|_format| {
#[cfg(feature = "uuid")]
{
true
}
#[cfg(not(feature = "uuid"))]
{
_format != &"Uuid"
}
})
.collect::<Vec<_>>();
let expected_formats = format!(
"unexpected format, expected one of: {}",
allowed_formats.join(", ")
);
let format = input.parse::<Ident>()?;
let name = &*format.to_string();

match name {
"Int32" => Ok(Self::Int32),
"Int64" => Ok(Self::Int64),
"Float" => Ok(Self::Float),
"Double" => Ok(Self::Double),
"Byte" => Ok(Self::Byte),
"Binary" => Ok(Self::Binary),
"Date" => Ok(Self::Date),
"DateTime" => Ok(Self::DateTime),
"Password" => Ok(Self::Password),
#[cfg(feature = "uuid")]
"Uuid" => Ok(Self::Uuid),
_ => Err(Error::new(
format.span(),
format!("unexpected format: {name}, expected one of: {expected_formats}"),
)),
}
}
}

impl ToTokens for ComponentFormat {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Int32 => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Int32)),
Self::Int64 => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Int64)),
Self::Float => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Float)),
Self::Double => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Double)),
Self::Byte => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Byte)),
Self::Binary => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Binary)),
Self::Date => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Date)),
Self::DateTime => {
tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::DateTime))
}
Self::Password => {
tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Password))
}
#[cfg(feature = "uuid")]
Self::Uuid => tokens.extend(quote!(utoipa::openapi::schema::ComponentFormat::Uuid)),
};
}
}

pub fn parse_component_attr<T: Sized + Parse>(attributes: &[Attribute]) -> Option<T> {
attributes
.iter()
Expand Down Expand Up @@ -489,7 +406,7 @@ impl ToTokens for Struct {
}
}

impl ToTokens for UnnamedFieldStruct {
impl ToTokens for UnnamedFieldStruct<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if let Some(ref default) = self.default {
tokens.extend(quote! {
Expand All @@ -511,7 +428,7 @@ impl ToTokens for UnnamedFieldStruct {
}
}

impl ToTokens for NamedField {
impl ToTokens for NamedField<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if let Some(ref default) = self.default {
tokens.extend(quote! {
Expand Down
2 changes: 1 addition & 1 deletion utoipa-gen/src/schema/into_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ impl ToTokens for ParamType<'_> {
utoipa::openapi::PropertyBuilder::new().component_type(#component_type)
});

let format = ComponentFormat(&*component.path);
let format: ComponentFormat = (&*component.path).into();
if format.is_known_format() {
tokens.extend(quote! {
.format(Some(#format))
Expand Down

0 comments on commit a42843e

Please sign in to comment.