using System.Collections.Concurrent; using System.Reflection; using RestEase.Implementation; using System.Linq; using RestEase.Platform; #if !NETSTANDARD1_1 using System.ComponentModel; using System.Runtime.Serialization; #endif namespace RestEase { /// /// /// A serializer that handles enum values specially, serializing them into their display value /// as defined by a EnumMember, DisplayName, or Display attribute (in that order). /// public class StringEnumRequestPathParamSerializer : RequestPathParamSerializer { private static readonly ConcurrentDictionary cache = new ConcurrentDictionary(); /// /// /// Serialize a path parameter whose value is scalar (not a collection), into a string value /// /// Type of the value to serialize /// Value of the path parameter /// Extra info which may be useful to the serializer /// A string value to use as path parameter /// /// If the value is an enum value, the serializer will check if it has an EnumMember, DisplayName or Display /// attribute, and if so return the value of that instead (in that order of preference). /// public override string? SerializePathParam(T value, RequestPathParamSerializerInfo info) { if (value == null) { return null; } var typeInfo = typeof(T).GetTypeInfo(); if (!typeInfo.IsEnum) { return ToStringHelper.ToString(value, info.Format, info.FormatProvider); } if (cache.TryGetValue(value, out string? stringValue)) { return stringValue; } stringValue = value.ToString(); var fieldInfo = typeInfo.GetField(stringValue); if (fieldInfo == null) { return CacheAdd(value, stringValue); } #if !NETSTANDARD1_1 var enumMemberAttribute = fieldInfo.GetCustomAttribute(); if (enumMemberAttribute != null) { return CacheAdd(value, enumMemberAttribute.Value); } var displayNameAttribute = fieldInfo.GetCustomAttribute(); if (displayNameAttribute != null) { return CacheAdd(value, displayNameAttribute.DisplayName); } #endif // netstandard can get this by referencing System.ComponentModel.DataAnnotations, (and framework // can get this by referencing the assembly). However we don't want a dependency on this nuget package, // for something so niche, so do a reflection-only load var displayAttribute = fieldInfo.CustomAttributes .FirstOrDefault(x => x.AttributeType.FullName == "System.ComponentModel.DataAnnotations.DisplayAttribute"); if (displayAttribute != null) { object? name = displayAttribute.NamedArguments.FirstOrDefault(x => x.MemberName == "Name").TypedValue.Value; if (name != null) { return CacheAdd(value, (string)name); } } return CacheAdd(value, stringValue); } private static string CacheAdd(object key, string value) { cache.TryAdd(key, value); return value; } } }