diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fb4819c6..c3684a54 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ unreleased Bug fixes: +- Use object property counts for ``Length`` validators on ``Dict`` and + non-many ``Nested`` fields. - Recognize ``trace`` operations defined in view docstrings, instead of silently dropping them (:pr:`1059`). Thanks :user:`binggao1230` for the PR. diff --git a/src/apispec/ext/marshmallow/field_converter.py b/src/apispec/ext/marshmallow/field_converter.py index 6fd76a63..69256e3e 100644 --- a/src/apispec/ext/marshmallow/field_converter.py +++ b/src/apispec/ext/marshmallow/field_converter.py @@ -364,11 +364,16 @@ def field2length( ) ] - is_array = isinstance( - field, (marshmallow.fields.Nested, marshmallow.fields.List) - ) - min_attr = "minItems" if is_array else "minLength" - max_attr = "maxItems" if is_array else "maxLength" + if isinstance(field, marshmallow.fields.Dict) or ( + isinstance(field, marshmallow.fields.Nested) + and not isinstance(field, marshmallow.fields.Pluck) + and not field.schema.many + ): + min_attr, max_attr = "minProperties", "maxProperties" + elif isinstance(field, (marshmallow.fields.Nested, marshmallow.fields.List)): + min_attr, max_attr = "minItems", "maxItems" + else: + min_attr, max_attr = "minLength", "maxLength" equal_list = [ validator.equal for validator in validators if validator.equal is not None diff --git a/tests/test_ext_marshmallow_openapi.py b/tests/test_ext_marshmallow_openapi.py index 9d832002..44b2b3cd 100644 --- a/tests/test_ext_marshmallow_openapi.py +++ b/tests/test_ext_marshmallow_openapi.py @@ -676,6 +676,20 @@ class ValidationSchema(Schema): custom_list_length = CustomList( fields.Str, validate=validate.Length(min=1, max=10) ) + dict_length = fields.Dict(validate=validate.Length(min=1, max=10)) + dict_equal_length = fields.Dict(validate=validate.Length(equal=5)) + nested_length = fields.Nested( + CategorySchema, validate=validate.Length(min=1, max=10) + ) + nested_many_length = fields.Nested( + CategorySchema, many=True, validate=validate.Length(min=1, max=10) + ) + nested_inherited_many_length = fields.Nested( + CategorySchema(many=True), validate=validate.Length(min=1, max=10) + ) + pluck_length = fields.Pluck( + CategorySchema, "name", validate=validate.Length(min=1, max=10) + ) string_length = fields.Str(validate=validate.Length(min=1, max=10)) custom_field_length = CustomStringField(validate=validate.Length(min=1, max=10)) multiple_lengths = fields.Str( @@ -703,6 +717,11 @@ class ValidationSchema(Schema): ("multiple_ranges", {"minimum": 3, "maximum": 7}), ("list_length", {"minItems": 1, "maxItems": 10}), ("custom_list_length", {"minItems": 1, "maxItems": 10}), + ("dict_length", {"minProperties": 1, "maxProperties": 10}), + ("dict_equal_length", {"minProperties": 5, "maxProperties": 5}), + ("nested_length", {"minProperties": 1, "maxProperties": 10}), + ("nested_many_length", {"minItems": 1, "maxItems": 10}), + ("nested_inherited_many_length", {"minItems": 1, "maxItems": 10}), ("string_length", {"minLength": 1, "maxLength": 10}), ("custom_field_length", {"minLength": 1, "maxLength": 10}), ("multiple_lengths", {"minLength": 3, "maxLength": 7}), @@ -717,3 +736,11 @@ def test_properties(self, field, properties, spec): for attr, expected_value in properties.items(): assert attr in result assert result[attr] == expected_value + + def test_pluck_length_does_not_add_object_properties(self, spec): + spec.components.schema("Validation", schema=self.ValidationSchema) + result = get_schemas(spec)["Validation"]["properties"]["pluck_length"] + + assert result["type"] == "string" + assert "minProperties" not in result + assert "maxProperties" not in result