Skip to content

Commit

Permalink
- refactoring: all regexes use a generate attribute
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent Biret <[email protected]>
  • Loading branch information
baywet committed Nov 28, 2023
1 parent 38c194e commit 07b9edc
Show file tree
Hide file tree
Showing 14 changed files with 81 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,8 @@ public IEnumerable<string> GetContentTypes(IEnumerable<string> searchTypes)
.ThenByDescending(static x => x.Key, StringComparer.OrdinalIgnoreCase)
.Select(static x => x.Key);
}
[GeneratedRegex(@"[^/+]+\+", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline, 2000)]
[GeneratedRegex(@"[^/+]+\+", RegexOptions.IgnoreCase | RegexOptions.Singleline, 2000)]
private static partial Regex vendorStripRegex();
private readonly static Regex vendorStripRegexInstance = vendorStripRegex();
private bool TryGetMimeType(string mimeType, out float result)
{
if (string.IsNullOrEmpty(mimeType))
Expand All @@ -140,10 +139,10 @@ private bool TryGetMimeType(string mimeType, out float result)

return _mimeTypes.TryGetValue(mimeType, out result) || // vendor and parameters
mimeType.Contains('+', StringComparison.OrdinalIgnoreCase) &&
_mimeTypes.TryGetValue(vendorStripRegexInstance.Replace(mimeType, string.Empty), out result) || // no vendor with parameters
_mimeTypes.TryGetValue(vendorStripRegex().Replace(mimeType, string.Empty), out result) || // no vendor with parameters
mimeType.Contains(';', StringComparison.OrdinalIgnoreCase) &&
mimeType.Split(';', StringSplitOptions.RemoveEmptyEntries)[0] is string noParametersMimeType &&
(_mimeTypes.TryGetValue(noParametersMimeType, out result) || // vendor without parameters
_mimeTypes.TryGetValue(vendorStripRegexInstance.Replace(noParametersMimeType, string.Empty), out result)); // no vendor without parameters
_mimeTypes.TryGetValue(vendorStripRegex().Replace(noParametersMimeType, string.Empty), out result)); // no vendor without parameters
}
}
6 changes: 1 addition & 5 deletions src/Kiota.Builder/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using System;

namespace Kiota.Builder;
namespace Kiota.Builder;
public static class Constants
{
public const string DefaultOpenApiLabel = "default";
public const string RawUrlParameterName = "request-raw-url";
public static readonly TimeSpan DefaultRegexTimeout = TimeSpan.FromMilliseconds(100);
public const string TempDirectoryName = "kiota";
}
5 changes: 2 additions & 3 deletions src/Kiota.Builder/EqualityComparers/OpenApiServerComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ namespace Kiota.Builder.EqualityComparers;

internal sealed partial class OpenApiServerComparer : IEqualityComparer<OpenApiServer>
{
private static readonly Regex _protocolCleanupRegex = GetCleanupRegex();
[GeneratedRegex("^https?://", RegexOptions.IgnoreCase | RegexOptions.Compiled, 200)]
private static partial Regex GetCleanupRegex();
private static partial Regex protocolCleanupRegex();
public bool Equals(OpenApiServer? x, OpenApiServer? y)
{
return x != null && y != null && GetHashCode(x) == GetHashCode(y);
Expand All @@ -19,6 +18,6 @@ public int GetHashCode([DisallowNull] OpenApiServer obj)
{
if (string.IsNullOrEmpty(obj?.Url))
return 0;
return _protocolCleanupRegex.Replace(obj.Url, string.Empty).GetHashCode(StringComparison.OrdinalIgnoreCase);
return protocolCleanupRegex().Replace(obj.Url, string.Empty).GetHashCode(StringComparison.OrdinalIgnoreCase);
}
}
7 changes: 4 additions & 3 deletions src/Kiota.Builder/Extensions/OpenApiOperationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
using Microsoft.OpenApi.Models;

namespace Kiota.Builder.Extensions;
public static class OpenApiOperationExtensions
public static partial class OpenApiOperationExtensions
{
internal static readonly HashSet<string> SuccessCodes = new(StringComparer.OrdinalIgnoreCase) { "200", "201", "202", "203", "206", "2XX" }; //204 excluded as it won't have a schema
[GeneratedRegex(@"[^/]+\+", RegexOptions.IgnoreCase | RegexOptions.Singleline, 100)]
private static partial Regex vendorSpecificCleanup();
/// <summary>
/// cleans application/vnd.github.mercy-preview+json to application/json
/// </summary>
private static readonly Regex vendorSpecificCleanup = new(@"[^/]+\+", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
internal static OpenApiSchema? GetResponseSchema(this OpenApiOperation operation, StructuredMimeTypesCollection structuredMimeTypes)
{
ArgumentNullException.ThrowIfNull(operation);
Expand Down Expand Up @@ -48,7 +49,7 @@ internal static IEnumerable<OpenApiSchema> GetValidSchemas(this IDictionary<stri
return source
.Where(static c => !string.IsNullOrEmpty(c.Key))
.Select(static c => (Key: c.Key.Split(';', StringSplitOptions.RemoveEmptyEntries)[0], c.Value))
.Where(c => structuredMimeTypes.Contains(c.Key) || structuredMimeTypes.Contains(vendorSpecificCleanup.Replace(c.Key, string.Empty)))
.Where(c => structuredMimeTypes.Contains(c.Key) || structuredMimeTypes.Contains(vendorSpecificCleanup().Replace(c.Key, string.Empty)))
.Select(static co => co.Value.Schema)
.Where(static s => s is not null);
}
Expand Down
48 changes: 28 additions & 20 deletions src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using Microsoft.OpenApi.Services;

namespace Kiota.Builder.Extensions;
public static class OpenApiUrlTreeNodeExtensions
public static partial class OpenApiUrlTreeNodeExtensions
{
private static string GetDotIfBothNotNullOfEmpty(string x, string y) => string.IsNullOrEmpty(x) || string.IsNullOrEmpty(y) ? string.Empty : ".";
private static readonly Func<string, string> replaceSingleParameterSegmentByItem =
Expand Down Expand Up @@ -38,9 +38,11 @@ public static string GetNodeNamespaceFromPath(this OpenApiUrlTreeNode currentNod
return currentNode.Path.GetNamespaceFromPath(prefix);
}
//{id}, name(idParam={id}), name(idParam='{id}'), name(idParam='{id}',idParam2='{id2}')
private static readonly Regex PathParametersRegex = new(@"(?:\w+)?=?'?\{(?<paramName>\w+)\}'?,?", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
[GeneratedRegex(@"(?:\w+)?=?'?\{(?<paramName>\w+)\}'?,?", RegexOptions.Singleline, 100)]
private static partial Regex PathParametersRegex();
// microsoft.graph.getRoleScopeTagsByIds(ids=@ids)
private static readonly Regex AtSignPathParameterRegex = new(@"=@(\w+)", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
[GeneratedRegex(@"=@(\w+)", RegexOptions.Singleline, 100)]
private static partial Regex AtSignPathParameterRegex();
private const char RequestParametersChar = '{';
private const char RequestParametersEndChar = '}';
private const string RequestParametersSectionChar = "(";
Expand All @@ -52,8 +54,8 @@ private static string CleanupParametersFromPath(string pathSegment)
{
if (string.IsNullOrEmpty(pathSegment))
return pathSegment;
return PathParametersRegex.Replace(
AtSignPathParameterRegex.Replace(pathSegment, "={$1}"),
return PathParametersRegex().Replace(
AtSignPathParameterRegex().Replace(pathSegment, "={$1}"),
requestParametersMatchEvaluator)
.Replace(RequestParametersSectionEndChar, string.Empty, StringComparison.OrdinalIgnoreCase)
.Replace(RequestParametersSectionChar, string.Empty, StringComparison.OrdinalIgnoreCase);
Expand All @@ -79,7 +81,8 @@ public static IEnumerable<OpenApiParameter> GetPathParametersForCurrentSegment(t
return Enumerable.Empty<OpenApiParameter>();
}
private const char PathNameSeparator = '\\';
private static readonly Regex idClassNameCleanup = new(@"-?id\d?}?$", RegexOptions.Compiled | RegexOptions.IgnoreCase, Constants.DefaultRegexTimeout);
[GeneratedRegex(@"-?id\d?}?$", RegexOptions.Singleline | RegexOptions.IgnoreCase, 100)]
private static partial Regex idClassNameCleanup();
///<summary>
/// Returns the class name for the node with more or less precision depending on the provided arguments
///</summary>
Expand Down Expand Up @@ -109,11 +112,11 @@ private static string GetSegmentName(this OpenApiUrlTreeNode currentNode, Struct
CleanupParametersFromPath(currentNode.Segment)?.ReplaceValueIdentifier()));
if (!string.IsNullOrEmpty(rawClassName) && string.IsNullOrEmpty(referenceName))
{
if (stripExtensionForIndexersTestRegex.IsMatch(rawClassName))
rawClassName = stripExtensionForIndexersRegex.Replace(rawClassName, string.Empty);
if ((currentNode?.DoesNodeBelongToItemSubnamespace() ?? false) && idClassNameCleanup.IsMatch(rawClassName))
if (stripExtensionForIndexersTestRegex().IsMatch(rawClassName))
rawClassName = stripExtensionForIndexersRegex().Replace(rawClassName, string.Empty);
if ((currentNode?.DoesNodeBelongToItemSubnamespace() ?? false) && idClassNameCleanup().IsMatch(rawClassName))
{
rawClassName = idClassNameCleanup.Replace(rawClassName, string.Empty);
rawClassName = idClassNameCleanup().Replace(rawClassName, string.Empty);
if (WithKeyword.Equals(rawClassName, StringComparison.Ordinal)) // in case the single parameter doesn't follow {classname-id} we get the previous segment
rawClassName = currentNode.Path
.Split(PathNameSeparator, StringSplitOptions.RemoveEmptyEntries)
Expand All @@ -139,8 +142,9 @@ private static string GetSegmentName(this OpenApiUrlTreeNode currentNode, Struct
"yml",
"txt",
};
private static readonly Regex descriptionCleanupRegex = new(@"[\r\n\t]", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
public static string CleanupDescription(this string? description) => string.IsNullOrEmpty(description) ? string.Empty : descriptionCleanupRegex.Replace(description, string.Empty);
[GeneratedRegex(@"[\r\n\t]", RegexOptions.Singleline, 100)]
private static partial Regex descriptionCleanupRegex();
public static string CleanupDescription(this string? description) => string.IsNullOrEmpty(description) ? string.Empty : descriptionCleanupRegex().Replace(description, string.Empty);
public static string GetPathItemDescription(this OpenApiUrlTreeNode currentNode, string label, string? defaultValue = default)
{
if (currentNode != null && !string.IsNullOrEmpty(label) && currentNode.PathItems.TryGetValue(label, out var pathItem))
Expand All @@ -158,7 +162,7 @@ public static bool IsPathSegmentWithSingleSimpleParameter(this OpenApiUrlTreeNod
private static bool IsPathSegmentWithSingleSimpleParameter(this string currentSegment)
{
if (string.IsNullOrEmpty(currentSegment)) return false;
var segmentWithoutExtension = stripExtensionForIndexersRegex.Replace(currentSegment, string.Empty);
var segmentWithoutExtension = stripExtensionForIndexersRegex().Replace(currentSegment, string.Empty);

return segmentWithoutExtension.StartsWith(RequestParametersChar) &&
segmentWithoutExtension.EndsWith(RequestParametersEndChar) &&
Expand All @@ -170,8 +174,10 @@ private static bool IsPathSegmentWithNumberOfParameters(this string currentSegme

return eval(currentSegment.Where(static x => x == RequestParametersChar));
}
private static readonly Regex stripExtensionForIndexersRegex = new(@"\.(?:json|yaml|yml|csv|txt)$", RegexOptions.Compiled, Constants.DefaultRegexTimeout); // so {param-name}.json is considered as indexer
private static readonly Regex stripExtensionForIndexersTestRegex = new(@"\{\w+\}\.(?:json|yaml|yml|csv|txt)$", RegexOptions.Compiled, Constants.DefaultRegexTimeout); // so {param-name}.json is considered as indexer
[GeneratedRegex(@"\.(?:json|yaml|yml|csv|txt)$", RegexOptions.Singleline, 100)]
private static partial Regex stripExtensionForIndexersRegex(); // so {param-name}.json is considered as indexer
[GeneratedRegex(@"\{\w+\}\.(?:json|yaml|yml|csv|txt)$", RegexOptions.Singleline, 100)]
private static partial Regex stripExtensionForIndexersTestRegex(); // so {param-name}.json is considered as indexer
public static bool IsComplexPathMultipleParameters(this OpenApiUrlTreeNode currentNode) =>
(currentNode?.Segment?.IsPathSegmentWithNumberOfParameters(static x => x.Any()) ?? false) && !currentNode.IsPathSegmentWithSingleSimpleParameter();
public static string GetUrlTemplate(this OpenApiUrlTreeNode currentNode)
Expand Down Expand Up @@ -209,30 +215,32 @@ public static string GetUrlTemplate(this OpenApiUrlTreeNode currentNode)
SanitizePathParameterNamesForUrlTemplate(currentNode.Path.Replace('\\', '/'), pathReservedPathParametersIds) +
queryStringParameters;
}
private static readonly Regex pathParamMatcher = new(@"{(?<paramname>[^}]+)}", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
[GeneratedRegex(@"{(?<paramname>[^}]+)}", RegexOptions.Singleline, 100)]
private static partial Regex pathParamMatcher();
private static string SanitizePathParameterNamesForUrlTemplate(string original, HashSet<string> reservedParameterNames)
{
if (string.IsNullOrEmpty(original) || !original.Contains('{', StringComparison.OrdinalIgnoreCase)) return original;
var parameters = pathParamMatcher.Matches(original);
var parameters = pathParamMatcher().Matches(original);
foreach (var value in parameters.Select(x => x.Groups["paramname"].Value))
original = original.Replace(value, (reservedParameterNames.Contains(value) ? "+" : string.Empty) + value.SanitizeParameterNameForUrlTemplate(), StringComparison.Ordinal);
return original;
}
public static string SanitizeParameterNameForUrlTemplate(this string original)
{
if (string.IsNullOrEmpty(original)) return original;
return Uri.EscapeDataString(stripExtensionForIndexersRegex
return Uri.EscapeDataString(stripExtensionForIndexersRegex()
.Replace(original, string.Empty) // {param-name}.json becomes {param-name}
.TrimStart('{')
.TrimEnd('}'))
.Replace("-", "%2D", StringComparison.OrdinalIgnoreCase)
.Replace(".", "%2E", StringComparison.OrdinalIgnoreCase)
.Replace("~", "%7E", StringComparison.OrdinalIgnoreCase);// - . ~ are invalid uri template character but don't get encoded by Uri.EscapeDataString
}
private static readonly Regex removePctEncodedCharacters = new(@"%[0-9A-F]{2}", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
[GeneratedRegex(@"%[0-9A-F]{2}", RegexOptions.Singleline, 100)]
private static partial Regex removePctEncodedCharacters();
public static string SanitizeParameterNameForCodeSymbols(this string original, string replaceEncodedCharactersWith = "")
{
if (string.IsNullOrEmpty(original)) return original;
return removePctEncodedCharacters.Replace(original.ToCamelCase('-', '.', '~').SanitizeParameterNameForUrlTemplate(), replaceEncodedCharactersWith);
return removePctEncodedCharacters().Replace(original.ToCamelCase('-', '.', '~').SanitizeParameterNameForUrlTemplate(), replaceEncodedCharactersWith);
}
}
13 changes: 7 additions & 6 deletions src/Kiota.Builder/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using System.Threading;

namespace Kiota.Builder.Extensions;
public static class StringExtensions
public static partial class StringExtensions
{
private const int MaxStackLimit = 1024;

Expand Down Expand Up @@ -149,21 +149,22 @@ public static string ReplaceDotsWithSlashInNamespaces(this string? namespaced)
///<summary>
/// Cleanup regex that removes all special characters from ASCII 0-127
///</summary>
private static readonly Regex propertyCleanupRegex = new(@"[""\s!#$%&'()*,./:;<=>?@\[\]\\^`’{}|~-](?<followingLetter>\w)?", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
[GeneratedRegex(@"[""\s!#$%&'()*,./:;<=>?@\[\]\\^`’{}|~-](?<followingLetter>\w)?", RegexOptions.Singleline, 100)]
private static partial Regex propertyCleanupRegex();
private const string CleanupGroupName = "followingLetter";
public static string CleanupSymbolName(this string? original)
{
if (string.IsNullOrEmpty(original)) return string.Empty;

string result = NormalizeSymbolsBeforeCleanup(original);

result = propertyCleanupRegex.Replace(result,
result = propertyCleanupRegex().Replace(result,
static x => x.Groups.Keys.Contains(CleanupGroupName) ?
x.Groups[CleanupGroupName].Value.ToFirstCharacterUpperCase() :
string.Empty); //strip out any invalid characters, and replace any following one by its uppercase version

if (result.Length != 0 && int.TryParse(result.AsSpan(0, 1), out var _)) // in most languages a number or starting with a number is not a valid symbol name
result = NumbersSpellingRegex.Replace(result, static x => x.Groups["number"]
result = NumbersSpellingRegex().Replace(result, static x => x.Groups["number"]
.Value
.Select(static x => SpelledOutNumbers[x])
.Aggregate(static (z, y) => z + y));
Expand All @@ -180,8 +181,8 @@ public static string CleanupSymbolName(this string? original)

return result;
}

private static readonly Regex NumbersSpellingRegex = new(@"^(?<number>\d+)", RegexOptions.Compiled, Constants.DefaultRegexTimeout);
[GeneratedRegex(@"^(?<number>\d+)", RegexOptions.Singleline, 100)]
private static partial Regex NumbersSpellingRegex();
private static readonly Dictionary<char, string> SpelledOutNumbers = new() {
{'0', "Zero"},
{'1', "One"},
Expand Down
7 changes: 3 additions & 4 deletions src/Kiota.Builder/KiotaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,9 @@ private async Task UpdateLockFile(CancellationToken cancellationToken)
await lockManagementService.WriteLockFileAsync(config.OutputPath, configurationLock, cancellationToken).ConfigureAwait(false);
}
private static readonly GlobComparer globComparer = new();
[GeneratedRegex(@"([\/\\])\{[\w\d-]+\}([\/\\])", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline, 2000)]
private static partial Regex MultiIndexSameLevelCleanupRegexTemplate();
private static readonly Regex MultiIndexSameLevelCleanupRegex = MultiIndexSameLevelCleanupRegexTemplate();
private static string ReplaceAllIndexesWithWildcard(string path, uint depth = 10) => depth == 0 ? path : ReplaceAllIndexesWithWildcard(MultiIndexSameLevelCleanupRegex.Replace(path, "$1{*}$2"), depth - 1); // the bound needs to be greedy to avoid replacing anything else than single path parameters
[GeneratedRegex(@"([\/\\])\{[\w\d-]+\}([\/\\])", RegexOptions.IgnoreCase | RegexOptions.Singleline, 2000)]
private static partial Regex MultiIndexSameLevelCleanupRegex();
private static string ReplaceAllIndexesWithWildcard(string path, uint depth = 10) => depth == 0 ? path : ReplaceAllIndexesWithWildcard(MultiIndexSameLevelCleanupRegex().Replace(path, "$1{*}$2"), depth - 1); // the bound needs to be greedy to avoid replacing anything else than single path parameters
private static Dictionary<Glob, HashSet<OperationType>> GetFilterPatternsFromConfiguration(HashSet<string> configPatterns)
{
return configPatterns.Select(static x =>
Expand Down
2 changes: 1 addition & 1 deletion src/Kiota.Builder/Refiners/RubyRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ parameterType.TypeDefinition is CodeClass parameterTypeClass &&
}
CrawlTree(currentElement, x => UpdateReferencesToDisambiguatedClasses(x, classesToUpdate, suffix));
}
[GeneratedRegex(@"\\.(<letter>\\w)", RegexOptions.IgnoreCase)]
[GeneratedRegex(@"\\.(<letter>\\w)", RegexOptions.IgnoreCase | RegexOptions.Singleline, 100)]
private static partial Regex CapitalizedFirstLetterAfterDot();
private static void FlattenModelsNamespaces(CodeElement currentElement, CodeNamespace modelsNS)
{
Expand Down
Loading

0 comments on commit 07b9edc

Please sign in to comment.