Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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