Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Utilities/RouteTemplateUtility.cs (65 lines of code) (raw):

namespace Amazon.Lambda.TestTool.Utilities; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Routing.Template; /// <summary> /// Provides utility methods for working with route templates and extracting path parameters. /// </summary> public static class RouteTemplateUtility { private const string TemporaryPrefix = "__aws_param__"; /// <summary> /// Extracts path parameters from an actual path based on a route template. /// </summary> /// <param name="routeTemplate">The route template to match against.</param> /// <param name="actualPath">The actual path to extract parameters from.</param> /// <returns>A dictionary of extracted path parameters and their values.</returns> /// <example> /// Using this method: /// <code> /// var routeTemplate = "/users/{id}/orders/{orderId}"; /// var actualPath = "/users/123/orders/456"; /// var parameters = RouteTemplateUtility.ExtractPathParameters(routeTemplate, actualPath); /// // parameters will contain: { {"id", "123"}, {"orderId", "456"} } /// </code> /// </example> public static Dictionary<string, string> ExtractPathParameters(string routeTemplate, string actualPath) { // Preprocess the route template to convert from .net style format to aws routeTemplate = PreprocessRouteTemplate(routeTemplate); var template = TemplateParser.Parse(routeTemplate); var matcher = new TemplateMatcher(template, new RouteValueDictionary()); var routeValues = new RouteValueDictionary(); if (matcher.TryMatch(actualPath, routeValues)) { var result = new Dictionary<string, string>(); foreach (var param in template.Parameters) { if (routeValues.TryGetValue(param.Name!, out var value)) { var stringValue = value?.ToString() ?? string.Empty; // For catch-all parameters, remove the leading slash if present if (param.IsCatchAll) { stringValue = stringValue.TrimStart('/'); } // Restore original parameter name var originalParamName = RestoreOriginalParamName(param.Name!); result[originalParamName] = stringValue; } } return result; } return new Dictionary<string, string>(); } /// <summary> /// Preprocesses a route template to make it compatible with ASP.NET Core's TemplateMatcher. /// </summary> /// <param name="template">The original route template, potentially in AWS API Gateway format.</param> /// <returns>A preprocessed route template compatible with ASP.NET Core's TemplateMatcher.</returns> /// <remarks> /// This method performs two main transformations: /// 1. Converts AWS-style {proxy+} to ASP.NET Core style {*proxy} /// 2. Handles AWS ignoring constraignts by temporarily renaming parameters /// (e.g., {abc:int} becomes {__aws_param__abc__int}) /// </remarks> private static string PreprocessRouteTemplate(string template) { // Convert AWS-style {proxy+} to ASP.NET Core style {*proxy} template = Regex.Replace(template, @"\{(\w+)\+\}", "{*$1}"); // AWS allows you to name a route as {abc:int}, which gets parsed by AWS as the path // variable abc:int. However, .NET template matcher, thinks {abc:int} means // abc variable with the int constraint. So we are converting variables that have // contstraints to a different name temporarily, so template matcher doesn't think they are constraints. return Regex.Replace(template, @"\{([^}]+):([^}]+)\}", match => { var paramName = match.Groups[1].Value; var constraint = match.Groups[2].Value; // There is a low chance that one of the parameters being used actually follows the syntax of {TemporaryPrefix}{paramName}__{constraint}. // But i dont think its signifigant enough to worry about. return $"{{{TemporaryPrefix}{paramName}__{constraint}}}"; }); } /// <summary> /// Restores the original parameter name after processing by TemplateMatcher. /// </summary> /// <param name="processedName">The parameter name after processing and matching.</param> /// <returns>The original parameter name.</returns> /// <remarks> /// This method reverses the transformation done in PreprocessRouteTemplate. /// For example, "__aws_param__abc__int" would be restored to "abc:int". /// </remarks> private static string RestoreOriginalParamName(string processedName) { if (processedName.StartsWith(TemporaryPrefix)) { var parts = processedName.Substring(TemporaryPrefix.Length).Split("__", 2); if (parts.Length == 2) { return $"{parts[0]}:{parts[1]}"; } } return processedName; } }