Skip to content

Commit

Permalink
Fix tuple params missing features
Browse files Browse the repository at this point in the history
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
  • Loading branch information
juhaku committed May 15, 2024
1 parent d70e5fd commit 16cfe35
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 11 deletions.
8 changes: 4 additions & 4 deletions utoipa-gen/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ impl<'p> PathAttr<'p> {
) {
let ext_params = ext_parameters.into_iter();

let (existing_params, new_params): (Vec<Parameter>, Vec<Parameter>) =
let (existing_incoming_params, new_params): (Vec<Parameter>, Vec<Parameter>) =
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);
}
}

Expand Down
15 changes: 11 additions & 4 deletions utoipa-gen/src/path/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()),
Expand All @@ -166,7 +171,8 @@ impl ToTokensDiagnostics for ParameterSchema<'_> {
object_name: "",
}),
required,
))
);
Ok(())
}
ParameterType::Parsed(inline_type) => {
let type_tree = inline_type.as_type_tree()?;
Expand All @@ -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),
Expand All @@ -184,7 +190,8 @@ impl ToTokensDiagnostics for ParameterSchema<'_> {
object_name: "",
}),
required,
))
);
Ok(())
}
}
}
Expand Down
144 changes: 144 additions & 0 deletions utoipa-gen/tests/path_derive_axum_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<String>,
}

#[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<String>, description = "Foo name", min_length = 3),
("nonnullable" = String, description = "Foo nonnullable", min_length = 3, max_length = 10),
("namequery" = Option<String>, 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<String>, query: Query<Params>) {}

#[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
);
}
8 changes: 5 additions & 3 deletions utoipa-gen/tests/path_parameter_derive_actix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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"
};
}

0 comments on commit 16cfe35

Please sign in to comment.