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" }; } }