summary refs log tree commit diff
path: root/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Classes/OpenAPI/OpenAPISchema.cs
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-10-14 09:07:55 +0200
committerRory& <root@rory.gay>2025-10-14 09:07:55 +0200
commit58d92080d544e255ee3fd0c0e722a7c6f1c474a7 (patch)
treef3ba88ea3ad8f03b8a0a01fce11bea0965d4b4c6 /extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Classes/OpenAPI/OpenAPISchema.cs
parentSchema fixes, api bug fix (diff)
downloadserver-ts-58d92080d544e255ee3fd0c0e722a7c6f1c474a7.tar.xz
Admin api changes
Diffstat (limited to 'extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Classes/OpenAPI/OpenAPISchema.cs')
-rw-r--r--extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Classes/OpenAPI/OpenAPISchema.cs352
1 files changed, 352 insertions, 0 deletions
diff --git a/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Classes/OpenAPI/OpenAPISchema.cs b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Classes/OpenAPI/OpenAPISchema.cs
new file mode 100644

index 00000000..8e399302 --- /dev/null +++ b/extra/admin-api/Utilities/Spacebar.AdminAPI.TestClient/Classes/OpenAPI/OpenAPISchema.cs
@@ -0,0 +1,352 @@ +using System.Collections.Frozen; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Spacebar.AdminAPI.TestClient.Classes.OpenAPI; + +public class OpenApiSchema { + [JsonPropertyName("openapi")] + public string Version { get; set; } = null!; + + [JsonPropertyName("info")] + public OpenApiInfo Info { get; set; } = null!; + + [JsonPropertyName("externalDocs")] + public OpenApiExternalDocs? ExternalDocs { get; set; } + + [JsonPropertyName("paths")] + public Dictionary<string, OpenApiPath> Paths { get; set; } = null!; + + [JsonPropertyName("servers")] + public List<OpenApiServer> Servers { get; set; } = null!; + + [JsonPropertyName("components")] + public OpenApiComponents? Components { get; set; } = null!; + + public class OpenApiComponents { + [JsonPropertyName("schemas")] + public Dictionary<string, OpenApiSchemaRef>? Schemas { get; set; } = null!; + } +} + +public class OpenApiServer { + [JsonPropertyName("url")] + public string Url { get; set; } = null!; + + [JsonPropertyName("description")] + public string? Description { get; set; } +} + +public class OpenApiPath { + public FrozenSet<string> GetAvailableMethods() { + List<string> methods = new(); + if (Get != null) methods.Add("GET"); + if (Post != null) methods.Add("POST"); + if (Put != null) methods.Add("PUT"); + if (Delete != null) methods.Add("DELETE"); + if (Patch != null) methods.Add("PATCH"); + if (Options != null) methods.Add("OPTIONS"); + return methods.ToFrozenSet(); + } + + public bool HasMethod(string method) { + return method.ToLower() switch { + "get" => Get != null, + "post" => Post != null, + "put" => Put != null, + "delete" => Delete != null, + "patch" => Patch != null, + "options" => Options != null, + _ => false + }; + } + + public OpenApiOperation? GetOperation(string method) { + if (!HasMethod(method)) return null; + return method.ToLower() switch { + "get" => Get, + "post" => Post, + "put" => Put, + "delete" => Delete, + "patch" => Patch, + "options" => Options, + _ => null + }; + } + + [JsonPropertyName("get")] + public OpenApiOperation? Get { get; set; } + + [JsonPropertyName("post")] + public OpenApiOperation? Post { get; set; } + + [JsonPropertyName("put")] + public OpenApiOperation? Put { get; set; } + + [JsonPropertyName("delete")] + public OpenApiOperation? Delete { get; set; } + + [JsonPropertyName("patch")] + public OpenApiOperation? Patch { get; set; } + + [JsonPropertyName("options")] + public OpenApiOperation? Options { get; set; } + + public class OpenApiOperation { + [JsonPropertyName("description")] + public string Description { get; set; } = null!; + + [JsonPropertyName("parameters")] + public List<OpenApiParameter>? Parameters { get; set; } + + [JsonPropertyName("requestBody")] + public OpenApiRequestBody? RequestBody { get; set; } + + public class OpenApiParameter { + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("in")] + public string In { get; set; } = null!; + + [JsonPropertyName("required")] + public bool Required { get; set; } + + [JsonPropertyName("schema")] + public OpenApiSchemaRef Schema { get; set; } = null!; + + [JsonPropertyName("description")] + public string? Description { get; set; } + } + } +} + +public class OpenApiExternalDocs { + [JsonPropertyName("description")] + public string Description { get; set; } = null!; + + [JsonPropertyName("url")] + public string Url { get; set; } = null!; +} + +public class OpenApiInfo { + [JsonPropertyName("title")] + public string Title { get; set; } = null!; + + [JsonPropertyName("version")] + public string Version { get; set; } = null!; + + [JsonPropertyName("description")] + public string Description { get; set; } = null!; + + [JsonPropertyName("license")] + public OpenApiLicense License { get; set; } = null!; + + public class OpenApiLicense { + [JsonPropertyName("name")] + public string Name { get; set; } = null!; + + [JsonPropertyName("url")] + public string Url { get; set; } = null!; + } +} + +public class OpenApiRequestBody { + [JsonPropertyName("content")] + public OpenApiContent Content { get; set; } = null!; + + [JsonPropertyName("required")] + public bool Required { get; set; } +} + +public class OpenApiContent { + [JsonPropertyName("application/json")] + public OpenApiSchemaContainer? ApplicationJson { get; set; } + + public class OpenApiSchemaContainer { + [JsonPropertyName("schema")] + public OpenApiSchemaRef Schema { get; set; } = null!; + } +} + +[JsonConverter(typeof(OpenApiSchemaRefConverter))] +public class OpenApiSchemaRef { + public string? Description { get; set; } + public string? Type { get; set; } = null!; + public List<string>? Types { get; set; } = null!; + public string? Ref { get; set; } = null!; + public Dictionary<string, OpenApiSchemaRef>? Properties { get; set; } + public List<string>? Required { get; set; } + public int? MinLength { get; set; } + public int? MaxLength { get; set; } + public int? MinItems { get; set; } + public int? MaxItems { get; set; } + public object? Constant { get; set; } + public object? Default { get; set; } + public bool Nullable { get; set; } + public List<OpenApiSchemaRef>? AnyOf { get; set; } + public List<object>? Enum { get; set; } + public string? Format { get; set; } + + public OpenApiSchemaRef? GetReferencedSchema(OpenApiSchema schema) { + if (Ref == null) return null; + string refKey = Ref.Replace("#/components/schemas/", ""); + if (schema.Components?.Schemas != null && schema.Components.Schemas.TryGetValue(refKey, out var referencedSchema)) { + return referencedSchema; + } + + throw new KeyNotFoundException($"Referenced schema '{refKey}' not found."); + } +} + +public class OpenApiSchemaRefConverter : JsonConverter<OpenApiSchemaRef> { + public override OpenApiSchemaRef? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType != JsonTokenType.StartObject) { + throw new JsonException("Expected StartObject token"); + } + + using var jsonDoc = JsonDocument.ParseValue(ref reader); + var jsonObject = jsonDoc.RootElement; + var schemaRef = new OpenApiSchemaRef(); + + foreach (var property in jsonObject.EnumerateObject()) { + switch (property.Name) { + case "type": + if (property.Value.ValueKind == JsonValueKind.String) { + schemaRef.Type = property.Value.GetString(); + } + else if (property.Value.ValueKind == JsonValueKind.Array) { + var types = new List<string>(); + foreach (var item in property.Value.EnumerateArray()) { + if (item.ValueKind == JsonValueKind.String) { + types.Add(item.GetString()!); + } + else throw new JsonException("Expected string in type array"); + } + + schemaRef.Types = types; + } + + break; + case "$ref": + schemaRef.Ref = property.Value.GetString(); + break; + case "description": + schemaRef.Description = property.Value.GetString(); + break; + case "properties": + schemaRef.Properties = property.Value.EnumerateObject().ToDictionary(x => x.Name, x => x.Value.Deserialize<OpenApiSchemaRef>(options)!); + break; + case "required": + schemaRef.Required = property.Value.EnumerateArray().Select(item => item.GetString()!).ToList(); + break; + case "minLength": + schemaRef.MinLength = property.Value.GetInt32(); + break; + case "maxLength": + schemaRef.MaxLength = property.Value.GetInt32(); + break; + case "minItems": + schemaRef.MinItems = property.Value.GetInt32(); + break; + case "maxItems": + schemaRef.MaxItems = property.Value.GetInt32(); + break; + case "const": + if (property.Value.ValueKind == JsonValueKind.String) + schemaRef.Constant = property.Value.GetString(); + else if (property.Value.ValueKind == JsonValueKind.Number) + schemaRef.Constant = property.Value.GetInt32(); + else if (property.Value.ValueKind is JsonValueKind.True or JsonValueKind.False) + schemaRef.Constant = property.Value.GetBoolean(); + else throw new JsonException($"Expected string|int|bool in const, got {property.Value.ValueKind}"); + break; + case "default": + if (property.Value.ValueKind == JsonValueKind.String) + schemaRef.Default = property.Value.GetString(); + else if (property.Value.ValueKind == JsonValueKind.Number) + schemaRef.Default = property.Value.GetInt32(); + else if (property.Value.ValueKind is JsonValueKind.True or JsonValueKind.False) + schemaRef.Default = property.Value.GetBoolean(); + else if (property.Value.ValueKind == JsonValueKind.Null) + schemaRef.Default = null; + else if (property.Value.ValueKind == JsonValueKind.Array) + if (property.Value.GetArrayLength() > 0) throw new JsonException("Expected empty array in default"); + else schemaRef.Default = Array.Empty<object>(); + else throw new JsonException($"Expected string|int|bool|null in default, got {property.Value.ValueKind}"); + break; + case "enum": + var enumValues = new List<object>(); + foreach (var item in property.Value.EnumerateArray()) { + if (item.ValueKind == JsonValueKind.String) + enumValues.Add(item.GetString()!); + else if (item.ValueKind == JsonValueKind.Number) + enumValues.Add(item.GetInt32()); + else if (item.ValueKind is JsonValueKind.True or JsonValueKind.False) + enumValues.Add(item.GetBoolean()); + else if (item.ValueKind == JsonValueKind.Null) + enumValues.Add(null!); + else throw new JsonException($"Expected string|int|bool|null in enum, got {item.ValueKind}"); + } + + schemaRef.Enum = enumValues; + break; + case "nullable": + schemaRef.Nullable = property.Value.GetBoolean(); + break; + case "anyOf": + schemaRef.AnyOf = property.Value.EnumerateArray().Select(item => item.Deserialize<OpenApiSchemaRef>(options)!).ToList(); + break; + case "format": + schemaRef.Format = property.Value.GetString(); + break; + case "additionalProperties": //TODO + case "patternProperties": // Side effect of using JsonValue in typescript + break; + default: + Console.WriteLine($"Got unexpected prop {property.Name} in OpenApiSchemaRef!"); + break; + } + } + + return schemaRef; + } + + public override void Write(Utf8JsonWriter writer, OpenApiSchemaRef value, JsonSerializerOptions options) { + // throw new NotImplementedException("Serialization not implemented for OpenApiSchemaRef"); + writer.WriteStartObject(); + if (value.Type != null) { + writer.WriteString("type", value.Type); + } + else if (value.Types != null) { + writer.WritePropertyName("type"); + writer.WriteStartArray(); + foreach (var type in value.Types) { + writer.WriteStringValue(type); + } + + writer.WriteEndArray(); + } + + if (value.Ref != null) { + writer.WriteString("$ref", value.Ref); + } + + if (value.Description != null) { + writer.WriteString("description", value.Description); + } + + if (value.Properties != null) { + writer.WritePropertyName("properties"); + writer.WriteStartObject(); + foreach (var prop in value.Properties) { + writer.WritePropertyName(prop.Key); + JsonSerializer.Serialize(writer, prop.Value, options); + } + + writer.WriteEndObject(); + } + + writer.WriteEndObject(); + } +} \ No newline at end of file