From 7e1173bf2c0810be86b02a18aa6d84bc3965b8db Mon Sep 17 00:00:00 2001 From: EonaCat Date: Sat, 20 Jun 2026 10:24:36 +0200 Subject: [PATCH] Initial version --- .gitignore | 27 +- .../Attributes/DoxaApiDescriptionAttribute.cs | 9 + DoxaApi/Attributes/DoxaApiExampleAttribute.cs | 9 + DoxaApi/Attributes/DoxaApiGroupAttribute.cs | 9 + DoxaApi/Attributes/DoxaApiHiddenAttribute.cs | 7 + DoxaApi/Attributes/DoxaApiSummaryAttribute.cs | 9 + DoxaApi/EonaCat.DoxaApi.csproj | 57 + DoxaApi/Exporter/ApiDocsExporter.cs | 4 + DoxaApi/Exporter/OpenApiExporter.cs | 272 +++ DoxaApi/Exporter/SwaggerExporter.cs | 304 ++++ DoxaApi/Generation/ApiDocumentGenerator.cs | 278 ++++ DoxaApi/Generation/DoxaApiOptions.cs | 13 + DoxaApi/Generation/SchemaBuilder.cs | 294 ++++ DoxaApi/Generation/XmlDocReader.cs | 103 ++ DoxaApi/Importer/ApiDocsImporter.cs | 20 + DoxaApi/Importer/OpenApiImporter.cs | 603 +++++++ DoxaApi/Middleware/ApiDocsMiddleware.cs | 182 ++ DoxaApi/Models/ApiDocument.cs | 19 + DoxaApi/Models/ApiEndpoint.cs | 37 + DoxaApi/Models/ApiGroup.cs | 16 + DoxaApi/Models/ApiInfo.cs | 16 + DoxaApi/Models/ApiParameter.cs | 25 + DoxaApi/Models/RequestBodyModel.cs | 19 + DoxaApi/Models/ResponseModel.cs | 16 + DoxaApi/Models/SchemaModel.cs | 31 + DoxaApi/ServiceCollectionExtensions.cs | 17 + DoxaApi/UI/Assets/app.css | 1473 +++++++++++++++++ DoxaApi/UI/Assets/app.js | 855 ++++++++++ DoxaApi/UI/Assets/index.html | 71 + EonaCat.DoxaApi.sln | 32 + LICENSE | 213 ++- README.md | 161 +- icon.png | Bin 0 -> 89562 bytes image.png | Bin 0 -> 123218 bytes .../SampleApi/Controllers/OrdersController.cs | 54 + .../SampleApi/Controllers/UsersController.cs | 102 ++ sample/SampleApi/Models/Models.cs | 86 + sample/SampleApi/Program.cs | 27 + .../SampleApi/Properties/launchSettings.json | 12 + sample/SampleApi/SampleApi.csproj | 19 + 40 files changed, 5438 insertions(+), 63 deletions(-) create mode 100644 DoxaApi/Attributes/DoxaApiDescriptionAttribute.cs create mode 100644 DoxaApi/Attributes/DoxaApiExampleAttribute.cs create mode 100644 DoxaApi/Attributes/DoxaApiGroupAttribute.cs create mode 100644 DoxaApi/Attributes/DoxaApiHiddenAttribute.cs create mode 100644 DoxaApi/Attributes/DoxaApiSummaryAttribute.cs create mode 100644 DoxaApi/EonaCat.DoxaApi.csproj create mode 100644 DoxaApi/Exporter/ApiDocsExporter.cs create mode 100644 DoxaApi/Exporter/OpenApiExporter.cs create mode 100644 DoxaApi/Exporter/SwaggerExporter.cs create mode 100644 DoxaApi/Generation/ApiDocumentGenerator.cs create mode 100644 DoxaApi/Generation/DoxaApiOptions.cs create mode 100644 DoxaApi/Generation/SchemaBuilder.cs create mode 100644 DoxaApi/Generation/XmlDocReader.cs create mode 100644 DoxaApi/Importer/ApiDocsImporter.cs create mode 100644 DoxaApi/Importer/OpenApiImporter.cs create mode 100644 DoxaApi/Middleware/ApiDocsMiddleware.cs create mode 100644 DoxaApi/Models/ApiDocument.cs create mode 100644 DoxaApi/Models/ApiEndpoint.cs create mode 100644 DoxaApi/Models/ApiGroup.cs create mode 100644 DoxaApi/Models/ApiInfo.cs create mode 100644 DoxaApi/Models/ApiParameter.cs create mode 100644 DoxaApi/Models/RequestBodyModel.cs create mode 100644 DoxaApi/Models/ResponseModel.cs create mode 100644 DoxaApi/Models/SchemaModel.cs create mode 100644 DoxaApi/ServiceCollectionExtensions.cs create mode 100644 DoxaApi/UI/Assets/app.css create mode 100644 DoxaApi/UI/Assets/app.js create mode 100644 DoxaApi/UI/Assets/index.html create mode 100644 EonaCat.DoxaApi.sln create mode 100644 icon.png create mode 100644 image.png create mode 100644 sample/SampleApi/Controllers/OrdersController.cs create mode 100644 sample/SampleApi/Controllers/UsersController.cs create mode 100644 sample/SampleApi/Models/Models.cs create mode 100644 sample/SampleApi/Program.cs create mode 100644 sample/SampleApi/Properties/launchSettings.json create mode 100644 sample/SampleApi/SampleApi.csproj diff --git a/.gitignore b/.gitignore index 7e2e97c..1998960 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser @@ -83,8 +83,6 @@ StyleCopReport.xml *.pgc *.pgd *.rsp -# but not Directory.Build.rsp, as it configures directory-level build defaults -!Directory.Build.rsp *.sbr *.tlb *.tli @@ -209,6 +207,9 @@ PublishScripts/ *.nuget.props *.nuget.targets +# Nuget personal access tokens and Credentials +nuget.config + # Microsoft Azure Build Output csx/ *.build.csdef @@ -297,17 +298,6 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -364,9 +354,6 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ -# Visual Studio History (VSHistory) files -.vshistory/ - # BeatPulse healthcheck temp database healthchecksdb @@ -398,6 +385,7 @@ FodyWeavers.xsd *.msp # JetBrains Rider +.idea/ *.sln.iml # ---> VisualStudioCode @@ -406,11 +394,8 @@ FodyWeavers.xsd !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json -!.vscode/*.code-snippets +*.code-workspace # Local History for Visual Studio Code .history/ -# Built Visual Studio Code Extensions -*.vsix - diff --git a/DoxaApi/Attributes/DoxaApiDescriptionAttribute.cs b/DoxaApi/Attributes/DoxaApiDescriptionAttribute.cs new file mode 100644 index 0000000..36485bf --- /dev/null +++ b/DoxaApi/Attributes/DoxaApiDescriptionAttribute.cs @@ -0,0 +1,9 @@ +namespace EonaCat.DoxaApi.Attributes +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class DoxaApiDescriptionAttribute : Attribute + { + public string Description { get; } + public DoxaApiDescriptionAttribute(string description) => Description = description; + } +} diff --git a/DoxaApi/Attributes/DoxaApiExampleAttribute.cs b/DoxaApi/Attributes/DoxaApiExampleAttribute.cs new file mode 100644 index 0000000..347bf82 --- /dev/null +++ b/DoxaApi/Attributes/DoxaApiExampleAttribute.cs @@ -0,0 +1,9 @@ +namespace EonaCat.DoxaApi.Attributes +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class DoxaApiExampleAttribute : Attribute + { + public string Json { get; } + public DoxaApiExampleAttribute(string json) => Json = json; + } +} diff --git a/DoxaApi/Attributes/DoxaApiGroupAttribute.cs b/DoxaApi/Attributes/DoxaApiGroupAttribute.cs new file mode 100644 index 0000000..73c9d66 --- /dev/null +++ b/DoxaApi/Attributes/DoxaApiGroupAttribute.cs @@ -0,0 +1,9 @@ +namespace EonaCat.DoxaApi.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class DoxaApiGroupAttribute : Attribute + { + public string Name { get; } + public DoxaApiGroupAttribute(string name) => Name = name; + } +} diff --git a/DoxaApi/Attributes/DoxaApiHiddenAttribute.cs b/DoxaApi/Attributes/DoxaApiHiddenAttribute.cs new file mode 100644 index 0000000..0ae45e7 --- /dev/null +++ b/DoxaApi/Attributes/DoxaApiHiddenAttribute.cs @@ -0,0 +1,7 @@ +namespace EonaCat.DoxaApi.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class DoxaApiHiddenAttribute : Attribute + { + } +} diff --git a/DoxaApi/Attributes/DoxaApiSummaryAttribute.cs b/DoxaApi/Attributes/DoxaApiSummaryAttribute.cs new file mode 100644 index 0000000..ca8974d --- /dev/null +++ b/DoxaApi/Attributes/DoxaApiSummaryAttribute.cs @@ -0,0 +1,9 @@ +namespace EonaCat.DoxaApi.Attributes +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class DoxaApiSummaryAttribute : Attribute + { + public string Summary { get; } + public DoxaApiSummaryAttribute(string summary) => Summary = summary; + } +} diff --git a/DoxaApi/EonaCat.DoxaApi.csproj b/DoxaApi/EonaCat.DoxaApi.csproj new file mode 100644 index 0000000..dd58ad6 --- /dev/null +++ b/DoxaApi/EonaCat.DoxaApi.csproj @@ -0,0 +1,57 @@ + + + + net8.0 + enable + enable + latest + EonaCat.DoxaApi + EonaCat.DoxaApi + + + EonaCat.DoxaApi + 0.0.2 + EonaCat (Jeroen Saey) + A modern, self-contained, dependency-free OpenAPI documentation UI for ASP.NET Core. + openapi;swagger;documentation;api;aspnetcore;doxa;api;docs;documentation;Jeroen;Saey;EonaCat;Scalar;Redoc;Postman;EchoAPI; + https://git.saey.me/EonaCat/EonaCat.DoxaApi + README.md + true + + true + false + True + EonaCat.DoxaApi + EonaCat + EonaCat.DoxaApi + EonaCat (Jeroen Saey) + https://git.saey.me/EonaCat/EonaCat.DoxaApi + icon.png + + LICENSE + + + + + + + + + + + + + True + \ + + + True + \ + + + True + \ + + + + diff --git a/DoxaApi/Exporter/ApiDocsExporter.cs b/DoxaApi/Exporter/ApiDocsExporter.cs new file mode 100644 index 0000000..a85256f --- /dev/null +++ b/DoxaApi/Exporter/ApiDocsExporter.cs @@ -0,0 +1,4 @@ +namespace EonaCat.DoxaApi.Exporter +{ + public static class DoxaApiExporter { } +} diff --git a/DoxaApi/Exporter/OpenApiExporter.cs b/DoxaApi/Exporter/OpenApiExporter.cs new file mode 100644 index 0000000..845f036 --- /dev/null +++ b/DoxaApi/Exporter/OpenApiExporter.cs @@ -0,0 +1,272 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using EonaCat.DoxaApi.Models; + +namespace EonaCat.DoxaApi.Interop +{ + public static class OpenApiExporter + { + public static JsonObject Export(ApiDocument doc) + { + var root = new JsonObject + { + ["openapi"] = "3.0.3", + ["info"] = BuildInfo(doc.Info), + }; + + if (doc.Servers.Count > 0) + { + var servers = new JsonArray(); + foreach (var s in doc.Servers) + { + servers.Add(new JsonObject { ["url"] = s }); + } + + root["servers"] = servers; + } + + var paths = new JsonObject(); + foreach (var group in doc.Groups) + { + foreach (var endpoint in group.Endpoints) + { + var openApiPath = ToOpenApiPath(endpoint.Path); + if (!paths.ContainsKey(openApiPath)) + { + paths[openApiPath] = new JsonObject(); + } + + var pathItem = (JsonObject)paths[openApiPath]!; + pathItem[endpoint.Method.ToLowerInvariant()] = BuildOperation(endpoint, group.Name); + } + } + root["paths"] = paths; + + if (doc.Schemas.Count > 0) + { + var schemas = new JsonObject(); + foreach (var (name, schema) in doc.Schemas) + { + schemas[name] = SchemaToOpenApi(schema); + } + + root["components"] = new JsonObject { ["schemas"] = schemas }; + } + + return root; + } + + private static JsonObject BuildInfo(ApiInfo info) + { + var obj = new JsonObject + { + ["title"] = info.Title, + ["version"] = info.Version + }; + if (info.Description is not null) + { + obj["description"] = info.Description; + } + + return obj; + } + + private static JsonObject BuildOperation(ApiEndpoint endpoint, string groupName) + { + var op = new JsonObject(); + + var tags = new JsonArray(); + foreach (var t in (endpoint.Tags.Count > 0 ? endpoint.Tags : new List { groupName })) + { + tags.Add(t); + } + + op["tags"] = tags; + + op["operationId"] = endpoint.OperationId; + + if (endpoint.Summary is not null) + { + op["summary"] = endpoint.Summary; + } + + if (endpoint.Description is not null) + { + op["description"] = endpoint.Description; + } + + if (endpoint.Deprecated) + { + op["deprecated"] = true; + } + + if (endpoint.Parameters.Count > 0) + { + var parameters = new JsonArray(); + foreach (var p in endpoint.Parameters) + { + var param = new JsonObject + { + ["name"] = p.Name, + ["in"] = p.In, + ["required"] = p.Required, + ["schema"] = SchemaToOpenApi(p.Schema) + }; + if (p.Description is not null) + { + param["description"] = p.Description; + } + + parameters.Add(param); + } + op["parameters"] = parameters; + } + + if (endpoint.RequestBody is not null) + { + var rb = endpoint.RequestBody; + var content = new JsonObject + { + [rb.ContentType] = new JsonObject { ["schema"] = SchemaToOpenApi(rb.Schema) } + }; + if (rb.Example is not null) + { + ((JsonObject)content[rb.ContentType]!)["example"] = + JsonNode.Parse(rb.Example) ?? JsonValue.Create(rb.Example)!; + } + op["requestBody"] = new JsonObject + { + ["required"] = rb.Required, + ["content"] = content + }; + } + + var responses = new JsonObject(); + foreach (var r in endpoint.Responses) + { + var resp = new JsonObject(); + resp["description"] = r.Description ?? HttpStatusDescription(r.StatusCode); + + if (r.Schema is not null && r.Schema.Type != "void") + { + resp["content"] = new JsonObject + { + ["application/json"] = new JsonObject + { + ["schema"] = SchemaToOpenApi(r.Schema) + } + }; + } + responses[r.StatusCode] = resp; + } + op["responses"] = responses; + + return op; + } + + private static JsonObject SchemaToOpenApi(SchemaModel schema) + { + + if (schema.RefName is not null) + { + return new JsonObject { ["$ref"] = $"#/components/schemas/{schema.RefName}" }; + } + + var obj = new JsonObject(); + + switch (schema.Type) + { + case "void": + return new JsonObject { ["type"] = "object" }; + + case "enum": + obj["type"] = "string"; + if (schema.EnumValues?.Count > 0) + { + var enums = new JsonArray(); + foreach (var v in schema.EnumValues) + { + enums.Add(v); + } + + obj["enum"] = enums; + } + break; + + case "array": + obj["type"] = "array"; + if (schema.Items is not null) + { + obj["items"] = SchemaToOpenApi(schema.Items); + } + + break; + + case "object": + obj["type"] = "object"; + if (schema.Properties?.Count > 0) + { + var props = new JsonObject(); + foreach (var (name, propSchema) in schema.Properties) + { + props[name] = SchemaToOpenApi(propSchema); + } + + obj["properties"] = props; + } + if (schema.Required?.Count > 0) + { + var req = new JsonArray(); + foreach (var r in schema.Required) + { + req.Add(r); + } + + obj["required"] = req; + } + + if (schema.Items is not null && schema.Properties is null) + { + obj["additionalProperties"] = SchemaToOpenApi(schema.Items); + } + + break; + + default: + obj["type"] = schema.Type; + if (schema.Format is not null) + { + obj["format"] = schema.Format; + } + + break; + } + + if (schema.Nullable) + { + obj["nullable"] = true; + } + + return obj; + } + + private static string ToOpenApiPath(string path) + + => path.StartsWith('/') ? path : "/" + path; + + private static string HttpStatusDescription(string code) => code switch + { + "200" => "OK", + "201" => "Created", + "204" => "No Content", + "400" => "Bad Request", + "401" => "Unauthorized", + "403" => "Forbidden", + "404" => "Not Found", + "409" => "Conflict", + "422" => "Unprocessable Entity", + "500" => "Internal Server Error", + _ => "Response" + }; + } +} diff --git a/DoxaApi/Exporter/SwaggerExporter.cs b/DoxaApi/Exporter/SwaggerExporter.cs new file mode 100644 index 0000000..639d1d4 --- /dev/null +++ b/DoxaApi/Exporter/SwaggerExporter.cs @@ -0,0 +1,304 @@ +using System.Text.Json.Nodes; +using EonaCat.DoxaApi.Models; + +namespace EonaCat.DoxaApi.Interop +{ + public static class SwaggerExporter + { + public static JsonObject Export(ApiDocument doc) + { + var root = new JsonObject + { + ["swagger"] = "2.0", + ["info"] = BuildInfo(doc.Info), + }; + + if (doc.Servers.Count > 0 && Uri.TryCreate(doc.Servers[0], UriKind.Absolute, out var uri)) + { + root["host"] = uri.Host + (uri.IsDefaultPort ? "" : $":{uri.Port}"); + root["basePath"] = string.IsNullOrEmpty(uri.AbsolutePath) ? "/" : uri.AbsolutePath; + var schemes = new JsonArray(); + schemes.Add(uri.Scheme); + root["schemes"] = schemes; + } + else + { + root["basePath"] = "/"; + } + + root["consumes"] = new JsonArray { "application/json" }; + root["produces"] = new JsonArray { "application/json" }; + + var paths = new JsonObject(); + foreach (var group in doc.Groups) + { + foreach (var endpoint in group.Endpoints) + { + var swaggerPath = ToSwaggerPath(endpoint.Path); + if (!paths.ContainsKey(swaggerPath)) + { + paths[swaggerPath] = new JsonObject(); + } + + var pathItem = (JsonObject)paths[swaggerPath]!; + pathItem[endpoint.Method.ToLowerInvariant()] = BuildOperation(endpoint, group.Name); + } + } + root["paths"] = paths; + + if (doc.Schemas.Count > 0) + { + var definitions = new JsonObject(); + foreach (var (name, schema) in doc.Schemas) + { + definitions[name] = SchemaToSwagger(schema); + } + + root["definitions"] = definitions; + } + + return root; + } + + private static JsonObject BuildInfo(ApiInfo info) + { + var obj = new JsonObject + { + ["title"] = info.Title, + ["version"] = info.Version + }; + if (info.Description is not null) + { + obj["description"] = info.Description; + } + + return obj; + } + + private static JsonObject BuildOperation(ApiEndpoint endpoint, string groupName) + { + var op = new JsonObject(); + + var tags = new JsonArray(); + foreach (var t in (endpoint.Tags.Count > 0 ? endpoint.Tags : new List { groupName })) + { + tags.Add(t); + } + + op["tags"] = tags; + + op["operationId"] = endpoint.OperationId; + + if (endpoint.Summary is not null) + { + op["summary"] = endpoint.Summary; + } + + if (endpoint.Description is not null) + { + op["description"] = endpoint.Description; + } + + if (endpoint.Deprecated) + { + op["deprecated"] = true; + } + + var parameters = new JsonArray(); + + foreach (var p in endpoint.Parameters) + { + var param = new JsonObject + { + ["name"] = p.Name, + ["in"] = p.In, + ["required"] = p.Required, + }; + if (p.Description is not null) + { + param["description"] = p.Description; + } + + InlineSchemaIntoParam(param, p.Schema); + parameters.Add(param); + } + + if (endpoint.RequestBody is not null) + { + var rb = endpoint.RequestBody; + var bodyParam = new JsonObject + { + ["name"] = "body", + ["in"] = "body", + ["required"] = rb.Required, + ["schema"] = SchemaToSwagger(rb.Schema) + }; + parameters.Add(bodyParam); + } + + if (parameters.Count > 0) + { + op["parameters"] = parameters; + } + + var responses = new JsonObject(); + foreach (var r in endpoint.Responses) + { + var resp = new JsonObject + { + ["description"] = r.Description ?? HttpStatusDescription(r.StatusCode) + }; + if (r.Schema is not null && r.Schema.Type != "void") + { + resp["schema"] = SchemaToSwagger(r.Schema); + } + + responses[r.StatusCode] = resp; + } + op["responses"] = responses; + + return op; + } + + private static void InlineSchemaIntoParam(JsonObject param, SchemaModel schema) + { + if (schema.RefName is not null) + { + + param["type"] = "string"; + return; + } + + switch (schema.Type) + { + case "array": + param["type"] = "array"; + if (schema.Items is not null) + { + var items = new JsonObject(); + InlineSchemaIntoParam(items, schema.Items); + param["items"] = items; + } + break; + + case "enum": + param["type"] = "string"; + if (schema.EnumValues?.Count > 0) + { + var enums = new JsonArray(); + foreach (var v in schema.EnumValues) + { + enums.Add(v); + } + + param["enum"] = enums; + } + break; + + default: + param["type"] = schema.Type == "void" ? "string" : schema.Type; + if (schema.Format is not null) + { + param["format"] = schema.Format; + } + + break; + } + } + + private static JsonObject SchemaToSwagger(SchemaModel schema) + { + if (schema.RefName is not null) + { + return new JsonObject { ["$ref"] = $"#/definitions/{schema.RefName}" }; + } + + var obj = new JsonObject(); + + switch (schema.Type) + { + case "void": + return new JsonObject { ["type"] = "object" }; + + case "enum": + obj["type"] = "string"; + if (schema.EnumValues?.Count > 0) + { + var enums = new JsonArray(); + foreach (var v in schema.EnumValues) + { + enums.Add(v); + } + + obj["enum"] = enums; + } + break; + + case "array": + obj["type"] = "array"; + if (schema.Items is not null) + { + obj["items"] = SchemaToSwagger(schema.Items); + } + + break; + + case "object": + obj["type"] = "object"; + if (schema.Properties?.Count > 0) + { + var props = new JsonObject(); + foreach (var (name, propSchema) in schema.Properties) + { + props[name] = SchemaToSwagger(propSchema); + } + + obj["properties"] = props; + } + if (schema.Required?.Count > 0) + { + var req = new JsonArray(); + foreach (var r in schema.Required) + { + req.Add(r); + } + + obj["required"] = req; + } + if (schema.Items is not null && schema.Properties is null) + { + obj["additionalProperties"] = SchemaToSwagger(schema.Items); + } + + break; + + default: + obj["type"] = schema.Type; + if (schema.Format is not null) + { + obj["format"] = schema.Format; + } + + break; + } + + return obj; + } + + private static string ToSwaggerPath(string path) + => path.StartsWith('/') ? path : "/" + path; + + private static string HttpStatusDescription(string code) => code switch + { + "200" => "OK", + "201" => "Created", + "204" => "No Content", + "400" => "Bad Request", + "401" => "Unauthorized", + "403" => "Forbidden", + "404" => "Not Found", + "500" => "Internal Server Error", + _ => "Response" + }; + } +} diff --git a/DoxaApi/Generation/ApiDocumentGenerator.cs b/DoxaApi/Generation/ApiDocumentGenerator.cs new file mode 100644 index 0000000..6c823ad --- /dev/null +++ b/DoxaApi/Generation/ApiDocumentGenerator.cs @@ -0,0 +1,278 @@ +using System.Reflection; +using System.Xml.Linq; +using EonaCat.DoxaApi.Attributes; +using EonaCat.DoxaApi.Models; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace EonaCat.DoxaApi.Generation +{ + public sealed class ApiDocumentGenerator + { + private readonly IReadOnlyList _actions; + private readonly DoxaApiOptions _options; + private readonly Dictionary _xmlReadersByAssembly = new(); + + public ApiDocumentGenerator(IReadOnlyList actions, DoxaApiOptions options) + { + _actions = actions; + _options = options; + } + + public ApiDocument Generate() + { + var doc = new ApiDocument + { + Info = new ApiInfo + { + Title = _options.Title, + Description = _options.Description, + Version = _options.Version + }, + Servers = _options.Servers.ToList() + }; + + var schemaRegistry = new Dictionary(); + var schemaBuilder = new SchemaBuilder(schemaRegistry); + var groups = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var action in _actions) + { + if (action is not ControllerActionDescriptor cad) + { + continue; + } + + if (cad.MethodInfo.GetCustomAttribute() is not null) + { + continue; + } + + if (cad.ControllerTypeInfo.GetCustomAttribute() is not null) + { + continue; + } + + var groupName = ResolveGroupName(cad); + if (!groups.TryGetValue(groupName, out var group)) + { + group = new ApiGroup { Name = groupName }; + groups[groupName] = group; + } + + var endpoint = BuildEndpoint(cad, schemaBuilder); + if (endpoint is not null) + { + group.Endpoints.Add(endpoint); + } + } + + doc.Groups = groups.Values + .OrderBy(g => g.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + + foreach (var group in doc.Groups) + { + group.Endpoints = group.Endpoints + .OrderBy(e => e.Path, StringComparer.OrdinalIgnoreCase) + .ThenBy(e => MethodSortOrder(e.Method)) + .ToList(); + } + + doc.Schemas = schemaRegistry; + + return doc; + } + + private static int MethodSortOrder(string method) => method switch + { + "GET" => 0, + "POST" => 1, + "PUT" => 2, + "PATCH" => 3, + "DELETE" => 4, + _ => 5 + }; + + private string ResolveGroupName(ControllerActionDescriptor cad) + { + var methodAttr = cad.MethodInfo.GetCustomAttribute(); + if (methodAttr is not null) + { + return methodAttr.Name; + } + + var classAttr = cad.ControllerTypeInfo.GetCustomAttribute(); + if (classAttr is not null) + { + return classAttr.Name; + } + + var name = cad.ControllerName; + return name; + } + + private ApiEndpoint? BuildEndpoint(ControllerActionDescriptor cad, SchemaBuilder schemaBuilder) + { + var httpMethod = cad.ActionConstraints? + .OfType() + .FirstOrDefault()?.HttpMethods.FirstOrDefault() ?? "GET"; + + var path = "/" + (cad.AttributeRouteInfo?.Template?.TrimStart('/') ?? cad.ActionName); + + var xmlReader = GetXmlReader(cad.MethodInfo.DeclaringType!.Assembly); + var xmlDoc = xmlReader?.GetMethodDoc(cad.MethodInfo); + + var summaryAttr = cad.MethodInfo.GetCustomAttribute(); + var descAttr = cad.MethodInfo.GetCustomAttribute(); + var exampleAttr = cad.MethodInfo.GetCustomAttribute(); + var obsoleteAttr = cad.MethodInfo.GetCustomAttribute(); + + var endpoint = new ApiEndpoint + { + OperationId = $"{cad.ControllerName}_{cad.ActionName}", + Method = httpMethod.ToUpperInvariant(), + Path = path, + Summary = summaryAttr?.Summary ?? xmlDoc?.Summary ?? HumanizeName(cad.ActionName), + Description = descAttr?.Description ?? xmlDoc?.Remarks, + Deprecated = obsoleteAttr is not null, + Tags = new List { ResolveGroupName(cad) } + }; + + foreach (var param in cad.Parameters) + { + var bindingSource = param.BindingInfo?.BindingSource; + var inLocation = bindingSource?.Id switch + { + "Path" => "path", + "Query" => "query", + "Header" => "header", + "Body" => "body", + "Form" => "form", + _ => InferLocationFromPath(param.Name, path) + }; + + if (inLocation == "body") + { + endpoint.RequestBody = new RequestBodyModel + { + Required = true, + Schema = schemaBuilder.Build(param.ParameterType), + Example = exampleAttr?.Json + }; + continue; + } + + var paramDoc = xmlDoc?.Params.GetValueOrDefault(param.Name); + + endpoint.Parameters.Add(new ApiParameter + { + Name = param.Name, + In = inLocation, + Required = inLocation == "path" || IsRequiredParam(param), + Description = paramDoc, + Schema = schemaBuilder.Build(param.ParameterType) + }); + } + + var returnType = cad.MethodInfo.ReturnType; + var responseSchema = schemaBuilder.Build(returnType); + var successCode = httpMethod.ToUpperInvariant() switch + { + "POST" => "201", + "DELETE" => "204", + _ => "200" + }; + + if (responseSchema.Type != "void") + { + endpoint.Responses.Add(new ResponseModel + { + StatusCode = successCode, + Description = xmlDoc?.Returns ?? "Success", + Schema = responseSchema + }); + } + else + { + endpoint.Responses.Add(new ResponseModel + { + StatusCode = successCode, + Description = "Success" + }); + } + + if (endpoint.Parameters.Any(p => p.Required) || endpoint.RequestBody is not null) + { + endpoint.Responses.Add(new ResponseModel { StatusCode = "400", Description = "Invalid request" }); + } + + return endpoint; + } + + private static bool IsRequiredParam(ParameterDescriptor param) + { + if (param is ControllerParameterDescriptor cpd) + { + var nullableUnderlying = Nullable.GetUnderlyingType(cpd.ParameterInfo.ParameterType); + bool hasDefault = cpd.ParameterInfo.HasDefaultValue; + bool isNullableType = nullableUnderlying is not null || !cpd.ParameterInfo.ParameterType.IsValueType; + return !hasDefault && !isNullableType; + } + return false; + } + + private static string InferLocationFromPath(string paramName, string path) + { + return path.Contains("{" + paramName + "}", StringComparison.OrdinalIgnoreCase) ? "path" : "query"; + } + + private static string HumanizeName(string name) + { + + var chars = new List(); + for (int i = 0; i < name.Length; i++) + { + if (i > 0 && char.IsUpper(name[i]) && !char.IsUpper(name[i - 1])) + { + chars.Add(' '); + } + + chars.Add(name[i]); + } + return new string(chars.ToArray()); + } + + private XmlDocReader? GetXmlReader(Assembly assembly) + { + var key = assembly.GetName().Name ?? assembly.FullName ?? "unknown"; + if (_xmlReadersByAssembly.TryGetValue(key, out var cached)) + { + return cached; + } + + var location = assembly.Location; + if (string.IsNullOrEmpty(location)) + { + return null; + } + + var xmlPath = Path.ChangeExtension(location, ".xml"); + XmlDocReader? reader = null; + if (File.Exists(xmlPath)) + { + try + { + reader = new XmlDocReader(XDocument.Load(xmlPath)); + } + catch + { + reader = null; + } + } + + _xmlReadersByAssembly[key] = reader; + return reader; + } + } +} diff --git a/DoxaApi/Generation/DoxaApiOptions.cs b/DoxaApi/Generation/DoxaApiOptions.cs new file mode 100644 index 0000000..35e87e4 --- /dev/null +++ b/DoxaApi/Generation/DoxaApiOptions.cs @@ -0,0 +1,13 @@ +namespace EonaCat.DoxaApi.Generation +{ + public sealed class DoxaApiOptions + { + public string Title { get; set; } = "DoxaApi Documentation"; + public string? Description { get; set; } + public string Version { get; set; } = "v1"; + public string RoutePrefix { get; set; } = "doxa"; + public List Servers { get; set; } = new(); + public string Theme { get; set; } = "auto"; + public string AccentColor { get; set; } = "#6366f1"; + } +} diff --git a/DoxaApi/Generation/SchemaBuilder.cs b/DoxaApi/Generation/SchemaBuilder.cs new file mode 100644 index 0000000..2ddc500 --- /dev/null +++ b/DoxaApi/Generation/SchemaBuilder.cs @@ -0,0 +1,294 @@ +using System.Collections; +using System.Reflection; +using EonaCat.DoxaApi.Models; + +namespace EonaCat.DoxaApi.Generation +{ + public sealed class SchemaBuilder + { + private readonly Dictionary _registry; + private readonly HashSet _inProgress = new(); + + public SchemaBuilder(Dictionary registry) + { + _registry = registry; + } + + public SchemaModel Build(Type type) + { + + type = UnwrapAsyncAndActionResult(type); + + var underlying = Nullable.GetUnderlyingType(type); + bool nullable = underlying is not null; + if (underlying is not null) + { + type = underlying; + } + + var schema = BuildCore(type); + schema.Nullable = nullable || schema.Nullable; + return schema; + } + + private static Type UnwrapAsyncAndActionResult(Type type) + { + if (type == typeof(void)) + { + return type; + } + + if (type.IsGenericType) + { + var def = type.GetGenericTypeDefinition(); + if (def == typeof(Task<>) || def.Name.StartsWith("ValueTask`")) + { + return UnwrapAsyncAndActionResult(type.GetGenericArguments()[0]); + } + + if (def.Name is "ActionResult`1") + { + return UnwrapAsyncAndActionResult(type.GetGenericArguments()[0]); + } + } + + if (type == typeof(Task)) + { + return typeof(void); + } + + return type; + } + + private SchemaModel BuildCore(Type type) + { + if (type == typeof(void)) + { + return new SchemaModel { Type = "void" }; + } + + if (type == typeof(string) || type == typeof(char)) + { + return new SchemaModel { Type = "string" }; + } + + if (type == typeof(bool)) + { + return new SchemaModel { Type = "boolean" }; + } + + if (type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || + type == typeof(ushort) || type == typeof(int) || type == typeof(uint) || + type == typeof(long) || type == typeof(ulong)) + { + return new SchemaModel { Type = "integer", Format = type.Name.ToLowerInvariant() }; + } + + if (type == typeof(float) || type == typeof(double) || type == typeof(decimal)) + { + return new SchemaModel { Type = "number", Format = type.Name.ToLowerInvariant() }; + } + + if (type == typeof(DateTime) || type == typeof(DateTimeOffset)) + { + return new SchemaModel { Type = "string", Format = "date-time" }; + } + + if (type == typeof(DateOnly)) + { + return new SchemaModel { Type = "string", Format = "date" }; + } + + if (type == typeof(TimeOnly) || type == typeof(TimeSpan)) + { + return new SchemaModel { Type = "string", Format = "time" }; + } + + if (type == typeof(Guid)) + { + return new SchemaModel { Type = "string", Format = "uuid" }; + } + + if (type == typeof(Uri)) + { + return new SchemaModel { Type = "string", Format = "uri" }; + } + + if (type == typeof(object)) + { + return new SchemaModel { Type = "object" }; + } + + if (type.IsEnum) + { + return new SchemaModel + { + Type = "enum", + EnumValues = Enum.GetNames(type).ToList() + }; + } + + if (IsDictionary(type, out var valueType)) + { + return new SchemaModel + { + Type = "object", + Items = Build(valueType!) + }; + } + + var elementType = GetEnumerableElementType(type); + if (elementType is not null) + { + return new SchemaModel + { + Type = "array", + Items = Build(elementType) + }; + } + + var refName = FriendlyTypeName(type); + + if (_registry.ContainsKey(refName)) + { + return new SchemaModel { Type = "object", RefName = refName }; + } + + if (_inProgress.Contains(type)) + { + return new SchemaModel { Type = "object", RefName = refName }; + } + + _inProgress.Add(type); + + var props = new Dictionary(); + var required = new List(); + + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (prop.GetIndexParameters().Length > 0) + { + continue; + } + + if (!prop.CanRead) + { + continue; + } + + var propSchema = Build(prop.PropertyType); + props[ToCamelCase(prop.Name)] = propSchema; + + if (!propSchema.Nullable && Nullable.GetUnderlyingType(prop.PropertyType) is null + && (prop.PropertyType.IsValueType || IsNonNullableReferenceProperty(prop))) + { + required.Add(ToCamelCase(prop.Name)); + } + } + + _registry[refName] = new SchemaModel + { + Type = "object", + Properties = props, + Required = required.Count > 0 ? required : null + }; + + _inProgress.Remove(type); + + return new SchemaModel { Type = "object", RefName = refName }; + } + + private static bool IsNonNullableReferenceProperty(PropertyInfo prop) + { + + try + { + var ctx = new NullabilityInfoContext(); + var info = ctx.Create(prop); + return info.ReadState == NullabilityState.NotNull; + } + catch + { + return false; + } + } + + private static Type? GetEnumerableElementType(Type type) + { + if (type == typeof(string)) + { + return null; + } + + if (type.IsArray) + { + return type.GetElementType(); + } + + if (type.IsGenericType) + { + var def = type.GetGenericTypeDefinition(); + if (def == typeof(List<>) || def == typeof(IList<>) || def == typeof(ICollection<>) || + def == typeof(IEnumerable<>) || def == typeof(IReadOnlyList<>) || def == typeof(IReadOnlyCollection<>) || + def == typeof(HashSet<>) || def == typeof(Queue<>) || def == typeof(Stack<>)) + { + return type.GetGenericArguments()[0]; + } + } + + foreach (var iface in type.GetInterfaces()) + { + if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + return iface.GetGenericArguments()[0]; + } + } + + if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string)) + { + return typeof(object); + } + + return null; + } + + private static bool IsDictionary(Type type, out Type? valueType) + { + valueType = null; + if (!type.IsGenericType) + { + return false; + } + + var def = type.GetGenericTypeDefinition(); + if (def == typeof(Dictionary<,>) || def == typeof(IDictionary<,>) || def == typeof(IReadOnlyDictionary<,>)) + { + valueType = type.GetGenericArguments()[1]; + return true; + } + return false; + } + + private static string FriendlyTypeName(Type type) + { + if (!type.IsGenericType) + { + return type.Name; + } + + var name = type.Name[..type.Name.IndexOf('`')]; + var args = string.Join(",", type.GetGenericArguments().Select(FriendlyTypeName)); + return $"{name}<{args}>"; + } + + private static string ToCamelCase(string name) + { + if (string.IsNullOrEmpty(name) || char.IsLower(name[0])) + { + return name; + } + + return char.ToLowerInvariant(name[0]) + name[1..]; + } + } +} diff --git a/DoxaApi/Generation/XmlDocReader.cs b/DoxaApi/Generation/XmlDocReader.cs new file mode 100644 index 0000000..a85ffb0 --- /dev/null +++ b/DoxaApi/Generation/XmlDocReader.cs @@ -0,0 +1,103 @@ +using System.Reflection; +using System.Text; +using System.Xml.Linq; + +namespace EonaCat.DoxaApi.Generation +{ + internal sealed class MethodXmlDoc + { + public string? Summary { get; set; } + public string? Remarks { get; set; } + public string? Returns { get; set; } + public Dictionary Params { get; } = new(); + } + + internal sealed class XmlDocReader + { + private readonly Dictionary _members = new(); + + public XmlDocReader(XDocument document) + { + var members = document.Root?.Element("members")?.Elements("member"); + if (members is null) + { + return; + } + + foreach (var member in members) + { + var name = member.Attribute("name")?.Value; + if (name is null || !name.StartsWith("M:")) + { + continue; + } + + var doc = new MethodXmlDoc + { + Summary = CleanText(member.Element("summary")?.Value), + Remarks = CleanText(member.Element("remarks")?.Value), + Returns = CleanText(member.Element("returns")?.Value) + }; + + foreach (var paramEl in member.Elements("param")) + { + var pName = paramEl.Attribute("name")?.Value; + if (pName is not null) + { + doc.Params[pName] = CleanText(paramEl.Value) ?? ""; + } + } + + _members[name] = doc; + } + } + + public MethodXmlDoc? GetMethodDoc(MethodInfo method) + { + var key = BuildMemberKey(method); + return _members.GetValueOrDefault(key); + } + + private static string? CleanText(string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) + { + return null; + } + + var lines = raw.Split('\n').Select(l => l.Trim()); + return string.Join(" ", lines).Trim(); + } + + private static string BuildMemberKey(MethodInfo method) + { + var sb = new StringBuilder("M:"); + sb.Append(method.DeclaringType!.FullName!.Replace('+', '.')); + sb.Append('.'); + sb.Append(method.Name); + + var parameters = method.GetParameters(); + if (parameters.Length > 0) + { + sb.Append('('); + sb.Append(string.Join(",", parameters.Select(p => XmlTypeName(p.ParameterType)))); + sb.Append(')'); + } + + return sb.ToString(); + } + + private static string XmlTypeName(Type type) + { + if (type.IsGenericType) + { + var def = type.GetGenericTypeDefinition(); + var name = def.FullName![..def.FullName!.IndexOf('`')]; + var args = string.Join(",", type.GetGenericArguments().Select(XmlTypeName)); + return $"{name}{{{args}}}"; + } + + return type.FullName?.Replace('+', '.') ?? type.Name; + } + } +} diff --git a/DoxaApi/Importer/ApiDocsImporter.cs b/DoxaApi/Importer/ApiDocsImporter.cs new file mode 100644 index 0000000..0c0dece --- /dev/null +++ b/DoxaApi/Importer/ApiDocsImporter.cs @@ -0,0 +1,20 @@ +using EonaCat.DoxaApi.Models; +using System.Text.Json; + +namespace EonaCat.DoxaApi.Interop +{ + public static class DoxaApiImporter + { + public static ApiDocument Import(string json) + { + return JsonSerializer.Deserialize(json) + ?? throw new InvalidOperationException("Invalid DoxaApi spec."); + } + + public static async Task ImportAsync(Stream stream) + { + var doc = await JsonSerializer.DeserializeAsync(stream); + return doc ?? throw new InvalidOperationException("Invalid DoxaApi spec."); + } + } +} diff --git a/DoxaApi/Importer/OpenApiImporter.cs b/DoxaApi/Importer/OpenApiImporter.cs new file mode 100644 index 0000000..2c4cb36 --- /dev/null +++ b/DoxaApi/Importer/OpenApiImporter.cs @@ -0,0 +1,603 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using EonaCat.DoxaApi.Models; + +namespace EonaCat.DoxaApi.Interop +{ + public static class OpenApiImporter + { + + public static ApiDocument Import(string json) + { + var node = JsonNode.Parse(json) ?? throw new ArgumentException("Input is not valid JSON."); + return Import(node); + } + + public static async Task ImportAsync(Stream stream) + { + var node = await JsonNode.ParseAsync(stream) + ?? throw new ArgumentException("Stream is not valid JSON."); + + return Import(node); + } + + public static ApiDocument Import(JsonNode root) + { + if (root["openapi"] is not null) + { + return ImportOpenApi3(root); + } + + if (root["swagger"] is not null) + { + return ImportSwagger2(root); + } + + if (root["groups"] is not null && + root["info"] is not null) + { + return JsonSerializer.Deserialize(root)! + ?? throw new InvalidOperationException(); + } + + throw new NotSupportedException( + "Supported formats: DoxaApi, OpenAPI 3.x, Swagger 2.0"); + } + + private static ApiDocument ImportOpenApi3(JsonNode root) + { + var doc = new ApiDocument(); + + if (root["info"] is JsonObject info) + { + doc.Info.Title = info["title"]?.GetValue() ?? "API"; + doc.Info.Version = info["version"]?.GetValue() ?? "v1"; + doc.Info.Description = info["description"]?.GetValue(); + } + + if (root["servers"] is JsonArray servers) + { + foreach (var s in servers) + { + if (s?["url"]?.GetValue() is string url) + { + doc.Servers.Add(url); + } + } + } + + if (root["components"]?["schemas"] is JsonObject compSchemas) + { + foreach (var (name, schemaNode) in compSchemas) + { + if (schemaNode is not null) + { + doc.Schemas[name] = ParseSchema3(schemaNode); + } + } + } + + var groups = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (root["paths"] is JsonObject paths) + { + foreach (var (rawPath, pathNode) in paths) + { + if (pathNode is not JsonObject pathItem) + { + continue; + } + + foreach (var (methodStr, opNode) in pathItem) + { + if (opNode is not JsonObject op) + { + continue; + } + + if (!IsHttpMethod(methodStr)) + { + continue; + } + + var endpoint = ParseOperation3(op, rawPath, methodStr.ToUpperInvariant()); + var groupName = endpoint.Tags.FirstOrDefault() ?? "Default"; + + if (!groups.TryGetValue(groupName, out var group)) + { + group = new ApiGroup { Name = groupName }; + groups[groupName] = group; + } + group.Endpoints.Add(endpoint); + } + } + } + + doc.Groups = groups.Values + .OrderBy(g => g.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + + return doc; + } + + private static ApiEndpoint ParseOperation3(JsonObject op, string path, string method) + { + var endpoint = new ApiEndpoint + { + OperationId = op["operationId"]?.GetValue() ?? $"{method}_{SanitizePath(path)}", + Summary = op["summary"]?.GetValue(), + Description = op["description"]?.GetValue(), + Method = method, + Path = path, + Deprecated = op["deprecated"]?.GetValue() ?? false, + Tags = ParseStringArray(op["tags"]) + }; + + if (op["parameters"] is JsonArray parameters) + { + foreach (var p in parameters) + { + if (p is not JsonObject param) + { + continue; + } + + var schema = p["schema"] is JsonNode schemaNode + ? ParseSchema3(schemaNode) + : new SchemaModel { Type = "string" }; + + endpoint.Parameters.Add(new ApiParameter + { + Name = param["name"]?.GetValue() ?? "", + In = param["in"]?.GetValue() ?? "query", + Required = param["required"]?.GetValue() ?? false, + Description = param["description"]?.GetValue(), + Schema = schema + }); + } + } + + if (op["requestBody"] is JsonObject rb) + { + var required = rb["required"]?.GetValue() ?? false; + var contentNode = rb["content"] as JsonObject; + + var (contentType, mediaObj) = PickMediaType(contentNode); + + SchemaModel schema; + string? example = null; + + if (mediaObj is JsonObject mo) + { + schema = mo["schema"] is JsonNode s ? ParseSchema3(s) : new SchemaModel { Type = "object" }; + example = mo["example"]?.ToJsonString(); + } + else + { + schema = new SchemaModel { Type = "object" }; + } + + endpoint.RequestBody = new RequestBodyModel + { + Required = required, + ContentType = contentType, + Schema = schema, + Example = example + }; + } + + if (op["responses"] is JsonObject responses) + { + foreach (var (statusCode, respNode) in responses) + { + if (respNode is not JsonObject resp) + { + continue; + } + + SchemaModel? schema = null; + if (resp["content"] is JsonObject content) + { + var (_, mediaObj) = PickMediaType(content); + if (mediaObj?["schema"] is JsonNode s) + { + schema = ParseSchema3(s); + } + } + + endpoint.Responses.Add(new ResponseModel + { + StatusCode = statusCode, + Description = resp["description"]?.GetValue(), + Schema = schema + }); + } + } + + return endpoint; + } + + private static SchemaModel ParseSchema3(JsonNode node) + { + if (node is not JsonObject obj) + { + return new SchemaModel { Type = "object" }; + } + + if (obj["$ref"]?.GetValue() is string refVal) + { + var refName = refVal.Split('/').Last(); + return new SchemaModel { Type = "object", RefName = refName }; + } + + bool nullable = obj["nullable"]?.GetValue() ?? false; + + if (obj["enum"] is JsonArray enumArray) + { + return new SchemaModel + { + Type = "enum", + EnumValues = enumArray.Select(e => e?.GetValue() ?? "").ToList(), + Nullable = nullable + }; + } + + var type = obj["type"]?.GetValue() ?? "object"; + + if (type == "array") + { + return new SchemaModel + { + Type = "array", + Items = obj["items"] is JsonNode items ? ParseSchema3(items) : null, + Nullable = nullable + }; + } + + if (type == "object") + { + Dictionary? props = null; + if (obj["properties"] is JsonObject propsNode) + { + props = new Dictionary(); + foreach (var (name, propNode) in propsNode) + { + if (propNode is not null) + { + props[name] = ParseSchema3(propNode); + } + } + } + + SchemaModel? dictItems = null; + if (obj["additionalProperties"] is JsonNode addProps) + { + dictItems = ParseSchema3(addProps); + } + + return new SchemaModel + { + Type = "object", + Properties = props, + Required = ParseStringList(obj["required"]), + Items = dictItems, + Nullable = nullable + }; + } + + return new SchemaModel + { + Type = type, + Format = obj["format"]?.GetValue(), + Nullable = nullable + }; + } + + private static ApiDocument ImportSwagger2(JsonNode root) + { + var doc = new ApiDocument(); + + if (root["info"] is JsonObject info) + { + doc.Info.Title = info["title"]?.GetValue() ?? "API"; + doc.Info.Version = info["version"]?.GetValue() ?? "v1"; + doc.Info.Description = info["description"]?.GetValue(); + } + + var host = root["host"]?.GetValue(); + var basePath = root["basePath"]?.GetValue() ?? "/"; + var scheme = root["schemes"] is JsonArray schemes && schemes.Count > 0 + ? schemes[0]?.GetValue() ?? "https" + : "https"; + + if (host is not null) + { + doc.Servers.Add($"{scheme}://{host}{basePath.TrimEnd('/')}"); + } + + if (root["definitions"] is JsonObject defs) + { + foreach (var (name, defNode) in defs) + { + if (defNode is not null) + { + doc.Schemas[name] = ParseSchema2(defNode); + } + } + } + + var groups = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (root["paths"] is JsonObject paths) + { + foreach (var (rawPath, pathNode) in paths) + { + if (pathNode is not JsonObject pathItem) + { + continue; + } + + foreach (var (methodStr, opNode) in pathItem) + { + if (opNode is not JsonObject op) + { + continue; + } + + if (!IsHttpMethod(methodStr)) + { + continue; + } + + var endpoint = ParseOperation2(op, rawPath, methodStr.ToUpperInvariant()); + var groupName = endpoint.Tags.FirstOrDefault() ?? "Default"; + + if (!groups.TryGetValue(groupName, out var group)) + { + group = new ApiGroup { Name = groupName }; + groups[groupName] = group; + } + group.Endpoints.Add(endpoint); + } + } + } + + doc.Groups = groups.Values + .OrderBy(g => g.Name, StringComparer.OrdinalIgnoreCase) + .ToList(); + + return doc; + } + + private static ApiEndpoint ParseOperation2(JsonObject op, string path, string method) + { + var endpoint = new ApiEndpoint + { + OperationId = op["operationId"]?.GetValue() ?? $"{method}_{SanitizePath(path)}", + Summary = op["summary"]?.GetValue(), + Description = op["description"]?.GetValue(), + Method = method, + Path = path, + Deprecated = op["deprecated"]?.GetValue() ?? false, + Tags = ParseStringArray(op["tags"]) + }; + + if (op["parameters"] is JsonArray parameters) + { + foreach (var p in parameters) + { + if (p is not JsonObject param) + { + continue; + } + + var inLoc = param["in"]?.GetValue() ?? "query"; + + if (inLoc == "body") + { + endpoint.RequestBody = new RequestBodyModel + { + Required = param["required"]?.GetValue() ?? false, + ContentType = "application/json", + Schema = param["schema"] is JsonNode s ? ParseSchema2(s) : new SchemaModel { Type = "object" } + }; + continue; + } + + endpoint.Parameters.Add(new ApiParameter + { + Name = param["name"]?.GetValue() ?? "", + In = inLoc, + Required = param["required"]?.GetValue() ?? false, + Description = param["description"]?.GetValue(), + Schema = ParseInlineSchema2(param) + }); + } + } + + if (op["responses"] is JsonObject responses) + { + foreach (var (statusCode, respNode) in responses) + { + if (respNode is not JsonObject resp) + { + continue; + } + + SchemaModel? schema = null; + if (resp["schema"] is JsonNode s) + { + schema = ParseSchema2(s); + } + + endpoint.Responses.Add(new ResponseModel + { + StatusCode = statusCode, + Description = resp["description"]?.GetValue(), + Schema = schema + }); + } + } + + return endpoint; + } + + private static SchemaModel ParseInlineSchema2(JsonObject param) + { + if (param["$ref"]?.GetValue() is string refVal) + { + return new SchemaModel { Type = "object", RefName = refVal.Split('/').Last() }; + } + + if (param["enum"] is JsonArray enumArray) + { + return new SchemaModel + { + Type = "enum", + EnumValues = enumArray.Select(e => e?.GetValue() ?? "").ToList() + }; + } + + var type = param["type"]?.GetValue() ?? "string"; + if (type == "array") + { + return new SchemaModel + { + Type = "array", + Items = param["items"] is JsonNode items ? ParseInlineSchema2((JsonObject)items) : null + }; + } + + return new SchemaModel + { + Type = type, + Format = param["format"]?.GetValue() + }; + } + + private static SchemaModel ParseSchema2(JsonNode node) + { + if (node is not JsonObject obj) + { + return new SchemaModel { Type = "object" }; + } + + if (obj["$ref"]?.GetValue() is string refVal) + { + return new SchemaModel { Type = "object", RefName = refVal.Split('/').Last() }; + } + + if (obj["enum"] is JsonArray enumArray) + { + return new SchemaModel + { + Type = "enum", + EnumValues = enumArray.Select(e => e?.GetValue() ?? "").ToList() + }; + } + + var type = obj["type"]?.GetValue() ?? "object"; + + if (type == "array") + { + return new SchemaModel + { + Type = "array", + Items = obj["items"] is JsonNode items ? ParseSchema2(items) : null + }; + } + + if (type == "object") + { + Dictionary? props = null; + if (obj["properties"] is JsonObject propsNode) + { + props = new Dictionary(); + foreach (var (name, propNode) in propsNode) + { + if (propNode is not null) + { + props[name] = ParseSchema2(propNode); + } + } + } + + SchemaModel? dictItems = null; + if (obj["additionalProperties"] is JsonNode addProps) + { + dictItems = ParseSchema2(addProps); + } + + return new SchemaModel + { + Type = "object", + Properties = props, + Required = ParseStringList(obj["required"]), + Items = dictItems + }; + } + + return new SchemaModel + { + Type = type, + Format = obj["format"]?.GetValue() + }; + } + + private static (string contentType, JsonObject? mediaObj) PickMediaType(JsonObject? content) + { + if (content is null) + { + return ("application/json", null); + } + + if (content["application/json"] is JsonObject json) + { + return ("application/json", json); + } + + foreach (var (ct, node) in content) + { + if (node is JsonObject obj) + { + return (ct, obj); + } + } + + return ("application/json", null); + } + + private static List ParseStringArray(JsonNode? node) + { + var list = new List(); + if (node is JsonArray arr) + { + foreach (var item in arr) + { + if (item?.GetValue() is string s) + { + list.Add(s); + } + } + } + + return list; + } + + private static List? ParseStringList(JsonNode? node) + { + if (node is not JsonArray arr || arr.Count == 0) + { + return null; + } + + return arr.Select(e => e?.GetValue() ?? "").ToList(); + } + + private static bool IsHttpMethod(string s) => + s is "get" or "post" or "put" or "patch" or "delete" or "head" or "options" or "trace"; + + private static string SanitizePath(string path) + => path.Trim('/').Replace('/', '_').Replace('{', '_').Replace('}', '_'); + } +} diff --git a/DoxaApi/Middleware/ApiDocsMiddleware.cs b/DoxaApi/Middleware/ApiDocsMiddleware.cs new file mode 100644 index 0000000..3b46954 --- /dev/null +++ b/DoxaApi/Middleware/ApiDocsMiddleware.cs @@ -0,0 +1,182 @@ +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? configure = null) + { + + var options = app.ApplicationServices.GetService() ?? 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(); + 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 = "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); + } + } +} diff --git a/DoxaApi/Models/ApiDocument.cs b/DoxaApi/Models/ApiDocument.cs new file mode 100644 index 0000000..d909d06 --- /dev/null +++ b/DoxaApi/Models/ApiDocument.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class ApiDocument + { + [JsonPropertyName("info")] + public ApiInfo Info { get; set; } = new(); + + [JsonPropertyName("servers")] + public List Servers { get; set; } = new(); + + [JsonPropertyName("groups")] + public List Groups { get; set; } = new(); + + [JsonPropertyName("schemas")] + public Dictionary Schemas { get; set; } = new(); + } +} diff --git a/DoxaApi/Models/ApiEndpoint.cs b/DoxaApi/Models/ApiEndpoint.cs new file mode 100644 index 0000000..947124e --- /dev/null +++ b/DoxaApi/Models/ApiEndpoint.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class ApiEndpoint + { + [JsonPropertyName("operationId")] + public string OperationId { get; set; } = ""; + + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("method")] + public string Method { get; set; } = "GET"; + + [JsonPropertyName("path")] + public string Path { get; set; } = "/"; + + [JsonPropertyName("deprecated")] + public bool Deprecated { get; set; } + + [JsonPropertyName("tags")] + public List Tags { get; set; } = new(); + + [JsonPropertyName("parameters")] + public List Parameters { get; set; } = new(); + + [JsonPropertyName("requestBody")] + public RequestBodyModel? RequestBody { get; set; } + + [JsonPropertyName("responses")] + public List Responses { get; set; } = new(); + } +} diff --git a/DoxaApi/Models/ApiGroup.cs b/DoxaApi/Models/ApiGroup.cs new file mode 100644 index 0000000..a2931f5 --- /dev/null +++ b/DoxaApi/Models/ApiGroup.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class ApiGroup + { + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("endpoints")] + public List Endpoints { get; set; } = new(); + } +} diff --git a/DoxaApi/Models/ApiInfo.cs b/DoxaApi/Models/ApiInfo.cs new file mode 100644 index 0000000..21a6452 --- /dev/null +++ b/DoxaApi/Models/ApiInfo.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class ApiInfo + { + [JsonPropertyName("title")] + public string Title { get; set; } = "API Documentation"; + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("version")] + public string Version { get; set; } = "v1"; + } +} diff --git a/DoxaApi/Models/ApiParameter.cs b/DoxaApi/Models/ApiParameter.cs new file mode 100644 index 0000000..992d1f5 --- /dev/null +++ b/DoxaApi/Models/ApiParameter.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class ApiParameter + { + [JsonPropertyName("name")] + public string Name { get; set; } = ""; + + [JsonPropertyName("in")] + public string In { get; set; } = "query"; + + [JsonPropertyName("required")] + public bool Required { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("schema")] + public SchemaModel Schema { get; set; } = new(); + + [JsonPropertyName("default")] + public object? Default { get; set; } + } +} diff --git a/DoxaApi/Models/RequestBodyModel.cs b/DoxaApi/Models/RequestBodyModel.cs new file mode 100644 index 0000000..fb8366b --- /dev/null +++ b/DoxaApi/Models/RequestBodyModel.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class RequestBodyModel + { + [JsonPropertyName("required")] + public bool Required { get; set; } + + [JsonPropertyName("contentType")] + public string ContentType { get; set; } = "application/json"; + + [JsonPropertyName("schema")] + public SchemaModel Schema { get; set; } = new(); + + [JsonPropertyName("example")] + public string? Example { get; set; } + } +} diff --git a/DoxaApi/Models/ResponseModel.cs b/DoxaApi/Models/ResponseModel.cs new file mode 100644 index 0000000..e2e0306 --- /dev/null +++ b/DoxaApi/Models/ResponseModel.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class ResponseModel + { + [JsonPropertyName("statusCode")] + public string StatusCode { get; set; } = "200"; + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("schema")] + public SchemaModel? Schema { get; set; } + } +} diff --git a/DoxaApi/Models/SchemaModel.cs b/DoxaApi/Models/SchemaModel.cs new file mode 100644 index 0000000..afc378d --- /dev/null +++ b/DoxaApi/Models/SchemaModel.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace EonaCat.DoxaApi.Models +{ + public sealed class SchemaModel + { + [JsonPropertyName("type")] + public string Type { get; set; } = "object"; + + [JsonPropertyName("format")] + public string? Format { get; set; } + + [JsonPropertyName("refName")] + public string? RefName { get; set; } + + [JsonPropertyName("items")] + public SchemaModel? Items { get; set; } + + [JsonPropertyName("properties")] + public Dictionary? Properties { get; set; } + + [JsonPropertyName("required")] + public List? Required { get; set; } + + [JsonPropertyName("enumValues")] + public List? EnumValues { get; set; } + + [JsonPropertyName("nullable")] + public bool Nullable { get; set; } + } +} diff --git a/DoxaApi/ServiceCollectionExtensions.cs b/DoxaApi/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..c027679 --- /dev/null +++ b/DoxaApi/ServiceCollectionExtensions.cs @@ -0,0 +1,17 @@ +using EonaCat.DoxaApi.Generation; +using Microsoft.Extensions.DependencyInjection; + +namespace EonaCat.DoxaApi +{ + public static class DoxaApiServiceCollectionExtensions + { + + public static IServiceCollection AddDoxaApi(this IServiceCollection services, Action? configure = null) + { + var options = new DoxaApiOptions(); + configure?.Invoke(options); + services.AddSingleton(options); + return services; + } + } +} diff --git a/DoxaApi/UI/Assets/app.css b/DoxaApi/UI/Assets/app.css new file mode 100644 index 0000000..77dcc27 --- /dev/null +++ b/DoxaApi/UI/Assets/app.css @@ -0,0 +1,1473 @@ +:root { + --accent: #6366f1; /* overridden inline per-instance */ + --accent-ink: #ffffff; + --m-get: #34D399; + --m-post: #5B9CFF; + --m-put: #F5B947; + --m-patch: #C792EA; + --m-delete: #FB7185; + --m-default: #8A93A6; + --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --font-mono: ui-monospace, "SF Mono", "Cascadia Code", Consolas, Menlo, monospace; + --radius-sm: 5px; + --radius-md: 8px; + --radius-lg: 14px; + --nav-w: 288px; + --try-w: 432px; + --topbar-h: 58px; + --ease: cubic-bezier(0.16, 1, 0.3, 1); +} + +[data-theme="dark"], [data-theme="auto"] { + --bg-0: #08090D; + --bg-1: #0E1015; + --bg-2: #15171E; + --bg-3: #1C1F28; + --bg-raised: #181B23; + --border: #232733; + --border-soft: #1A1D26; + --text-0: #EEF0F4; + --text-1: #9CA3B5; + --text-2: #696F80; + --code-bg: #0B0C10; + --shadow: 0 16px 48px rgba(0,0,0,0.55); + --glow-alpha: 0.16; + --grid-line: rgba(255,255,255,0.025); +} + +[data-theme="light"] { + --bg-0: #FAFAFA; + --bg-1: #FFFFFF; + --bg-2: #F5F5F7; + --bg-3: #ECEDF1; + --bg-raised: #FFFFFF; + --border: #E5E6EB; + --border-soft: #EEEFF2; + --text-0: #14151A; + --text-1: #5B5F6B; + --text-2: #92959E; + --code-bg: #F5F5F7; + --shadow: 0 16px 48px rgba(20,21,26,0.10); + --glow-alpha: 0.08; + --grid-line: rgba(0,0,0,0.025); +} + +@media (prefers-color-scheme: light) { + [data-theme="auto"] { + --bg-0: #FAFAFA; + --bg-1: #FFFFFF; + --bg-2: #F5F5F7; + --bg-3: #ECEDF1; + --bg-raised: #FFFFFF; + --border: #E5E6EB; + --border-soft: #EEEFF2; + --text-0: #14151A; + --text-1: #5B5F6B; + --text-2: #92959E; + --code-bg: #F5F5F7; + --shadow: 0 16px 48px rgba(20,21,26,0.10); + --glow-alpha: 0.08; + --grid-line: rgba(0,0,0,0.025); + } +} + +* { + box-sizing: border-box; +} + +html, body { + height: 100%; +} + +body { + margin: 0; + background: var(--bg-0); + color: var(--text-0); + font-family: var(--font-ui); + font-size: 14px; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +button { + font-family: inherit; +} + +a { + color: inherit; +} + +::selection { + background: color-mix(in srgb, var(--accent) 35%, transparent); +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +/* scrollbars */ +* { + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} + + *::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + *::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 8px; + } + + *::-webkit-scrollbar-track { + background: transparent; + } + +.app-shell { + height: 100vh; + display: flex; + flex-direction: column; + background-image: linear-gradient(var(--grid-line) 1px, transparent 1px), linear-gradient(90deg, var(--grid-line) 1px, transparent 1px); + background-size: 28px 28px; +} + +.topbar { + height: var(--topbar-h); + flex: 0 0 auto; + display: flex; + align-items: center; + gap: 22px; + padding: 0 20px; + border-bottom: 1px solid var(--border); + background: color-mix(in srgb, var(--bg-1) 92%, transparent); + backdrop-filter: blur(10px); + position: relative; + z-index: 30; +} + +.topbar-brand { + display: flex; + align-items: center; + gap: 10px; + min-width: 160px; +} + +.brand-mark { + width: 26px; + height: 26px; + flex: 0 0 auto; + border-radius: 7px; + background: linear-gradient(155deg, var(--accent), color-mix(in srgb, var(--accent) 55%, #000 15%)); + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent), 0 4px 14px color-mix(in srgb, var(--accent) var(--glow-alpha), transparent); +} + + .brand-mark svg { + width: 15px; + height: 15px; + color: var(--accent-ink); + } + +.brand-title { + font-weight: 650; + font-size: 15px; + letter-spacing: -0.01em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.brand-version { + font-family: var(--font-mono); + font-size: 10.5px; + color: var(--text-2); + background: var(--bg-3); + border: 1px solid var(--border); + padding: 1px 6px; + border-radius: 20px; + margin-left: 2px; +} + +.topbar-search { + flex: 1 1 auto; + max-width: 480px; + display: flex; + align-items: center; + gap: 9px; + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 0 11px; + height: 36px; + color: var(--text-2); + transition: border-color .15s var(--ease), box-shadow .15s var(--ease); +} + + .topbar-search:focus-within { + border-color: var(--accent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 14%, transparent); + } + +.search-icon { + width: 15px; + height: 15px; + flex: 0 0 auto; +} + +.topbar-search input { + flex: 1 1 auto; + background: transparent; + border: none; + outline: none; + color: var(--text-0); + font-size: 13.5px; + min-width: 0; +} + + .topbar-search input::placeholder { + color: var(--text-2); + } + +.topbar-search kbd { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-2); + background: var(--bg-3); + border: 1px solid var(--border); + border-radius: 4px; + padding: 1px 5px; +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 10px; + margin-left: auto; +} + +.icon-btn { + width: 34px; + height: 34px; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius-md); + color: var(--text-1); + cursor: pointer; + transition: background .12s ease, border-color .12s ease, color .12s ease; +} + + .icon-btn:hover { + background: var(--bg-2); + border-color: var(--border); + color: var(--text-0); + } + + .icon-btn svg { + width: 17px; + height: 17px; + } + +.icon-moon { + display: none; +} + +[data-theme="light"] .icon-sun { + display: none; +} + +[data-theme="light"] .icon-moon { + display: block; +} + +.text-link { + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-2); + text-decoration: none; + border: 1px solid var(--border); + padding: 6px 10px; + border-radius: var(--radius-md); + transition: color .12s ease, border-color .12s ease; + display: inline-flex; + align-items: center; + gap: 6px; +} + + .text-link:hover { + color: var(--text-0); + border-color: var(--text-2); + } + +.body-grid { + flex: 1 1 auto; + display: grid; + grid-template-columns: var(--nav-w) 1fr var(--try-w); + min-height: 0; +} + +/* Nav pane (left) */ +.nav-pane { + border-right: 1px solid var(--border); + background: color-mix(in srgb, var(--bg-1) 96%, transparent); + overflow-y: auto; + min-width: 0; + position: relative; +} + +.nav-content { + padding: 16px 10px 40px; +} + +.nav-overview-link { + display: flex; + align-items: center; + gap: 9px; + width: 100%; + background: transparent; + border: 1px solid transparent; + padding: 8px 10px; + margin-bottom: 14px; + color: var(--text-1); + font-weight: 600; + font-size: 12.5px; + cursor: pointer; + border-radius: var(--radius-sm); + text-align: left; +} + + .nav-overview-link svg { + width: 14px; + height: 14px; + flex: 0 0 auto; + color: var(--text-2); + } + + .nav-overview-link:hover { + background: var(--bg-2); + color: var(--text-0); + } + + .nav-overview-link.active { + background: var(--bg-3); + color: var(--text-0); + border-color: var(--border); + } + +.nav-group { + margin-bottom: 4px; +} + +.nav-group-header { + display: flex; + align-items: center; + gap: 7px; + width: 100%; + background: transparent; + border: none; + padding: 8px 8px; + color: var(--text-1); + font-weight: 650; + font-size: 11.5px; + letter-spacing: 0.05em; + text-transform: uppercase; + cursor: pointer; + border-radius: var(--radius-sm); +} + + .nav-group-header:hover { + color: var(--text-0); + background: var(--bg-2); + } + + .nav-group-header .chev { + width: 11px; + height: 11px; + transition: transform .18s var(--ease); + flex: 0 0 auto; + color: var(--text-2); + } + +.nav-group.collapsed .chev { + transform: rotate(-90deg); +} + +.nav-group.collapsed .nav-endpoints { + display: none; +} + +.nav-group-count { + margin-left: auto; + font-family: var(--font-mono); + font-size: 10.5px; + color: var(--text-2); + background: var(--bg-3); + border-radius: 20px; + padding: 1px 6px; +} + +.nav-endpoints { + display: flex; + flex-direction: column; + gap: 1px; + padding: 2px 0 10px; + position: relative; +} + +.nav-endpoint { + display: flex; + align-items: center; + gap: 9px; + padding: 7px 8px 7px 10px; + border-radius: var(--radius-sm); + border: none; + background: transparent; + cursor: pointer; + text-align: left; + width: 100%; + position: relative; + transition: background .1s ease; +} + + .nav-endpoint::before { + content: ""; + position: absolute; + left: 0; + top: 4px; + bottom: 4px; + width: 3px; + border-radius: 3px; + background: var(--method-color, var(--text-2)); + opacity: 0; + transition: opacity .12s ease; + } + + .nav-endpoint:hover { + background: var(--bg-2); + } + + .nav-endpoint.active { + background: var(--bg-3); + } + + .nav-endpoint.active::before { + opacity: 1; + } + +.method-tag { + font-family: var(--font-mono); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.02em; + color: var(--method-color, var(--text-2)); + width: 36px; + flex: 0 0 auto; +} + +.nav-endpoint-path { + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-1); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.nav-endpoint.active .nav-endpoint-path { + color: var(--text-0); +} + +.nav-endpoint.deprecated .nav-endpoint-path { + text-decoration: line-through; + color: var(--text-2); +} + +.nav-empty { + padding: 30px 14px; + color: var(--text-2); + font-size: 12.5px; + text-align: center; + line-height: 1.6; +} + +/* Detail pane (middle) */ +.detail-pane { + overflow-y: auto; + min-width: 0; + background: var(--bg-0); +} + +.detail-content { + max-width: 800px; + padding: 0 36px 90px; +} + +/* Welcome / overview screen */ +.overview-hero { + padding: 56px 0 36px; + border-bottom: 1px solid var(--border-soft); + margin-bottom: 32px; +} + +.overview-eyebrow { + display: inline-flex; + align-items: center; + gap: 7px; + font-family: var(--font-mono); + font-size: 11.5px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--accent); + background: color-mix(in srgb, var(--accent) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent); + padding: 4px 10px; + border-radius: 20px; + margin-bottom: 18px; +} + + .overview-eyebrow svg { + width: 12px; + height: 12px; + } + +.overview-title { + font-size: 34px; + font-weight: 700; + letter-spacing: -0.02em; + margin: 0 0 12px; + line-height: 1.15; +} + +.overview-desc { + font-size: 15px; + color: var(--text-1); + line-height: 1.65; + max-width: 560px; + margin: 0 0 26px; +} + +.overview-meta { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.overview-pill { + display: inline-flex; + align-items: center; + gap: 7px; + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-1); + background: var(--bg-2); + border: 1px solid var(--border); + padding: 6px 11px; + border-radius: var(--radius-md); +} + + .overview-pill svg { + width: 13px; + height: 13px; + color: var(--text-2); + } + +.overview-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1px; + background: var(--border); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + overflow: hidden; + margin-bottom: 36px; +} + +.overview-stat { + background: var(--bg-1); + padding: 18px 20px; +} + +.overview-stat-value { + font-size: 26px; + font-weight: 700; + letter-spacing: -0.02em; + font-family: var(--font-mono); +} + +.overview-stat-label { + font-size: 11.5px; + color: var(--text-2); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-top: 3px; +} + +.overview-section-title { + font-size: 11.5px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--text-2); + margin: 0 0 14px; +} + +.overview-group-card { + border: 1px solid var(--border); + border-radius: var(--radius-lg); + margin-bottom: 14px; + overflow: hidden; + background: var(--bg-1); +} + +.overview-group-card-header { + padding: 14px 18px; + border-bottom: 1px solid var(--border-soft); + font-weight: 650; + font-size: 14px; + display: flex; + align-items: center; + justify-content: space-between; +} + +.overview-group-routes { + display: flex; + flex-direction: column; +} + +.overview-route-row { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 18px; + border-bottom: 1px solid var(--border-soft); + cursor: pointer; + transition: background .1s ease; +} + + .overview-route-row:last-child { + border-bottom: none; + } + + .overview-route-row:hover { + background: var(--bg-2); + } + + .overview-route-row .method-badge { + width: 58px; + text-align: center; + flex: 0 0 auto; + } + + .overview-route-row .route-path { + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--text-0); + flex: 0 0 auto; + } + + .overview-route-row .route-summary { + color: var(--text-2); + font-size: 12.5px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .overview-route-row .route-arrow { + margin-left: auto; + width: 14px; + height: 14px; + color: var(--text-2); + flex: 0 0 auto; + opacity: 0; + transform: translateX(-3px); + transition: opacity .12s ease, transform .12s ease; + } + + .overview-route-row:hover .route-arrow { + opacity: 1; + transform: none; + } + +/* Endpoint detail */ +.endpoint-header { + padding: 30px 0 0; + margin-bottom: 6px; +} + +.breadcrumb { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--text-2); + margin-bottom: 16px; +} + + .breadcrumb svg { + width: 11px; + height: 11px; + } + + .breadcrumb .current { + color: var(--text-1); + } + +.request-line { + display: flex; + align-items: stretch; + font-family: var(--font-mono); + border: 1px solid var(--border); + border-radius: var(--radius-md); + background: var(--code-bg); + overflow: hidden; + margin-bottom: 18px; +} + +.method-badge { + font-size: 11.5px; + font-weight: 700; + letter-spacing: 0.03em; + color: var(--bg-0); + background: var(--method-color, var(--text-2)); + padding: 9px 14px; + flex: 0 0 auto; + display: flex; + align-items: center; +} + +.request-line .endpoint-path { + flex: 1 1 auto; + font-size: 14px; + color: var(--text-0); + word-break: break-all; + padding: 9px 14px; + display: flex; + align-items: center; +} + +.request-line .copy-route-btn { + flex: 0 0 auto; + border: none; + border-left: 1px solid var(--border); + background: transparent; + color: var(--text-2); + cursor: pointer; + padding: 0 13px; + display: flex; + align-items: center; + transition: color .12s ease, background .12s ease; +} + + .request-line .copy-route-btn:hover { + color: var(--text-0); + background: var(--bg-2); + } + + .request-line .copy-route-btn svg { + width: 14px; + height: 14px; + } + +.endpoint-summary { + font-size: 23px; + font-weight: 650; + letter-spacing: -0.015em; + margin: 0 0 8px; + line-height: 1.3; +} + +.endpoint-description { + color: var(--text-1); + font-size: 14px; + line-height: 1.65; + margin: 0; + max-width: 640px; +} + +.deprecated-banner { + display: flex; + align-items: center; + gap: 8px; + margin-top: 14px; + font-size: 12.5px; + color: var(--m-delete); + background: color-mix(in srgb, var(--m-delete) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--m-delete) 28%, transparent); + padding: 9px 12px; + border-radius: var(--radius-md); +} + + .deprecated-banner svg { + width: 15px; + height: 15px; + flex: 0 0 auto; + } + +.section { + margin-top: 34px; +} + +.section-title { + font-size: 11.5px; + font-weight: 700; + letter-spacing: 0.07em; + text-transform: uppercase; + color: var(--text-2); + margin: 0 0 14px; + display: flex; + align-items: center; + gap: 8px; +} + + .section-title .count { + font-family: var(--font-mono); + font-weight: 600; + color: var(--text-2); + background: var(--bg-2); + border-radius: 20px; + padding: 0px 7px; + font-size: 10.5px; + } + +.param-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} + + .param-table th { + text-align: left; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--text-2); + font-weight: 650; + padding: 9px 12px; + background: var(--bg-2); + border-bottom: 1px solid var(--border); + } + + .param-table td { + padding: 11px 12px; + border-bottom: 1px solid var(--border-soft); + vertical-align: top; + } + + .param-table tr:last-child td { + border-bottom: none; + } + +.param-name { + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--text-0); + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; +} + +.param-required { + color: var(--m-delete); + font-size: 11px; +} + +.param-loc { + font-family: var(--font-mono); + font-size: 10.5px; + color: var(--text-2); + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: 4px; + padding: 1px 5px; +} + +.param-type { + font-family: var(--font-mono); + font-size: 12px; + color: var(--accent); +} + +.param-desc { + color: var(--text-1); + font-size: 12.5px; + line-height: 1.55; +} + +.schema-box { + background: var(--code-bg); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 16px 18px; + font-family: var(--font-mono); + font-size: 12.5px; + line-height: 1.75; + overflow-x: auto; + position: relative; +} + +.schema-line { + white-space: pre; +} + +.schema-key { + color: var(--text-0); +} + +.schema-punct { + color: var(--text-2); +} + +.schema-type { + color: var(--accent); +} + +.schema-comment { + color: var(--text-2); + font-style: italic; +} + +.schema-required-mark { + color: var(--m-delete); +} + +.schema-nullable-mark { + color: var(--text-2); + font-style: italic; +} + +.response-block { + margin-bottom: 14px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} + +.response-block-header { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + background: var(--bg-2); + border-bottom: 1px solid var(--border); +} + +.status-pill { + font-family: var(--font-mono); + font-size: 11.5px; + font-weight: 700; + padding: 2px 8px; + border-radius: 4px; +} + +.status-2xx { + color: var(--m-get); + background: color-mix(in srgb, var(--m-get) 15%, transparent); +} + +.status-4xx { + color: var(--m-put); + background: color-mix(in srgb, var(--m-put) 15%, transparent); +} + +.status-5xx { + color: var(--m-delete); + background: color-mix(in srgb, var(--m-delete) 15%, transparent); +} + +.response-desc { + font-size: 12.5px; + color: var(--text-1); +} + +.response-block-body { + padding: 13px 15px; +} + +/* Try pane (right) */ +.try-pane { + border-left: 1px solid var(--border); + background: color-mix(in srgb, var(--bg-1) 96%, transparent); + overflow-y: auto; + min-width: 0; +} + +.try-content { + padding: 24px 22px 60px; +} + +.try-empty { + padding: 70px 18px; + text-align: center; + color: var(--text-2); + font-size: 12.5px; + line-height: 1.6; +} + + .try-empty svg { + width: 30px; + height: 30px; + color: var(--border); + margin-bottom: 12px; + } + +.try-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 18px; + padding-bottom: 14px; + border-bottom: 1px solid var(--border-soft); +} + +.try-title { + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-2); + display: flex; + align-items: center; + gap: 7px; +} + + .try-title .live-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--method-color, var(--m-get)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--method-color, var(--m-get)) 25%, transparent); + } + +.try-tabs { + display: flex; + gap: 2px; + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 2px; + margin-bottom: 18px; +} + +.try-tab { + flex: 1 1 auto; + text-align: center; + border: none; + background: transparent; + color: var(--text-2); + font-size: 12px; + font-weight: 600; + padding: 7px 8px; + border-radius: 6px; + cursor: pointer; + font-family: var(--font-mono); +} + + .try-tab.active { + background: var(--bg-raised); + color: var(--text-0); + box-shadow: 0 1px 2px rgba(0,0,0,0.08); + } + +.field-group { + margin-bottom: 16px; +} + +.field-label { + display: flex; + align-items: center; + gap: 6px; + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-1); + margin-bottom: 6px; +} + +.field-sublabel { + font-family: var(--font-mono); + margin-bottom: 4px; + color: var(--text-2); + font-size: 11px; + display: flex; + align-items: center; + gap: 5px; +} + + .field-sublabel .req-star { + color: var(--m-delete); + } + + .field-sublabel .type-hint { + color: var(--text-2); + font-weight: 400; + margin-left: auto; + opacity: 0.8; + } + +.field-input { + width: 100%; + background: var(--bg-2); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + padding: 9px 11px; + color: var(--text-0); + font-family: var(--font-mono); + font-size: 12.5px; + outline: none; + transition: border-color .12s ease, box-shadow .12s ease; +} + + .field-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); + } + +textarea.field-input { + resize: vertical; + min-height: 140px; + line-height: 1.65; +} + +.send-btn { + width: 100%; + background: var(--method-color, var(--accent)); + color: #08090D; + border: none; + border-radius: var(--radius-md); + padding: 11px; + font-weight: 700; + font-size: 13px; + cursor: pointer; + margin-top: 8px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: filter .12s ease, transform .04s ease; + font-family: var(--font-mono); +} + + .send-btn:hover { + filter: brightness(1.08); + } + + .send-btn:active { + transform: scale(0.99); + } + + .send-btn:disabled { + opacity: 0.6; + cursor: default; + } + + .send-btn svg { + width: 14px; + height: 14px; + } + +.spinner { + width: 14px; + height: 14px; + border: 2px solid rgba(0,0,0,0.25); + border-top-color: #08090D; + border-radius: 50%; + animation: spin 0.7s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.response-panel { + margin-top: 24px; + border-top: 1px solid var(--border); + padding-top: 20px; +} + +.response-meta { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; + flex-wrap: wrap; +} + +.response-time { + font-family: var(--font-mono); + font-size: 11.5px; + color: var(--text-2); + display: flex; + align-items: center; + gap: 4px; +} + + .response-time svg { + width: 11px; + height: 11px; + } + +.response-body { + background: var(--code-bg); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 13px 15px; + font-family: var(--font-mono); + font-size: 12px; + line-height: 1.65; + max-height: 420px; + overflow: auto; + white-space: pre-wrap; + word-break: break-word; +} + +.response-error { + color: var(--m-delete); +} + +.copy-btn { + background: transparent; + border: 1px solid var(--border); + color: var(--text-2); + border-radius: 5px; + padding: 4px 9px; + font-size: 11px; + cursor: pointer; + font-family: var(--font-mono); + display: inline-flex; + align-items: center; + gap: 5px; + transition: color .12s ease, border-color .12s ease; +} + + .copy-btn svg { + width: 11px; + height: 11px; + } + + .copy-btn:hover { + color: var(--text-0); + border-color: var(--text-1); + } + +.json-key { + color: #7FB3FF; +} + +.json-string { + color: #5FE3A8; +} + +.json-number { + color: #F5B947; +} + +.json-boolean { + color: #D5A6F5; +} + +.json-null { + color: var(--text-2); +} + +.action-group { + display: flex; + gap: 8px; +} + +.action-btn { + height: 34px; + padding: 0 12px; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--bg-2); + color: var(--text-0); + cursor: pointer; + font-weight: 600; + transition: all .18s var(--ease); +} + + .action-btn:hover { + transform: translateY(-1px); + border-color: color-mix(in srgb,var(--accent) 40%,var(--border)); + box-shadow: 0 8px 24px color-mix(in srgb,var(--accent) 15%,transparent); + } + +.action-btn-primary { + background: linear-gradient( 135deg, var(--accent), color-mix(in srgb,var(--accent) 70%,#ffffff 10%) ); + color: white; + border-color: transparent; +} + + +/* curl snippet */ +.curl-box { + background: var(--code-bg); + border: 1px solid var(--border); + border-radius: var(--radius-md); + padding: 13px 15px; + font-family: var(--font-mono); + font-size: 11.5px; + line-height: 1.7; + overflow-x: auto; + white-space: pre; + position: relative; + color: var(--text-1); +} + + .curl-box .curl-flag { + color: var(--accent); + } + + .curl-box .curl-string { + color: #5FE3A8; + } + +.curl-header-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +/* responsive: collapse try pane on narrow screens behind a toggle */ +@media (max-width: 1180px) { + :root { + --try-w: 380px; + } +} + +@media (max-width: 980px) { + .body-grid { + grid-template-columns: 240px 1fr; + } + + .try-pane { + display: none; + } + + .try-pane.open { + display: block; + position: fixed; + right: 0; + top: var(--topbar-h); + bottom: 0; + width: 380px; + box-shadow: var(--shadow); + z-index: 20; + } +} + +@media (max-width: 680px) { + .body-grid { + grid-template-columns: 1fr; + } + + .nav-pane { + display: none; + position: fixed; + left: 0; + top: var(--topbar-h); + bottom: 0; + width: 270px; + z-index: 20; + box-shadow: var(--shadow); + } + + .nav-pane.open { + display: block; + } + + .topbar-search { + max-width: none; + } + + .overview-stats { + grid-template-columns: 1fr; + } +} + +.skeleton { + background: linear-gradient(90deg, var(--bg-2) 25%, var(--bg-3) 37%, var(--bg-2) 63%); + background-size: 400% 100%; + animation: shimmer 1.4s ease infinite; + border-radius: var(--radius-sm); +} + +@keyframes shimmer { + 0% { + background-position: 100% 50%; + } + + 100% { + background-position: 0 50%; + } +} + +.fade-in { + animation: fadeIn .22s var(--ease); +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(3px); + } + + to { + opacity: 1; + transform: none; + } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.001ms !important; + transition-duration: 0.001ms !important; + } +} + + +.api-switcher{ + display:flex; + align-items:center; + margin-right:12px; +} + +#apiSelector{ + min-width:220px; + height:38px; + border-radius:10px; + padding:0 12px; + background:var(--panel,#1e1e1e); + color:var(--text,#fff); + border:1px solid var(--border,#3a3a3a); + font-weight:600; + cursor:pointer; +} + +#apiSelector:hover{ + filter:brightness(1.05); +} + +#apiSelector:focus{ + outline:none; +} + +.request-builder,.header-row,.query-row{display:flex;gap:8px;margin-bottom:8px} +.header-key,.query-key{flex:0 0 35%} +.header-value,.query-value,.request-url{flex:1} +.postman-section{margin-top:12px;padding-top:12px;border-top:1px solid var(--border)} diff --git a/DoxaApi/UI/Assets/app.js b/DoxaApi/UI/Assets/app.js new file mode 100644 index 0000000..28c0c65 --- /dev/null +++ b/DoxaApi/UI/Assets/app.js @@ -0,0 +1,855 @@ +(function () { + "use strict"; + + const SPEC_URL = window.__DOXA_API_SPEC_URL__ || "doxaApi.json"; + + let apis = []; + let activeApiIndex = 0; + let spec = null; + let activeEndpoint = null; + let activeTryTab = "body"; + let collapsedGroups = new Set(JSON.parse(localStorage.getItem("DoxaApi.collapsed") || "[]")); + + const el = { + nav: document.getElementById("navContent"), + detail: document.getElementById("detailContent"), + try: document.getElementById("tryContent"), + search: document.getElementById("searchInput"), + themeToggle: document.getElementById("themeToggle"), + brandTitle: document.getElementById("brandTitle"), + brandVersion: document.getElementById("brandVersion"), + apiSelector: document.getElementById("apiSelector"), + + }; + + function refreshApiSelector(){ + if(!el.apiSelector) return; + el.apiSelector.innerHTML = apis.map((a,i)=>``).join(''); + el.apiSelector.value=String(activeApiIndex); + } + + async function init() { + renderSkeleton(); + try { + const res = await fetch(SPEC_URL); + if (!res.ok) throw new Error("HTTP " + res.status); + + const importedSpec = await res.json(); + const extend = apis.length > 0 && window.confirm("Extend the currently selected API?\n\nOK = Extend Current API\nCancel = Import As New API (default)"); + + if (extend) { + spec.groups = [...(spec.groups||[]), ...(importedSpec.groups||[])]; + spec.schemas = Object.assign(spec.schemas||{}, importedSpec.schemas||{}); + } else { + apis.push(importedSpec); + activeApiIndex = apis.length - 1; + spec = importedSpec; + } + + refreshApiSelector(); + if(spec.info && spec.info.title) el.brandTitle.textContent = spec.info.title; + if(spec.info && spec.info.version) el.brandVersion.textContent = spec.info.version; + + } catch (err) { + el.nav.innerHTML = ``; + return; + } + + if (spec.info && spec.info.title) el.brandTitle.textContent = spec.info.title; + if (spec.info && spec.info.version) el.brandVersion.textContent = spec.info.version; + document.title = (spec.info && spec.info.title) || "API Documentation"; + + renderNav(""); + renderOverview(); + renderTryEmpty(); + bindGlobalEvents(); +} + + function renderSkeleton() { + el.nav.innerHTML = Array.from({ length: 6 }) + .map(() => `
`) + .join(""); + } + + function totalEndpointCount() { + return (spec.groups || []).reduce((n, g) => n + g.endpoints.length, 0); + } + + function totalSchemaCount() { + return Object.keys(spec.schemas || {}).length; + } + + // Nav rendering + function renderNav(filterText) { + const term = filterText.trim().toLowerCase(); + const groups = spec.groups || []; + + let html = ``; + + if (groups.length === 0) { + html += ``; + el.nav.innerHTML = html; + return; + } + + let totalMatches = 0; + let groupHtml = ""; + + for (const group of groups) { + const endpoints = group.endpoints.filter((e) => matchesFilter(e, term)); + if (term && endpoints.length === 0) continue; + totalMatches += endpoints.length; + + const isCollapsed = collapsedGroups.has(group.name) && !term; + + groupHtml += ` + `; + } + + if (term && totalMatches === 0) { + html += ``; + } else { + html += groupHtml; + } + + el.nav.innerHTML = html; + } + + function navEndpointHtml(group, endpoint) { + const isActive = + activeEndpoint && + activeEndpoint.endpoint.operationId === endpoint.operationId; + return ` + `; + } + + function matchesFilter(endpoint, term) { + if (!term) return true; + return ( + endpoint.path.toLowerCase().includes(term) || + (endpoint.summary || "").toLowerCase().includes(term) || + endpoint.method.toLowerCase().includes(term) + ); + } + + // Overview / welcome screen + function renderOverview() { + const info = spec.info || {}; + const groups = spec.groups || []; + + let html = `
`; + html += `
`; + html += `
+ + API reference +
`; + html += `

${escapeHtml(info.title || "API Documentation")}

`; + if (info.description) { + html += `

${escapeHtml(info.description)}

`; + } + html += `
`; + if (info.version) { + html += ` + + ${escapeHtml(info.version)} + `; + } + if (spec.servers && spec.servers.length) { + html += ` + + ${escapeHtml(spec.servers[0])} + `; + } + html += `
`; + html += `
`; // hero + + html += `
`; + html += `
${groups.length}
Groups
`; + html += `
${totalEndpointCount()}
Endpoints
`; + html += `
${totalSchemaCount()}
Schemas
`; + html += `
`; + + html += `

Browse by group

`; + for (const group of groups) { + html += `
+
+ ${escapeHtml(group.name)} + ${group.endpoints.length} +
+
`; + for (const ep of group.endpoints) { + html += `
+ ${ep.method} + ${escapeHtml(ep.path)} + ${escapeHtml(ep.summary || "")} + +
`; + } + html += `
`; + } + + html += `
`; + el.detail.innerHTML = html; + el.detail.scrollTop = 0; + } + + function renderTryEmpty() { + el.try.innerHTML = `
+ + Pick an endpoint to send a live request. +
`; + } + + function selectEndpoint(group, endpoint) { + activeEndpoint = { group, endpoint }; + activeTryTab = "body"; + document.querySelectorAll(".nav-endpoint").forEach((b) => { + b.classList.toggle("active", b.dataset.op === endpoint.operationId); + }); + const overviewLink = document.querySelector(".nav-overview-link"); + if (overviewLink) overviewLink.classList.remove("active"); + renderDetail(group, endpoint); + renderTry(group, endpoint); + } + + function renderDetail(group, endpoint) { + const methodColorVar = `var(--m-${endpoint.method.toLowerCase()}, var(--m-default))`; + + let html = `
`; + html += `
`; + html += ``; + html += `
+ ${endpoint.method} + ${escapeHtml(endpoint.path)} + +
`; + html += `

${escapeHtml(endpoint.summary || endpoint.operationId)}

`; + if (endpoint.description) { + html += `

${escapeHtml(endpoint.description)}

`; + } + if (endpoint.deprecated) { + html += `
+ + Deprecated - this endpoint may be removed in a future version +
`; + } + html += `
`; + + if (endpoint.parameters && endpoint.parameters.length > 0) { + html += `

Parameters ${endpoint.parameters.length}

`; + html += ``; + for (const p of endpoint.parameters) { + html += ` + + + + + `; + } + html += `
NameLocated inTypeDescription
${escapeHtml(p.name)}${p.required ? '*' : ""}${p.in}${schemaTypeLabel(p.schema)}${escapeHtml(p.description || "-")}
`; + } + + if (endpoint.requestBody) { + html += `

Request body

`; + html += `
${renderSchemaTree(endpoint.requestBody.schema, 0)}
`; + } + + if (endpoint.responses && endpoint.responses.length > 0) { + html += `

Responses ${endpoint.responses.length}

`; + for (const r of endpoint.responses) { + const cls = r.statusCode[0] === "2" ? "status-2xx" : r.statusCode[0] === "4" ? "status-4xx" : "status-5xx"; + html += `
+
+ ${r.statusCode} + ${escapeHtml(r.description || "")} +
`; + if (r.schema) { + html += `
${renderSchemaTree(r.schema, 0)}
`; + } + html += `
`; + } + html += `
`; + } + + html += `
`; + el.detail.innerHTML = html; + el.detail.scrollTop = 0; + + const copyBtn = document.getElementById("copyRouteBtn"); + if (copyBtn) { + copyBtn.addEventListener("click", () => { + navigator.clipboard.writeText(endpoint.path).then(() => flashIcon(copyBtn)); + }); + } + } + + function flashIcon(btn) { + const original = btn.innerHTML; + btn.innerHTML = ``; + setTimeout(() => (btn.innerHTML = original), 1100); + } + + function schemaTypeLabel(schema) { + if (!schema) return "any"; + if (schema.refName) return schema.refName; + if (schema.type === "array") return schemaTypeLabel(schema.items) + "[]"; + if (schema.type === "enum") return "enum"; + return schema.format ? `${schema.type} (${schema.format})` : schema.type; + } + + function renderSchemaTree(schema, depth) { + if (!schema) return `unknown`; + const indent = " ".repeat(depth); + + if (schema.refName && spec.schemas && spec.schemas[schema.refName] && depth < 6) { + const resolved = spec.schemas[schema.refName]; + return renderSchemaTree({ ...resolved, refName: undefined }, depth); + } + + if (schema.type === "object" && schema.properties) { + const required = new Set(schema.required || []); + let lines = [`{`]; + const entries = Object.entries(schema.properties); + entries.forEach(([key, propSchema], i) => { + const isReq = required.has(key); + const comma = i < entries.length - 1 ? "," : ""; + const nullableMark = propSchema && propSchema.nullable ? '?' : ""; + lines.push( + `${indent} ${escapeHtml(key)}${isReq ? '*' : ""}${nullableMark}: ${renderInlineType(propSchema, depth + 1)}${comma}` + ); + }); + lines.push(`${indent}}`); + return lines.join("\n"); + } + + if (schema.type === "array") { + return `[\n${indent} ${renderInlineType(schema.items, depth + 1)}\n${indent}]`; + } + + if (schema.type === "enum") { + return `enum (${(schema.enumValues || []).join(" | ")})`; + } + + return `${schema.type}${schema.format ? " (" + schema.format + ")" : ""}`; + } + + function renderInlineType(schema, depth) { + if (!schema) return `any`; + if (schema.refName) { + if (spec.schemas && spec.schemas[schema.refName] && depth < 6) { + return renderSchemaTree({ ...spec.schemas[schema.refName] }, depth); + } + return `${escapeHtml(schema.refName)}`; + } + if (schema.type === "object" && schema.properties) return renderSchemaTree(schema, depth); + if (schema.type === "array") { + return `Array<${renderInlineType(schema.items, depth)}>`; + } + if (schema.type === "enum") { + return `(${(schema.enumValues || []).join(" | ")})`; + } + return `${schema.type}${schema.format ? " (" + schema.format + ")" : ""}`; + } + + // Try-it-out pane + function renderTry(group, endpoint) { + const methodColorVar = `var(--m-${endpoint.method.toLowerCase()}, var(--m-default))`; + let html = `
`; + html += `
+ Try it +
`; + + html += `
+ + +
`; + + html += `
`; + + const pathParams = endpoint.parameters.filter((p) => p.in === "path"); + const queryParams = endpoint.parameters.filter((p) => p.in === "query"); + const headerParams = endpoint.parameters.filter((p) => p.in === "header"); + + if (pathParams.length) { + html += `
Path parameters
`; + for (const p of pathParams) { + html += `
+
${escapeHtml(p.name)}${p.required ? '*' : ""}${schemaTypeLabel(p.schema)}
+ +
`; + } + html += `
`; + } + + if (queryParams.length) { + html += `
Query parameters
`; + for (const p of queryParams) { + html += `
+
${escapeHtml(p.name)}${p.required ? '*' : ""}${schemaTypeLabel(p.schema)}
+ +
`; + } + html += `
`; + } + + if (headerParams.length) { + html += `
Headers
`; + for (const p of headerParams) { + html += `
+
${escapeHtml(p.name)}${p.required ? '*' : ""}${schemaTypeLabel(p.schema)}
+ +
`; + } + html += `
`; + } + + if (endpoint.requestBody) { + const example = endpoint.requestBody.example || generateExampleJson(endpoint.requestBody.schema, 0); + html += `
+
Request body (JSON)
+ +
`; + } + + html += ``; + + html += `
`; + html += `
`; // tryTabBody + + html += `
+
+ Shell snippet + +
+
${buildCurlSnippet(endpoint)}
+
`; + + html += `
`; + + el.try.innerHTML = html; + + document.getElementById("sendBtn").addEventListener("click", () => sendTryRequest(endpoint)); + + el.try.querySelectorAll("[data-try-tab]").forEach((btn) => { + btn.addEventListener("click", () => { + activeTryTab = btn.dataset.tryTab; + renderTry(group, endpoint); + }); + }); + + const copyCurlBtn = document.getElementById("copyCurlBtn"); + if (copyCurlBtn) { + copyCurlBtn.addEventListener("click", () => { + const text = document.getElementById("curlSnippet").textContent; + navigator.clipboard.writeText(text).then(() => { + const original = copyCurlBtn.textContent; + copyCurlBtn.textContent = "Copied"; + setTimeout(() => (copyCurlBtn.innerHTML = `Copy`), 1100); + }); + }); + } + } + + function buildCurlSnippet(endpoint) { + const base = (spec.servers && spec.servers[0]) || ""; + let path = endpoint.path; + // replace path params with placeholder tokens for readability + const lines = []; + lines.push(`curl -X ${endpoint.method} \\`); + lines.push(` "${escapeHtml(base)}${escapeHtml(path)}" \\`); + lines.push(` -H "Accept: application/json"`); + if (endpoint.requestBody) { + const example = endpoint.requestBody.example || generateExampleJson(endpoint.requestBody.schema, 0); + lines[lines.length - 1] += " \\"; + lines.push(` -H "Content-Type: ${escapeHtml(endpoint.requestBody.contentType || "application/json")}" \\`); + lines.push(` -d '${escapeHtml(example)}'`); + } + return lines.join("\n"); + } + + function generateExampleJson(schema, depth) { + const value = generateExampleValue(schema, depth, new Set()); + return JSON.stringify(value, null, 2); + } + + function generateExampleValue(schema, depth, seen) { + if (!schema || depth > 6) return null; + + if (schema.refName) { + if (seen.has(schema.refName)) return {}; + const resolved = spec.schemas && spec.schemas[schema.refName]; + if (!resolved) return {}; + const nextSeen = new Set(seen); + nextSeen.add(schema.refName); + return generateExampleValue(resolved, depth + 1, nextSeen); + } + + switch (schema.type) { + case "string": + if (schema.format === "date-time") return new Date().toISOString(); + if (schema.format === "uuid") return "00000000-0000-0000-0000-000000000000"; + return "string"; + case "integer": + return 0; + case "number": + return 0; + case "boolean": + return true; + case "enum": + return (schema.enumValues && schema.enumValues[0]) || "string"; + case "array": + return [generateExampleValue(schema.items, depth + 1, seen)]; + case "object": { + if (!schema.properties) return {}; + const obj = {}; + for (const [key, propSchema] of Object.entries(schema.properties)) { + obj[key] = generateExampleValue(propSchema, depth + 1, seen); + } + return obj; + } + default: + return null; + } + } + + async function sendTryRequest(endpoint) { + const btn = document.getElementById("sendBtn"); + const label = document.getElementById("sendBtnLabel"); + const panel = document.getElementById("responsePanel"); + + let path = endpoint.path; + document.querySelectorAll('[data-param-in="path"]').forEach((input) => { + const name = input.dataset.paramName; + path = path.replace(`{${name}}`, encodeURIComponent(input.value || "")); + }); + + const url = new URL(path, window.location.origin); + document.querySelectorAll('[data-param-in="query"]').forEach((input) => { + if (input.value) url.searchParams.set(input.dataset.paramName, input.value); + }); + + const headers = { Accept: "application/json" }; + document.querySelectorAll('[data-param-in="header"]').forEach((input) => { + if (input.value) headers[input.dataset.paramName] = input.value; + }); + + let body = undefined; + if (endpoint.requestBody) { + headers["Content-Type"] = endpoint.requestBody.contentType || "application/json"; + const bodyEl = document.getElementById("tryBody"); + body = bodyEl ? bodyEl.value : undefined; + } + + btn.disabled = true; + label.textContent = "Sending…"; + btn.querySelector("svg").style.display = "none"; + btn.insertBefore(spinnerEl(), btn.firstChild); + + const startTime = performance.now(); + + try { + const res = await fetch(url.toString(), { + method: endpoint.method, + headers, + body: endpoint.method === "GET" || endpoint.method === "HEAD" ? undefined : body, + }); + + const elapsedMs = Math.round(performance.now() - startTime); + const contentType = res.headers.get("content-type") || ""; + let bodyText; + let isJson = false; + + if (contentType.includes("application/json")) { + try { + const json = await res.json(); + bodyText = JSON.stringify(json, null, 2); + isJson = true; + } catch { + bodyText = await res.text(); + } + } else { + bodyText = await res.text(); + } + + renderResponsePanel(panel, { + status: res.status, + ok: res.ok, + elapsedMs, + body: bodyText, + isJson, + }); + } catch (err) { + const elapsedMs = Math.round(performance.now() - startTime); + renderResponsePanel(panel, { + status: null, + ok: false, + elapsedMs, + body: String(err && err.message ? err.message : err), + isJson: false, + networkError: true, + }); + } finally { + btn.disabled = false; + label.textContent = "Send request"; + const spinner = btn.querySelector(".spinner"); + if (spinner) spinner.remove(); + btn.querySelector("svg").style.display = ""; + } + } + + function spinnerEl() { + const s = document.createElement("span"); + s.className = "spinner"; + return s; + } + + function renderResponsePanel(panel, result) { + const statusClass = result.networkError + ? "status-5xx" + : result.status < 300 + ? "status-2xx" + : result.status < 500 + ? "status-4xx" + : "status-5xx"; + + const statusLabel = result.networkError ? "Network error" : result.status; + + panel.innerHTML = ` +
+
+ ${statusLabel} + + + ${result.elapsedMs} ms + + +
+
${result.isJson ? syntaxHighlightJson(result.body) : escapeHtml(result.body) + }
+
`; + + document.getElementById("copyResponseBtn").addEventListener("click", () => { + navigator.clipboard.writeText(result.body).then(() => { + const btn = document.getElementById("copyResponseBtn"); + btn.lastChild.textContent = "Copied"; + setTimeout(() => (btn.lastChild.textContent = "Copy"), 1200); + }); + }); + } + + function syntaxHighlightJson(json) { + const escaped = escapeHtml(json); + return escaped.replace( + /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false)\b|\bnull\b|-?\d+(\.\d+)?([eE][+-]?\d+)?)/g, + (match) => { + let cls = "json-number"; + if (/^"/.test(match)) { + cls = /:$/.test(match) ? "json-key" : "json-string"; + } else if (/true|false/.test(match)) { + cls = "json-boolean"; + } else if (/null/.test(match)) { + cls = "json-null"; + } + return `${match}`; + } + ); + } + + // Global events + function bindGlobalEvents() + { + const importBtn = document.getElementById("importBtn"); + const importFile = document.getElementById("importFile"); + const exportDoxaApiBtn = document.getElementById("exportDoxaApiBtn"); + const exportOpenApiBtn = document.getElementById("exportOpenApiBtn"); + const exportSwaggerBtn = document.getElementById("exportSwaggerBtn"); + + importBtn?.addEventListener("click", () => importFile.click()); + + importFile?.addEventListener("change", async (e) => { + const file = e.target.files?.[0]; + if (!file) return; + + const text = await file.text(); + + const res = await fetch("import", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: text + }); + + if (!res.ok) { + alert(await res.text()); + return; + } + + const importedSpec = await res.json(); + const extend = apis.length > 0 && window.confirm("Extend the currently selected API?\n\nOK = Extend Current API\nCancel = Import As New API (default)"); + + if (extend) { + spec.groups = [...(spec.groups||[]), ...(importedSpec.groups||[])]; + spec.schemas = Object.assign(spec.schemas||{}, importedSpec.schemas||{}); + } else { + apis.push(importedSpec); + activeApiIndex = apis.length - 1; + spec = importedSpec; + } + + refreshApiSelector(); + if(spec.info && spec.info.title) el.brandTitle.textContent = spec.info.title; + if(spec.info && spec.info.version) el.brandVersion.textContent = spec.info.version; + + activeEndpoint = null; + + renderNav(""); + renderOverview(); + renderTryEmpty(); + }); + + function download(url, filename) { + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + } + + exportDoxaApiBtn?.addEventListener("click", () => download("doxaApi.json", "DoxaApi.json")); + exportOpenApiBtn?.addEventListener("click", () => download("openapi.json", "openapi.json")); + exportSwaggerBtn?.addEventListener("click", () => download("swagger.json", "swagger.json")); + + el.nav.addEventListener("click", (e) => { + const overviewBtn = e.target.closest("[data-overview]"); + if (overviewBtn) { + activeEndpoint = null; + document.querySelectorAll(".nav-endpoint").forEach((b) => b.classList.remove("active")); + overviewBtn.classList.add("active"); + renderOverview(); + renderTryEmpty(); + return; + } + + const groupToggle = e.target.closest("[data-toggle-group]"); + if (groupToggle) { + const name = groupToggle.dataset.toggleGroup; + if (collapsedGroups.has(name)) collapsedGroups.delete(name); + else collapsedGroups.add(name); + localStorage.setItem("DoxaApi.collapsed", JSON.stringify([...collapsedGroups])); + renderNav(el.search.value); + return; + } + + const endpointBtn = e.target.closest("[data-op]"); + if (endpointBtn) { + const opId = endpointBtn.dataset.op; + for (const group of spec.groups) { + const endpoint = group.endpoints.find((ep) => ep.operationId === opId); + if (endpoint) { + selectEndpoint(group, endpoint); + break; + } + } + } + }); + + el.detail.addEventListener("click", (e) => { + const row = e.target.closest("[data-op]"); + if (!row) return; + const opId = row.dataset.op; + for (const group of spec.groups) { + const endpoint = group.endpoints.find((ep) => ep.operationId === opId); + if (endpoint) { + selectEndpoint(group, endpoint); + break; + } + } + }); + + el.search.addEventListener("input", () => renderNav(el.search.value)); + + document.addEventListener("keydown", (e) => { + if (e.key === "/" && document.activeElement !== el.search) { + e.preventDefault(); + el.search.focus(); + } + if (e.key === "Escape" && document.activeElement === el.search) { + el.search.blur(); + } + }); + + el.themeToggle.addEventListener("click", () => { + const root = document.documentElement; + const current = root.getAttribute("data-theme"); + const isDark = + current === "dark" || (current === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches); + const next = isDark ? "light" : "dark"; + root.setAttribute("data-theme", next); + localStorage.setItem("DoxaApi.theme", next); + }); + + const savedTheme = localStorage.getItem("DoxaApi.theme"); + if (savedTheme) document.documentElement.setAttribute("data-theme", savedTheme); + } + + // Helpers + function escapeHtml(str) { + return String(str ?? "").replace(/[&<>"']/g, (c) => ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }[c])); + } + + function escapeAttr(str) { + return escapeHtml(str); + } + + init(); +})(); + +window.DoxaApiPostmanLike = { + methods:["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"], + buildRequest:function(){ + return { + method: document.getElementById("customMethod")?.value || "GET", + url: document.getElementById("customUrl")?.value || "", + headers: [] + }; + } +}; diff --git a/DoxaApi/UI/Assets/index.html b/DoxaApi/UI/Assets/index.html new file mode 100644 index 0000000..7fd6146 --- /dev/null +++ b/DoxaApi/UI/Assets/index.html @@ -0,0 +1,71 @@ + + + + + + SampleApi + + + + + +
+ +
+
+ + + + + + + SampleApi + +
+
+
+
+ + + + +
+ + + +
+
+ +
+ + + + +
+
+
+ + + +
+
+ + + + + diff --git a/EonaCat.DoxaApi.sln b/EonaCat.DoxaApi.sln new file mode 100644 index 0000000..f3829f3 --- /dev/null +++ b/EonaCat.DoxaApi.sln @@ -0,0 +1,32 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.7.11903.348 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EonaCat.DoxaApi", "DoxaApi\EonaCat.DoxaApi.csproj", "{11111111-1111-1111-1111-111111111111}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApi", "sample\SampleApi\SampleApi.csproj", "{22222222-2222-2222-2222-222222222222}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3B167511-F7C3-4F80-B199-1E02ED686E11}" + ProjectSection(SolutionItems) = preProject + image.png = image.png + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11111111-1111-1111-1111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11111111-1111-1111-1111-111111111111}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11111111-1111-1111-1111-111111111111}.Release|Any CPU.Build.0 = Release|Any CPU + {22222222-2222-2222-2222-222222222222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22222222-2222-2222-2222-222222222222}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22222222-2222-2222-2222-222222222222}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22222222-2222-2222-2222-222222222222}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/LICENSE b/LICENSE index 494f695..ab37379 100644 --- a/LICENSE +++ b/LICENSE @@ -1,73 +1,204 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + https://EonaCat.com/license/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + OF SOFTWARE BY EONACAT (JEROEN SAEY) -1. Definitions. + 1. Definitions. -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. -END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS -APPENDIX: How to apply the Apache License to your work. + APPENDIX: How to apply the Apache License to your work. -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. -Copyright 2026 EonaCat + Copyright [yyyy] [name of copyright owner] -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index c982bde..62fc359 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,162 @@ # EonaCat.DoxaApi -EonaCat.DoxaApi \ No newline at end of file +A self-contained, API documentation UI for ASP.NET Core + +EonaCat.DoxaApi scans your controllers with plain `System.Reflection` and ASP.NET Core's own `IActionDescriptorCollectionProvider`, +builds a small OpenAPI-like JSON document with `System.Text.Json`, and serves a UI +This can also be used in an offline environment! + +![alt text](image.png) + +## Install + +```bash +dotnet add package EonaCat.DoxaApi +``` + +## Quick start + +```csharp +using EonaCat.DoxaApi; +using EonaCat.DoxaApi.Middleware; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddControllers(); +builder.Services.AddEonaCat.DoxaApi(options => +{ + options.Title = "My API"; + options.Description = "Internal service API"; + options.AccentColor = "#6366f1"; // any hex color +}); + +var app = builder.Build(); +app.UseRouting(); +app.UseEonaCat.DoxaApi(); // serves UI at /doxa and spec at /doxa/DoxaApi.json +app.MapControllers(); + +app.Run(); +``` + +Run your app and open `https://localhost:xxxx/doxa`. + +## Features + +- **Route-table style navigation** - endpoints grouped by controller, each method color-coded (GET/POST/PUT/PATCH/DELETE), searchable with `/`. +- **Schema viewer** - nested request/response shapes rendered as readable, syntax-colored trees, with required fields marked. +- **Try it out** - a real three-pane layout: browse → inspect → call, with path/query/header inputs, an editable JSON body (pre-filled with a generated example), and a live response panel with status, timing, and syntax-highlighted JSON. +- **Light & dark themes**, persisted, with a system-preference default. +- **XML doc comment support** - `/// `, ``, and `` are read directly from your project's generated `.xml` doc file (enable `true` in your csproj). +- **Attributes for fine control**: `[EonaCat.DoxaApiGroup]`, `[EonaCat.DoxaApiSummary]`, `[EonaCat.DoxaApiDescription]`, `[EonaCat.DoxaApiExample]`, `[EonaCat.DoxaApiHidden]`. +- **Zero external NuGet dependencies** - only references your app's own ASP.NET Core shared framework. + +## Configuration reference + +```csharp +builder.Services.AddEonaCat.DoxaApi(options => +{ + options.Title = "My API"; // shown in the top bar and browser tab + options.Description = "..."; // shown on the welcome screen + options.Version = "v1"; + options.RoutePrefix = "doxa"; // UI served at /{RoutePrefix} + options.AccentColor = "#6366f1"; // primary button/accent color + options.Theme = "auto"; // "auto" | "light" | "dark" +}); +``` + +## Attributes + +```csharp +[EonaCat.DoxaApiGroup("Users")] // override the left-nav group name +public class UsersController : ControllerBase +{ + /// Lists all users. // picked up automatically + [HttpGet] + public ActionResult> GetUsers() => ...; + + [HttpPost] + [EonaCat.DoxaApiExample("""{ "name": "Ada" }""")] // overrides the auto-generated example + public ActionResult Create(CreateUserRequest request) => ...; + + [HttpDelete("{id}")] + [EonaCat.DoxaApiHidden] // omit from docs entirely + public IActionResult Delete(Guid id) => ...; +} +``` + +## OpenAPI & Swagger + +EonaCat.DoxaApi exposes dedicated endpoints for importing and exporting industry-standard spec formats alongside its own native JSON. + +### Export + +| URL | Format | Notes | +|-----|--------|-------| +| `/{RoutePrefix}/DoxaApi.json` | Native EonaCat.DoxaApi JSON | Always available; used by the built-in UI | +| `/{RoutePrefix}/openapi.json` | **OpenAPI 3.0.3** | Import into Postman, Insomnia, Stoplight, etc. | +| `/{RoutePrefix}/swagger.json` | **Swagger 2.0** | Import into older tools, AWS API Gateway, Azure APIM, etc. | + +```bash +# Download the OpenAPI 3.0 spec +curl http://localhost:5000/doxa/openapi.json -o openapi.json + +# Download the Swagger 2.0 spec +curl http://localhost:5000/doxa/swagger.json -o swagger.json +``` + +### Import + +`POST /{RoutePrefix}/import` + +Send any OpenAPI 3.x or Swagger 2.0 JSON document in the request body. The server detects the format automatically and returns the native EonaCat.DoxaApi document. + +```bash +# Import a third-party OpenAPI spec and get the EonaCat.DoxaApi document back +curl -X POST http://localhost:5000/doxa/import \ + -H "Content-Type: application/json" \ + --data-binary @path/to/openapi.json +``` + +**Use cases:** +- Preview any public or third-party OpenAPI/Swagger spec in the EonaCat.DoxaApi UI +- Validate that your spec round-trips correctly through import → export +- Use the import endpoint as a conversion proxy (OpenAPI 3 ↔ Swagger 2) + +### Programmatic use + +The `OpenApiExporter`, `SwaggerExporter`, and `OpenApiImporter` classes in the +`EonaCat.DoxaApi.Interop` namespace are `public static` and can be used directly in your +own code: + +```csharp +using EonaCat.DoxaApi.Interop; +using EonaCat.DoxaApi.Models; + +// Export: ApiDocument → OpenAPI 3.0 JsonObject +ApiDocument doc = /* ... */; +System.Text.Json.Nodes.JsonObject openApi = OpenApiExporter.Export(doc); +string json = openApi.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); + +// Export: ApiDocument → Swagger 2.0 JsonObject +System.Text.Json.Nodes.JsonObject swagger = SwaggerExporter.Export(doc); + +// Import: OpenAPI 3.x or Swagger 2.0 string → ApiDocument +ApiDocument imported = OpenApiImporter.Import(File.ReadAllText("openapi.json")); + +// Import from a Stream +await using var stream = File.OpenRead("swagger.json"); +ApiDocument imported2 = OpenApiImporter.Import(stream); +``` + +## Sample project + +See `/sample/SampleApi` for a complete working example with two controllers (`Users`, `Orders`) demonstrating nested objects, enums, arrays, path/query parameters, and a deprecated endpoint. + +```bash +cd sample/SampleApi +dotnet run +# open http://localhost:5000/doxa +``` + +## License + +MIT diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0595b89951c74a6c669463d4e2ba62274036fa30 GIT binary patch literal 89562 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 IMBDrS02~H<8~^|S literal 0 HcmV?d00001 diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..5ec406a6c803c66dc64c566fa61ddbfa0b9db352 GIT binary patch literal 123218 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 literal 0 HcmV?d00001 diff --git a/sample/SampleApi/Controllers/OrdersController.cs b/sample/SampleApi/Controllers/OrdersController.cs new file mode 100644 index 0000000..61a8c46 --- /dev/null +++ b/sample/SampleApi/Controllers/OrdersController.cs @@ -0,0 +1,54 @@ +using EonaCat.DoxaApi.Attributes; +using Microsoft.AspNetCore.Mvc; +using SampleApi.Models; + +namespace SampleApi.Controllers +{ + [ApiController] + [Route("api/orders")] + [DoxaApiGroup("Orders")] + public class OrdersController : ControllerBase + { + private static readonly List _orders = new(); + + [HttpGet] + public ActionResult> GetOrders() + => Ok(_orders.OrderByDescending(o => o.PlacedAt).ToList()); + + [HttpGet("{id}")] + public ActionResult GetById(Guid id) + { + var order = _orders.FirstOrDefault(o => o.Id == id); + return order is null ? NotFound() : Ok(order); + } + + [HttpPost] + public ActionResult Create([FromBody] CreateOrderRequest request) + { + var order = new Order + { + Id = Guid.NewGuid(), + UserId = request.UserId, + Lines = request.Lines, + Total = request.Lines.Sum(l => l.UnitPrice * l.Quantity), + Status = OrderStatus.Pending, + PlacedAt = DateTime.UtcNow + }; + _orders.Add(order); + return CreatedAtAction(nameof(GetById), new { id = order.Id }, order); + } + + [HttpPost("{id}/cancel")] + public ActionResult Cancel(Guid id) + { + var order = _orders.FirstOrDefault(o => o.Id == id); + if (order is null) + { + return NotFound(); + } + + order.Status = OrderStatus.Cancelled; + return Ok(order); + } + } +} diff --git a/sample/SampleApi/Controllers/UsersController.cs b/sample/SampleApi/Controllers/UsersController.cs new file mode 100644 index 0000000..dd9a61b --- /dev/null +++ b/sample/SampleApi/Controllers/UsersController.cs @@ -0,0 +1,102 @@ +using EonaCat.DoxaApi.Attributes; +using Microsoft.AspNetCore.Mvc; +using SampleApi.Models; + +namespace SampleApi.Controllers +{ + [ApiController] + [Route("api/users")] + [DoxaApiGroup("Users")] + public class UsersController : ControllerBase + { + private static readonly List _users = new() + { + new User { Id = Guid.NewGuid(), Name = "Ada Lovelace", Email = "ada@example.com", Role = UserRole.Admin, CreatedAt = DateTime.UtcNow }, + new User { Id = Guid.NewGuid(), Name = "Alan Turing", Email = "alan@example.com", Role = UserRole.Member, CreatedAt = DateTime.UtcNow }, + }; + + [HttpGet] + public ActionResult> GetUsers([FromQuery] UserRole? role, [FromQuery] int page = 1) + { + var query = _users.AsEnumerable(); + if (role is not null) + { + query = query.Where(u => u.Role == role); + } + + return Ok(query.ToList()); + } + + [HttpGet("{id}")] + public ActionResult GetById(Guid id) + { + var user = _users.FirstOrDefault(u => u.Id == id); + return user is null ? NotFound() : Ok(user); + } + + [HttpPost] + [DoxaApiExample(""" + { + "name": "Grace Hopper", + "email": "grace@example.com", + "role": "Member", + "address": { "street": "1 Compiler Way", "city": "Arlington", "postalCode": "22201", "country": "US" } + } + """)] + public ActionResult Create([FromBody] CreateUserRequest request) + { + var user = new User + { + Id = Guid.NewGuid(), + Name = request.Name, + Email = request.Email, + Role = request.Role, + Address = request.Address, + CreatedAt = DateTime.UtcNow + }; + _users.Add(user); + return CreatedAtAction(nameof(GetById), new { id = user.Id }, user); + } + + [HttpPatch("{id}")] + public ActionResult Update(Guid id, [FromBody] UpdateUserRequest request) + { + var user = _users.FirstOrDefault(u => u.Id == id); + if (user is null) + { + return NotFound(); + } + + if (request.Name is not null) + { + user.Name = request.Name; + } + + if (request.Email is not null) + { + user.Email = request.Email; + } + + if (request.Role is not null) + { + user.Role = request.Role.Value; + } + + return Ok(user); + } + + [HttpDelete("{id}")] + [Obsolete("Use POST /api/users/{id}/archive instead.")] + public IActionResult Delete(Guid id) + { + var user = _users.FirstOrDefault(u => u.Id == id); + if (user is null) + { + return NotFound(); + } + + _users.Remove(user); + return NoContent(); + } + } +} diff --git a/sample/SampleApi/Models/Models.cs b/sample/SampleApi/Models/Models.cs new file mode 100644 index 0000000..a033c62 --- /dev/null +++ b/sample/SampleApi/Models/Models.cs @@ -0,0 +1,86 @@ +namespace SampleApi.Models +{ + public enum UserRole + { + Admin, + Member, + Guest + } + + public class User + { + + public Guid Id { get; set; } + + public string Name { get; set; } = ""; + + public string Email { get; set; } = ""; + + public UserRole Role { get; set; } + + public DateTime CreatedAt { get; set; } + + public Address? Address { get; set; } + + public List Tags { get; set; } = new(); + } + + public class Address + { + public string Street { get; set; } = ""; + public string City { get; set; } = ""; + public string PostalCode { get; set; } = ""; + public string Country { get; set; } = ""; + } + + public class CreateUserRequest + { + + public string Name { get; set; } = ""; + + public string Email { get; set; } = ""; + + public UserRole Role { get; set; } = UserRole.Member; + + public Address? Address { get; set; } + } + + public class UpdateUserRequest + { + public string? Name { get; set; } + public string? Email { get; set; } + public UserRole? Role { get; set; } + } + + public class Order + { + public Guid Id { get; set; } + public Guid UserId { get; set; } + public List Lines { get; set; } = new(); + public decimal Total { get; set; } + public OrderStatus Status { get; set; } + public DateTime PlacedAt { get; set; } + } + + public class OrderLine + { + public string Sku { get; set; } = ""; + public int Quantity { get; set; } + public decimal UnitPrice { get; set; } + } + + public enum OrderStatus + { + Pending, + Paid, + Shipped, + Delivered, + Cancelled + } + + public class CreateOrderRequest + { + public Guid UserId { get; set; } + public List Lines { get; set; } = new(); + } +} diff --git a/sample/SampleApi/Program.cs b/sample/SampleApi/Program.cs new file mode 100644 index 0000000..eece97d --- /dev/null +++ b/sample/SampleApi/Program.cs @@ -0,0 +1,27 @@ +using EonaCat.DoxaApi; +using EonaCat.DoxaApi.Middleware; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddDoxaApi(options => +{ + options.Title = "Sample API"; + options.Description = "A demo service showing off the DoxaApi UI - users and orders."; + options.Version = "v1"; + options.AccentColor = "#6366f1"; +}); + +var app = builder.Build(); + +app.UseRouting(); + +app.UseDoxaApi(options => +{ + + options.RoutePrefix = "doxa"; +}); + +app.MapControllers(); +app.MapGet("/", () => Results.Redirect("/doxa")); +app.Run(); diff --git a/sample/SampleApi/Properties/launchSettings.json b/sample/SampleApi/Properties/launchSettings.json new file mode 100644 index 0000000..38c14d7 --- /dev/null +++ b/sample/SampleApi/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "SampleApi": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:53010;http://localhost:53011" + } + } +} \ No newline at end of file diff --git a/sample/SampleApi/SampleApi.csproj b/sample/SampleApi/SampleApi.csproj new file mode 100644 index 0000000..98d4fca --- /dev/null +++ b/sample/SampleApi/SampleApi.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + true + $(NoWarn);CS1591 + + + + + + + +