Skip to content

Commit 54cec2e

Browse files
authored
fix: schema handling for collection types (#60460)
1 parent 0fe271c commit 54cec2e

File tree

4 files changed

+67
-13
lines changed

4 files changed

+67
-13
lines changed

src/OpenApi/src/Extensions/JsonNodeSchemaExtensions.cs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,24 +115,20 @@ internal static void ApplyValidationAttributes(this JsonNode schema, IEnumerable
115115
}
116116
else if (attribute is MaxLengthAttribute maxLengthAttribute)
117117
{
118-
var targetKey = schema[OpenApiSchemaKeywords.TypeKeyword]?.GetValue<string>() == "array" ? OpenApiSchemaKeywords.MaxItemsKeyword : OpenApiSchemaKeywords.MaxLengthKeyword;
119-
schema[targetKey] = maxLengthAttribute.Length;
118+
var isArray = MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes && schemaTypes.HasFlag(JsonSchemaType.Array);
119+
var key = isArray ? OpenApiSchemaKeywords.MaxItemsKeyword : OpenApiSchemaKeywords.MaxLengthKeyword;
120+
schema[key] = maxLengthAttribute.Length;
120121
}
121122
else if (attribute is MinLengthAttribute minLengthAttribute)
122123
{
123-
if (MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes &&
124-
schemaTypes.HasFlag(JsonSchemaType.Array))
125-
{
126-
schema[OpenApiSchemaKeywords.MinItemsKeyword] = minLengthAttribute.Length;
127-
}
128-
else
129-
{
130-
schema[OpenApiSchemaKeywords.MinLengthKeyword] = minLengthAttribute.Length;
131-
}
124+
var isArray = MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes && schemaTypes.HasFlag(JsonSchemaType.Array);
125+
var key = isArray ? OpenApiSchemaKeywords.MinItemsKeyword : OpenApiSchemaKeywords.MinLengthKeyword;
126+
schema[key] = minLengthAttribute.Length;
132127
}
133128
else if (attribute is LengthAttribute lengthAttribute)
134129
{
135-
var targetKeySuffix = schema[OpenApiSchemaKeywords.TypeKeyword]?.GetValue<string>() == "array" ? "Items" : "Length";
130+
var isArray = MapJsonNodeToSchemaType(schema[OpenApiSchemaKeywords.TypeKeyword]) is { } schemaTypes && schemaTypes.HasFlag(JsonSchemaType.Array);
131+
var targetKeySuffix = isArray ? "Items" : "Length";
136132
schema[$"min{targetKeySuffix}"] = lengthAttribute.MinimumLength;
137133
schema[$"max{targetKeySuffix}"] = lengthAttribute.MaximumLength;
138134
}

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.RequestBodySchemas.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,35 @@ await VerifyOpenApiDocument(builder, document =>
106106
Assert.Equal("name", property.Key);
107107
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type);
108108
Assert.Equal(5, property.Value.MinLength);
109+
Assert.Equal(10, property.Value.MaxLength);
109110
Assert.Null(property.Value.Default);
110111
},
111112
property =>
113+
{
114+
Assert.Equal("description", property.Key);
115+
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type);
116+
Assert.Equal(5, property.Value.MinLength);
117+
Assert.Equal(10, property.Value.MaxLength);
118+
},
119+
property =>
112120
{
113121
Assert.Equal("isPrivate", property.Key);
114122
Assert.Equal(JsonSchemaType.Boolean, property.Value.Type);
115123
Assert.True(property.Value.Default.GetValue<bool>());
124+
},
125+
property =>
126+
{
127+
Assert.Equal("items", property.Key);
128+
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, property.Value.Type);
129+
Assert.Equal(10, property.Value.MaxItems);
130+
},
131+
property =>
132+
{
133+
Assert.Equal("tags", property.Key);
134+
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, property.Value.Type);
135+
Assert.Equal(5, property.Value.MinItems);
136+
Assert.Equal(10, property.Value.MaxItems);
116137
});
117-
118138
});
119139
}
120140

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiSchemaService/OpenApiSchemaService.ResponseSchemas.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,34 @@ await VerifyOpenApiDocument(builder, document =>
130130
Assert.Equal("name", property.Key);
131131
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type);
132132
Assert.Equal(5, property.Value.MinLength);
133+
Assert.Equal(10, property.Value.MaxLength);
134+
Assert.Null(property.Value.Default);
135+
},
136+
property =>
137+
{
138+
Assert.Equal("description", property.Key);
139+
Assert.Equal(JsonSchemaType.String | JsonSchemaType.Null, property.Value.Type);
140+
Assert.Equal(5, property.Value.MinLength);
141+
Assert.Equal(10, property.Value.MaxLength);
133142
},
134143
property =>
135144
{
136145
Assert.Equal("isPrivate", property.Key);
137146
Assert.Equal(JsonSchemaType.Boolean, property.Value.Type);
138147
Assert.True(property.Value.Default.GetValue<bool>());
148+
},
149+
property =>
150+
{
151+
Assert.Equal("items", property.Key);
152+
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, property.Value.Type);
153+
Assert.Equal(10, property.Value.MaxItems);
154+
},
155+
property =>
156+
{
157+
Assert.Equal("tags", property.Key);
158+
Assert.Equal(JsonSchemaType.Array | JsonSchemaType.Null, property.Value.Type);
159+
Assert.Equal(5, property.Value.MinItems);
160+
Assert.Equal(10, property.Value.MaxItems);
139161
});
140162

141163
});

src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Shared/SharedTypes.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,29 @@ internal class ProjectBoard
183183
public int Id { get; set; }
184184

185185
[MinLength(5)]
186+
[MaxLength(10)]
186187
[DefaultValue(null)]
187188
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in tests.")]
188189
public string? Name { get; set; }
189190

191+
[Length(5, 10)]
192+
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in tests.")]
193+
public string? Description { get; set; }
194+
190195
[DefaultValue(true)]
191196
public required bool IsPrivate { get; set; }
197+
198+
[MaxLength(10)]
199+
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in tests.")]
200+
public IList<ProjectBoardItem>? Items { get; set; }
201+
202+
[Length(5, 10)]
203+
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in tests.")]
204+
public IEnumerable<string>? Tags { get; set; }
192205
}
206+
207+
internal sealed record ProjectBoardItem(string Name);
208+
193209
#nullable restore
194210

195211
internal class Account

0 commit comments

Comments
 (0)