From 16cfe353623f469095d641d8e67f2e515033ee59 Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Wed, 15 May 2024 14:08:18 +0300 Subject: [PATCH] Fix tuple params missing features When `axum_extras`,`actix_extras` or `rocket_extras` was enabled the udpate of existing parameters did not take in account the features that was defined in the tuple style arguments and was overridden with empty features. This PR fixes this behavior where features will be also updated accordingly. Resolves #804 --- utoipa-gen/src/path.rs | 8 +- utoipa-gen/src/path/parameter.rs | 15 +- utoipa-gen/tests/path_derive_axum_test.rs | 144 ++++++++++++++++++ .../tests/path_parameter_derive_actix.rs | 8 +- 4 files changed, 164 insertions(+), 11 deletions(-) diff --git a/utoipa-gen/src/path.rs b/utoipa-gen/src/path.rs index b6aa10ba..2f06a558 100644 --- a/utoipa-gen/src/path.rs +++ b/utoipa-gen/src/path.rs @@ -76,12 +76,12 @@ impl<'p> PathAttr<'p> { ) { let ext_params = ext_parameters.into_iter(); - let (existing_params, new_params): (Vec, Vec) = + let (existing_incoming_params, new_params): (Vec, Vec) = ext_params.partition(|param| self.params.iter().any(|p| p == param)); - for existing in existing_params { - if let Some(param) = self.params.iter_mut().find(|p| **p == existing) { - param.merge(existing); + for existing_incoming in existing_incoming_params { + if let Some(param) = self.params.iter_mut().find(|p| **p == existing_incoming) { + param.merge(existing_incoming); } } diff --git a/utoipa-gen/src/path/parameter.rs b/utoipa-gen/src/path/parameter.rs index 77cdd6af..c22ac067 100644 --- a/utoipa-gen/src/path/parameter.rs +++ b/utoipa-gen/src/path/parameter.rs @@ -52,7 +52,12 @@ impl<'p> Parameter<'p> { pub fn merge(&mut self, other: Parameter<'p>) { match (self, other) { (Self::Value(value), Parameter::Value(other)) => { + let (schema_features, _) = &value.features; value.parameter_schema = other.parameter_schema; + + if let Some(parameter_schema) = &mut value.parameter_schema { + parameter_schema.features.clone_from(schema_features); + } } (Self::IntoParamsIdent(into_params), Parameter::IntoParamsIdent(other)) => { *into_params = other; @@ -157,7 +162,7 @@ impl ToTokensDiagnostics for ParameterSchema<'_> { ParameterType::External(type_tree) => { let required: Required = (!type_tree.is_option()).into(); - Ok(to_tokens( + to_tokens( ComponentSchema::new(component::ComponentSchemaProps { type_tree, features: Some(self.features.clone()), @@ -166,7 +171,8 @@ impl ToTokensDiagnostics for ParameterSchema<'_> { object_name: "", }), required, - )) + ); + Ok(()) } ParameterType::Parsed(inline_type) => { let type_tree = inline_type.as_type_tree()?; @@ -175,7 +181,7 @@ impl ToTokensDiagnostics for ParameterSchema<'_> { schema_features.clone_from(&self.features); schema_features.push(Feature::Inline(inline_type.is_inline.into())); - Ok(to_tokens( + to_tokens( ComponentSchema::new(component::ComponentSchemaProps { type_tree: &type_tree, features: Some(schema_features), @@ -184,7 +190,8 @@ impl ToTokensDiagnostics for ParameterSchema<'_> { object_name: "", }), required, - )) + ); + Ok(()) } } } diff --git a/utoipa-gen/tests/path_derive_axum_test.rs b/utoipa-gen/tests/path_derive_axum_test.rs index d7a1dfe8..f3b2cd0b 100644 --- a/utoipa-gen/tests/path_derive_axum_test.rs +++ b/utoipa-gen/tests/path_derive_axum_test.rs @@ -180,6 +180,7 @@ fn get_todo_with_extension() { fn derive_path_params_into_params_unnamed() { #[derive(Deserialize, IntoParams)] #[into_params(names("id", "name"))] + #[allow(dead_code)] struct IdAndName(u64, String); #[utoipa::path( @@ -235,6 +236,7 @@ fn derive_path_params_with_ignored_parameter() { struct Auth; #[derive(Deserialize, IntoParams)] #[into_params(names("id", "name"))] + #[allow(dead_code)] struct IdAndName(u64, String); #[utoipa::path( @@ -537,6 +539,7 @@ fn path_param_single_arg_primitive_type() { #[test] fn path_param_single_arg_non_primitive_type() { #[derive(utoipa::ToSchema)] + #[allow(dead_code)] struct Id(String); #[utoipa::path( @@ -577,6 +580,7 @@ fn path_param_single_arg_non_primitive_type() { fn path_param_single_arg_non_primitive_type_into_params() { #[derive(utoipa::ToSchema, utoipa::IntoParams)] #[into_params(names("id"))] + #[allow(dead_code)] struct Id(String); #[utoipa::path( @@ -611,3 +615,143 @@ fn path_param_single_arg_non_primitive_type_into_params() { ]) ) } + +#[test] +fn derive_path_with_validation_attributes_axum() { + #[derive(IntoParams)] + #[allow(dead_code)] + struct Params { + #[param(maximum = 10, minimum = 5, multiple_of = 2.5)] + id: i32, + + #[param(max_length = 10, min_length = 5, pattern = "[a-z]*")] + value: String, + + #[param(max_items = 5, min_items = 1)] + items: Vec, + } + + #[utoipa::path( + get, + path = "foo/{foo_id}", + responses( + (status = 200, description = "success response") + ), + params( + ("foo_id" = String, Path, min_length = 1, description = "Id of Foo to get"), + Params, + ("name" = Option, description = "Foo name", min_length = 3), + ("nonnullable" = String, description = "Foo nonnullable", min_length = 3, max_length = 10), + ("namequery" = Option, Query, description = "Foo name", min_length = 3), + ("nonnullablequery" = String, Query, description = "Foo nonnullable", min_length = 3, max_length = 10), + ) + )] + #[allow(unused)] + fn get_foo(path: Path, query: Query) {} + + #[derive(OpenApi, Default)] + #[openapi(paths(get_foo))] + struct ApiDoc; + + let doc = serde_json::to_value(ApiDoc::openapi()).unwrap(); + let parameters = doc.pointer("/paths/foo~1{foo_id}/get/parameters").unwrap(); + + let config = Config::new(CompareMode::Strict).numeric_mode(NumericMode::AssumeFloat); + + assert_json_matches!( + parameters, + json!([ + { + "schema": { + "type": "string", + "minLength": 1, + }, + "required": true, + "name": "foo_id", + "in": "path", + "description": "Id of Foo to get" + }, + { + "schema": { + "format": "int32", + "type": "integer", + "maximum": 10.0, + "minimum": 5.0, + "multipleOf": 2.5, + }, + "required": true, + "name": "id", + "in": "query" + }, + { + "schema": { + "type": "string", + "maxLength": 10, + "minLength": 5, + "pattern": "[a-z]*" + }, + "required": true, + "name": "value", + "in": "query" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string", + }, + "maxItems": 5, + "minItems": 1, + }, + "required": true, + "name": "items", + "in": "query" + }, + { + "schema": { + "type": "string", + "nullable": true, + "minLength": 3, + }, + "required": true, + "name": "name", + "in": "path", + "description": "Foo name" + }, + { + "schema": { + "type": "string", + "minLength": 3, + "maxLength": 10, + }, + "required": true, + "name": "nonnullable", + "in": "path", + "description": "Foo nonnullable" + }, + { + "schema": { + "type": "string", + "nullable": true, + "minLength": 3, + }, + "required": false, + "name": "namequery", + "in": "query", + "description": "Foo name" + }, + { + "schema": { + "type": "string", + "minLength": 3, + "maxLength": 10, + }, + "required": true, + "name": "nonnullablequery", + "in": "query", + "description": "Foo nonnullable" + } + ]), + config + ); +} diff --git a/utoipa-gen/tests/path_parameter_derive_actix.rs b/utoipa-gen/tests/path_parameter_derive_actix.rs index 732ddc24..f943a89b 100644 --- a/utoipa-gen/tests/path_parameter_derive_actix.rs +++ b/utoipa-gen/tests/path_parameter_derive_actix.rs @@ -166,7 +166,8 @@ fn derive_params_from_method_args_actix_success() { } #[test] -fn derive_path_with_date_params_implicit() { +#[cfg(feature = "chrono")] +fn derive_path_with_date_params_chrono_implicit() { mod mod_derive_path_with_date_params { use actix_web::{get, web, HttpResponse, Responder}; use chrono::{DateTime, Utc}; @@ -221,7 +222,8 @@ fn derive_path_with_date_params_implicit() { } #[test] -fn derive_path_with_date_params_explicit_ignored() { +#[cfg(feature = "time")] +fn derive_path_with_date_params_explicit_ignored_time() { mod mod_derive_path_with_date_params { use actix_web::{get, web, HttpResponse, Responder}; use serde_json::json; @@ -270,6 +272,6 @@ fn derive_path_with_date_params_explicit_ignored() { "[1].required" = r#"true"#, "Parameter required" "[1].deprecated" = r#"null"#, "Parameter deprecated" "[1].schema.type" = r#""string""#, "Parameter schema type" - "[1].schema.format" = r#"null"#, "Parameter schema format" + "[1].schema.format" = r#""date-time""#, "Parameter schema format" }; }