Skip to content

Commit

Permalink
asyncapi#173: Deprecating endpoint mapping in favor of plain middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
ch-ti8m-michalpenka committed Aug 23, 2023
1 parent 02d631d commit 26a4e89
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 161 deletions.
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@ See [examples/StreetlightsAPI](https://github.com/tehmantra/saunter/blob/main/ex
4. Add saunter middleware to host the AsyncApi json document. In the `Configure` method of `Startup.cs`:
```csharp
app.UseEndpoints(endpoints =>
{
endpoints.MapAsyncApiDocuments();
endpoints.MapAsyncApiUi();
});
app.UseAsyncApi();
```
5. Use the published AsyncApi document:
Expand Down Expand Up @@ -253,7 +249,7 @@ Each document can be accessed by specifying the name in the URL

## Contributing

See our [contributing guide](https://github.com/tehmantra/saunter/blob/main/CONTRIBUTING.md/CONTRIBUTING.md).
See our [contributing guide](https://github.com/tehmantra/saunter/blob/main/CONTRIBUTING.md).
Feel free to get involved in the project by opening issues, or submitting pull requests.

Expand Down
15 changes: 15 additions & 0 deletions src/Saunter/ApplicationBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Builder;
using Saunter.UI;

namespace Saunter;

public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseAsyncApi(this IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseMiddleware<AsyncApiMiddleware>();
applicationBuilder.UseMiddleware<AsyncApiUiMiddleware>();

return applicationBuilder;
}
}
4 changes: 4 additions & 0 deletions src/Saunter/AsyncApiEndpointRouteBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
Expand All @@ -12,6 +13,8 @@ public static class AsyncApiEndpointRouteBuilderExtensions
/// <summary>
/// Maps the AsyncAPI document endpoint
/// </summary>
///
[Obsolete("This property is obsolete. Use UseAsyncApi instead.", false)]
public static IEndpointConventionBuilder MapAsyncApiDocuments(
this IEndpointRouteBuilder endpoints)
{
Expand All @@ -29,6 +32,7 @@ public static IEndpointConventionBuilder MapAsyncApiDocuments(
/// <summary>
/// Maps the AsyncAPI UI endpoint(s)
/// </summary>
[Obsolete("This property is obsolete. Use UseAsyncApi instead.", false)]
public static IEndpointConventionBuilder MapAsyncApiUi(this IEndpointRouteBuilder endpoints)
{
var pipeline = endpoints.CreateApplicationBuilder()
Expand Down
80 changes: 40 additions & 40 deletions src/Saunter/AsyncApiMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,57 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Saunter.Serialization;
using Saunter.Utils;

namespace Saunter
namespace Saunter;

internal sealed class AsyncApiMiddleware
{
public class AsyncApiMiddleware
private readonly RequestDelegate _next;
private readonly IAsyncApiDocumentProvider _asyncApiDocumentProvider;
private readonly IAsyncApiDocumentSerializer _asyncApiDocumentSerializer;
private readonly AsyncApiOptions _options;

public AsyncApiMiddleware(
RequestDelegate next,
IOptions<AsyncApiOptions> options,
IAsyncApiDocumentProvider asyncApiDocumentProvider,
IAsyncApiDocumentSerializer asyncApiDocumentSerializer)
{
private readonly RequestDelegate _next;
private readonly IAsyncApiDocumentProvider _asyncApiDocumentProvider;
private readonly IAsyncApiDocumentSerializer _asyncApiDocumentSerializer;
private readonly AsyncApiOptions _options;
_next = next;
_asyncApiDocumentProvider = asyncApiDocumentProvider;
_asyncApiDocumentSerializer = asyncApiDocumentSerializer;
_options = options.Value;
}

public AsyncApiMiddleware(RequestDelegate next, IOptions<AsyncApiOptions> options, IAsyncApiDocumentProvider asyncApiDocumentProvider, IAsyncApiDocumentSerializer asyncApiDocumentSerializer)
public async Task InvokeAsync(HttpContext context)
{
if (!context.IsRequestingAsyncApiDocument(_options))
{
_next = next;
_asyncApiDocumentProvider = asyncApiDocumentProvider;
_asyncApiDocumentSerializer = asyncApiDocumentSerializer;
_options = options.Value;
await _next(context);
return;
}

public async Task Invoke(HttpContext context)
var prototype = _options.AsyncApi;
if (context.TryGetDocument(_options, out var documentName) && !_options.NamedApis.TryGetValue(documentName, out prototype))
{
if (!IsRequestingAsyncApiSchema(context.Request))
{
await _next(context);
return;
}

var prototype = _options.AsyncApi;
if (context.TryGetDocument(out var documentName) && !_options.NamedApis.TryGetValue(documentName, out prototype))
{
await _next(context);
return;
}

var asyncApiSchema = _asyncApiDocumentProvider.GetDocument(_options, prototype);

await RespondWithAsyncApiSchemaJson(context.Response, asyncApiSchema, _asyncApiDocumentSerializer, _options);
await _next(context);
return;
}

private static async Task RespondWithAsyncApiSchemaJson(HttpResponse response, AsyncApiSchema.v2.AsyncApiDocument asyncApiSchema, IAsyncApiDocumentSerializer asyncApiDocumentSerializer, AsyncApiOptions options)
{
var asyncApiSchemaJson = asyncApiDocumentSerializer.Serialize(asyncApiSchema);
response.StatusCode = (int)HttpStatusCode.OK;
response.ContentType = asyncApiDocumentSerializer.ContentType;
var asyncApiSchema = _asyncApiDocumentProvider.GetDocument(_options, prototype);

await response.WriteAsync(asyncApiSchemaJson);
}
await RespondWithAsyncApiSchemaJsonAsync(context.Response, asyncApiSchema, _asyncApiDocumentSerializer);
}

private bool IsRequestingAsyncApiSchema(HttpRequest request)
{
return HttpMethods.IsGet(request.Method) && request.Path.IsMatchingRoute(_options.Middleware.Route);
}
private static async Task RespondWithAsyncApiSchemaJsonAsync(
HttpResponse response,
AsyncApiSchema.v2.AsyncApiDocument asyncApiSchema,
IAsyncApiDocumentSerializer asyncApiDocumentSerializer)
{
var asyncApiSchemaJson = asyncApiDocumentSerializer.Serialize(asyncApiSchema);
response.StatusCode = (int)HttpStatusCode.OK;
response.ContentType = asyncApiDocumentSerializer.ContentType;

await response.WriteAsync(asyncApiSchemaJson);
}
}
86 changes: 86 additions & 0 deletions src/Saunter/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Linq;
using Microsoft.AspNetCore.Http;

namespace Saunter;

internal static class HttpContextExtensions
{
internal const string UriDocumentPlaceholder = "{document}";
private const string UriDocumentPlaceholderEncoded = "%7Bdocument%7D";
private const string UriDocumentFile = "/asyncapi.json";

public static bool TryGetDocument(this HttpContext context, AsyncApiOptions options, out string documentName)
{
foreach (var documentNameSpecified in options.NamedApis.Values.Select(x => x.DocumentName))
{
var pathStart = options.Middleware.Route
.Replace(UriDocumentPlaceholder, documentNameSpecified)
.Replace(UriDocumentFile, string.Empty);

if (!HttpMethods.IsGet(context.Request.Method)
|| !context.Request.Path.StartsWithSegments(pathStart))
{
continue;
}

documentName = documentNameSpecified;
return true;
}

documentName = string.Empty;
return false;
}

public static bool IsRequestingUiBase(this HttpContext context, AsyncApiOptions options)
{
var uiBaseRoute = options.Middleware.UiBaseRoute;
return IsRequestingAsyncApiUrl(context, options, uiBaseRoute)
|| IsRequestingAsyncApiUrl(context, options, uiBaseRoute.TrimEnd('/'));
}

public static bool IsRequestingAsyncApiUi(this HttpContext context, AsyncApiOptions options)
{
var uiIndexRoute = options.Middleware.UiBaseRoute?.TrimEnd('/') + "/index.html";
return context.IsRequestingAsyncApiUrl(options, uiIndexRoute);
}

public static bool IsRequestingAsyncApiDocument(this HttpContext context, AsyncApiOptions options)
{
var uiIndexRoute = options.Middleware.Route;
return context.IsRequestingAsyncApiUrl(options, uiIndexRoute);
}

public static string GetAsyncApiUiIndexFullRoute(this HttpContext context, AsyncApiOptions options)
{
var uiIndexRoute = options.Middleware.UiBaseRoute?.TrimEnd('/') + "/index.html";
return context.GetFullRoute(options, uiIndexRoute);
}

public static string GetAsyncApiDocumentFullRoute(this HttpContext context, AsyncApiOptions options)
{
var documentRoute = options.Middleware.Route;
return context.GetFullRoute(options, documentRoute);
}

private static bool IsRequestingAsyncApiUrl(this HttpContext context, AsyncApiOptions options, string asyncApiBaseRoute)
{
if (context.TryGetDocument(options, out var documentName))
{
asyncApiBaseRoute = asyncApiBaseRoute.Replace(UriDocumentPlaceholder, documentName);
}

return HttpMethods.IsGet(context.Request.Method) && context.Request.Path.Equals(asyncApiBaseRoute);
}

private static string GetFullRoute(this HttpContext context, AsyncApiOptions options, string route)
{
if (context.TryGetDocument(options, out var documentName))
{
route = route
.Replace(UriDocumentPlaceholder, documentName)
.Replace(UriDocumentPlaceholderEncoded, documentName);
}

return context.Request.PathBase.Add(route);
}
}
Loading

0 comments on commit 26a4e89

Please sign in to comment.