Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Path matching bug #40

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
namespace Menes
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.RegularExpressions;
using Corvus.Extensions;
using Menes.Exceptions;
using Menes.Internal;
using Menes.Validation;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Tavis.UriTemplates;
Expand Down Expand Up @@ -41,6 +46,8 @@ namespace Menes
/// </remarks>
public class OpenApiDocumentProvider : IOpenApiDocumentProvider
{
private static readonly ConcurrentDictionary<OpenApiServer, Regex> RegexCache = new ConcurrentDictionary<OpenApiServer, Regex>();

private readonly IList<OpenApiPathTemplate> pathTemplates = new List<OpenApiPathTemplate>();
private readonly ILogger<OpenApiDocumentProvider> logger;
private readonly List<OpenApiDocument> addedOpenApiDocuments;
Expand Down Expand Up @@ -137,7 +144,7 @@ public void Add(OpenApiDocument document)
}

this.addedOpenApiDocuments.Add(document);
this.pathTemplates.AddRange(document.Paths.Select(path => new OpenApiPathTemplate(path.Key, path.Value)));
this.pathTemplates.AddRange(document.Paths.Select(path => new OpenApiPathTemplate(path.Key, path.Value, document)));
this.pathTemplatesByOperationId = null;
if (this.logger.IsEnabled(LogLevel.Trace))
{
Expand All @@ -150,20 +157,27 @@ public bool GetOperationPathTemplate(string requestPath, string method, [NotNull
{
foreach (OpenApiPathTemplate template in this.pathTemplates)
{
if (template.Match.IsMatch(requestPath))
Match match = template.Match.Match(requestPath);

if (match.Success)
{
if (template.PathItem.Operations.TryGetValue(method.ToOperationType(), out OpenApiOperation operation))
OpenApiServer? server = MatchServer(match, requestPath, template);

if (server != null)
{
this.logger.LogInformation(
"Matched request [{method}] [{requestPath}] with template [{template}] to [{operation}]",
method,
requestPath,
template.UriTemplate,
operation.GetOperationId());

// This is the success path
operationPathTemplate = new OpenApiOperationPathTemplate(operation, template);
return true;
if (template.PathItem.Operations.TryGetValue(method.ToOperationType(), out OpenApiOperation operation))
{
this.logger.LogInformation(
"Matched request [{method}] [{requestPath}] with template [{template}] to [{operation}]",
method,
requestPath,
template.UriTemplate,
operation.GetOperationId());

// This is the success path
operationPathTemplate = new OpenApiOperationPathTemplate(operation, template, server);
return true;
}
}
}
}
Expand All @@ -176,5 +190,31 @@ public bool GetOperationPathTemplate(string requestPath, string method, [NotNull
operationPathTemplate = null;
return false;
}

private static OpenApiServer? MatchServer(Match match, string requestPath, OpenApiPathTemplate template)
{
string precursor = match.Index == 0 ? string.Empty : requestPath.Substring(0, match.Index);
if (template.PathItem.Servers.Count > 0)
{
return MatchPrecursor(precursor, template.PathItem.Servers);
}

if (template.Document?.Servers.Count > 0)
{
return MatchPrecursor(precursor, template.Document.Servers);
}

return null;
}

private static OpenApiServer? MatchPrecursor(string precursor, IList<OpenApiServer> servers)
{
return servers.FirstOrDefault(s =>
{
Regex regex = RegexCache.GetOrAdd(s, new Regex(UriTemplate.CreateMatchingRegex2(s.Url), RegexOptions.Compiled));
Match match = regex.Match(precursor);
return match.Success && match.Index == 0;
});
}
}
}
23 changes: 21 additions & 2 deletions Solutions/Menes.Abstractions/Menes/OpenApiOperationPathTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Menes
using System.Linq;
using Corvus.Extensions;
using Microsoft.OpenApi.Models;
using Tavis.UriTemplates;

/// <summary>
/// A URI match for an operation.
Expand All @@ -20,10 +21,12 @@ public class OpenApiOperationPathTemplate
/// </summary>
/// <param name="operation">The matching OpenAPI operation.</param>
/// <param name="openApiPathTemplate">The matching OpenAPI path template.</param>
public OpenApiOperationPathTemplate(OpenApiOperation operation, OpenApiPathTemplate openApiPathTemplate)
/// <param name="server">The server for this path template.</param>
public OpenApiOperationPathTemplate(OpenApiOperation operation, OpenApiPathTemplate openApiPathTemplate, OpenApiServer? server)
{
this.Operation = operation;
this.OpenApiPathTemplate = openApiPathTemplate;
this.Server = server;
}

/// <summary>
Expand All @@ -36,6 +39,11 @@ public OpenApiOperationPathTemplate(OpenApiOperation operation, OpenApiPathTempl
/// </summary>
public OpenApiPathTemplate OpenApiPathTemplate { get; }

/// <summary>
/// Gets the server associated with this path template.
/// </summary>
public OpenApiServer? Server { get; }

/// <summary>
/// Builds the list of Open API parameters for this match.
/// </summary>
Expand All @@ -58,7 +66,18 @@ public IList<OpenApiParameter> BuildOpenApiParameters()
/// <returns>A dictionary of parameter names to values.</returns>
public IDictionary<string, object> BuildTemplateParameterValues(Uri requestUri)
{
return this.OpenApiPathTemplate.UriTemplate.GetParameters(requestUri);
IDictionary<string, object> pathParameters = this.OpenApiPathTemplate.UriTemplate.GetParameters(requestUri);

if (this.Server != null)
{
IDictionary<string, object> serverParameters = new UriTemplate(this.Server.Url).GetParameters(requestUri);
if (serverParameters != null)
{
pathParameters = pathParameters.Merge(serverParameters);
}
}

return pathParameters;
}
}
}
11 changes: 9 additions & 2 deletions Solutions/Menes.Abstractions/Menes/OpenApiPathTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ public class OpenApiPathTemplate
/// </summary>
/// <param name="uriTemplate">The uri template.</param>
/// <param name="item">The path item.</param>
public OpenApiPathTemplate(string uriTemplate, OpenApiPathItem item)
/// <param name="document">The Open API document in which this is a path template.</param>
public OpenApiPathTemplate(string uriTemplate, OpenApiPathItem item, OpenApiDocument? document)
{
this.UriTemplate = new UriTemplate(uriTemplate);
this.PathItem = item;
this.Match = new Regex(UriTemplate.CreateMatchingRegex(uriTemplate), RegexOptions.Compiled);
this.Match = new Regex(UriTemplate.CreateMatchingRegex2(uriTemplate), RegexOptions.Compiled);
this.Document = document;
}

/// <summary>
Expand All @@ -39,5 +41,10 @@ public OpenApiPathTemplate(string uriTemplate, OpenApiPathItem item)
/// Gets the <see cref="Regex"/> which provides a match.
/// </summary>
public Regex Match { get; }

/// <summary>
/// Gets the <see cref="OpenApiDocument"/> containing this path template.
/// </summary>
public OpenApiDocument? Document { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Menes.Hosting.AspNetCore
using System.Threading.Tasks;
using Menes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;

/// <summary>
Expand All @@ -23,7 +24,7 @@ public static class HttpRequestExtensions
/// <returns>The result of the request.</returns>
public static Task<IActionResult> HandleRequestAsync(this IOpenApiHost<HttpRequest, IActionResult> host, HttpRequest httpRequest, object parameters)
{
return host.HandleRequestAsync(httpRequest.Path, httpRequest.Method, httpRequest, parameters);
return host.HandleRequestAsync(httpRequest.GetDisplayUrl(), httpRequest.Method, httpRequest, parameters);
}
}
}
Loading