From 0ba150a7c94ad163b5be3b2e7e36129f53561df9 Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Tue, 26 Apr 2022 16:20:25 +0300 Subject: [PATCH] Add support for UUID * Add support for UUID with uuid feature flag. Uuid types are presented as String with uuid format in OpenAPI doc. --- Cargo.toml | 2 ++ README.md | 3 +++ src/lib.rs | 2 ++ src/openapi/schema.rs | 5 +++++ tests/component_derive_test.rs | 17 ++++++++++++++++ tests/path_derive.rs | 34 ++++++++++++++++++++++++++++++++ utoipa-gen/Cargo.toml | 2 ++ utoipa-gen/src/component_type.rs | 25 +++++++++++++++++++---- 8 files changed, 86 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d944d761..b3949163 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ chrono_types = ["utoipa-gen/chrono_types"] chrono_types_with_format = ["utoipa-gen/chrono_types_with_format"] decimal = ["utoipa-gen/decimal"] yaml = ["serde_yaml"] +uuid = ["utoipa-gen/uuid"] [dependencies] serde = { version = "1.0", features = ["derive"] } @@ -42,6 +43,7 @@ paste = "1" chrono = { version = "0.4", features = ["serde"] } rust_decimal = "1" rocket = "0.5.0-rc.1" +uuid = "1" [workspace] members = [ diff --git a/README.md b/README.md index a7dcfa61..692c16c9 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Utoipa build](https://github.com/juhaku/utoipa/actions/workflows/build.yaml/badge.svg)](https://github.com/juhaku/utoipa/actions/workflows/build.yaml) [![crates.io](https://img.shields.io/static/v1?label=crates.io&message=0.2.0&color=orange&logo=rust)](https://crates.io/crates/utoipa/0.2.0) [![docs.rs](https://img.shields.io/static/v1?label=docs.rs&message=utoipa&color=blue&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/utoipa/0.2.0/utoipa/) +![rustc](https://img.shields.io/static/v1?label=rustc&message=1.60%2B&color=orange&logo=rust) Want to have your API documented with OpenAPI? But you dont want to see the trouble with manual yaml or json tweaking? Would like it to be so easy that it would almost @@ -64,6 +65,8 @@ and the `ipa` is _api_ reversed. Aaand... `ipa` is also awesome type of beer :be * **decimal** Add support for [rust_decimal](https://crates.io/crates/rust_decimal) `Decimal` type. **By default** it is interpreted as `String`. If you wish to change the format you need to override the type. See the `value_type` in [component derive docs](https://docs.rs/utoipa/0.2.0/utoipa/derive.Component.html). +* **uuid** Add support for [UUID](https://github.com/uuid-rs/uuid). `Uuid` type will be presented as `String` with + format `uuid` in OpenAPI spec. ## Install diff --git a/src/lib.rs b/src/lib.rs index a93d1358..a914e0a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,8 @@ //! * **decimal** Add support for [rust_decimal](https://crates.io/crates/rust_decimal) `Decimal` type. **By default** //! it is interpreted as `String`. If you wish to change the format you need to override the type. //! See the `value_type` in [component derive docs][component_derive]. +//! * **uuid** Add support for [UUID](https://github.com/uuid-rs/uuid). `Uuid` type will be presented as `String` with +//! format `uuid` in OpenAPI spec. //! //! # Install //! diff --git a/src/openapi/schema.rs b/src/openapi/schema.rs index 7aa08c4d..6cb0087a 100644 --- a/src/openapi/schema.rs +++ b/src/openapi/schema.rs @@ -766,6 +766,11 @@ pub enum ComponentFormat { DateTime, /// Hint to UI to obsucre input. Password, + /// Used with [`String`] values to indicate value is in UUID format. + /// + /// **uuid** feature need to be enabled. + #[cfg(feature = "uuid")] + Uuid, } #[cfg(test)] diff --git a/tests/component_derive_test.rs b/tests/component_derive_test.rs index 0a66dd2d..8efc8500 100644 --- a/tests/component_derive_test.rs +++ b/tests/component_derive_test.rs @@ -779,3 +779,20 @@ fn derive_struct_with_rust_decimal_with_type_override() { "properties.rating.format" = r#""float""#, "Post rating format" } } + +#[cfg(feature = "uuid")] +#[test] +fn derive_struct_with_uuid_type() { + use uuid::Uuid; + + let post = api_doc! { + struct Post { + id: Uuid, + } + }; + + assert_value! {post=> + "properties.id.type" = r#""string""#, "Post id type" + "properties.id.format" = r#""uuid""#, "Post id format" + } +} diff --git a/tests/path_derive.rs b/tests/path_derive.rs index 680ca179..3480db40 100644 --- a/tests/path_derive.rs +++ b/tests/path_derive.rs @@ -230,3 +230,37 @@ fn derive_path_with_security_requirements() { "security.[2].jwt_token" = "[]", "jwt_token auth scopes" } } + +#[cfg(feature = "uuid")] +#[test] +fn derive_path_with_uuid() { + use uuid::Uuid; + + #[utoipa::path( + get, + path = "/items/{id}", + responses( + (status = 200, description = "success response") + ), + params( + ("id" = Uuid, description = "Foo uuid"), + ) + )] + #[allow(unused)] + fn get_items(id: Uuid) -> String { + "".to_string() + } + let operation = test_api_fn_doc! { + get_items, + operation: get, + path: "/items/{id}" + }; + + assert_value! {operation=> + "parameters.[0].schema.type" = r#""string""#, "Parameter id type" + "parameters.[0].schema.format" = r#""uuid""#, "Parameter id format" + "parameters.[0].description" = r#""Foo uuid""#, "Parameter id description" + "parameters.[0].name" = r#""id""#, "Parameter id id" + "parameters.[0].in" = r#""path""#, "Parameter in" + } +} diff --git a/utoipa-gen/Cargo.toml b/utoipa-gen/Cargo.toml index 71c8a6d4..1ec2d92c 100644 --- a/utoipa-gen/Cargo.toml +++ b/utoipa-gen/Cargo.toml @@ -21,6 +21,7 @@ quote = "1.0" proc-macro-error = "1.0" regex = { version = "1.5", optional = true } lazy_static = { version = "1.4", optional = true } +uuid = { version = "1", optional = true } [dev-dependencies] utoipa = { path = ".." } @@ -36,3 +37,4 @@ chrono_types_with_format = [] json = [] decimal = [] rocket_extras = ["regex", "lazy_static"] +uuid = ["dep:uuid"] \ No newline at end of file diff --git a/utoipa-gen/src/component_type.rs b/utoipa-gen/src/component_type.rs index df49d731..b869e1bd 100644 --- a/utoipa-gen/src/component_type.rs +++ b/utoipa-gen/src/component_type.rs @@ -17,7 +17,8 @@ where feature = "chrono_types", feature = "chrono_types_with_format", feature = "decimal", - feature = "rocket_extras" + feature = "rocket_extras", + feature = "uuid" )))] { is_primitive(name) @@ -27,7 +28,8 @@ where feature = "chrono_types", feature = "chrono_types_with_format", feature = "decimal", - feature = "rocket_extras" + feature = "rocket_extras", + feature = "uuid", ))] { let mut primitive = is_primitive(name); @@ -47,6 +49,11 @@ where primitive = matches!(name, "PathBuf"); } + #[cfg(feature = "uuid")] + if !primitive { + primitive = matches!(name, "Uuid"); + } + primitive } } @@ -112,6 +119,8 @@ where "Decimal" => tokens.extend(quote! { utoipa::openapi::ComponentType::String }), #[cfg(feature = "rocket_extras")] "PathBuf" => tokens.extend(quote! { utoipa::openapi::ComponentType::String }), + #[cfg(feature = "uuid")] + "Uuid" => tokens.extend(quote! { utoipa::openapi::ComponentType::String }), _ => tokens.extend(quote! { utoipa::openapi::ComponentType::Object }), } } @@ -125,19 +134,25 @@ impl ComponentFormat { pub fn is_known_format(&self) -> bool { let name = &*self.0.to_string(); - #[cfg(not(feature = "chrono_types_with_format"))] + #[cfg(not(any(feature = "chrono_types_with_format", feature = "uuid")))] { is_known_format(name) } - #[cfg(feature = "chrono_types_with_format")] + #[cfg(any(feature = "chrono_types_with_format", feature = "uuid"))] { let mut known_format = is_known_format(name); + #[cfg(feature = "chrono_types_with_format")] if !known_format { known_format = matches!(name, "DateTime" | "Date"); } + #[cfg(feature = "uuid")] + if !known_format { + known_format = matches!(name, "Uuid"); + } + known_format } } @@ -165,6 +180,8 @@ impl ToTokens for ComponentFormat { "DateTime" => tokens.extend(quote! { utoipa::openapi::ComponentFormat::DateTime }), #[cfg(feature = "chrono_types_with_format")] "Date" => tokens.extend(quote! { utoipa::openapi::ComponentFormat::Date }), + #[cfg(feature = "uuid")] + "Uuid" => tokens.extend(quote! { utoipa::openapi::ComponentFormat::Uuid }), _ => (), } }