From 7e83235e8c93bc4433cfcc159ac868401d76d105 Mon Sep 17 00:00:00 2001 From: beyarkay Date: Wed, 7 Jun 2023 21:07:24 +0200 Subject: [PATCH 1/4] Add support for chrono::NaiveTime Since the OpenAPI spec does not explicitly support times without attached dates, I have implemented this in the same way that support for `duration` was done, but formatting them as strings. --- utoipa-gen/src/schema_type.rs | 9 +++++++-- utoipa-gen/tests/schema_derive_test.rs | 5 ++++- utoipa/src/lib.rs | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/utoipa-gen/src/schema_type.rs b/utoipa-gen/src/schema_type.rs index fef9f385..fe6ce5c9 100644 --- a/utoipa-gen/src/schema_type.rs +++ b/utoipa-gen/src/schema_type.rs @@ -153,7 +153,7 @@ fn is_primitive(name: &str) -> bool { fn is_primitive_chrono(name: &str) -> bool { matches!( name, - "DateTime" | "Date" | "NaiveDate" | "Duration" | "NaiveDateTime" + "DateTime" | "Date" | "NaiveDate" | "NaiveTime" | "Duration" | "NaiveDateTime" ) } @@ -184,6 +184,8 @@ impl ToTokens for SchemaType<'_> { "NaiveDateTime" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), #[cfg(feature = "chrono")] "NaiveDate" => tokens.extend(quote!(utoipa::openapi::SchemaType::String)), + #[cfg(feature = "chrono")] + "NaiveTime" => tokens.extend(quote!(utoipa::openapi::SchemaType::String)), #[cfg(any(feature = "chrono", feature = "time"))] "Date" | "Duration" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), #[cfg(feature = "decimal")] @@ -266,7 +268,10 @@ impl Type<'_> { #[cfg(feature = "chrono")] if !known_format { - known_format = matches!(name, "DateTime" | "Date" | "NaiveDate" | "NaiveDateTime"); + known_format = matches!( + name, + "DateTime" | "Date" | "NaiveDate" | "NaiveTime" | "NaiveDateTime" + ); } #[cfg(feature = "uuid")] diff --git a/utoipa-gen/tests/schema_derive_test.rs b/utoipa-gen/tests/schema_derive_test.rs index 377caa26..02e95371 100644 --- a/utoipa-gen/tests/schema_derive_test.rs +++ b/utoipa-gen/tests/schema_derive_test.rs @@ -2694,7 +2694,7 @@ fn derive_struct_xml_with_optional_vec() { #[test] fn derive_component_with_chrono_feature() { #![allow(deprecated)] // allow deprecated Date in tests as long as it is available from chrono - use chrono::{Date, DateTime, Duration, NaiveDate, NaiveDateTime, Utc}; + use chrono::{Date, DateTime, Duration, NaiveDate, NaiveDateTime, NaiveTime, Utc}; let post = api_doc! { struct Post { @@ -2704,6 +2704,7 @@ fn derive_component_with_chrono_feature() { naive_datetime: NaiveDateTime, date: Date, naive_date: NaiveDate, + naive_time: NaiveTime, duration: Duration, } }; @@ -2717,6 +2718,8 @@ fn derive_component_with_chrono_feature() { "properties.date.format" = r#""date""#, "Post date format" "properties.naive_date.type" = r#""string""#, "Post date type" "properties.naive_date.format" = r#""date""#, "Post date format" + "properties.naive_time.type" = r#""string""#, "Post time type" + "properties.naive_time.format" = r#""null""#, "Post time format" "properties.duration.type" = r#""string""#, "Post duration type" "properties.duration.format" = r#"null"#, "Post duration format" "properties.id.type" = r#""integer""#, "Post id type" diff --git a/utoipa/src/lib.rs b/utoipa/src/lib.rs index 5293d650..742f7b21 100644 --- a/utoipa/src/lib.rs +++ b/utoipa/src/lib.rs @@ -53,7 +53,7 @@ //! without defining the `parameter_in` attribute. See [axum extras support][axum_path] //! or [examples](https://github.com/juhaku/utoipa/tree/master/examples) for more details. //! * **debug** Add extra traits such as debug traits to openapi definitions and elsewhere. -//! * **chrono** Add support for [chrono](https://crates.io/crates/chrono) `DateTime`, `Date`, `NaiveDate` and `Duration` +//! * **chrono** Add support for [chrono](https://crates.io/crates/chrono) `DateTime`, `Date`, `NaiveDate`, `NaiveTime` and `Duration` //! types. By default these types are parsed to `string` types with additional `format` information. //! `format: date-time` for `DateTime` and `format: date` for `Date` and `NaiveDate` according //! [RFC3339](https://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14) as `ISO-8601`. To From 87ec0800dd4014bc04a7160051b5543016e3285b Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Sun, 11 Jun 2023 19:28:35 +0300 Subject: [PATCH 2/4] Fix naive time support and enabled dependencies Remove `NaiveTime` from `known_format` since it does not have a additional format specifier and fix the chrono test. Additionally fix uuid dependency when running tests and update dev dependencies. --- utoipa-gen/Cargo.toml | 4 ++-- utoipa-gen/src/schema_type.rs | 27 ++++++++++++++++++-------- utoipa-gen/tests/schema_derive_test.rs | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/utoipa-gen/Cargo.toml b/utoipa-gen/Cargo.toml index 019e762a..e1e0fb61 100644 --- a/utoipa-gen/Cargo.toml +++ b/utoipa-gen/Cargo.toml @@ -34,7 +34,7 @@ rust_decimal = "1" chrono = { version = "0.4", features = ["serde"] } assert-json-diff = "2" time = { version = "0.3", features = ["serde-human-readable"] } -serde_with = "2.3" +serde_with = "3.0" [features] # See README.md for list and explanations of features @@ -45,7 +45,7 @@ yaml = [] decimal = [] rocket_extras = ["regex", "lazy_static", "syn/extra-traits"] non_strict_integers = [] -uuid = ["dep:uuid"] +uuid = ["dep:uuid", "utoipa/uuid"] axum_extras = ["syn/extra-traits"] time = [] smallvec = [] diff --git a/utoipa-gen/src/schema_type.rs b/utoipa-gen/src/schema_type.rs index fe6ce5c9..47fc0dca 100644 --- a/utoipa-gen/src/schema_type.rs +++ b/utoipa-gen/src/schema_type.rs @@ -174,26 +174,28 @@ impl ToTokens for SchemaType<'_> { "String" | "str" | "char" => { tokens.extend(quote! {utoipa::openapi::SchemaType::String}) } + "bool" => tokens.extend(quote! { utoipa::openapi::SchemaType::Boolean }), + "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" => tokens.extend(quote! { utoipa::openapi::SchemaType::Integer }), "f32" | "f64" => tokens.extend(quote! { utoipa::openapi::SchemaType::Number }), + #[cfg(feature = "chrono")] - "DateTime" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), - #[cfg(feature = "chrono")] - "NaiveDateTime" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), - #[cfg(feature = "chrono")] - "NaiveDate" => tokens.extend(quote!(utoipa::openapi::SchemaType::String)), - #[cfg(feature = "chrono")] - "NaiveTime" => tokens.extend(quote!(utoipa::openapi::SchemaType::String)), + "DateTime" | "NaiveDateTime" | "NaiveDate" | "NaiveTime" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), + #[cfg(any(feature = "chrono", feature = "time"))] "Date" | "Duration" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), + #[cfg(feature = "decimal")] "Decimal" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), + #[cfg(feature = "rocket_extras")] "PathBuf" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), + #[cfg(feature = "uuid")] "Uuid" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), + #[cfg(feature = "time")] "PrimitiveDateTime" | "OffsetDateTime" => { tokens.extend(quote! { utoipa::openapi::SchemaType::String }) @@ -270,7 +272,7 @@ impl Type<'_> { if !known_format { known_format = matches!( name, - "DateTime" | "Date" | "NaiveDate" | "NaiveTime" | "NaiveDateTime" + "DateTime" | "Date" | "NaiveDate" | "NaiveDateTime" ); } @@ -318,26 +320,35 @@ impl ToTokens for Type<'_> { "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) }) } + #[cfg(not(feature="non_strict_integers"))] "u64" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Int64) }), + "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(feature = "chrono")] "NaiveDate" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Date) }), + #[cfg(feature = "chrono")] "DateTime" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::DateTime) }), + #[cfg(feature = "chrono")] "NaiveDateTime" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::DateTime) }), + #[cfg(any(feature = "chrono", feature = "time"))] "Date" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Date) }), + #[cfg(feature = "uuid")] "Uuid" => tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Uuid) }), + #[cfg(feature = "time")] "PrimitiveDateTime" | "OffsetDateTime" => { tokens.extend(quote! { utoipa::openapi::SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::DateTime) }) diff --git a/utoipa-gen/tests/schema_derive_test.rs b/utoipa-gen/tests/schema_derive_test.rs index 02e95371..e96c67e5 100644 --- a/utoipa-gen/tests/schema_derive_test.rs +++ b/utoipa-gen/tests/schema_derive_test.rs @@ -2719,7 +2719,7 @@ fn derive_component_with_chrono_feature() { "properties.naive_date.type" = r#""string""#, "Post date type" "properties.naive_date.format" = r#""date""#, "Post date format" "properties.naive_time.type" = r#""string""#, "Post time type" - "properties.naive_time.format" = r#""null""#, "Post time format" + "properties.naive_time.format" = r#"null"#, "Post time format" "properties.duration.type" = r#""string""#, "Post duration type" "properties.duration.format" = r#"null"#, "Post duration format" "properties.id.type" = r#""integer""#, "Post id type" From 4fa514b49c087ebf48bfe8c9989969a3bdd0929a Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Sun, 11 Jun 2023 19:47:17 +0300 Subject: [PATCH 3/4] Format code --- utoipa-gen/src/schema_type.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/utoipa-gen/src/schema_type.rs b/utoipa-gen/src/schema_type.rs index 47fc0dca..34bcfc21 100644 --- a/utoipa-gen/src/schema_type.rs +++ b/utoipa-gen/src/schema_type.rs @@ -182,7 +182,9 @@ impl ToTokens for SchemaType<'_> { "f32" | "f64" => tokens.extend(quote! { utoipa::openapi::SchemaType::Number }), #[cfg(feature = "chrono")] - "DateTime" | "NaiveDateTime" | "NaiveDate" | "NaiveTime" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), + "DateTime" | "NaiveDateTime" | "NaiveDate" | "NaiveTime" => { + tokens.extend(quote! { utoipa::openapi::SchemaType::String }) + } #[cfg(any(feature = "chrono", feature = "time"))] "Date" | "Duration" => tokens.extend(quote! { utoipa::openapi::SchemaType::String }), @@ -270,10 +272,7 @@ impl Type<'_> { #[cfg(feature = "chrono")] if !known_format { - known_format = matches!( - name, - "DateTime" | "Date" | "NaiveDate" | "NaiveDateTime" - ); + known_format = matches!(name, "DateTime" | "Date" | "NaiveDate" | "NaiveDateTime"); } #[cfg(feature = "uuid")] From b7180a32d15486b024e08bed061381bc3628e9b8 Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Sun, 11 Jun 2023 19:50:34 +0300 Subject: [PATCH 4/4] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6d0be36..23465310 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ and the `ipa` is _api_ reversed. Aaand... `ipa` is also an awesome type of beer defining the `parameter_in` attribute. See [docs](https://docs.rs/utoipa/latest/utoipa/attr.path.html#axum_extras-feature-support-for-axum) or [examples](./examples) for more details. - **debug** Add extra traits such as debug traits to openapi definitions and elsewhere. -- **chrono** Add support for [chrono](https://crates.io/crates/chrono) `DateTime`, `Date`, `NaiveDate`, `NaiveDateTime` and `Duration` +- **chrono** Add support for [chrono](https://crates.io/crates/chrono) `DateTime`, `Date`, `NaiveDate`, `NaiveDateTime`, `NaiveTime` and `Duration` types. By default these types are parsed to `string` types with additional `format` information. `format: date-time` for `DateTime` and `NaiveDateTime` and `format: date` for `Date` and `NaiveDate` according [RFC3339](https://www.rfc-editor.org/rfc/rfc3339#section-5.6) as `ISO-8601`. To