Skip to content

Commit 0525372

Browse files
Copilotbaywet
andcommitted
Fix regex to accept lowercase status code ranges (4xx, 5xx) and add GeneratedRegex support
Co-authored-by: baywet <[email protected]>
1 parent bd225c1 commit 0525372

File tree

2 files changed

+122
-3
lines changed

2 files changed

+122
-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 (key != "default" && !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: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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+
[Theory]
43+
[InlineData("invalid")]
44+
[InlineData("600")]
45+
[InlineData("6XX")]
46+
[InlineData("6xx")]
47+
[InlineData("1XXX")]
48+
[InlineData("XX")]
49+
[InlineData("x")]
50+
[InlineData("")]
51+
public void ValidateResponseKeyIsInvalid(string responseKey)
52+
{
53+
// Arrange
54+
var responses = new OpenApiResponses
55+
{
56+
[responseKey] = new OpenApiResponse { Description = "Test response" }
57+
};
58+
59+
// Act
60+
var errors = responses.Validate(ValidationRuleSet.GetDefaultRuleSet());
61+
62+
// Assert
63+
Assert.NotEmpty(errors);
64+
var error = errors.First();
65+
Assert.Contains("Responses key must be 'default', an HTTP status code", error.Message);
66+
Assert.Contains("case insensitive", error.Message);
67+
}
68+
69+
[Fact]
70+
public void ValidateResponsesMustContainAtLeastOneResponse()
71+
{
72+
// Arrange
73+
var responses = new OpenApiResponses();
74+
75+
// Act
76+
var errors = responses.Validate(ValidationRuleSet.GetDefaultRuleSet());
77+
78+
// Assert
79+
Assert.NotEmpty(errors);
80+
var error = errors.First();
81+
Assert.Contains("Responses must contain at least one response", error.Message);
82+
}
83+
84+
[Fact]
85+
public void ValidateMixedCaseResponseKeysAreAllowed()
86+
{
87+
// Arrange - Test the specific issue case mentioned in the bug report
88+
var responses = new OpenApiResponses
89+
{
90+
["4xx"] = new OpenApiResponse { Description = "Client error" },
91+
["5XX"] = new OpenApiResponse { Description = "Server error" },
92+
["200"] = new OpenApiResponse { Description = "Success" },
93+
["default"] = new OpenApiResponse { Description = "Default response" }
94+
};
95+
96+
// Act
97+
var errors = responses.Validate(ValidationRuleSet.GetDefaultRuleSet());
98+
99+
// Assert
100+
Assert.Empty(errors);
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)