Skip to content

Commit

Permalink
Add support for OpenApi v3 (#33)
Browse files Browse the repository at this point in the history
* add support for OpenApi v3

* fix handling of relative url
  • Loading branch information
danadesrosiers authored and Burgyn committed Jun 14, 2019
1 parent 5d513d6 commit 3e53127
Show file tree
Hide file tree
Showing 11 changed files with 1,118 additions and 13 deletions.
43 changes: 43 additions & 0 deletions src/MMLib.SwaggerForOcelot/OpenApiProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace MMLib.SwaggerForOcelot
{
/// <summary>
/// Swagger properties constants.
/// </summary>
public static class OpenApiProperties
{
/// <summary>
/// The servers property name.
/// </summary>
public const string Servers = "servers";

/// <summary>
/// The uri property name. Property is a child of <seealso cref="Servers"/>.
/// </summary>
public const string Url = "url";

/// <summary>
/// The paths property name.
/// </summary>
public const string Paths = "paths";

/// <summary>
/// The components property name.
/// </summary>
public const string Components = "components";

/// <summary>
/// The schemas property name. Property is a child of <seealso cref="Components"/>.
/// </summary>
public const string Schemas = "schemas";

/// <summary>
/// The tags property name.
/// </summary>
public const string Tags = "tags";

/// <summary>
/// The tag's name property name. Property is a child of <seealso cref="Tags"/>.
/// </summary>
public const string TagName = "name";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

namespace MMLib.SwaggerForOcelot.Transformation
{
Expand All @@ -13,11 +14,26 @@ namespace MMLib.SwaggerForOcelot.Transformation
/// <seealso cref="MMLib.SwaggerForOcelot.Transformation.ISwaggerJsonTransformer" />
public class SwaggerJsonTransformer : ISwaggerJsonTransformer
{

/// <inheritdoc/>
public string Transform(string swaggerJson, IEnumerable<ReRouteOptions> reRoutes, string hostOverride)
{
JObject swagger = JObject.Parse(swaggerJson);

if (swagger.ContainsKey("swagger"))
{
return TransformSwagger(swagger, reRoutes, hostOverride);
}

if (swagger.ContainsKey("openapi"))
{
return TransformOpenApi(swagger, reRoutes, hostOverride);
}

throw new InvalidOperationException("Unknown swagger/openapi version");
}

private string TransformSwagger(JObject swagger, IEnumerable<ReRouteOptions> reRoutes, string hostOverride)
{
var paths = swagger[SwaggerProperties.Paths];
var basePath = swagger.ContainsKey(SwaggerProperties.BasePath)
? swagger.GetValue(SwaggerProperties.BasePath).ToString()
Expand All @@ -37,18 +53,62 @@ public string Transform(string swaggerJson, IEnumerable<ReRouteOptions> reRoutes
RemoveItems<JProperty>(
swagger[SwaggerProperties.Definitions],
paths,
(i) => $"$..[?(@*.$ref == '#/definitions/{i.Name}')]",
(i) => $"$..[?(@*.*.items.$ref == '#/definitions/{i.Name}')]");
i => $"$..[?(@*.$ref == '#/{SwaggerProperties.Definitions}/{i.Name}')]",
i => $"$..[?(@*.*.items.$ref == '#/{SwaggerProperties.Definitions}/{i.Name}')]");
if (swagger["tags"] != null)
{
RemoveItems<JObject>(
swagger[SwaggerProperties.Tags],
paths,
(i) => $"$..tags[?(@ == '{i[SwaggerProperties.TagName]}')]");
i => $"$..tags[?(@ == '{i[SwaggerProperties.TagName]}')]");
}
}

return swagger.ToString(Formatting.Indented);
}

private string TransformOpenApi(JObject openApi, IEnumerable<ReRouteOptions> reRoutes, string hostOverride = "")
{
var paths = openApi[OpenApiProperties.Paths];
if (openApi.ContainsKey(OpenApiProperties.Servers))
{
foreach (var server in openApi.GetValue(OpenApiProperties.Servers))
{
if (server[OpenApiProperties.Url] != null)
{
var url = new Uri(server.Value<string>(OpenApiProperties.Url), UriKind.RelativeOrAbsolute);
server[OpenApiProperties.Url] = hostOverride + (url.IsAbsoluteUri ? url.AbsolutePath : url.OriginalString);
}
}
}

// NOTE: Only supporting one server for now.
var basePath = "";
if (openApi.ContainsKey(OpenApiProperties.Servers))
{
var firstUrl = openApi.GetValue(OpenApiProperties.Servers).First.Value<string>(OpenApiProperties.Url);
basePath = hostOverride.Length > 0 ? new Uri(firstUrl).AbsolutePath : firstUrl;
}

if (paths != null)
{
RemovePaths(reRoutes, paths, basePath);

RemoveItems<JProperty>(
openApi[OpenApiProperties.Components][OpenApiProperties.Schemas],
paths,
i => $"$..[?(@*.$ref == '#/{OpenApiProperties.Components}/{OpenApiProperties.Schemas}/{i.Name}')]",
i => $"$..[?(@*.*.items.$ref == '#/{OpenApiProperties.Components}/{OpenApiProperties.Schemas}/{i.Name}')]");
if (openApi["tags"] != null)
{
RemoveItems<JObject>(
openApi[OpenApiProperties.Tags],
paths,
i => $"$..tags[?(@ == '{i[OpenApiProperties.TagName]}')]");
}
}

return swagger.ToString(Newtonsoft.Json.Formatting.Indented);
return openApi.ToString(Formatting.Indented);
}

private void RemovePaths(IEnumerable<ReRouteOptions> reRoutes, JToken paths, string basePath)
Expand Down Expand Up @@ -188,4 +248,4 @@ private static void RenameToken(JProperty property, string newName)
property.Replace(newProperty);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace MMLib.SwaggerForOcelot.Tests
public class BuilderExtensionsShould
{
[Fact]
public void ThrowWhenSwaggerEndPointsSectionIsMising() => TestWithInvalidConfiguration("{}");
public void ThrowWhenSwaggerEndPointsSectionIsMissing() => TestWithInvalidConfiguration("{}");

[Fact]
public void ThrowWhenSwaggerEndPointsSectionIsEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<None Remove="Resources\SwaggerPetsTransformed.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\OpenApiBase.json" />
<EmbeddedResource Include="Resources\OpenApiBaseTransformed.json" />
<EmbeddedResource Include="Resources\OpenApiWithHostOverrideBaseTransformed.json" />
<EmbeddedResource Include="Resources\SwaggerNestedClassTransformed.json" />
<EmbeddedResource Include="Resources\SwaggerNestedClass.json" />
<EmbeddedResource Include="Resources\SwaggerPetsOnlyAnyActions.json">
Expand All @@ -34,6 +37,8 @@
</EmbeddedResource>
<EmbeddedResource Include="Resources\SwaggerWithBasePathBase.json" />
<EmbeddedResource Include="Resources\SwaggerWithBasePathBaseTransformed.json" />
<EmbeddedResource Include="Resources\OpenApiWithServersBase.json" />
<EmbeddedResource Include="Resources\OpenApiWithServersBaseTransformed.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\SwaggerBase.json" />
Expand Down
104 changes: 104 additions & 0 deletions tests/MMLib.SwaggerForOcelot.Tests/OpenApiJsonFormatterShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using MMLib.SwaggerForOcelot.Configuration;
using MMLib.SwaggerForOcelot.Transformation;
using Newtonsoft.Json.Linq;
using Xunit;

namespace MMLib.SwaggerForOcelot.Tests
{
public class OpenApiJsonFormatterShould
{
[Fact]
public async Task CreateNewJsonByBasicConfiguration()
{
var reroutes = new List<ReRouteOptions>
{
new ReRouteOptions
{
SwaggerKey = "projects",
UpstreamPathTemplate ="/api/projects/{everything}",
DownstreamPathTemplate ="/api/{everything}"}
};

await TransformAndCheck(reroutes, "OpenApiBase", "OpenApiBaseTransformed", "localhost:8000");
}

[Fact]
public async Task CreateNewJsonByBasicConfigurationWithVirtualDirectory()
{
var reroutes = new List<ReRouteOptions>
{
new ReRouteOptions
{
SwaggerKey = "projects",
VirtualDirectory = "/project",
UpstreamPathTemplate ="/api/projects/{everything}",
DownstreamPathTemplate ="/project/api/{everything}"}
};

await TransformAndCheck(reroutes, "OpenApiBase", "OpenApiBaseTransformed", "localhost:8000");
}

[Fact]
public async Task CreateNewJsonWithServers()
{
var reroutes = new List<ReRouteOptions>
{
new ReRouteOptions
{
SwaggerKey = "projects",
UpstreamPathTemplate ="/api/projects/{everything}",
DownstreamPathTemplate ="/api/{everything}"
}
};

await TransformAndCheck(reroutes, "OpenApiWithServersBase", "OpenApiWithServersBaseTransformed");
}

[Fact]
public async Task CreateNewJsonWithHostOverride()
{
var reroutes = new List<ReRouteOptions>
{
new ReRouteOptions
{
SwaggerKey = "projects",
UpstreamPathTemplate ="/api/projects/{everything}",
DownstreamPathTemplate ="/api/{everything}"
}
};

await TransformAndCheck(reroutes, "OpenApiWithServersBase", "OpenApiWithHostOverrideBaseTransformed", "http://override.host.it");
}

private async Task TransformAndCheck(
IEnumerable<ReRouteOptions> reroutes,
string openApiBaseFileName,
string expectedOpenApiFileName,
string servers = "")
{
var transformer = new SwaggerJsonTransformer();
string openApiBase = await GetBaseOpenApi(openApiBaseFileName);

var transformed = transformer.Transform(openApiBase, reroutes, servers);

await AreEqual(transformed, expectedOpenApiFileName);
}

private static async Task<string> GetBaseOpenApi(string openApiName)
=> await AssemblyHelper.GetStringFromResourceFileAsync($"{openApiName}.json");

private static async Task AreEqual(string transformed, string expectedOpenApiFileName)
{
var transformedJson = JObject.Parse(transformed);
var expectedJson = JObject.Parse(await AssemblyHelper
.GetStringFromResourceFileAsync($"{expectedOpenApiFileName}.json"));

JObject.DeepEquals(transformedJson, expectedJson)
.Should()
.BeTrue();
}
}
}
Loading

0 comments on commit 3e53127

Please sign in to comment.