diff --git a/src/MMLib.SwaggerForOcelot/Aggregates/RoutesDocumentationProvider.cs b/src/MMLib.SwaggerForOcelot/Aggregates/RoutesDocumentationProvider.cs index 3319658..6d2c2ea 100644 --- a/src/MMLib.SwaggerForOcelot/Aggregates/RoutesDocumentationProvider.cs +++ b/src/MMLib.SwaggerForOcelot/Aggregates/RoutesDocumentationProvider.cs @@ -46,7 +46,7 @@ private RouteDocs GetRouteDocs(RouteOptions route) JToken paths = docs[OpenApiProperties.Paths]; string downstreamPath = GetDownstreamPath(route); JProperty path = paths.OfType().FirstOrDefault(p => - downstreamPath.Equals(p.Name.WithSlashEnding(), StringComparison.CurrentCultureIgnoreCase)); + downstreamPath.Equals(p.Name.WithSlashEnding(), StringComparison.OrdinalIgnoreCase)); return new RouteDocs() { diff --git a/src/MMLib.SwaggerForOcelot/Configuration/RouteOptions.cs b/src/MMLib.SwaggerForOcelot/Configuration/RouteOptions.cs index 2e695aa..31e179f 100644 --- a/src/MMLib.SwaggerForOcelot/Configuration/RouteOptions.cs +++ b/src/MMLib.SwaggerForOcelot/Configuration/RouteOptions.cs @@ -162,7 +162,7 @@ private string DownstreamPathWithVirtualDirectory /// Gets a value indicating whether this instance can catch all. /// public bool CanCatchAll - => DownstreamPathTemplate.EndsWith(CatchAllPlaceHolder, StringComparison.CurrentCultureIgnoreCase); + => DownstreamPathTemplate.EndsWith(CatchAllPlaceHolder, StringComparison.OrdinalIgnoreCase); /// /// Gets the upstream path. diff --git a/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs b/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs index e3654af..f6056c1 100644 --- a/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs +++ b/src/MMLib.SwaggerForOcelot/Transformation/SwaggerJsonTransformer.cs @@ -171,7 +171,7 @@ private string TransformOpenApi( private void RenameAndRemovePaths(IEnumerable routes, JToken paths, string basePath) { var oldPaths = new List(); - var newPaths = new Dictionary(); + var newPaths = new Dictionary(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < paths.Count(); i++) { var oldPath = paths.ElementAt(i) as JProperty; @@ -281,9 +281,35 @@ static bool MatchPaths(RouteOptions route, string downstreamPath) : route.DownstreamPathWithSlash.Equals(downstreamPath, StringComparison.OrdinalIgnoreCase); string downstreamPathWithBasePath = PathHelper.BuildPath(basePath, downstreamPath); - return routes + var matchedRoutes = routes .Where(route => route.ContainsHttpMethod(method) && MatchPaths(route, downstreamPathWithBasePath)) .ToList(); + + RemoveRedundantRoutes(matchedRoutes); + return matchedRoutes; + } + + // Redundant routes are routes with the ALMOST same upstream path templates. For example these path templates + // are redundant: + // - /api/projects/Projects + // - /api/projects/Projects/ + // - /api/projects/Projects/{everything} + // + // `route.UpstreamPath` contains route without trailing slash and without catch-all placeholder, so all previous + // routes have the same upstream path `/api/projects/Projects`. The logic is to keep just the shortestof the path + // templates. If we would keep all routes, it will throw an exception during the generation of the swagger document + // later because of the same paths. + private static void RemoveRedundantRoutes(List routes) + { + IEnumerable> groups = routes + .GroupBy(route => route.UpstreamPath, StringComparer.OrdinalIgnoreCase) + .Where(group => group.Count() > 1); + foreach (var group in groups) + { + group.OrderBy(r => r.DownstreamPathTemplate.Length) + .Skip(1) + .ForEach(r => routes.Remove(r)); + } } private static void AddHost(JObject swagger, string swaggerHost) @@ -305,7 +331,7 @@ private static string ConvertDownstreamPathToUpstreamPath(string downstreamPath, downstreamPath = PathHelper.BuildPath(downstreamBasePath, downstreamPath); } - int pos = downstreamPath.IndexOf(downstreamPattern, StringComparison.CurrentCultureIgnoreCase); + int pos = downstreamPath.IndexOf(downstreamPattern, StringComparison.OrdinalIgnoreCase); if (pos < 0) { return downstreamPath;