273 lines
8.2 KiB
C#
273 lines
8.2 KiB
C#
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<string> { 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"
|
|
};
|
|
}
|
|
}
|