Skip to content

Commit

Permalink
Add parsing support for non strict integers (#1012)
Browse files Browse the repository at this point in the history
Prior to this PR the schema `format = ...` attribute was missing support
for `non_strict_integers` feature flag enabled formats. This commit adds
support for those formats.

E.g. now the following will work if `non_strict_integers` feature flag
is enabled.
```rust
struct Numbers {
    #[schema(format = UInt8)]
    ui8: String,
}
```

Fixes #996
  • Loading branch information
juhaku committed Aug 31, 2024
1 parent 04c490d commit 9c79ef2
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 16 deletions.
89 changes: 73 additions & 16 deletions utoipa-gen/src/schema_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,20 @@ impl ToTokensDiagnostics for Type<'_> {
#[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,
Expand All @@ -517,31 +529,63 @@ pub enum Variant {

impl Parse for Variant {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
const FORMATS: [&str; 12] = [
"Int32", "Int64", "Float", "Double", "Byte", "Binary", "Date", "DateTime", "Password",
"Uuid", "Ulid", "Uri",
];
let excluded_format: &[&str] = &[
#[cfg(not(feature = "uuid"))]
let default_formats = [
"Int32",
"Int64",
"Float",
"Double",
"Byte",
"Binary",
"Date",
"DateTime",
"Password",
#[cfg(feature = "uuid")]
"Uuid",
#[cfg(not(feature = "ulid"))]
#[cfg(feature = "ulid")]
"Ulid",
#[cfg(not(feature = "url"))]
#[cfg(feature = "url")]
"Uri",
];
let known_formats = FORMATS
.into_iter()
.filter(|format| !excluded_format.contains(format))
.collect::<Vec<_>>();
#[cfg(feature = "non_strict_integers")]
let non_strict_integer_formats = [
"Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64",
];

#[cfg(feature = "non_strict_integers")]
let formats = {
let mut formats = default_formats
.into_iter()
.chain(non_strict_integer_formats)
.collect::<Vec<_>>();
formats.sort_unstable();
formats.join(", ")
};
#[cfg(not(feature = "non_strict_integers"))]
let formats = {
let formats = default_formats.into_iter().collect::<Vec<_>>();
formats.join(", ")
};

let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
let format = input.parse::<Ident>()?;
let name = &*format.to_string();

match name {
#[cfg(feature = "non_strict_integers")]
"Int8" => Ok(Self::Int8),
#[cfg(feature = "non_strict_integers")]
"Int16" => Ok(Self::Int16),
"Int32" => Ok(Self::Int32),
"Int64" => Ok(Self::Int64),
#[cfg(feature = "non_strict_integers")]
"UInt8" => Ok(Self::UInt8),
#[cfg(feature = "non_strict_integers")]
"UInt16" => Ok(Self::UInt16),
#[cfg(feature = "non_strict_integers")]
"UInt32" => Ok(Self::UInt32),
#[cfg(feature = "non_strict_integers")]
"UInt64" => Ok(Self::UInt64),
"Float" => Ok(Self::Float),
"Double" => Ok(Self::Double),
"Byte" => Ok(Self::Byte),
Expand All @@ -557,10 +601,7 @@ impl Parse for Variant {
"Uri" => Ok(Self::Uri),
_ => Err(Error::new(
format.span(),
format!(
"unexpected format: {name}, expected one of: {}",
known_formats.join(", ")
),
format!("unexpected format: {name}, expected one of: {formats}"),
)),
}
} else if lookahead.peek(LitStr) {
Expand All @@ -575,12 +616,28 @@ impl Parse for Variant {
impl ToTokens for Variant {
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)}),
#[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::Int64 => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Int64
))),
#[cfg(feature = "non_strict_integers")]
Self::UInt8 => tokens.extend(quote! {utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt8)}),
#[cfg(feature = "non_strict_integers")]
Self::UInt16 => tokens.extend(quote! {utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::UInt16)}),
#[cfg(feature = "non_strict_integers")]
Self::UInt32 => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::UInt32
))),
#[cfg(feature = "non_strict_integers")]
Self::UInt64 => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::UInt64
))),
Self::Float => tokens.extend(quote!(utoipa::openapi::SchemaFormat::KnownFormat(
utoipa::openapi::KnownFormat::Float
))),
Expand Down
53 changes: 53 additions & 0 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4397,6 +4397,59 @@ fn derive_struct_with_validation_fields() {
);
}

#[test]
#[cfg(feature = "non_strict_integers")]
fn uint_non_strict_integers_format() {
let value = api_doc! {
struct Numbers {
#[schema(format = UInt8)]
ui8: String,
#[schema(format = UInt16)]
ui16: String,
#[schema(format = UInt32)]
ui32: String,
#[schema(format = UInt64)]
ui64: String,
#[schema(format = UInt16)]
i16: String,
#[schema(format = Int8)]
i8: String,
}
};

assert_json_eq!(
value,
json!({
"properties": {
"ui8": {
"type": "integer",
"format": "uint8"
},
"ui16": {
"type": "integer",
"format": "uint16"
},
"ui32": {
"type": "integer",
"format": "uint32"
},
"ui64": {
"type": "integer",
"format": "uint64"
},
"i16": {
"type": "integer",
"format": "int16"
},
"i8": {
"type": "integer",
"format": "int8"
}
}
})
)
}

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

0 comments on commit 9c79ef2

Please sign in to comment.