183 lines
7.3 KiB
C#
183 lines
7.3 KiB
C#
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using EonaCat.DoxaApi.Generation;
|
|
using EonaCat.DoxaApi.Interop;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc.Abstractions;
|
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace EonaCat.DoxaApi.Middleware
|
|
{
|
|
public static class DoxaApiMiddlewareExtensions
|
|
{
|
|
private static readonly JsonSerializerOptions _writeOptions = new()
|
|
{
|
|
WriteIndented = true,
|
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
|
};
|
|
|
|
public static IApplicationBuilder UseDoxaApi(this IApplicationBuilder app, Action<DoxaApiOptions>? configure = null)
|
|
{
|
|
|
|
var options = app.ApplicationServices.GetService<DoxaApiOptions>() ?? new DoxaApiOptions();
|
|
configure?.Invoke(options);
|
|
|
|
var prefix = "/" + options.RoutePrefix.Trim('/');
|
|
|
|
app.Map(prefix + "/doxaApi.json", specApp =>
|
|
{
|
|
specApp.Run(async context =>
|
|
{
|
|
var document = GenerateDocument(context, options);
|
|
context.Response.ContentType = "application/json; charset=utf-8";
|
|
await JsonSerializer.SerializeAsync(context.Response.Body, document, _writeOptions);
|
|
});
|
|
});
|
|
|
|
app.Map(prefix + "/openapi.json", oaApp =>
|
|
{
|
|
oaApp.Run(async context =>
|
|
{
|
|
var document = GenerateDocument(context, options);
|
|
var openApi = OpenApiExporter.Export(document);
|
|
context.Response.ContentType = "application/json; charset=utf-8";
|
|
await context.Response.WriteAsync(
|
|
openApi.ToJsonString(_writeOptions), Encoding.UTF8);
|
|
});
|
|
});
|
|
|
|
app.Map(prefix + "/swagger.json", swApp =>
|
|
{
|
|
swApp.Run(async context =>
|
|
{
|
|
var document = GenerateDocument(context, options);
|
|
var swagger = SwaggerExporter.Export(document);
|
|
context.Response.ContentType = "application/json; charset=utf-8";
|
|
await context.Response.WriteAsync(
|
|
swagger.ToJsonString(_writeOptions), Encoding.UTF8);
|
|
});
|
|
});
|
|
|
|
app.Map(prefix + "/import", importApp =>
|
|
{
|
|
importApp.Run(async context =>
|
|
{
|
|
if (!HttpMethods.IsPost(context.Request.Method))
|
|
{
|
|
context.Response.StatusCode = 405;
|
|
context.Response.Headers["Allow"] = "POST";
|
|
await context.Response.WriteAsync("Method Not Allowed - use POST with a JSON body.");
|
|
return;
|
|
}
|
|
|
|
if (context.Request.ContentLength == 0 || context.Request.Body is null)
|
|
{
|
|
context.Response.StatusCode = 400;
|
|
await context.Response.WriteAsync("Request body is empty.");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var imported = await OpenApiImporter.ImportAsync(context.Request.Body);
|
|
context.Response.ContentType = "application/json; charset=utf-8";
|
|
await JsonSerializer.SerializeAsync(context.Response.Body, imported, _writeOptions);
|
|
}
|
|
catch (NotSupportedException ex)
|
|
{
|
|
context.Response.StatusCode = 400;
|
|
await context.Response.WriteAsync($"Unsupported spec format: {ex.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
context.Response.StatusCode = 400;
|
|
await context.Response.WriteAsync($"Failed to parse spec: {ex.Message}");
|
|
}
|
|
});
|
|
});
|
|
|
|
app.Map(prefix, uiApp =>
|
|
{
|
|
uiApp.Run(async context =>
|
|
{
|
|
var requestPath = context.Request.Path.Value ?? "";
|
|
var assetName = requestPath.Trim('/');
|
|
if (string.IsNullOrEmpty(assetName) || assetName == options.RoutePrefix.Trim('/'))
|
|
{
|
|
assetName = "index.html";
|
|
}
|
|
|
|
var asset = EmbeddedAssetLoader.Load(assetName);
|
|
if (asset is null)
|
|
{
|
|
context.Response.StatusCode = 404;
|
|
await context.Response.WriteAsync("Not found");
|
|
return;
|
|
}
|
|
|
|
context.Response.ContentType = asset.Value.ContentType;
|
|
|
|
if (assetName == "index.html")
|
|
{
|
|
var html = Encoding.UTF8.GetString(asset.Value.Bytes);
|
|
html = html.Replace("__DOXA_API_TITLE__", options.Title)
|
|
.Replace("__DOXA_API_ACCENT__", options.AccentColor)
|
|
.Replace("__DOXA_API_THEME__", options.Theme)
|
|
.Replace("__DOXA_API_BASE__", prefix + "/")
|
|
.Replace("__DOXA_API_SPEC_PATH__", prefix + "/doxaApi.json");
|
|
await context.Response.WriteAsync(html, Encoding.UTF8);
|
|
return;
|
|
}
|
|
|
|
await context.Response.Body.WriteAsync(asset.Value.Bytes);
|
|
});
|
|
});
|
|
|
|
return app;
|
|
}
|
|
|
|
private static Models.ApiDocument GenerateDocument(HttpContext context, DoxaApiOptions options)
|
|
{
|
|
var provider = context.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
|
|
var actions = provider.ActionDescriptors.Items;
|
|
return new ApiDocumentGenerator(actions, options).Generate();
|
|
}
|
|
}
|
|
|
|
internal static class EmbeddedAssetLoader
|
|
{
|
|
private static readonly Assembly _assembly = typeof(EmbeddedAssetLoader).Assembly;
|
|
private static readonly string _prefix = "EonaCat.DoxaApi.UI.Assets.";
|
|
|
|
public static (byte[] Bytes, string ContentType)? Load(string assetName)
|
|
{
|
|
var resourceName = _prefix + assetName.Replace('/', '.');
|
|
using var stream = _assembly.GetManifestResourceStream(resourceName);
|
|
if (stream is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
using var ms = new MemoryStream();
|
|
stream.CopyTo(ms);
|
|
|
|
var contentType = Path.GetExtension(assetName) switch
|
|
{
|
|
".html" => "text/html; charset=utf-8",
|
|
".css" => "text/css; charset=utf-8",
|
|
".js" => "application/javascript; charset=utf-8",
|
|
".svg" => "image/svg+xml",
|
|
".png" => "image/png",
|
|
".ico" => "image/x-icon",
|
|
_ => "application/octet-stream"
|
|
};
|
|
|
|
return (ms.ToArray(), contentType);
|
|
}
|
|
}
|
|
}
|