Skip to content

Commit 09f661f

Browse files
authored
Merge pull request #2415 from microsoft/copilot/fix-2414
fix: validation to accept lowercase status code ranges (4xx, 5xx) in OpenAPI responses
1 parent 5d46ece commit 09f661f

File tree

2 files changed

+112
-3
lines changed

2 files changed

+112
-3
lines changed

src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4+
using System;
45
using System.Linq;
56
using System.Text.RegularExpressions;
67

@@ -10,8 +11,17 @@ namespace Microsoft.OpenApi
1011
/// The validation rules for <see cref="OpenApiResponses"/>.
1112
/// </summary>
1213
[OpenApiRule]
13-
public static class OpenApiResponsesRules
14+
public static partial class OpenApiResponsesRules
1415
{
16+
/// <summary>
17+
/// The response key regex pattern for status codes and ranges.
18+
/// </summary>
19+
#if NET8_0_OR_GREATER
20+
[GeneratedRegex(@"^[1-5](?>[0-9]{2}|[xX]{2})$", RegexOptions.None, matchTimeoutMilliseconds: 100)]
21+
internal static partial Regex StatusCodeRegex();
22+
#else
23+
internal static readonly Regex StatusCodeRegex = new(@"^[1-5](?>[0-9]{2}|[xX]{2})$", RegexOptions.None, TimeSpan.FromMilliseconds(100));
24+
#endif
1525
/// <summary>
1626
/// An OpenAPI operation must contain at least one response
1727
/// </summary>
@@ -37,12 +47,18 @@ public static class OpenApiResponsesRules
3747
{
3848
context.Enter(key);
3949

40-
if (key != "default" && !Regex.IsMatch(key, "^[1-5](?>[0-9]{2}|XX)$"))
50+
if (!"default".Equals(key, StringComparison.OrdinalIgnoreCase) && !StatusCodeRegex
51+
#if NET8_0_OR_GREATER
52+
().IsMatch(key)
53+
#else
54+
.IsMatch(key)
55+
#endif
56+
)
4157
{
4258
context.CreateError(nameof(ResponsesMustBeIdentifiedByDefaultOrStatusCode),
4359
"Responses key must be 'default', an HTTP status code, " +
4460
"or one of the following strings representing a range of HTTP status codes: " +
45-
"'1XX', '2XX', '3XX', '4XX', '5XX'");
61+
"'1XX', '2XX', '3XX', '4XX', '5XX' (case insensitive)");
4662
}
4763

4864
context.Exit();
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Xunit;
7+
8+
namespace Microsoft.OpenApi.Validations.Tests
9+
{
10+
public class OpenApiResponsesValidationTests
11+
{
12+
[Theory]
13+
[InlineData("200")]
14+
[InlineData("404")]
15+
[InlineData("500")]
16+
[InlineData("1XX")]
17+
[InlineData("2XX")]
18+
[InlineData("3XX")]
19+
[InlineData("4XX")]
20+
[InlineData("5XX")]
21+
[InlineData("1xx")]
22+
[InlineData("2xx")]
23+
[InlineData("3xx")]
24+
[InlineData("4xx")]
25+
[InlineData("5xx")]
26+
[InlineData("default")]
27+
public void ValidateResponseKeyIsValid(string responseKey)
28+
{
29+
// Arrange
30+
var responses = new OpenApiResponses
31+
{
32+
[responseKey] = new OpenApiResponse { Description = "Test response" }
33+
};
34+
35+
// Act
36+
var errors = responses.Validate(ValidationRuleSet.GetDefaultRuleSet());
37+
38+
// Assert
39+
Assert.Empty(errors);
40+
}
41+
42+
[Fact]
43+
public void ValidateMixedCaseResponseKeysAreAllowed()
44+
{
45+
// Arrange - Test the specific issue case mentioned in the bug report
46+
var responses = new OpenApiResponses
47+
{
48+
["4xx"] = new OpenApiResponse { Description = "Client error" },
49+
["5XX"] = new OpenApiResponse { Description = "Server error" },
50+
["200"] = new OpenApiResponse { Description = "Success" },
51+
["default"] = new OpenApiResponse { Description = "Default response" }
52+
};
53+
54+
// Act
55+
var errors = responses.Validate(ValidationRuleSet.GetDefaultRuleSet());
56+
57+
// Assert
58+
Assert.Empty(errors);
59+
}
60+
61+
[Fact]
62+
public void ValidateLowercase4xxIsAccepted()
63+
{
64+
// Arrange - Test the specific reported issue
65+
var responses = new OpenApiResponses
66+
{
67+
["4xx"] = new OpenApiResponse { Description = "Client error" }
68+
};
69+
70+
// Act
71+
var errors = responses.Validate(ValidationRuleSet.GetDefaultRuleSet());
72+
73+
// Assert
74+
Assert.Empty(errors);
75+
}
76+
77+
[Fact]
78+
public void ValidateLowercase5xxIsAccepted()
79+
{
80+
// Arrange - Test the specific reported issue
81+
var responses = new OpenApiResponses
82+
{
83+
["5xx"] = new OpenApiResponse { Description = "Server error" }
84+
};
85+
86+
// Act
87+
var errors = responses.Validate(ValidationRuleSet.GetDefaultRuleSet());
88+
89+
// Assert
90+
Assert.Empty(errors);
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)