| File: Internal\Json\EnumNameHelpers.cs | Web Access |
| Project: src\aspnetcore\src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.JsonTranscoding\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj (Microsoft.AspNetCore.Grpc.JsonTranscoding) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Concurrent; using System.Linq; using System.Reflection; using Google.Protobuf.Reflection; namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json; internal static class JsonNamingHelpers { private static readonly ConcurrentDictionary<Type, EnumMapping> _enumMappings = new ConcurrentDictionary<Type, EnumMapping>(); internal static EnumValueDescriptor? GetEnumFieldReadValue(EnumDescriptor enumDescriptor, string value, GrpcJsonSettings settings) { string resolvedName; if (settings.RemoveEnumPrefix) { var nameMapping = GetEnumMapping(enumDescriptor); if (!nameMapping.RemoveEnumPrefixMapping.TryGetValue(value, out var n)) { return null; } resolvedName = n; } else { resolvedName = value; } return enumDescriptor.FindValueByName(resolvedName); } internal static string? GetEnumFieldWriteName(EnumDescriptor enumDescriptor, object value, GrpcJsonSettings settings) { var enumMapping = GetEnumMapping(enumDescriptor); // If this returns false, name will be null, which is what we want. if (!enumMapping.WriteMapping.TryGetValue(value, out var mapping)) { return null; } return settings.RemoveEnumPrefix ? mapping.RemoveEnumPrefixName : mapping.OriginalName; } private static EnumMapping GetEnumMapping(EnumDescriptor enumDescriptor) { return _enumMappings.GetOrAdd( enumDescriptor.ClrType, static (t, descriptor) => GetEnumMapping(descriptor.Name, t), enumDescriptor); } private static EnumMapping GetEnumMapping(string enumName, Type enumType) { var nameMappings = enumType.GetTypeInfo().DeclaredFields .Where(f => f.IsStatic) .Where(f => f.GetCustomAttributes<OriginalNameAttribute>().FirstOrDefault()?.PreferredAlias ?? true) .Select(f => { // If the attribute hasn't been applied, fall back to the name of the field. var fieldName = f.GetCustomAttributes<OriginalNameAttribute>().FirstOrDefault()?.Name ?? f.Name; return new NameMapping { Value = f.GetValue(null)!, OriginalName = fieldName, RemoveEnumPrefixName = GetEnumValueName(enumName, fieldName) }; }) .ToList(); var writeMapping = nameMappings.ToDictionary(m => m.Value, m => m); // Add original names as fallback when mapping enum values with removed prefixes. // There are added to the dictionary first so they are overridden by the mappings with removed prefixes. var removeEnumPrefixMapping = nameMappings.ToDictionary(m => m.OriginalName, m => m.OriginalName); // Protobuf codegen prevents collision of enum names when the prefix is removed. // For example, the following enum will fail to build because both fields would resolve to "OK": // // enum Status { // STATUS_OK = 0; // OK = 1; // } // // Tooling error message: // ---------------------- // Enum name OK has the same name as STATUS_OK if you ignore case and strip out the enum name prefix (if any). // (If you are using allow_alias, please assign the same number to each enum value name.) // // Just in case it does happen, map to the first value rather than error. foreach (var item in nameMappings.GroupBy(m => m.RemoveEnumPrefixName).Select(g => KeyValuePair.Create(g.Key, g.First().OriginalName))) { removeEnumPrefixMapping[item.Key] = item.Value; } return new EnumMapping { WriteMapping = writeMapping, RemoveEnumPrefixMapping = removeEnumPrefixMapping }; } // Remove the prefix from the specified value. Ignore case and underscores in the comparison. private static string TryRemovePrefix(string prefix, string value) { var normalizedPrefix = prefix.Replace("_", string.Empty, StringComparison.Ordinal).ToLowerInvariant(); var prefixIndex = 0; var valueIndex = 0; while (prefixIndex < normalizedPrefix.Length && valueIndex < value.Length) { if (value[valueIndex] == '_') { valueIndex++; continue; } if (char.ToLowerInvariant(value[valueIndex]) != normalizedPrefix[prefixIndex]) { return value; } prefixIndex++; valueIndex++; } if (prefixIndex < normalizedPrefix.Length) { return value; } while (valueIndex < value.Length && value[valueIndex] == '_') { valueIndex++; } return valueIndex == value.Length ? value : value.Substring(valueIndex); } private static string GetEnumValueName(string enumName, string valueName) { var result = TryRemovePrefix(enumName, valueName); // Prefix name starting with a digit with an underscore to ensure it is a valid identifier. return result.Length > 0 && char.IsDigit(result[0]) ? $"_{result}" : result; } private sealed class EnumMapping { public required Dictionary<object, NameMapping> WriteMapping { get; init; } public required Dictionary<string, string> RemoveEnumPrefixMapping { get; init; } } private sealed class NameMapping { public required object Value { get; init; } public required string OriginalName { get; init; } public required string RemoveEnumPrefixName { get; init; } } }