Skip to content

Commit

Permalink
Add missing formats for KnownFormat parsing (#1178)
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 authored Nov 1, 2024
1 parent 9e85e0e commit ca643ef
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 ca643ef

Please sign in to comment.