using EonaCat.DoxaApi.Models; using EonaCat.Json; using EonaCat.Json.Linq; namespace EonaCat.DoxaApi.Interop { public static class OpenApiImporter { public static ApiDocument Import(string json) { var node = JToken.Parse(json); return Import(node); } public static async Task ImportAsync(Stream stream) { using var reader = new StreamReader(stream); var json = await reader.ReadToEndAsync(); return Import(JToken.Parse(json)); } public static ApiDocument Import(JToken root) { if (root["openapi"] != null) { return ImportOpenApi3(root); } if (root["swagger"] != null) { return ImportSwagger2(root); } if (root["groups"] != null && root["info"] != null) { return DoxaApiImporter.Import( root.ToString(Formatting.None)); } throw new NotSupportedException( "Supported formats: DoxaApi, OpenAPI 3.x, Swagger 2.0"); } private static ApiDocument ImportOpenApi3(JToken root) { var doc = new ApiDocument(); if (root["info"] is JObject info) { doc.Info.Title = info["title"]?.Value() ?? "API"; doc.Info.Version = info["version"]?.Value() ?? "v1"; doc.Info.Description = info["description"]?.Value(); } if (root["servers"] is JArray servers) { foreach (var s in servers) { var url = s["url"]?.Value(); if (url != null) { doc.Servers.Add(url); } } } if (root["components"]?["schemas"] is JObject schemas) { foreach (var item in schemas.Properties()) { doc.Schemas[item.Name] = ParseSchema3(item.Value); } } if (root["components"]?["securitySchemes"] is JObject security) { foreach (var item in security.Properties()) { if (item.Value is JObject scheme) { doc.SecuritySchemes[item.Name] = ParseSecurityScheme3( item.Name, scheme); } } } var groups = new Dictionary( StringComparer.OrdinalIgnoreCase); if (root["paths"] is JObject paths) { foreach (var path in paths.Properties()) { if (path.Value is not JObject pathItem) { continue; } foreach (var method in pathItem.Properties()) { if (!IsHttpMethod(method.Name)) { continue; } if (method.Value is not JObject op) { continue; } var endpoint = ParseOperation3( op, path.Name, method.Name.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( x => x.Name, StringComparer.OrdinalIgnoreCase) .ToList(); return doc; } private static ApiEndpoint ParseOperation3( JObject op, string path, string method) { var endpoint = new ApiEndpoint { OperationId = op["operationId"]?.Value() ?? $"{method}_{SanitizePath(path)}", Summary = op["summary"]?.Value(), Description = op["description"]?.Value(), Method = method, Path = path, Deprecated = op["deprecated"]?.Value() ?? false, Tags = ParseStringArray(op["tags"]), Security = ParseSecurityArray(op["security"]) }; if (op["parameters"] is JArray parameters) { foreach (var item in parameters) { if (item is not JObject param) { continue; } endpoint.Parameters.Add(new ApiParameter { Name = param["name"]?.Value() ?? "", In = param["in"]?.Value() ?? "query", Required = param["required"]?.Value() ?? false, Description = param["description"]?.Value(), Schema = param["schema"] != null ? ParseSchema3(param["schema"]) : new SchemaModel { Type = "string" } }); } } if (op["requestBody"] is JObject rb) { var content = rb["content"] as JObject; var picked = PickMediaType(content); SchemaModel schema; string? example = null; if (picked.mediaObj != null) { schema = picked.mediaObj["schema"] != null ? ParseSchema3( picked.mediaObj["schema"]) : new SchemaModel { Type = "object" }; example = picked.mediaObj["example"] ?.ToString(Formatting.None); } else { schema = new SchemaModel { Type = "object" }; } endpoint.RequestBody = new RequestBodyModel { Required = rb["required"]?.Value() ?? false, ContentType = picked.contentType, Schema = schema, Example = example }; } if (op["responses"] is JObject responses) { foreach (var response in responses.Properties()) { if (response.Value is not JObject resp) { continue; } SchemaModel? schema = null; if (resp["content"] is JObject content) { var picked = PickMediaType(content); if (picked.mediaObj?["schema"] != null) { schema = ParseSchema3( picked.mediaObj["schema"]); } } endpoint.Responses.Add( new ResponseModel { StatusCode = response.Name, Description = resp["description"] ?.Value(), Schema = schema }); } } return endpoint; } private static SchemaModel ParseSchema3(JToken node) { if (node is not JObject obj) { return new SchemaModel { Type = "object" }; } if (obj["$ref"]?.Value() is string refValue) { return new SchemaModel { Type = "object", RefName = refValue.Split('/').Last() }; } var nullable = obj["nullable"]?.Value() ?? false; if (obj["enum"] is JArray enums) { return new SchemaModel { Type = "enum", EnumValues = enums .Select(x => x?.Value() ?? "") .ToList(), Nullable = nullable }; } var type = obj["type"]?.Value() ?? "object"; if (type == "array") { return new SchemaModel { Type = "array", Items = obj["items"] != null ? ParseSchema3( obj["items"]) : null, Nullable = nullable }; } if (type == "object") { Dictionary? props = null; if (obj["properties"] is JObject properties) { props = new Dictionary(); foreach (var property in properties.Properties()) { props[property.Name] = ParseSchema3( property.Value); } } SchemaModel? dictionaryItems = null; if (obj["additionalProperties"] != null) { dictionaryItems = ParseSchema3( obj["additionalProperties"]); } return new SchemaModel { Type = "object", Properties = props, Required = ParseStringList( obj["required"]), Items = dictionaryItems, Nullable = nullable }; } return new SchemaModel { Type = type, Format = obj["format"]?.Value(), Nullable = nullable }; } private static ApiDocument ImportSwagger2(JToken root) { var doc = new ApiDocument(); if (root["info"] is JObject info) { doc.Info.Title = info["title"]?.Value() ?? "API"; doc.Info.Version = info["version"]?.Value() ?? "v1"; doc.Info.Description = info["description"]?.Value(); } var host = root["host"]?.Value(); var basePath = root["basePath"]?.Value() ?? "/"; var scheme = root["schemes"] is JArray schemes && schemes.Count > 0 ? schemes[0]?.Value() ?? "https" : "https"; if (host != null) { doc.Servers.Add( $"{scheme}://{host}{basePath.TrimEnd('/')}"); } if (root["definitions"] is JObject definitions) { foreach (var item in definitions.Properties()) { doc.Schemas[item.Name] = ParseSchema2(item.Value); } } if (root["securityDefinitions"] is JObject security) { foreach (var item in security.Properties()) { if (item.Value is JObject obj) { doc.SecuritySchemes[item.Name] = ParseSecurityScheme2( item.Name, obj); } } } return doc; } private static SchemaModel ParseSchema2(JToken node) { if (node is not JObject obj) { return new SchemaModel { Type = "object" }; } if (obj["$ref"]?.Value() is string refValue) { return new SchemaModel { Type = "object", RefName = refValue.Split('/').Last() }; } if (obj["enum"] is JArray enums) { return new SchemaModel { Type = "enum", EnumValues = enums.Select(x => x?.Value() ?? "") .ToList() }; } var type = obj["type"]?.Value() ?? "object"; if (type == "array") { return new SchemaModel { Type = "array", Items = obj["items"] != null ? ParseSchema2(obj["items"]) : null }; } if (type == "object") { Dictionary? props = null; if (obj["properties"] is JObject properties) { props = new Dictionary(); foreach (var property in properties.Properties()) { props[property.Name] = ParseSchema2(property.Value); } } SchemaModel? dictionaryItems = null; if (obj["additionalProperties"] != null) { dictionaryItems = ParseSchema2( obj["additionalProperties"]); } return new SchemaModel { Type = "object", Properties = props, Required = ParseStringList( obj["required"]), Items = dictionaryItems }; } return new SchemaModel { Type = type, Format = obj["format"]?.Value() }; } private static (string contentType, JObject? mediaObj) PickMediaType(JObject? content) { if (content == null) { return ("application/json", null); } if (content["application/json"] is JObject json) { return ("application/json", json); } foreach (var item in content.Properties()) { if (item.Value is JObject obj) { return (item.Name, obj); } } return ("application/json", null); } private static List ParseSecurityArray( JToken? node) { var result = new List(); if (node is not JArray arr) { return result; } foreach (var item in arr) { if (item is JObject obj) { foreach (var prop in obj.Properties()) { result.Add(prop.Name); } } } return result; } private static SecuritySchemeModel ParseSecurityScheme2( string id, JObject obj) { var type = obj["type"]?.Value() ?? "apiKey"; var scheme = new SecuritySchemeModel { Id = id, Description = obj["description"] ?.Value() }; if (type == "basic") { scheme.Type = "http"; scheme.Scheme = "basic"; } else if (type == "apiKey") { scheme.Type = "apiKey"; scheme.Name = obj["name"]?.Value(); scheme.In = obj["in"]?.Value(); } else { scheme.Type = type; } return scheme; } private static SecuritySchemeModel ParseSecurityScheme3( string id, JObject obj) { var scheme = new SecuritySchemeModel { Id = id, Type = obj["type"]?.Value() ?? "apiKey", Description = obj["description"] ?.Value() }; switch (scheme.Type) { case "apiKey": scheme.Name = obj["name"]?.Value(); scheme.In = obj["in"]?.Value(); break; case "http": scheme.Scheme = obj["scheme"] ?.Value(); scheme.BearerFormat = obj["bearerFormat"] ?.Value(); break; case "oauth2": if (obj["flows"] is JObject flows) { scheme.Flows = new OAuthFlowsModel { ClientCredentials = ParseOAuthFlow3( flows["clientCredentials"] as JObject), AuthorizationCode = ParseOAuthFlow3( flows["authorizationCode"] as JObject), Password = ParseOAuthFlow3( flows["password"] as JObject), Implicit = ParseOAuthFlow3( flows["implicit"] as JObject) }; } break; } return scheme; } private static OAuthFlowModel? ParseOAuthFlow3( JObject? obj) { if (obj == null) { return null; } var flow = new OAuthFlowModel { AuthorizationUrl = obj["authorizationUrl"] ?.Value(), TokenUrl = obj["tokenUrl"] ?.Value(), RefreshUrl = obj["refreshUrl"] ?.Value() }; if (obj["scopes"] is JObject scopes) { foreach (var item in scopes.Properties()) { flow.Scopes[item.Name] = item.Value.Value() ?? ""; } } return flow; } private static List ParseStringArray( JToken? node) { var list = new List(); if (node is JArray arr) { foreach (var item in arr) { var value = item.Value(); if (value != null) { list.Add(value); } } } return list; } private static List? ParseStringList( JToken? node) { if (node is not JArray arr || arr.Count == 0) { return null; } return arr .Select(x => x.Value() ?? "") .ToList(); } private static bool IsHttpMethod(string s) { return s is "get" or "post" or "put" or "patch" or "delete" or "head" or "options" or "trace"; } private static string SanitizePath(string path) { return path .Trim('/') .Replace('/', '_') .Replace('{', '_') .Replace('}', '_'); } } }