Skip to content

Commit

Permalink
Refactor KnownFormat
Browse files Browse the repository at this point in the history
This commit refactors `KnownFormat` and removes some unnecessary
duplicate code.
  • Loading branch information
juhaku committed Oct 10, 2024
1 parent fc22ddf commit fe2277f
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 203 deletions.
1 change: 1 addition & 0 deletions utoipa-gen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

### Changed

* Refactor `KnownFormat`
* Add path rewrite support (https://github.com/juhaku/utoipa/pull/1110)
* Fix broken tests
* Fix typos
Expand Down
4 changes: 2 additions & 2 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use syn::{
};

use crate::doc_comment::CommentAttributes;
use crate::schema_type::{PrimitiveType, SchemaFormat, SchemaTypeInner};
use crate::schema_type::{KnownFormat, PrimitiveType, SchemaTypeInner};
use crate::{
as_tokens_or_diagnostics, Array, AttributesExt, Diagnostics, GenericsExt, OptionExt,
ToTokensDiagnostics,
Expand Down Expand Up @@ -1125,7 +1125,7 @@ impl ComponentSchema {
utoipa::openapi::ObjectBuilder::new().schema_type(#schema_type_tokens)
});

let format: SchemaFormat = (type_path).into();
let format = KnownFormat::from_path(type_path)?;
if format.is_known_format() {
tokens.extend(quote! {
.format(Some(#format))
Expand Down
6 changes: 3 additions & 3 deletions utoipa-gen/src/component/features/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::component::serde::RenameRule;
use crate::component::{schema, GenericType, TypeTree};
use crate::parse_utils::LitStrOrExpr;
use crate::path::parameter::{self, ParameterStyle};
use crate::schema_type::SchemaFormat;
use crate::schema_type::KnownFormat;
use crate::{parse_utils, AnyValue, Array, Diagnostics};

use super::{impl_feature, Feature, Parse};
Expand Down Expand Up @@ -183,12 +183,12 @@ impl From<XmlAttr> for Feature {
impl_feature! {
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct Format(SchemaFormat<'static>);
pub struct Format(KnownFormat);
}

impl Parse for Format {
fn parse(input: syn::parse::ParseStream, _: Ident) -> syn::Result<Self> {
parse_utils::parse_next(input, || input.parse::<SchemaFormat>()).map(Self)
parse_utils::parse_next(input, || input.parse::<KnownFormat>()).map(Self)
}
}

Expand Down
274 changes: 76 additions & 198 deletions utoipa-gen/src/schema_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,238 +297,115 @@ impl ToTokensDiagnostics for SchemaType<'_> {
}
}

/// Either Rust type component variant or enum variant schema variant.
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum SchemaFormat<'c> {
/// [`utoipa::openapi::schema::SchemaFormat`] enum variant schema format.
Variant(Variant),
/// Rust type schema format.
Type(Type<'c>),
}

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

impl<'a> From<&'a Path> for SchemaFormat<'a> {
fn from(path: &'a Path) -> Self {
Self::Type(Type(path))
}
}

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

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

/// Tokenizes OpenAPI data type format correctly by given Rust type.
/// [`Parse`] and [`ToTokens`] implementation for [`utoipa::openapi::schema::SchemaFormat`].
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct Type<'a>(&'a syn::Path);

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.segments.last() {
Some(segment) => segment,
None => return false,
};
let name = &*last_segment.ident.to_string();

#[cfg(not(any(
feature = "chrono",
feature = "decimal_float",
feature = "uuid",
feature = "ulid",
feature = "url",
feature = "time"
)))]
{
is_known_format(name)
}

#[cfg(any(
feature = "chrono",
feature = "decimal_float",
feature = "uuid",
feature = "ulid",
feature = "url",
feature = "time"
))]
{
let mut known_format = is_known_format(name);

#[cfg(feature = "chrono")]
if !known_format {
known_format = matches!(name, "DateTime" | "Date" | "NaiveDate" | "NaiveDateTime");
}

#[cfg(feature = "decimal_float")]
if !known_format {
known_format = matches!(name, "Decimal");
}

#[cfg(feature = "uuid")]
if !known_format {
known_format = matches!(name, "Uuid");
}

#[cfg(feature = "ulid")]
if !known_format {
known_format = matches!(name, "Ulid");
}

#[cfg(feature = "url")]
if !known_format {
known_format = matches!(name, "Url");
}

#[cfg(feature = "time")]
if !known_format {
known_format = matches!(name, "Date" | "PrimitiveDateTime" | "OffsetDateTime");
}

known_format
}
}
}

#[inline]
fn is_known_format(name: &str) -> bool {
matches!(
name,
"i8" | "i16" | "i32" | "u8" | "u16" | "u32" | "i64" | "u64" | "f32" | "f64"
)
pub enum KnownFormat {
#[cfg(feature = "non_strict_integers")]
Int8,
#[cfg(feature = "non_strict_integers")]
Int16,
Int32,
Int64,
#[cfg(feature = "non_strict_integers")]
UInt8,
#[cfg(feature = "non_strict_integers")]
UInt16,
#[cfg(feature = "non_strict_integers")]
UInt32,
#[cfg(feature = "non_strict_integers")]
UInt64,
Float,
Double,
Byte,
Binary,
Date,
DateTime,
Password,
#[cfg(feature = "uuid")]
Uuid,
#[cfg(feature = "ulid")]
Ulid,
#[cfg(feature = "url")]
Uri,
/// Custom format is reserved only for manual entry.
Custom(String),
/// This is not really tokenized, but is actually only for purpose of having some format in
/// case we do not know what the format is actually.
#[allow(unused)]
Unknonw,
}

impl ToTokensDiagnostics for Type<'_> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) -> Result<(), Diagnostics> {
let last_segment = self.0.segments.last().ok_or_else(|| {
impl KnownFormat {
pub fn from_path(path: &syn::Path) -> Result<Self, Diagnostics> {
let last_segment = path.segments.last().ok_or_else(|| {
Diagnostics::with_span(
self.0.span(),
path.span(),
"type should have at least one segment in the path",
)
})?;
let name = &*last_segment.ident.to_string();

match name {
#[cfg(feature="non_strict_integers")]
"i8" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int8) }),
#[cfg(feature="non_strict_integers")]
"u8" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt8) }),
#[cfg(feature="non_strict_integers")]
"i16" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int16) }),
#[cfg(feature="non_strict_integers")]
"u16" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt16) }),
#[cfg(feature="non_strict_integers")]
#[cfg(feature="non_strict_integers")]
"u32" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt32) }),
#[cfg(feature="non_strict_integers")]
"u64" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt64) }),

#[cfg(not(feature="non_strict_integers"))]
"i8" | "i16" | "u8" | "u16" | "u32" => {
tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int32) })
}
let variant = match name {
#[cfg(feature = "non_strict_integers")]
"i8" => Self::Int8,
#[cfg(feature = "non_strict_integers")]
"u8" => Self::UInt8,
#[cfg(feature = "non_strict_integers")]
"i16" => Self::Int16,
#[cfg(feature = "non_strict_integers")]
"u16" => Self::UInt16,
#[cfg(feature = "non_strict_integers")]
"u32" => Self::UInt32,
#[cfg(feature = "non_strict_integers")]
"u64" => Self::UInt64,

#[cfg(not(feature="non_strict_integers"))]
"u64" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int64) }),
#[cfg(not(feature = "non_strict_integers"))]
"i8" | "i16" | "u8" | "u16" | "u32" => Self::Int32,

"i32" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int32) }),
"i64" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int64) }),
"f32" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Float) }),
"f64" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Double) }),
#[cfg(not(feature = "non_strict_integers"))]
"u64" => Self::Int64,

#[cfg(feature = "chrono")]
"NaiveDate" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Date) }),
"i32" => Self::Int32,
"i64" => Self::Int64,
"f32" => Self::Float,
"f64" => Self::Double,

#[cfg(feature = "chrono")]
"DateTime" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::DateTime) }),
"NaiveDate" => Self::Date,

#[cfg(feature = "chrono")]
"NaiveDateTime" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::DateTime) }),
"DateTime" | "NaiveDateTime" => Self::DateTime,

#[cfg(any(feature = "chrono", feature = "time"))]
"Date" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Date) }),
"Date" => Self::Date,

#[cfg(feature = "decimal_float")]
"Decimal" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Double) }),
"Decimal" => Self::Double,

#[cfg(feature = "uuid")]
"Uuid" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Uuid) }),
"Uuid" => Self::Uuid,

#[cfg(feature = "ulid")]
"Ulid" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Ulid) }),
"Ulid" => Self::Ulid,

#[cfg(feature = "url")]
"Url" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Uri) }),
"Url" => Self::Uri,

#[cfg(feature = "time")]
"PrimitiveDateTime" | "OffsetDateTime" => {
tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::DateTime) })
}
_ => (),
"PrimitiveDateTime" | "OffsetDateTime" => Self::DateTime,
_ => Self::Unknonw,
};

Ok(())
Ok(variant)
}
}

/// [`Parse`] and [`ToTokens`] implementation for [`utoipa::openapi::schema::SchemaFormat`].
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum Variant {
#[cfg(feature = "non_strict_integers")]
Int8,
#[cfg(feature = "non_strict_integers")]
Int16,
Int32,
Int64,
#[cfg(feature = "non_strict_integers")]
UInt8,
#[cfg(feature = "non_strict_integers")]
UInt16,
#[cfg(feature = "non_strict_integers")]
UInt32,
#[cfg(feature = "non_strict_integers")]
UInt64,
Float,
Double,
Byte,
Binary,
Date,
DateTime,
Password,
#[cfg(feature = "uuid")]
Uuid,
#[cfg(feature = "ulid")]
Ulid,
#[cfg(feature = "url")]
Uri,
Custom(String),
pub fn is_known_format(&self) -> bool {
!matches!(self, Self::Unknonw)
}
}

impl Parse for Variant {
impl Parse for KnownFormat {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let default_formats = [
"Int32",
Expand Down Expand Up @@ -614,7 +491,7 @@ impl Parse for Variant {
}
}

impl ToTokens for Variant {
impl ToTokens for KnownFormat {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
#[cfg(feature = "non_strict_integers")]
Expand Down Expand Up @@ -675,6 +552,7 @@ impl ToTokens for Variant {
Self::Custom(value) => tokens.extend(quote!(utoipa::openapi::SchemaFormat::Custom(
String::from(#value)
))),
Self::Unknonw => (), // unknown we just skip it
};
}
}
Expand Down
3 changes: 3 additions & 0 deletions utoipa-gen/tests/path_derive_actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ fn derive_into_params_in_another_module() {

#[test]
fn path_with_all_args() {
#![allow(unused)]
#[derive(utoipa::ToSchema, serde::Serialize, serde::Deserialize)]
struct Item(String);

Expand Down Expand Up @@ -894,6 +895,8 @@ fn path_with_all_args() {
#[test]
#[cfg(feature = "uuid")]
fn path_with_all_args_using_uuid() {
#![allow(unused)]

#[derive(utoipa::ToSchema, serde::Serialize, serde::Deserialize)]
struct Item(String);

Expand Down

0 comments on commit fe2277f

Please sign in to comment.