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

#173: Deprecating endpoint mapping in favor of plain middleware #174

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
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
8 changes: 1 addition & 7 deletions examples/StreetlightsAPI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,7 @@ public void Configure(IApplicationBuilder app)
app.UseRouting();
app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyMethod());

app.UseEndpoints(endpoints =>
{
endpoints.MapAsyncApiDocuments();
endpoints.MapAsyncApiUi();

endpoints.MapControllers();
});
app.UseAsyncApi();

// Print the AsyncAPI doc location
var logger = app.ApplicationServices.GetService<ILoggerFactory>().CreateLogger<Program>();
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be cached instead of serialising every time

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My apologies but that's what's been used all the time. That's not what this PR is about. It's about enabling others to use plain middlewares in order to be less limited.

That being said, thanks for your review.

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