Skip to content

Commit ce2211c

Browse files
Copilotbaywet
andauthored
fix(library): use conditional regex fallback for older frameworks
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent 6bf72b6 commit ce2211c

3 files changed

Lines changed: 12 additions & 34 deletions

File tree

‎agents.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Regex handling
2+
3+
- For fixed patterns on modern targets, use source-generated regexes with explicit match timeouts (`GeneratedRegex` under `NET8_0_OR_GREATER`).
4+
- Use conditional compilation to provide a regular `Regex` with the same pattern and an explicit match timeout for older targets. Do not duplicate regex validation with a manually maintained character scanner.
5+
- Older-runtime regex matching may still time out under load because timeouts use wall-clock time. If consumers encounter this limitation, recommend upgrading to a modern runtime that uses the source-generated implementation.
6+
- Keep shared patterns in constants and reference those constants in validation diagnostics and tests.

‎src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs‎

Lines changed: 4 additions & 30 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.Collections.Generic;
56
using System.Text.RegularExpressions;
67

@@ -20,6 +21,8 @@ public static partial class OpenApiComponentsRules
2021
#if NET8_0_OR_GREATER
2122
[GeneratedRegex(KeyPattern, RegexOptions.None, matchTimeoutMilliseconds: 100)]
2223
private static partial Regex KeyRegex();
24+
#else
25+
private static readonly Regex KeyRegex = new(KeyPattern, RegexOptions.None, TimeSpan.FromMilliseconds(100));
2326
#endif
2427

2528
/// <summary>
@@ -61,7 +64,7 @@ private static void ValidateKeys(IValidationContext context, IEnumerable<string>
6164
#if NET8_0_OR_GREATER
6265
var isValidKey = KeyRegex().IsMatch(key);
6366
#else
64-
var isValidKey = IsValidKey(key);
67+
var isValidKey = KeyRegex.IsMatch(key);
6568
#endif
6669
if (!isValidKey)
6770
{
@@ -71,34 +74,5 @@ private static void ValidateKeys(IValidationContext context, IEnumerable<string>
7174
}
7275
}
7376

74-
internal static bool IsValidKey(string key)
75-
{
76-
// Match the fixed character class without wall-clock timeouts on downlevel targets.
77-
var length = key.Length;
78-
// The regex's $ anchor also accepts a single final newline.
79-
if (length > 0 && key[length - 1] == '\n')
80-
{
81-
length--;
82-
}
83-
84-
if (length == 0)
85-
{
86-
return false;
87-
}
88-
89-
for (var i = 0; i < length; i++)
90-
{
91-
var character = key[i];
92-
if (!(character >= 'a' && character <= 'z') &&
93-
!(character >= 'A' && character <= 'Z') &&
94-
!(character >= '0' && character <= '9') &&
95-
character != '.' && character != '-' && character != '_')
96-
{
97-
return false;
98-
}
99-
}
100-
101-
return true;
102-
}
10377
}
10478
}

‎test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void ValidateKeyMustMatchRegularExpressionInComponents()
3636
Assert.False(result);
3737
Assert.NotNull(errors);
3838
var error = Assert.Single(errors);
39-
Assert.Equal(string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, "responses", @"^[a-zA-Z0-9\.\-_]+$"),
39+
Assert.Equal(string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, "responses", OpenApiComponentsRules.KeyPattern),
4040
error.Message);
4141
}
4242

@@ -67,7 +67,6 @@ public void ValidateComponentKeyPreservesRegexBehavior(string key, bool isValid)
6767
rules.Add(typeof(OpenApiComponents), OpenApiComponentsRules.KeyMustBeRegularExpression);
6868
var errors = components.Validate(rules);
6969

70-
Assert.Equal(isValid, OpenApiComponentsRules.IsValidKey(key));
7170
Assert.Equal(isValid, !errors.Any());
7271
}
7372

@@ -77,7 +76,6 @@ public void ValidateComponentKeyPreservesRegexBehavior(string key, bool isValid)
7776
public async Task LoadAsyncValidatesLongComponentKeys(bool isValid)
7877
{
7978
var key = new string('a', 1_000_000) + (isValid ? string.Empty : "!");
80-
Assert.Equal(isValid, OpenApiComponentsRules.IsValidKey(key));
8179
var json = """
8280
{"openapi":"3.1.0","info":{"title":"Test","version":"1.0"},"paths":{},"components":{"schemas":{
8381
""" + JsonSerializer.Serialize(key) + ":{\"type\":\"string\"}}}}";
@@ -95,7 +93,7 @@ public async Task LoadAsyncValidatesLongComponentKeys(bool isValid)
9593
{
9694
var error = Assert.Single(result.Diagnostic.Errors);
9795
Assert.Equal(string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr,
98-
key, "schemas", @"^[a-zA-Z0-9\.\-_]+$"), error.Message);
96+
key, "schemas", OpenApiComponentsRules.KeyPattern), error.Message);
9997
}
10098
}
10199
}

0 commit comments

Comments
 (0)