diff --git a/Refit.Tests/FormValueDictionaryTests.cs b/Refit.Tests/FormValueDictionaryTests.cs index 93c3f2583..411b0181e 100644 --- a/Refit.Tests/FormValueDictionaryTests.cs +++ b/Refit.Tests/FormValueDictionaryTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Runtime.Serialization; using Newtonsoft.Json; using Xunit; @@ -178,6 +179,29 @@ public void SkipsNullValuesFromDictionary() Assert.Contains("foo", target.Keys); } + + [Fact] + public void SerializesEnumWithEnumMemberAttribute() + { + var source = new Dictionary() + { + { "A", EnumWithEnumMember.A }, + { "B", EnumWithEnumMember.B } + }; + + var expected = new Dictionary + { + { "A", "A" }, + { "B", "b" } + }; + + + var actual = new FormValueDictionary(source, settings); + + Assert.Equal(expected, actual); + } + + public class AliasingTestClass { [AliasAs("f")] @@ -195,5 +219,13 @@ public class AliasingTestClass [AliasAs("fr")] public int? Frob { get; set; } } + + public enum EnumWithEnumMember + { + A, + + [EnumMember(Value = "b")] + B + } } } diff --git a/Refit.Tests/RequestBuilder.cs b/Refit.Tests/RequestBuilder.cs index 0606632c5..258ef4df2 100644 --- a/Refit.Tests/RequestBuilder.cs +++ b/Refit.Tests/RequestBuilder.cs @@ -9,6 +9,7 @@ using System.Threading; using Xunit; using System.Collections; +using System.Runtime.Serialization; namespace Refit.Tests { @@ -95,7 +96,8 @@ public void TooManyComplexTypesThrows() { var input = typeof(IRestMethodInfoTests); - Assert.Throws(() => { + Assert.Throws(() => + { var fixture = new RestMethodInfo( input, input.GetMethods().First(x => x.Name == "TooManyComplexTypes")); @@ -484,6 +486,9 @@ public interface IDummyHttpApi [Multipart] [Post("/foo?&name={name}")] Task PostWithQueryStringParameters(FileInfo source, string name); + + [Get("/query")] + Task QueryWithEnum(FooWithEnumMember foo); } interface ICancellableMethods @@ -495,6 +500,14 @@ interface ICancellableMethods } + public enum FooWithEnumMember + { + A, + + [EnumMember(Value = "b")] + B + } + public class SomeRequestData { [AliasAs("rpn")] @@ -1197,10 +1210,24 @@ public void QueryStringExcludesPropertiesWithPrivateGetters() Assert.Equal("/query?FullName=Mickey%20Mouse", uri.PathAndQuery); } + [Theory] + [InlineData(FooWithEnumMember.A, "/query?foo=A")] + [InlineData(FooWithEnumMember.B, "/query?foo=b")] + public void QueryStringUsesEnumMemberAttribute(FooWithEnumMember queryParameter, string expectedQuery) + { + var fixture = new RequestBuilderImplementation(); + var factory = fixture.BuildRequestFactoryForMethod("QueryWithEnum"); + + var output = factory(new object[] { queryParameter }); + + var uri = new Uri(new Uri("http://api"), output.RequestUri); + Assert.Equal(expectedQuery, uri.PathAndQuery); + } + [Fact] [UseCulture("es-ES")] // Spain uses a , instead of a . - public void DefaultParmeterFormatterIsInvariant() + public void DefaultParameterFormatterIsInvariant() { var settings = new RefitSettings(); var fixture = new RequestBuilderImplementation(settings); @@ -1234,7 +1261,8 @@ public static Func BuildRequestFactoryForMethod(th var testHttpMessageHandler = new TestHttpMessageHandler(); - return paramList => { + return paramList => + { var task = (Task)factory(new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, paramList); task.Wait(); return testHttpMessageHandler.RequestMessage; @@ -1251,7 +1279,8 @@ public static Func RunRequest(this IRequestBui testHttpMessageHandler.Content = new StringContent(returnContent); } - return paramList => { + return paramList => + { var task = (Task)factory(new HttpClient(testHttpMessageHandler) { BaseAddress = new Uri("http://api/") }, paramList); task.Wait(); return testHttpMessageHandler; diff --git a/Refit/RefitSettings.cs b/Refit/RefitSettings.cs index 182c31b9d..5c17fbe55 100644 --- a/Refit/RefitSettings.cs +++ b/Refit/RefitSettings.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Net.Http; using System.Reflection; +using System.Runtime.Serialization; using System.Threading.Tasks; using Newtonsoft.Json; @@ -38,32 +40,55 @@ public interface IFormUrlEncodedParameterFormatter public class DefaultUrlParameterFormatter : IUrlParameterFormatter { + static readonly ConcurrentDictionary> enumMemberCache + = new ConcurrentDictionary>(); + public virtual string Format(object parameterValue, ParameterInfo parameterInfo) { // See if we have a format var formatString = parameterInfo.GetCustomAttribute(true)?.Format; + EnumMemberAttribute enummember = null; + if (parameterValue != null && parameterInfo.ParameterType.GetTypeInfo().IsEnum) + { + var cached = enumMemberCache.GetOrAdd(parameterInfo.ParameterType, t => new ConcurrentDictionary()); + enummember = cached.GetOrAdd(parameterValue.ToString(), val => parameterInfo.ParameterType.GetMember(val).First().GetCustomAttribute()); + } + return parameterValue == null ? null : string.Format(CultureInfo.InvariantCulture, string.IsNullOrWhiteSpace(formatString) ? "{0}" : $"{{0:{formatString}}}", - parameterValue); + enummember?.Value ?? parameterValue); } } public class DefaultFormUrlEncodedParameterFormatter : IFormUrlEncodedParameterFormatter { + static readonly ConcurrentDictionary> enumMemberCache + = new ConcurrentDictionary>(); + public virtual string Format(object parameterValue, string formatString) { - return parameterValue == null - ? null - : string.Format(CultureInfo.InvariantCulture, - string.IsNullOrWhiteSpace(formatString) - ? "{0}" - : $"{{0:{formatString}}}", - parameterValue); + if (parameterValue == null) + return null; + + var parameterType = parameterValue.GetType(); + + EnumMemberAttribute enummember = null; + if (parameterValue != null && parameterType.GetTypeInfo().IsEnum) + { + var cached = enumMemberCache.GetOrAdd(parameterType, t => new ConcurrentDictionary()); + enummember = cached.GetOrAdd(parameterValue.ToString(), val => parameterType.GetMember(val).First().GetCustomAttribute()); + } + + return string.Format(CultureInfo.InvariantCulture, + string.IsNullOrWhiteSpace(formatString) + ? "{0}" + : $"{{0:{formatString}}}", + enummember?.Value ?? parameterValue); } } }