diff --git a/app/MindWork AI Studio/Tools/Services/RustEnumConverter.cs b/app/MindWork AI Studio/Tools/Services/RustEnumConverter.cs new file mode 100644 index 00000000..3f9c24ee --- /dev/null +++ b/app/MindWork AI Studio/Tools/Services/RustEnumConverter.cs @@ -0,0 +1,112 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AIStudio.Tools.Services; + +/// +/// Converts enum values for Rust communication. +/// +/// +/// Rust expects PascalCase enum values (e.g., "VoiceRecordingToggle"), +/// while .NET uses UPPER_SNAKE_CASE (e.g., "VOICE_RECORDING_TOGGLE"). +/// This converter handles the bidirectional conversion. +/// +public sealed class RustEnumConverter : JsonConverter +{ + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(); + + public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum; + + public override object? Read(ref Utf8JsonReader reader, Type enumType, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var text = reader.GetString(); + text = ConvertToUpperSnakeCase(text); + + if (Enum.TryParse(enumType, text, out var result)) + return result; + } + + LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{enumType.Name}' enum; token type: {reader.TokenType}"); + return Activator.CreateInstance(enumType); + } + + public override object ReadAsPropertyName(ref Utf8JsonReader reader, Type enumType, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + var text = reader.GetString(); + text = ConvertToUpperSnakeCase(text); + + if (Enum.TryParse(enumType, text, out var result)) + return result; + } + + LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{enumType.Name}' enum; token type: {reader.TokenType}"); + return Activator.CreateInstance(enumType)!; + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + writer.WriteStringValue(ConvertToPascalCase(value.ToString())); + } + + public override void WriteAsPropertyName(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + writer.WritePropertyName(ConvertToPascalCase(value.ToString())!); + } + + /// + /// Converts UPPER_SNAKE_CASE to PascalCase. + /// + /// The text to convert (e.g., "VOICE_RECORDING_TOGGLE"). + /// The converted text as PascalCase (e.g., "VoiceRecordingToggle"). + private static string ConvertToPascalCase(string? text) + { + if (string.IsNullOrWhiteSpace(text)) + return string.Empty; + + var parts = text.Split('_', StringSplitOptions.RemoveEmptyEntries); + var sb = new StringBuilder(); + + foreach (var part in parts) + { + if (part.Length == 0) + continue; + + // First character uppercase, rest lowercase: + sb.Append(char.ToUpperInvariant(part[0])); + if (part.Length > 1) + sb.Append(part[1..].ToLowerInvariant()); + } + + return sb.ToString(); + } + + /// + /// Converts a string to UPPER_SNAKE_CASE. + /// + /// The text to convert. + /// The converted text as UPPER_SNAKE_CASE. + private static string ConvertToUpperSnakeCase(string? text) + { + if (string.IsNullOrWhiteSpace(text)) + return string.Empty; + + var sb = new StringBuilder(text.Length); + var lastCharWasLowerCase = false; + + foreach (var c in text) + { + if (char.IsUpper(c) && lastCharWasLowerCase) + sb.Append('_'); + + sb.Append(char.ToUpperInvariant(c)); + lastCharWasLowerCase = char.IsLower(c); + } + + return sb.ToString(); + } +} diff --git a/app/MindWork AI Studio/Tools/Services/RustService.cs b/app/MindWork AI Studio/Tools/Services/RustService.cs index d4106cde..6272378c 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.cs @@ -1,7 +1,6 @@ using System.Security.Cryptography; using System.Text.Json; -using AIStudio.Settings; using AIStudio.Tools.PluginSystem; using Version = System.Version; @@ -24,7 +23,7 @@ public sealed partial class RustService : BackgroundService PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, Converters = { - new TolerantEnumConverter(), + new RustEnumConverter(), }, };