Files
WireMock.Net-wiremock/src/WireMock.Net.Extensions.Routing/Utils/RoutePattern.cs
Gennadii Saltyshchak be2ea67b89 Add new package WireMock.Net.Extensions.Routing which provides minimal-API-style routing for WireMock.Net (#1344)
* Add new package WireMock.Net.Extensions.Routing

* Update documentation for WireMock.Net.Extensions.Routing

* Cleanup imports

* Add header to all source files inside WireMock.Net.Extensions.Routing

* Add header to all source files inside WireMock.Net.Extensions.Routing.Tests

* Revert unintended changes

* Remove redundant build configurations

* Remove incorrect links from documentation

* Update nuget package references

* Revert unintended changes

* Migrate to AwesomeAssertions

* Remove redundant project reference

* Adjust formatting

* Migrate to primary constructor

* Refactoring: rename delegate parameter

* Abstract over JSON converter

* Replace WireMock with WireMock.Net in comments

* Move local functions to the bottom of the methods
2025-08-18 19:52:42 +02:00

108 lines
3.5 KiB
C#

// Copyright © WireMock.Net
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.RegularExpressions;
using WireMock.Net.Extensions.Routing.Extensions;
namespace WireMock.Net.Extensions.Routing.Utils;
internal static class RoutePattern
{
private static readonly Regex ArgRegex =
new(@"{(?'name'\w+)(?::(?'type'\w+))?}", RegexOptions.Compiled);
public static IDictionary<string, object> GetArgs(string pattern, string route) =>
TryGetArgs(pattern, route, out var args)
? args
: throw new InvalidOperationException(
$"Url {route} does not match route pattern {pattern}");
public static bool TryGetArgs(
string pattern, string route, [NotNullWhen(true)] out IDictionary<string, object>? args)
{
var regex = new Regex(ToRegex(pattern), RegexOptions.IgnoreCase);
var match = regex.Match(route);
if (!match.Success)
{
args = null;
return false;
}
var routeArgTypeMap = GetArgTypeMap(pattern);
args = match.Groups
.Cast<Group>()
.Where(g => g.Index > 0)
.ToDictionary(g => g.Name, g => routeArgTypeMap[g.Name].Parse(g.Value));
return true;
}
public static string ToRegex(string pattern)
{
return ArgRegex
.Replace(pattern, m => $"(?'{m.Groups["name"].Value}'{GetArgMatchingRegex(m)})")
.ToMatchFullStringRegex();
static string GetArgMatchingRegex(Match match) =>
ArgType.GetByName(match.Groups["type"].Value).GetRegex();
}
private static IDictionary<string, ArgType> GetArgTypeMap(string pattern) =>
ArgRegex
.Matches(pattern)
.ToDictionary(
m => m.Groups["name"].Value, m => ArgType.GetByName(m.Groups["type"].Value));
private abstract record ArgType
{
private ArgType(string name)
{
Name = name;
}
public string Name { get; }
public static ArgType String { get; } = new StringArgType();
public static ArgType Int { get; } = new IntArgType();
private static IReadOnlyCollection<ArgType> All { get; } =
typeof(ArgType)
.GetProperties(BindingFlags.Public | BindingFlags.Static)
.Where(p => p.PropertyType.IsAssignableTo(typeof(ArgType)))
.Select(p => p.GetValue(null))
.Cast<ArgType>()
.ToList();
private static IReadOnlyDictionary<string, ArgType> MapByName { get; } =
All.ToDictionary(x => x.Name);
public static ArgType GetByName(string name) =>
GetByNameOrDefault(name)
?? throw new InvalidOperationException($"Route argument type {name} is not found");
public static ArgType? GetByNameOrDefault(string name) =>
!string.IsNullOrEmpty(name)
? MapByName.GetValueOrDefault(name)
: String;
public abstract object Parse(string input);
public abstract string GetRegex();
private sealed record StringArgType() : ArgType("string")
{
public override object Parse(string input) => input;
public override string GetRegex() => @".*";
}
private sealed record IntArgType() : ArgType("int")
{
public override object Parse(string input) => int.Parse(input);
public override string GetRegex() => @"-?\d+";
}
}
}