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