Files
EonaCat.DoxaApi/DoxaApi/Importer/OpenApiImporter.cs
T
2026-06-23 20:12:51 +02:00

928 lines
23 KiB
C#

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<ApiDocument> 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<string>() ?? "API";
doc.Info.Version =
info["version"]?.Value<string>() ?? "v1";
doc.Info.Description =
info["description"]?.Value<string>();
}
if (root["servers"] is JArray servers)
{
foreach (var s in servers)
{
var url = s["url"]?.Value<string>();
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<string, ApiGroup>(
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<string>()
?? $"{method}_{SanitizePath(path)}",
Summary =
op["summary"]?.Value<string>(),
Description =
op["description"]?.Value<string>(),
Method = method,
Path = path,
Deprecated =
op["deprecated"]?.Value<bool>()
?? 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<string>()
?? "",
In =
param["in"]?.Value<string>()
?? "query",
Required =
param["required"]?.Value<bool>()
?? false,
Description =
param["description"]?.Value<string>(),
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<bool>()
?? 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<string>(),
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<string>() is string refValue)
{
return new SchemaModel
{
Type = "object",
RefName =
refValue.Split('/').Last()
};
}
var nullable =
obj["nullable"]?.Value<bool>()
?? false;
if (obj["enum"] is JArray enums)
{
return new SchemaModel
{
Type = "enum",
EnumValues =
enums
.Select(x =>
x?.Value<string>() ?? "")
.ToList(),
Nullable = nullable
};
}
var type =
obj["type"]?.Value<string>()
?? "object";
if (type == "array")
{
return new SchemaModel
{
Type = "array",
Items =
obj["items"] != null
? ParseSchema3(
obj["items"])
: null,
Nullable = nullable
};
}
if (type == "object")
{
Dictionary<string, SchemaModel>? props = null;
if (obj["properties"] is JObject properties)
{
props =
new Dictionary<string, SchemaModel>();
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<string>(),
Nullable = nullable
};
}
private static ApiDocument ImportSwagger2(JToken root)
{
var doc = new ApiDocument();
if (root["info"] is JObject info)
{
doc.Info.Title =
info["title"]?.Value<string>()
?? "API";
doc.Info.Version =
info["version"]?.Value<string>()
?? "v1";
doc.Info.Description =
info["description"]?.Value<string>();
}
var host =
root["host"]?.Value<string>();
var basePath =
root["basePath"]?.Value<string>()
?? "/";
var scheme =
root["schemes"] is JArray schemes
&& schemes.Count > 0
? schemes[0]?.Value<string>()
?? "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<string>() 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<string>() ?? "")
.ToList()
};
}
var type =
obj["type"]?.Value<string>()
?? "object";
if (type == "array")
{
return new SchemaModel
{
Type = "array",
Items =
obj["items"] != null
? ParseSchema2(obj["items"])
: null
};
}
if (type == "object")
{
Dictionary<string, SchemaModel>? props = null;
if (obj["properties"] is JObject properties)
{
props =
new Dictionary<string, SchemaModel>();
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<string>()
};
}
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<string> ParseSecurityArray(
JToken? node)
{
var result = new List<string>();
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<string>()
?? "apiKey";
var scheme =
new SecuritySchemeModel
{
Id = id,
Description =
obj["description"]
?.Value<string>()
};
if (type == "basic")
{
scheme.Type = "http";
scheme.Scheme = "basic";
}
else if (type == "apiKey")
{
scheme.Type = "apiKey";
scheme.Name =
obj["name"]?.Value<string>();
scheme.In =
obj["in"]?.Value<string>();
}
else
{
scheme.Type = type;
}
return scheme;
}
private static SecuritySchemeModel ParseSecurityScheme3(
string id,
JObject obj)
{
var scheme =
new SecuritySchemeModel
{
Id = id,
Type =
obj["type"]?.Value<string>()
?? "apiKey",
Description =
obj["description"]
?.Value<string>()
};
switch (scheme.Type)
{
case "apiKey":
scheme.Name =
obj["name"]?.Value<string>();
scheme.In =
obj["in"]?.Value<string>();
break;
case "http":
scheme.Scheme =
obj["scheme"]
?.Value<string>();
scheme.BearerFormat =
obj["bearerFormat"]
?.Value<string>();
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<string>(),
TokenUrl =
obj["tokenUrl"]
?.Value<string>(),
RefreshUrl =
obj["refreshUrl"]
?.Value<string>()
};
if (obj["scopes"] is JObject scopes)
{
foreach (var item in scopes.Properties())
{
flow.Scopes[item.Name] =
item.Value.Value<string>()
?? "";
}
}
return flow;
}
private static List<string> ParseStringArray(
JToken? node)
{
var list = new List<string>();
if (node is JArray arr)
{
foreach (var item in arr)
{
var value =
item.Value<string>();
if (value != null)
{
list.Add(value);
}
}
}
return list;
}
private static List<string>? ParseStringList(
JToken? node)
{
if (node is not JArray arr ||
arr.Count == 0)
{
return null;
}
return arr
.Select(x =>
x.Value<string>() ?? "")
.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('}', '_');
}
}
}