Skip to content

Commit

Permalink
Add missing formats for KnownFormat parsing
Browse files Browse the repository at this point in the history
This commit adds missing new formats introduced in OpenAPI 3.1 upgrade
also to `utoipa-gen` macro parsing.

Closes #1175
  • Loading branch information
juhaku committed Nov 1, 2024
1 parent 9e85e0e commit b225715
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 38 deletions.
6 changes: 6 additions & 0 deletions utoipa-gen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog - utoipa-gen

## Unreleased

### Changed

* Added missing formats for `KnownFormat` parsing (https://github.com/juhaku/utoipa/pull/1178)

## 5.1.3 - Oct 27 2024

### Fixed
Expand Down
178 changes: 140 additions & 38 deletions utoipa-gen/src/schema_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,17 +326,34 @@ pub enum KnownFormat {
Binary,
Date,
DateTime,
Duration,
Password,
#[cfg(feature = "uuid")]
Uuid,
#[cfg(feature = "ulid")]
Ulid,
#[cfg(feature = "url")]
Uri,
#[cfg(feature = "url")]
UriReference,
#[cfg(feature = "url")]
Iri,
#[cfg(feature = "url")]
IriReference,
Email,
IdnEmail,
Hostname,
IdnHostname,
Ipv4,
Ipv6,
UriTemplate,
JsonPointer,
RelativeJsonPointer,
Regex,
/// 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.
/// This is not tokenized, but is present for purpose of having some format in
/// case we do not know the format. E.g. We cannot determine the format based on type path.
#[allow(unused)]
Unknown,
}
Expand Down Expand Up @@ -408,10 +425,8 @@ impl KnownFormat {
pub fn is_known_format(&self) -> bool {
!matches!(self, Self::Unknown)
}
}

impl Parse for KnownFormat {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
fn get_allowed_formats() -> String {
let default_formats = [
"Int32",
"Int64",
Expand All @@ -421,13 +436,30 @@ impl Parse for KnownFormat {
"Binary",
"Date",
"DateTime",
"Duration",
"Password",
#[cfg(feature = "uuid")]
"Uuid",
#[cfg(feature = "ulid")]
"Ulid",
#[cfg(feature = "url")]
"Uri",
#[cfg(feature = "url")]
"UriReference",
#[cfg(feature = "url")]
"Iri",
#[cfg(feature = "url")]
"IriReference",
"Email",
"IdnEmail",
"Hostname",
"IdnHostname",
"Ipv4",
"Ipv6",
"UriTemplate",
"JsonPointer",
"RelativeJsonPointer",
"Regex",
];
#[cfg(feature = "non_strict_integers")]
let non_strict_integer_formats = [
Expand All @@ -449,6 +481,14 @@ impl Parse for KnownFormat {
formats.join(", ")
};

formats
}
}

impl Parse for KnownFormat {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let formats = KnownFormat::get_allowed_formats();

let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let format = input.parse::<Ident>()?;
Expand All @@ -475,13 +515,30 @@ impl Parse for KnownFormat {
"Binary" => Ok(Self::Binary),
"Date" => Ok(Self::Date),
"DateTime" => Ok(Self::DateTime),
"Duration" => Ok(Self::Duration),
"Password" => Ok(Self::Password),
#[cfg(feature = "uuid")]
"Uuid" => Ok(Self::Uuid),
#[cfg(feature = "ulid")]
"Ulid" => Ok(Self::Ulid),
#[cfg(feature = "url")]
"Uri" => Ok(Self::Uri),
#[cfg(feature = "url")]
"UriReference" => Ok(Self::UriReference),
#[cfg(feature = "url")]
"Iri" => Ok(Self::Iri),
#[cfg(feature = "url")]
"IriReference" => Ok(Self::IriReference),
"Email" => Ok(Self::Email),
"IdnEmail" => Ok(Self::IdnEmail),
"Hostname" => Ok(Self::Hostname),
"IdnHostname" => Ok(Self::IdnHostname),
"Ipv4" => Ok(Self::Ipv4),
"Ipv6" => Ok(Self::Ipv6),
"UriTemplate" => Ok(Self::UriTemplate),
"JsonPointer" => Ok(Self::JsonPointer),
"RelativeJsonPointer" => Ok(Self::RelativeJsonPointer),
"Regex" => Ok(Self::Regex),
_ => Err(Error::new(
format.span(),
format!("unexpected format: {name}, expected one of: {formats}"),
Expand All @@ -500,61 +557,106 @@ impl ToTokens for KnownFormat {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
#[cfg(feature = "non_strict_integers")]
Self::Int8 => tokens.extend(quote! {utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int8)}),
Self::Int8 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::Int8)}),
#[cfg(feature = "non_strict_integers")]
Self::Int16 => tokens.extend(quote! {utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int16)}),
Self::Int32 => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Int32
Self::Int16 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::Int16)}),
Self::Int32 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Int32
))),
Self::Int64 => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Int64
Self::Int64 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Int64
))),
#[cfg(feature = "non_strict_integers")]
Self::UInt8 => tokens.extend(quote! {utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt8)}),
Self::UInt8 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::UInt8)}),
#[cfg(feature = "non_strict_integers")]
Self::UInt16 => tokens.extend(quote! {utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt16)}),
Self::UInt16 => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(utoipa::openapi::schema::KnownFormat::UInt16)}),
#[cfg(feature = "non_strict_integers")]
Self::UInt32 => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::UInt32
Self::UInt32 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::UInt32
))),
#[cfg(feature = "non_strict_integers")]
Self::UInt64 => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::UInt64
Self::UInt64 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::UInt64
))),
Self::Float => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Float
Self::Float => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Float
))),
Self::Double => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Double
Self::Double => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Double
))),
Self::Byte => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Byte
Self::Byte => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Byte
))),
Self::Binary => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Binary
Self::Binary => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Binary
))),
Self::Date => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Date
Self::Date => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Date
))),
Self::DateTime => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::DateTime
Self::DateTime => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::DateTime
))),
Self::Password => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Password
Self::Duration => tokens.extend(quote! {utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Duration
) }),
Self::Password => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Password
))),
#[cfg(feature = "uuid")]
Self::Uuid => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Uuid
Self::Uuid => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Uuid
))),
#[cfg(feature = "ulid")]
Self::Ulid => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Ulid
Self::Ulid => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Ulid
))),
#[cfg(feature = "url")]
Self::Uri => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Uri
))),
#[cfg(feature = "url")]
Self::UriReference => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::UriReference
))),
#[cfg(feature = "url")]
Self::Uri => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Uri
Self::Iri => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Iri
))),
#[cfg(feature = "url")]
Self::IriReference => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::IriReference
))),
Self::Email => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Email
))),
Self::IdnEmail => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::IdnEmail
))),
Self::Hostname => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Hostname
))),
Self::IdnHostname => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::IdnHostname
))),
Self::Ipv4 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Ipv4
))),
Self::Ipv6 => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Ipv6
))),
Self::UriTemplate => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::UriTemplate
))),
Self::JsonPointer => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::JsonPointer
))),
Self::RelativeJsonPointer => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::RelativeJsonPointer
))),
Self::Regex => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Regex
))),
Self::Custom(value) => tokens.extend(quote!(utoipa::openapi::SchemaFormat::Custom(
Self::Custom(value) => tokens.extend(quote!(utoipa::openapi::schema::SchemaFormat::Custom(
String::from(#value)
))),
Self::Unknown => (), // unknown we just skip it
Expand Down
13 changes: 13 additions & 0 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3339,6 +3339,19 @@ fn derive_unnamed_struct_schema_type_override_with_format() {
}
}

#[test]
fn derive_unnamed_struct_schema_ipv4() {
let value = api_doc! {
#[schema(format = Ipv4)]
struct Ipv4(String);
};

assert_value! {value=>
"type" = r#""string""#, "Value type"
"format" = r#""ipv4""#, "Value format"
}
}

#[test]
fn derive_struct_override_type_with_object_type() {
let value = api_doc! {
Expand Down

0 comments on commit b225715

Please sign in to comment.