Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ private static void ApplyMinLengthAttribute(OpenApiSchema schema, MinLengthAttri
{
schema.MinItems = minLengthAttribute.Length;
}
else if (schema.Type is { } objectType && objectType.HasFlag(JsonSchemaTypes.Object))
{
schema.MinProperties = minLengthAttribute.Length;
}
else
{
schema.MinLength = minLengthAttribute.Length;
Expand All @@ -209,6 +213,10 @@ private static void ApplyMaxLengthAttribute(OpenApiSchema schema, MaxLengthAttri
{
schema.MaxItems = maxLengthAttribute.Length;
}
else if (schema.Type is { } objectType && objectType.HasFlag(JsonSchemaTypes.Object))
{
schema.MaxProperties = maxLengthAttribute.Length;
}
else
{
schema.MaxLength = maxLengthAttribute.Length;
Expand All @@ -234,6 +242,11 @@ private static void ApplyLengthAttribute(OpenApiSchema schema, LengthAttribute l
schema.MinItems = lengthAttribute.MinimumLength;
schema.MaxItems = lengthAttribute.MaximumLength;
}
else if (schema.Type is { } objectType && objectType.HasFlag(JsonSchemaTypes.Object))
{
schema.MinProperties = lengthAttribute.MinimumLength;
schema.MaxProperties = lengthAttribute.MaximumLength;
}
else
{
schema.MinLength = lengthAttribute.MinimumLength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,16 @@ public void GenerateSchema_SetsValidationProperties_IfComplexTypeHasValidationAt
Assert.Equal(3, schema.Properties["StringWithMinMaxLength"].MaxLength);
Assert.Equal(1, schema.Properties["ArrayWithMinMaxLength"].MinItems);
Assert.Equal(3, schema.Properties["ArrayWithMinMaxLength"].MaxItems);
Assert.Equal(1, schema.Properties["DictionaryWithMinMaxLength"].MinProperties);
Assert.Equal(3, schema.Properties["DictionaryWithMinMaxLength"].MaxProperties);
Assert.Null(schema.Properties["DictionaryWithMinMaxLength"].MinLength);
Assert.Null(schema.Properties["DictionaryWithMinMaxLength"].MaxLength);
Assert.Equal(1, schema.Properties["StringWithLength"].MinLength);
Assert.Equal(3, schema.Properties["StringWithLength"].MaxLength);
Assert.Equal(1, schema.Properties["ArrayWithLength"].MinItems);
Assert.Equal(3, schema.Properties["ArrayWithLength"].MaxItems);
Assert.Equal(1, schema.Properties["DictionaryWithLength"].MinProperties);
Assert.Equal(3, schema.Properties["DictionaryWithLength"].MaxProperties);
Assert.NotNull(schema.Properties["IntWithExclusiveRange"].ExclusiveMinimum);
Assert.NotNull(schema.Properties["IntWithExclusiveRange"].ExclusiveMaximum);
Assert.Equal("byte", schema.Properties["StringWithBase64"].Format);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,10 +373,16 @@ public void GenerateSchema_SetsValidationProperties_IfComplexTypeHasValidationAt
Assert.Equal(3, schema.Properties["StringWithMinMaxLength"].MaxLength);
Assert.Equal(1, schema.Properties["ArrayWithMinMaxLength"].MinItems);
Assert.Equal(3, schema.Properties["ArrayWithMinMaxLength"].MaxItems);
Assert.Equal(1, schema.Properties["DictionaryWithMinMaxLength"].MinProperties);
Assert.Equal(3, schema.Properties["DictionaryWithMinMaxLength"].MaxProperties);
Assert.Null(schema.Properties["DictionaryWithMinMaxLength"].MinLength);
Assert.Null(schema.Properties["DictionaryWithMinMaxLength"].MaxLength);
Assert.Equal(1, schema.Properties["StringWithLength"].MinLength);
Assert.Equal(3, schema.Properties["StringWithLength"].MaxLength);
Assert.Equal(1, schema.Properties["ArrayWithLength"].MinItems);
Assert.Equal(3, schema.Properties["ArrayWithLength"].MaxItems);
Assert.Equal(1, schema.Properties["DictionaryWithLength"].MinProperties);
Assert.Equal(3, schema.Properties["DictionaryWithLength"].MaxProperties);
Assert.NotNull(schema.Properties["IntWithExclusiveRange"].ExclusiveMinimum);
Assert.NotNull(schema.Properties["IntWithExclusiveRange"].ExclusiveMaximum);
Assert.Equal("byte", schema.Properties["StringWithBase64"].Format);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Microsoft.OpenApi;

Expand Down Expand Up @@ -149,6 +150,189 @@ public static void ApplyValidationAttributes_Handles_DataTypeAttribute_CustomDat
Assert.Equal(customDataType, schema.Format);
}

[Fact]
public static void ApplyValidationAttributes_MinLength_On_Dictionary_Maps_To_MinProperties()
{
// Arrange — dictionary schema is represented as an Object with AdditionalProperties
Comment thread
martincostello marked this conversation as resolved.
Outdated
var schema = new OpenApiSchema
{
Type = JsonSchemaType.Object,
AdditionalPropertiesAllowed = true,
AdditionalProperties = new OpenApiSchema { Type = JsonSchemaType.String },
};

// Act
schema.ApplyValidationAttributes([new MinLengthAttribute(1)]);

// Assert
Assert.Equal(1, schema.MinProperties);
Assert.Null(schema.MinLength);
Assert.Null(schema.MinItems);
}

[Fact]
public static void ApplyValidationAttributes_MaxLength_On_Dictionary_Maps_To_MaxProperties()
{
// Arrange
var schema = new OpenApiSchema
{
Type = JsonSchemaType.Object,
AdditionalPropertiesAllowed = true,
AdditionalProperties = new OpenApiSchema { Type = JsonSchemaType.String },
};

// Act
schema.ApplyValidationAttributes([new MaxLengthAttribute(10)]);

// Assert
Assert.Equal(10, schema.MaxProperties);
Assert.Null(schema.MaxLength);
Assert.Null(schema.MaxItems);
}

[Fact]
public static void ApplyValidationAttributes_Length_On_Dictionary_Maps_To_Min_And_MaxProperties()
{
// Arrange
var schema = new OpenApiSchema
{
Type = JsonSchemaType.Object,
AdditionalPropertiesAllowed = true,
AdditionalProperties = new OpenApiSchema { Type = JsonSchemaType.String },
};

// Act
schema.ApplyValidationAttributes([new LengthAttribute(2, 5)]);

// Assert
Assert.Equal(2, schema.MinProperties);
Assert.Equal(5, schema.MaxProperties);
Assert.Null(schema.MinLength);
Assert.Null(schema.MaxLength);
Assert.Null(schema.MinItems);
Assert.Null(schema.MaxItems);
}

[Fact]
public static void ApplyValidationAttributes_MinLength_On_String_Still_Maps_To_MinLength()
{
// Arrange — regression guard for the existing string path
var schema = new OpenApiSchema { Type = JsonSchemaType.String };

// Act
schema.ApplyValidationAttributes([new MinLengthAttribute(3)]);

// Assert
Assert.Equal(3, schema.MinLength);
Assert.Null(schema.MinProperties);
Assert.Null(schema.MinItems);
}

[Fact]
public static void ApplyValidationAttributes_MinLength_On_Array_Still_Maps_To_MinItems()
{
// Arrange — regression guard for the existing array path
var schema = new OpenApiSchema { Type = JsonSchemaType.Array };

// Act
schema.ApplyValidationAttributes([new MinLengthAttribute(3)]);

// Assert
Assert.Equal(3, schema.MinItems);
Assert.Null(schema.MinProperties);
Assert.Null(schema.MinLength);
}

[Fact]
public static void ApplyValidationAttributes_MaxLength_On_String_Still_Maps_To_MaxLength()
{
var schema = new OpenApiSchema { Type = JsonSchemaType.String };

schema.ApplyValidationAttributes([new MaxLengthAttribute(5)]);

Assert.Equal(5, schema.MaxLength);
Assert.Null(schema.MaxProperties);
Assert.Null(schema.MaxItems);
}

[Fact]
public static void ApplyValidationAttributes_MaxLength_On_Array_Still_Maps_To_MaxItems()
{
var schema = new OpenApiSchema { Type = JsonSchemaType.Array };

schema.ApplyValidationAttributes([new MaxLengthAttribute(5)]);

Assert.Equal(5, schema.MaxItems);
Assert.Null(schema.MaxProperties);
Assert.Null(schema.MaxLength);
}

[Fact]
public static void ApplyValidationAttributes_Length_On_String_Still_Maps_To_MinAndMaxLength()
{
var schema = new OpenApiSchema { Type = JsonSchemaType.String };

schema.ApplyValidationAttributes([new LengthAttribute(1, 5)]);

Assert.Equal(1, schema.MinLength);
Assert.Equal(5, schema.MaxLength);
Assert.Null(schema.MinProperties);
Assert.Null(schema.MaxProperties);
}

[Fact]
public static void ApplyValidationAttributes_Length_On_Array_Still_Maps_To_MinAndMaxItems()
{
var schema = new OpenApiSchema { Type = JsonSchemaType.Array };

schema.ApplyValidationAttributes([new LengthAttribute(1, 5)]);

Assert.Equal(1, schema.MinItems);
Assert.Equal(5, schema.MaxItems);
Assert.Null(schema.MinProperties);
Assert.Null(schema.MaxProperties);
}

[Fact]
public static void ApplyValidationAttributes_MinLength_On_EnumKeyedDictionarySchema_Maps_To_MinProperties()
{
// Enum-keyed dictionaries are emitted as Object with known Properties and
// AdditionalPropertiesAllowed = false (see SchemaGenerator.CreateDictionarySchema).
// The fix must still route MinLength to MinProperties in this shape.
var schema = new OpenApiSchema
{
Type = JsonSchemaType.Object,
Properties = new Dictionary<string, IOpenApiSchema>
{
["Foo"] = new OpenApiSchema { Type = JsonSchemaType.String },
["Bar"] = new OpenApiSchema { Type = JsonSchemaType.String },
},
AdditionalPropertiesAllowed = false,
};

schema.ApplyValidationAttributes([new MinLengthAttribute(1)]);

Assert.Equal(1, schema.MinProperties);
Assert.Null(schema.MinLength);
}

[Fact]
public static void ApplyValidationAttributes_MinLength_On_NullableObjectSchema_Maps_To_MinProperties()
{
// A nullable dictionary has Type = Object | Null. HasFlag(Object) must still route to MinProperties.
var schema = new OpenApiSchema
{
Type = JsonSchemaType.Object | JsonSchemaType.Null,
AdditionalPropertiesAllowed = true,
AdditionalProperties = new OpenApiSchema { Type = JsonSchemaType.String },
};

schema.ApplyValidationAttributes([new MinLengthAttribute(2)]);

Assert.Equal(2, schema.MinProperties);
Assert.Null(schema.MinLength);
}

private sealed class CultureSwitcher : IDisposable
{
private readonly CultureInfo _previous;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Swashbuckle.AspNetCore.TestSupport;
Expand All @@ -14,12 +15,18 @@ public class TypeWithValidationAttributes
[MinLength(1), MaxLength(3)]
public string[] ArrayWithMinMaxLength { get; set; }

[MinLength(1), MaxLength(3)]
public IReadOnlyDictionary<string, string> DictionaryWithMinMaxLength { get; set; }

[Length(1, 3)]
public string StringWithLength { get; set; }

[Length(1, 3)]
public string[] ArrayWithLength { get; set; }

[Length(1, 3)]
public Dictionary<string, string> DictionaryWithLength { get; set; }

[Range(1, 10, MinimumIsExclusive = true, MaximumIsExclusive = true)]
public int IntWithExclusiveRange { get; set; }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;

Expand All @@ -13,10 +14,14 @@ public class TypeWithValidationAttributesViaMetadataType

public string[] ArrayWithMinMaxLength { get; set; }

public IReadOnlyDictionary<string, string> DictionaryWithMinMaxLength { get; set; }
Comment thread
martincostello marked this conversation as resolved.
Outdated

public string StringWithLength { get; set; }

public string[] ArrayWithLength { get; set; }

public Dictionary<string, string> DictionaryWithLength { get; set; }
Comment thread
martincostello marked this conversation as resolved.
Outdated

public string StringWithBase64 { get; set; }

public double IntWithExclusiveRange { get; set; }
Expand Down Expand Up @@ -49,12 +54,18 @@ public class MetadataType
[MinLength(1), MaxLength(3)]
public string[] ArrayWithMinMaxLength { get; set; }

[MinLength(1), MaxLength(3)]
public IReadOnlyDictionary<string, string> DictionaryWithMinMaxLength { get; set; }

[Length(1, 3)]
public string StringWithLength { get; set; }

[Length(1, 3)]
public string[] ArrayWithLength { get; set; }

[Length(1, 3)]
public Dictionary<string, string> DictionaryWithLength { get; set; }

[Range(1, 10, MinimumIsExclusive = true, MaximumIsExclusive = true)]
public int IntWithExclusiveRange { get; set; }

Expand Down