767 lines
26 KiB
C#
767 lines
26 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using EonaCat.DoxaApi.Models;
|
|
|
|
namespace EonaCat.DoxaApi.Interop
|
|
{
|
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
// See the LICENSE file or go to https://EonaCat.com/license for full license details.
|
|
|
|
public static class OpenApiImporter
|
|
{
|
|
public static ApiDocument Import(string json)
|
|
{
|
|
var node = JsonNode.Parse(json) ?? throw new ArgumentException("Input is not valid JSON.");
|
|
return Import(node);
|
|
}
|
|
|
|
public static async Task<ApiDocument> ImportAsync(Stream stream)
|
|
{
|
|
var node = await JsonNode.ParseAsync(stream)
|
|
?? throw new ArgumentException("Stream is not valid JSON.");
|
|
|
|
return Import(node);
|
|
}
|
|
|
|
public static ApiDocument Import(JsonNode root)
|
|
{
|
|
if (root["openapi"] is not null)
|
|
{
|
|
return ImportOpenApi3(root);
|
|
}
|
|
|
|
if (root["swagger"] is not null)
|
|
{
|
|
return ImportSwagger2(root);
|
|
}
|
|
|
|
if (root["groups"] is not null &&
|
|
root["info"] is not null)
|
|
{
|
|
return JsonSerializer.Deserialize<ApiDocument>(root)!
|
|
?? throw new InvalidOperationException();
|
|
}
|
|
|
|
throw new NotSupportedException(
|
|
"Supported formats: DoxaApi, OpenAPI 3.x, Swagger 2.0");
|
|
}
|
|
|
|
private static ApiDocument ImportOpenApi3(JsonNode root)
|
|
{
|
|
var doc = new ApiDocument();
|
|
|
|
if (root["info"] is JsonObject info)
|
|
{
|
|
doc.Info.Title = info["title"]?.GetValue<string>() ?? "API";
|
|
doc.Info.Version = info["version"]?.GetValue<string>() ?? "v1";
|
|
doc.Info.Description = info["description"]?.GetValue<string>();
|
|
}
|
|
|
|
if (root["servers"] is JsonArray servers)
|
|
{
|
|
foreach (var s in servers)
|
|
{
|
|
if (s?["url"]?.GetValue<string>() is string url)
|
|
{
|
|
doc.Servers.Add(url);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (root["components"]?["schemas"] is JsonObject compSchemas)
|
|
{
|
|
foreach (var (name, schemaNode) in compSchemas)
|
|
{
|
|
if (schemaNode is not null)
|
|
{
|
|
doc.Schemas[name] = ParseSchema3(schemaNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (root["components"]?["securitySchemes"] is JsonObject compSecuritySchemes)
|
|
{
|
|
foreach (var (id, schemeNode) in compSecuritySchemes)
|
|
{
|
|
if (schemeNode is JsonObject schemeObj)
|
|
{
|
|
doc.SecuritySchemes[id] = ParseSecurityScheme3(id, schemeObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
var groups = new Dictionary<string, ApiGroup>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
if (root["paths"] is JsonObject paths)
|
|
{
|
|
foreach (var (rawPath, pathNode) in paths)
|
|
{
|
|
if (pathNode is not JsonObject pathItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var (methodStr, opNode) in pathItem)
|
|
{
|
|
if (opNode is not JsonObject op)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!IsHttpMethod(methodStr))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var endpoint = ParseOperation3(op, rawPath, methodStr.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(g => g.Name, StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
|
|
return doc;
|
|
}
|
|
|
|
private static ApiEndpoint ParseOperation3(JsonObject op, string path, string method)
|
|
{
|
|
var endpoint = new ApiEndpoint
|
|
{
|
|
OperationId = op["operationId"]?.GetValue<string>() ?? $"{method}_{SanitizePath(path)}",
|
|
Summary = op["summary"]?.GetValue<string>(),
|
|
Description = op["description"]?.GetValue<string>(),
|
|
Method = method,
|
|
Path = path,
|
|
Deprecated = op["deprecated"]?.GetValue<bool>() ?? false,
|
|
Tags = ParseStringArray(op["tags"]),
|
|
Security = ParseSecurityArray(op["security"])
|
|
};
|
|
|
|
if (op["parameters"] is JsonArray opParameters)
|
|
{
|
|
foreach (var p in opParameters)
|
|
{
|
|
if (p is not JsonObject param)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var schema = param["schema"] is JsonNode schemaNode
|
|
? ParseSchema3(schemaNode)
|
|
: new SchemaModel { Type = "string" };
|
|
|
|
endpoint.Parameters.Add(new ApiParameter
|
|
{
|
|
Name = param["name"]?.GetValue<string>() ?? "",
|
|
In = param["in"]?.GetValue<string>() ?? "query",
|
|
Required = param["required"]?.GetValue<bool>() ?? false,
|
|
Description = param["description"]?.GetValue<string>(),
|
|
Schema = schema
|
|
});
|
|
}
|
|
}
|
|
|
|
if (op["requestBody"] is JsonObject rb)
|
|
{
|
|
var required = rb["required"]?.GetValue<bool>() ?? false;
|
|
var contentNode = rb["content"] as JsonObject;
|
|
|
|
var (contentType, mediaObj) = PickMediaType(contentNode);
|
|
|
|
SchemaModel schema;
|
|
string? example = null;
|
|
|
|
if (mediaObj is JsonObject mo)
|
|
{
|
|
schema = mo["schema"] is JsonNode s ? ParseSchema3(s) : new SchemaModel { Type = "object" };
|
|
example = mo["example"]?.ToJsonString();
|
|
}
|
|
else
|
|
{
|
|
schema = new SchemaModel { Type = "object" };
|
|
}
|
|
|
|
endpoint.RequestBody = new RequestBodyModel
|
|
{
|
|
Required = required,
|
|
ContentType = contentType,
|
|
Schema = schema,
|
|
Example = example
|
|
};
|
|
}
|
|
|
|
if (op["responses"] is JsonObject responses)
|
|
{
|
|
foreach (var (statusCode, respNode) in responses)
|
|
{
|
|
if (respNode is not JsonObject resp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SchemaModel? schema = null;
|
|
if (resp["content"] is JsonObject content)
|
|
{
|
|
var (_, mediaObj) = PickMediaType(content);
|
|
if (mediaObj?["schema"] is JsonNode s)
|
|
{
|
|
schema = ParseSchema3(s);
|
|
}
|
|
}
|
|
|
|
endpoint.Responses.Add(new ResponseModel
|
|
{
|
|
StatusCode = statusCode,
|
|
Description = resp["description"]?.GetValue<string>(),
|
|
Schema = schema
|
|
});
|
|
}
|
|
}
|
|
|
|
return endpoint;
|
|
}
|
|
|
|
private static SchemaModel ParseSchema3(JsonNode node)
|
|
{
|
|
if (node is not JsonObject obj)
|
|
{
|
|
return new SchemaModel { Type = "object" };
|
|
}
|
|
|
|
if (obj["$ref"]?.GetValue<string>() is string refVal)
|
|
{
|
|
var refName = refVal.Split('/').Last();
|
|
return new SchemaModel { Type = "object", RefName = refName };
|
|
}
|
|
|
|
bool nullable = obj["nullable"]?.GetValue<bool>() ?? false;
|
|
|
|
if (obj["enum"] is JsonArray enumArray)
|
|
{
|
|
return new SchemaModel
|
|
{
|
|
Type = "enum",
|
|
EnumValues = enumArray.Select(e => e?.GetValue<string>() ?? "").ToList(),
|
|
Nullable = nullable
|
|
};
|
|
}
|
|
|
|
var type = obj["type"]?.GetValue<string>() ?? "object";
|
|
|
|
if (type == "array")
|
|
{
|
|
return new SchemaModel
|
|
{
|
|
Type = "array",
|
|
Items = obj["items"] is JsonNode items ? ParseSchema3(items) : null,
|
|
Nullable = nullable
|
|
};
|
|
}
|
|
|
|
if (type == "object")
|
|
{
|
|
Dictionary<string, SchemaModel>? props = null;
|
|
if (obj["properties"] is JsonObject propsNode)
|
|
{
|
|
props = new Dictionary<string, SchemaModel>();
|
|
foreach (var (name, propNode) in propsNode)
|
|
{
|
|
if (propNode is not null)
|
|
{
|
|
props[name] = ParseSchema3(propNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
SchemaModel? dictItems = null;
|
|
if (obj["additionalProperties"] is JsonNode addProps)
|
|
{
|
|
dictItems = ParseSchema3(addProps);
|
|
}
|
|
|
|
return new SchemaModel
|
|
{
|
|
Type = "object",
|
|
Properties = props,
|
|
Required = ParseStringList(obj["required"]),
|
|
Items = dictItems,
|
|
Nullable = nullable
|
|
};
|
|
}
|
|
|
|
return new SchemaModel
|
|
{
|
|
Type = type,
|
|
Format = obj["format"]?.GetValue<string>(),
|
|
Nullable = nullable
|
|
};
|
|
}
|
|
|
|
private static ApiDocument ImportSwagger2(JsonNode root)
|
|
{
|
|
var doc = new ApiDocument();
|
|
|
|
if (root["info"] is JsonObject info)
|
|
{
|
|
doc.Info.Title = info["title"]?.GetValue<string>() ?? "API";
|
|
doc.Info.Version = info["version"]?.GetValue<string>() ?? "v1";
|
|
doc.Info.Description = info["description"]?.GetValue<string>();
|
|
}
|
|
|
|
var host = root["host"]?.GetValue<string>();
|
|
var basePath = root["basePath"]?.GetValue<string>() ?? "/";
|
|
var scheme = root["schemes"] is JsonArray schemes && schemes.Count > 0
|
|
? schemes[0]?.GetValue<string>() ?? "https"
|
|
: "https";
|
|
|
|
if (host is not null)
|
|
{
|
|
doc.Servers.Add($"{scheme}://{host}{basePath.TrimEnd('/')}");
|
|
}
|
|
|
|
if (root["definitions"] is JsonObject defs)
|
|
{
|
|
foreach (var (name, defNode) in defs)
|
|
{
|
|
if (defNode is not null)
|
|
{
|
|
doc.Schemas[name] = ParseSchema2(defNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (root["securityDefinitions"] is JsonObject secDefs)
|
|
{
|
|
foreach (var (id, defNode) in secDefs)
|
|
{
|
|
if (defNode is JsonObject defObj)
|
|
{
|
|
doc.SecuritySchemes[id] = ParseSecurityScheme2(id, defObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
var groups = new Dictionary<string, ApiGroup>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
if (root["paths"] is JsonObject paths)
|
|
{
|
|
foreach (var (rawPath, pathNode) in paths)
|
|
{
|
|
if (pathNode is not JsonObject pathItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var (methodStr, opNode) in pathItem)
|
|
{
|
|
if (opNode is not JsonObject op)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!IsHttpMethod(methodStr))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var endpoint = ParseOperation2(op, rawPath, methodStr.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(g => g.Name, StringComparer.OrdinalIgnoreCase)
|
|
.ToList();
|
|
|
|
return doc;
|
|
}
|
|
|
|
private static ApiEndpoint ParseOperation2(JsonObject op, string path, string method)
|
|
{
|
|
var endpoint = new ApiEndpoint
|
|
{
|
|
OperationId = op["operationId"]?.GetValue<string>() ?? $"{method}_{SanitizePath(path)}",
|
|
Summary = op["summary"]?.GetValue<string>(),
|
|
Description = op["description"]?.GetValue<string>(),
|
|
Method = method,
|
|
Path = path,
|
|
Deprecated = op["deprecated"]?.GetValue<bool>() ?? false,
|
|
Tags = ParseStringArray(op["tags"]),
|
|
Security = ParseSecurityArray(op["security"])
|
|
};
|
|
|
|
if (op["parameters"] is JsonArray parameters)
|
|
{
|
|
foreach (var p in parameters)
|
|
{
|
|
if (p is not JsonObject param)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var inLoc = param["in"]?.GetValue<string>() ?? "query";
|
|
|
|
if (inLoc == "body")
|
|
{
|
|
endpoint.RequestBody = new RequestBodyModel
|
|
{
|
|
Required = param["required"]?.GetValue<bool>() ?? false,
|
|
ContentType = "application/json",
|
|
Schema = param["schema"] is JsonNode s ? ParseSchema2(s) : new SchemaModel { Type = "object" }
|
|
};
|
|
continue;
|
|
}
|
|
|
|
endpoint.Parameters.Add(new ApiParameter
|
|
{
|
|
Name = param["name"]?.GetValue<string>() ?? "",
|
|
In = inLoc,
|
|
Required = param["required"]?.GetValue<bool>() ?? false,
|
|
Description = param["description"]?.GetValue<string>(),
|
|
Schema = ParseInlineSchema2(param)
|
|
});
|
|
}
|
|
}
|
|
|
|
if (op["responses"] is JsonObject responses)
|
|
{
|
|
foreach (var (statusCode, respNode) in responses)
|
|
{
|
|
if (respNode is not JsonObject resp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SchemaModel? schema = null;
|
|
if (resp["schema"] is JsonNode s)
|
|
{
|
|
schema = ParseSchema2(s);
|
|
}
|
|
|
|
endpoint.Responses.Add(new ResponseModel
|
|
{
|
|
StatusCode = statusCode,
|
|
Description = resp["description"]?.GetValue<string>(),
|
|
Schema = schema
|
|
});
|
|
}
|
|
}
|
|
|
|
return endpoint;
|
|
}
|
|
|
|
private static SchemaModel ParseInlineSchema2(JsonObject param)
|
|
{
|
|
if (param["$ref"]?.GetValue<string>() is string refVal)
|
|
{
|
|
return new SchemaModel { Type = "object", RefName = refVal.Split('/').Last() };
|
|
}
|
|
|
|
if (param["enum"] is JsonArray enumArray)
|
|
{
|
|
return new SchemaModel
|
|
{
|
|
Type = "enum",
|
|
EnumValues = enumArray.Select(e => e?.GetValue<string>() ?? "").ToList()
|
|
};
|
|
}
|
|
|
|
var type = param["type"]?.GetValue<string>() ?? "string";
|
|
if (type == "array")
|
|
{
|
|
return new SchemaModel
|
|
{
|
|
Type = "array",
|
|
Items = param["items"] is JsonNode items ? ParseInlineSchema2((JsonObject)items) : null
|
|
};
|
|
}
|
|
|
|
return new SchemaModel
|
|
{
|
|
Type = type,
|
|
Format = param["format"]?.GetValue<string>()
|
|
};
|
|
}
|
|
|
|
private static SchemaModel ParseSchema2(JsonNode node)
|
|
{
|
|
if (node is not JsonObject obj)
|
|
{
|
|
return new SchemaModel { Type = "object" };
|
|
}
|
|
|
|
if (obj["$ref"]?.GetValue<string>() is string refVal)
|
|
{
|
|
return new SchemaModel { Type = "object", RefName = refVal.Split('/').Last() };
|
|
}
|
|
|
|
if (obj["enum"] is JsonArray enumArray)
|
|
{
|
|
return new SchemaModel
|
|
{
|
|
Type = "enum",
|
|
EnumValues = enumArray.Select(e => e?.GetValue<string>() ?? "").ToList()
|
|
};
|
|
}
|
|
|
|
var type = obj["type"]?.GetValue<string>() ?? "object";
|
|
|
|
if (type == "array")
|
|
{
|
|
return new SchemaModel
|
|
{
|
|
Type = "array",
|
|
Items = obj["items"] is JsonNode items ? ParseSchema2(items) : null
|
|
};
|
|
}
|
|
|
|
if (type == "object")
|
|
{
|
|
Dictionary<string, SchemaModel>? props = null;
|
|
if (obj["properties"] is JsonObject propsNode)
|
|
{
|
|
props = new Dictionary<string, SchemaModel>();
|
|
foreach (var (name, propNode) in propsNode)
|
|
{
|
|
if (propNode is not null)
|
|
{
|
|
props[name] = ParseSchema2(propNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
SchemaModel? dictItems = null;
|
|
if (obj["additionalProperties"] is JsonNode addProps)
|
|
{
|
|
dictItems = ParseSchema2(addProps);
|
|
}
|
|
|
|
return new SchemaModel
|
|
{
|
|
Type = "object",
|
|
Properties = props,
|
|
Required = ParseStringList(obj["required"]),
|
|
Items = dictItems
|
|
};
|
|
}
|
|
|
|
return new SchemaModel
|
|
{
|
|
Type = type,
|
|
Format = obj["format"]?.GetValue<string>()
|
|
};
|
|
}
|
|
|
|
private static (string contentType, JsonObject? mediaObj) PickMediaType(JsonObject? content)
|
|
{
|
|
if (content is null)
|
|
{
|
|
return ("application/json", null);
|
|
}
|
|
|
|
if (content["application/json"] is JsonObject json)
|
|
{
|
|
return ("application/json", json);
|
|
}
|
|
|
|
foreach (var (ct, node) in content)
|
|
{
|
|
if (node is JsonObject obj)
|
|
{
|
|
return (ct, obj);
|
|
}
|
|
}
|
|
|
|
return ("application/json", null);
|
|
}
|
|
|
|
private static List<string> ParseSecurityArray(JsonNode? node)
|
|
{
|
|
var list = new List<string>();
|
|
if (node is not JsonArray arr)
|
|
{
|
|
return list;
|
|
}
|
|
|
|
foreach (var requirement in arr)
|
|
{
|
|
if (requirement is JsonObject reqObj)
|
|
{
|
|
foreach (var (schemeId, _) in reqObj)
|
|
{
|
|
list.Add(schemeId);
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private static SecuritySchemeModel ParseSecurityScheme2(string id, JsonObject obj)
|
|
{
|
|
var type = obj["type"]?.GetValue<string>() ?? "apiKey";
|
|
var scheme = new SecuritySchemeModel
|
|
{
|
|
Id = id,
|
|
Description = obj["description"]?.GetValue<string>()
|
|
};
|
|
|
|
if (type == "basic")
|
|
{
|
|
scheme.Type = "http";
|
|
scheme.Scheme = "basic";
|
|
}
|
|
else if (type == "apiKey")
|
|
{
|
|
scheme.Type = "apiKey";
|
|
scheme.Name = obj["name"]?.GetValue<string>();
|
|
scheme.In = obj["in"]?.GetValue<string>();
|
|
}
|
|
else if (type == "oauth2")
|
|
{
|
|
scheme.Type = "oauth2";
|
|
var flow = obj["flow"]?.GetValue<string>();
|
|
var flowModel = new OAuthFlowModel
|
|
{
|
|
AuthorizationUrl = obj["authorizationUrl"]?.GetValue<string>(),
|
|
TokenUrl = obj["tokenUrl"]?.GetValue<string>()
|
|
};
|
|
if (obj["scopes"] is JsonObject scopesObj)
|
|
{
|
|
foreach (var (k, v) in scopesObj)
|
|
{
|
|
flowModel.Scopes[k] = v?.GetValue<string>() ?? "";
|
|
}
|
|
}
|
|
|
|
scheme.Flows = flow switch
|
|
{
|
|
"application" => new OAuthFlowsModel { ClientCredentials = flowModel },
|
|
"accessCode" => new OAuthFlowsModel { AuthorizationCode = flowModel },
|
|
"password" => new OAuthFlowsModel { Password = flowModel },
|
|
_ => new OAuthFlowsModel { Implicit = flowModel }
|
|
};
|
|
}
|
|
else
|
|
{
|
|
scheme.Type = type;
|
|
}
|
|
|
|
return scheme;
|
|
}
|
|
|
|
private static SecuritySchemeModel ParseSecurityScheme3(string id, JsonObject obj)
|
|
{
|
|
var scheme = new SecuritySchemeModel
|
|
{
|
|
Id = id,
|
|
Type = obj["type"]?.GetValue<string>() ?? "apiKey",
|
|
Description = obj["description"]?.GetValue<string>()
|
|
};
|
|
|
|
switch (scheme.Type)
|
|
{
|
|
case "apiKey":
|
|
scheme.Name = obj["name"]?.GetValue<string>();
|
|
scheme.In = obj["in"]?.GetValue<string>();
|
|
break;
|
|
|
|
case "http":
|
|
scheme.Scheme = obj["scheme"]?.GetValue<string>();
|
|
scheme.BearerFormat = obj["bearerFormat"]?.GetValue<string>();
|
|
break;
|
|
|
|
case "oauth2":
|
|
if (obj["flows"] is JsonObject flowsObj)
|
|
{
|
|
scheme.Flows = new OAuthFlowsModel
|
|
{
|
|
ClientCredentials = ParseOAuthFlow3(flowsObj["clientCredentials"] as JsonObject),
|
|
AuthorizationCode = ParseOAuthFlow3(flowsObj["authorizationCode"] as JsonObject),
|
|
Password = ParseOAuthFlow3(flowsObj["password"] as JsonObject),
|
|
Implicit = ParseOAuthFlow3(flowsObj["implicit"] as JsonObject)
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
|
|
return scheme;
|
|
}
|
|
|
|
private static OAuthFlowModel? ParseOAuthFlow3(JsonObject? obj)
|
|
{
|
|
if (obj is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var flow = new OAuthFlowModel
|
|
{
|
|
AuthorizationUrl = obj["authorizationUrl"]?.GetValue<string>(),
|
|
TokenUrl = obj["tokenUrl"]?.GetValue<string>(),
|
|
RefreshUrl = obj["refreshUrl"]?.GetValue<string>()
|
|
};
|
|
|
|
if (obj["scopes"] is JsonObject scopesObj)
|
|
{
|
|
foreach (var (k, v) in scopesObj)
|
|
{
|
|
flow.Scopes[k] = v?.GetValue<string>() ?? "";
|
|
}
|
|
}
|
|
|
|
return flow;
|
|
}
|
|
|
|
private static List<string> ParseStringArray(JsonNode? node)
|
|
{
|
|
var list = new List<string>();
|
|
if (node is JsonArray arr)
|
|
{
|
|
foreach (var item in arr)
|
|
{
|
|
if (item?.GetValue<string>() is string s)
|
|
{
|
|
list.Add(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
private static List<string>? ParseStringList(JsonNode? node)
|
|
{
|
|
if (node is not JsonArray arr || arr.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return arr.Select(e => e?.GetValue<string>() ?? "").ToList();
|
|
}
|
|
|
|
private static bool IsHttpMethod(string s) =>
|
|
s is "get" or "post" or "put" or "patch" or "delete" or "head" or "options" or "trace";
|
|
|
|
private static string SanitizePath(string path)
|
|
=> path.Trim('/').Replace('/', '_').Replace('{', '_').Replace('}', '_');
|
|
}
|
|
} |