Open API

This concern dynamically generates and serves an Open API v3 specification using NSwag for all detected API endpoints.

using GenHTTP.Engine.Internal;

using GenHTTP.Modules.Functional;
using GenHTTP.Modules.OpenApi;

var api = Inline.Create()
                .Get("/users", () => new List<User>())
                .Get("/user/:id", (int id) => new User(id, "John Doe"))
                .AddOpenApi();

await Host.Create()
          .Handler(api)
          .Development()
          .Console()
          .RunAsync();

record User(int ID, string Name);

This example provides a functional API with two operations and hosts a self-describing Open API specification via http://localhost:8080/openapi.json.

Automatically generated Open API v3 specification

You can import this specification into other tools such as Postman or Swagger Editor to execute requests or generate client implementations.

Generated specification loaded into Swagger Editor

Endpoints

The Open API concern provides the following endpoints:

Endpoint Description
/openapi.json Open API specification serialized to application/json.
/openapi.yaml Open API specification serialized to application/yaml.
/openapi.yml Open API specification serialized to application/yaml.
/openapi Serialized depending on the Accept header of the request, falls back to application/json.

Customizing

To customize the generated specification you can pass a function that will be executed just before the document is serialized and sent to the requesting client.

using GenHTTP.Engine.Internal;

using GenHTTP.Modules.Functional;
using GenHTTP.Modules.OpenApi;

var description = ApiDescription.Create()
                                .Title("My awesome API")
                                .Version("2.0.7")
                                .PostProcessor((r, doc) => doc.Info.TermsOfService = "https://mycompany.com/tos");

var api = Inline.Create()
                .Add(description);

await Host.Create()
          .Handler(api)
          .Development()
          .Console()
          .RunAsync();

Please note that method annotations are currently not supported, besides the [Obsolete] attribute marking an operation as deprecated. You will find the corresponding feature request on GitHub.

Caching

As automatically generating a specification is an expensive task, the concern will cache the generated document for improved performance. In some cases you might want the specification to be generated on every request, especially if the result depends on the request sent by the client. In this case, you can disable caching, causing the document to be generated on every request.

var description = ApiDescription.Create()
                                .Caching(false);

Custom Discovery

By default, automatic discovery will handle functional handlers, webservices and controllers and will traverse automatically through layouts and concerns. If you would like to support an additional handler, you can initialize a registry and pass a custom IApiExplorer.

The following example adds support for redirects, so the generated Open API specification will describe the operation.

using GenHTTP.Api.Content;
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Basics;
using GenHTTP.Modules.Basics.Providers;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.OpenApi;
using GenHTTP.Modules.OpenApi.Discovery;
using NSwag;

var discovery = ApiDiscovery.Default()
                            .Add<RedirectExplorer>();

var description = ApiDescription.With(discovery).Caching(false);

var api = Layout.Create()
                .Add("google", Redirect.To("https://google.com"))
                .Add(description);

await Host.Create()
          .Handler(api)
          .Development()
          .Console()
          .RunAsync();

class RedirectExplorer : IApiExplorer
{

    public bool CanExplore(IHandler handler) => handler is RedirectProvider;

    public void Explore(IHandler handler, List<string> path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
    {
        if (handler is RedirectProvider redirect)
        {
            var apiPath = OpenApiExtensions.BuildPath(string.Empty, path);

            var pathItem = new OpenApiPathItem();

            var operation = new OpenApiOperation();

            var response = new OpenApiResponse();

            response.Headers.Add("Location", new OpenApiHeader() { Default = redirect.Target });

            var status = redirect.Temporary ? "307" : "308";

            operation.Responses.Add(status, response);

            pathItem.Add("get", operation);

            document.Paths.Add(apiPath, pathItem);
        }
    }

}

If you would like to have an API explorer for a handler that is shipped with the framework, feel free to get in touch via a GitHub issue or via Discord.

Share