Extend url parameters default formatting (#1781)
* Refactor DefaultUrlParameterFormatter * Extend URL parameters formatting through DefaultUrlParameterFormatter * Add DefaultUrlParameterFormatterTests * Rename DefaultUrlParameterFormatterTests test methods * Union DefaultUrlParameterFormatterTestRequest * Update API * Add DefaultUrlParameterFormatter tests with URI building --------- Co-authored-by: Chris Pulman <chris.pulman@yahoo.com>
This commit is contained in:
Родитель
5259e08344
Коммит
7cfdeded63
|
@ -119,6 +119,8 @@ namespace Refit
|
|||
public class DefaultUrlParameterFormatter : Refit.IUrlParameterFormatter
|
||||
{
|
||||
public DefaultUrlParameterFormatter() { }
|
||||
public void AddFormat<TParameter>(string format) { }
|
||||
public void AddFormat<TContainer, TParameter>(string format) { }
|
||||
public virtual string? Format(object? parameterValue, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type) { }
|
||||
}
|
||||
public class DefaultUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter
|
||||
|
|
|
@ -119,6 +119,8 @@ namespace Refit
|
|||
public class DefaultUrlParameterFormatter : Refit.IUrlParameterFormatter
|
||||
{
|
||||
public DefaultUrlParameterFormatter() { }
|
||||
public void AddFormat<TParameter>(string format) { }
|
||||
public void AddFormat<TContainer, TParameter>(string format) { }
|
||||
public virtual string? Format(object? parameterValue, System.Reflection.ICustomAttributeProvider attributeProvider, System.Type type) { }
|
||||
}
|
||||
public class DefaultUrlParameterKeyFormatter : Refit.IUrlParameterKeyFormatter
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Refit.Tests;
|
||||
|
||||
public class DefaultUrlParameterFormatterTests
|
||||
{
|
||||
class DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
[Query(Format = "yyyy")] public DateTime? DateTimeWithAttributeFormatYear { get; set; }
|
||||
|
||||
public DateTime? DateTime { get; set; }
|
||||
|
||||
public IEnumerable<DateTime> DateTimeCollection { get; set; }
|
||||
|
||||
public IDictionary<int, DateTime> DateTimeDictionary { get; set; }
|
||||
|
||||
public IDictionary<DateTime, int> DateTimeKeyedDictionary { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullParameterValue_ReturnsNull()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTime = null
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTime,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTime))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Null(output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoFormatters_UseDefaultFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTime = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTime,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTime))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("08/21/2023 00:00:00", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryAttributeFormatOnly_UseQueryAttributeFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTimeWithAttributeFormatYear,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("2023", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryAttributeAndGeneralFormat_UseQueryAttributeFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy-MM-dd");
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTimeWithAttributeFormatYear,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("2023", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueryAttributeAndSpecificFormat_UseQueryAttributeFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DefaultUrlParameterFormatterTestRequest, DateTime>("yyyy-MM-dd");
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTimeWithAttributeFormatYear,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("2023", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllFormats_UseQueryAttributeFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTimeWithAttributeFormatYear = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy-MM-dd");
|
||||
urlParameterFormatter.AddFormat<DefaultUrlParameterFormatterTestRequest, DateTime>("yyyy-MM-dd");
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTimeWithAttributeFormatYear,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTimeWithAttributeFormatYear))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("2023", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GeneralFormatOnly_UseGeneralFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTime = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy");
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTime,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTime))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("2023", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpecificFormatOnly_UseSpecificFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTime = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DefaultUrlParameterFormatterTestRequest, DateTime>("yyyy");
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTime,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTime))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("2023", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GeneralAndSpecificFormats_UseSpecificFormat()
|
||||
{
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTime = new DateTime(2023, 8, 21)
|
||||
};
|
||||
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy-MM-dd");
|
||||
urlParameterFormatter.AddFormat<DefaultUrlParameterFormatterTestRequest, DateTime>("yyyy");
|
||||
|
||||
var output = urlParameterFormatter.Format(
|
||||
parameters.DateTime,
|
||||
parameters.GetType().GetProperty(nameof(parameters.DateTime))!,
|
||||
parameters.GetType());
|
||||
|
||||
Assert.Equal("2023", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestWithPlainDateTimeQueryParameter_ProducesCorrectQueryString()
|
||||
{
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy");
|
||||
|
||||
var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter };
|
||||
var fixture = new RequestBuilderImplementation<IDummyHttpApi>(refitSettings);
|
||||
var factory = fixture.BuildRequestFactoryForMethod(
|
||||
nameof(IDummyHttpApi.PostWithComplexTypeQuery)
|
||||
);
|
||||
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTime = new DateTime(2023, 8, 21),
|
||||
};
|
||||
|
||||
var output = factory([parameters]);
|
||||
var uri = new Uri(new Uri("http://api"), output.RequestUri);
|
||||
|
||||
Assert.Equal(
|
||||
"?DateTime=2023",
|
||||
uri.Query
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestWithDateTimeCollectionQueryParameter_ProducesCorrectQueryString()
|
||||
{
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy");
|
||||
|
||||
var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter };
|
||||
var fixture = new RequestBuilderImplementation<IDummyHttpApi>(refitSettings);
|
||||
var factory = fixture.BuildRequestFactoryForMethod(
|
||||
nameof(IDummyHttpApi.PostWithComplexTypeQuery)
|
||||
);
|
||||
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTimeCollection = [new DateTime(2023, 8, 21), new DateTime(2024, 8, 21)],
|
||||
};
|
||||
|
||||
var output = factory([parameters]);
|
||||
var uri = new Uri(new Uri("http://api"), output.RequestUri);
|
||||
|
||||
Assert.Equal(
|
||||
"?DateTimeCollection=2023%2C2024",
|
||||
uri.Query
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestWithDateTimeDictionaryQueryParameter_ProducesCorrectQueryString()
|
||||
{
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy");
|
||||
|
||||
var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter };
|
||||
var fixture = new RequestBuilderImplementation<IDummyHttpApi>(refitSettings);
|
||||
var factory = fixture.BuildRequestFactoryForMethod(
|
||||
nameof(IDummyHttpApi.PostWithComplexTypeQuery)
|
||||
);
|
||||
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTimeDictionary = new Dictionary<int, DateTime>
|
||||
{
|
||||
{ 1, new DateTime(2023, 8, 21) },
|
||||
{ 2, new DateTime(2024, 8, 21) },
|
||||
},
|
||||
};
|
||||
|
||||
var output = factory([parameters]);
|
||||
var uri = new Uri(new Uri("http://api"), output.RequestUri);
|
||||
|
||||
Assert.Equal(
|
||||
"?DateTimeDictionary.1=2023&DateTimeDictionary.2=2024",
|
||||
uri.Query
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestWithDateTimeKeyedDictionaryQueryParameter_ProducesCorrectQueryString()
|
||||
{
|
||||
var urlParameterFormatter = new DefaultUrlParameterFormatter();
|
||||
urlParameterFormatter.AddFormat<DateTime>("yyyy");
|
||||
|
||||
var refitSettings = new RefitSettings { UrlParameterFormatter = urlParameterFormatter };
|
||||
var fixture = new RequestBuilderImplementation<IDummyHttpApi>(refitSettings);
|
||||
var factory = fixture.BuildRequestFactoryForMethod(
|
||||
nameof(IDummyHttpApi.PostWithComplexTypeQuery)
|
||||
);
|
||||
|
||||
var parameters = new DefaultUrlParameterFormatterTestRequest
|
||||
{
|
||||
DateTimeKeyedDictionary = new Dictionary<DateTime, int>
|
||||
{
|
||||
{ new DateTime(2023, 8, 21), 1 },
|
||||
{ new DateTime(2024, 8, 21), 2 },
|
||||
},
|
||||
};
|
||||
|
||||
var output = factory([parameters]);
|
||||
var uri = new Uri(new Uri("http://api"), output.RequestUri);
|
||||
|
||||
Assert.Equal(
|
||||
"?DateTimeKeyedDictionary.2023=1&DateTimeKeyedDictionary.2024=2",
|
||||
uri.Query
|
||||
);
|
||||
}
|
||||
}
|
|
@ -40,7 +40,8 @@ namespace Refit
|
|||
IFormUrlEncodedParameterFormatter? formUrlEncodedParameterFormatter
|
||||
)
|
||||
: this(contentSerializer, urlParameterFormatter, formUrlEncodedParameterFormatter, null)
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RefitSettings"/> instance with the specified parameters
|
||||
|
@ -184,7 +185,7 @@ namespace Refit
|
|||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="attributeProvider">The attribute provider.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="type">Container class type.</param>
|
||||
/// <returns></returns>
|
||||
string? Format(object? value, ICustomAttributeProvider attributeProvider, Type type);
|
||||
}
|
||||
|
@ -226,12 +227,39 @@ namespace Refit
|
|||
ConcurrentDictionary<string, EnumMemberAttribute?>
|
||||
> EnumMemberCache = new();
|
||||
|
||||
Dictionary<(Type containerType, Type parameterType), string> SpecificFormats { get; } = new();
|
||||
|
||||
Dictionary<Type, string> GeneralFormats { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Add format for specified parameter type contained within container class of specified type.
|
||||
/// Might be suppressed by a QueryAttribute format.
|
||||
/// </summary>
|
||||
/// <param name="format">The format string.</param>
|
||||
/// <typeparam name="TContainer">Container class type.</typeparam>
|
||||
/// <typeparam name="TParameter">Parameter type.</typeparam>
|
||||
public void AddFormat<TContainer, TParameter>(string format)
|
||||
{
|
||||
SpecificFormats.Add((typeof(TContainer), typeof(TParameter)), format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add format for specified parameter type.
|
||||
/// Might be suppressed by a QueryAttribute format or a container specific format.
|
||||
/// </summary>
|
||||
/// <param name="format">The format string.</param>
|
||||
/// <typeparam name="TParameter">Parameter type.</typeparam>
|
||||
public void AddFormat<TParameter>(string format)
|
||||
{
|
||||
GeneralFormats.Add(typeof(TParameter), format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the specified parameter value.
|
||||
/// </summary>
|
||||
/// <param name="parameterValue">The parameter value.</param>
|
||||
/// <param name="attributeProvider">The attribute provider.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="type">Container class type.</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException">attributeProvider</exception>
|
||||
public virtual string? Format(
|
||||
|
@ -245,6 +273,11 @@ namespace Refit
|
|||
throw new ArgumentNullException(nameof(attributeProvider));
|
||||
}
|
||||
|
||||
if (parameterValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// See if we have a format
|
||||
var formatString = attributeProvider
|
||||
.GetCustomAttributes(typeof(QueryAttribute), true)
|
||||
|
@ -252,34 +285,41 @@ namespace Refit
|
|||
.FirstOrDefault()
|
||||
?.Format;
|
||||
|
||||
EnumMemberAttribute? enummember = null;
|
||||
if (parameterValue != null)
|
||||
EnumMemberAttribute? enumMember = null;
|
||||
var parameterType = parameterValue.GetType();
|
||||
if (parameterType.IsEnum)
|
||||
{
|
||||
var parameterType = parameterValue.GetType();
|
||||
if (parameterType.IsEnum)
|
||||
{
|
||||
var cached = EnumMemberCache.GetOrAdd(
|
||||
parameterType,
|
||||
t => new ConcurrentDictionary<string, EnumMemberAttribute?>()
|
||||
);
|
||||
enummember = cached.GetOrAdd(
|
||||
parameterValue.ToString()!,
|
||||
val =>
|
||||
parameterType
|
||||
.GetMember(val)
|
||||
.First()
|
||||
.GetCustomAttribute<EnumMemberAttribute>()
|
||||
);
|
||||
}
|
||||
var cached = EnumMemberCache.GetOrAdd(
|
||||
parameterType,
|
||||
t => new ConcurrentDictionary<string, EnumMemberAttribute?>()
|
||||
);
|
||||
enumMember = cached.GetOrAdd(
|
||||
parameterValue.ToString()!,
|
||||
val =>
|
||||
parameterType
|
||||
.GetMember(val)
|
||||
.First()
|
||||
.GetCustomAttribute<EnumMemberAttribute>()
|
||||
);
|
||||
}
|
||||
|
||||
return parameterValue == null
|
||||
? null
|
||||
: string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
string.IsNullOrWhiteSpace(formatString) ? "{0}" : $"{{0:{formatString}}}",
|
||||
enummember?.Value ?? parameterValue
|
||||
);
|
||||
if (string.IsNullOrWhiteSpace(formatString) &&
|
||||
SpecificFormats.TryGetValue((type, parameterType), out var specificFormat))
|
||||
{
|
||||
formatString = specificFormat;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(formatString) &&
|
||||
GeneralFormats.TryGetValue(parameterType, out var generalFormat))
|
||||
{
|
||||
formatString = generalFormat;
|
||||
}
|
||||
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
string.IsNullOrWhiteSpace(formatString) ? "{0}" : $"{{0:{formatString}}}",
|
||||
enumMember?.Value ?? parameterValue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,18 +342,20 @@ namespace Refit
|
|||
public virtual string? Format(object? parameterValue, string? formatString)
|
||||
{
|
||||
if (parameterValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var parameterType = parameterValue.GetType();
|
||||
|
||||
EnumMemberAttribute? enummember = null;
|
||||
EnumMemberAttribute? enumMember = null;
|
||||
if (parameterType.GetTypeInfo().IsEnum)
|
||||
{
|
||||
var cached = EnumMemberCache.GetOrAdd(
|
||||
parameterType,
|
||||
t => new ConcurrentDictionary<string, EnumMemberAttribute?>()
|
||||
);
|
||||
enummember = cached.GetOrAdd(
|
||||
enumMember = cached.GetOrAdd(
|
||||
parameterValue.ToString()!,
|
||||
val =>
|
||||
parameterType
|
||||
|
@ -326,7 +368,7 @@ namespace Refit
|
|||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
string.IsNullOrWhiteSpace(formatString) ? "{0}" : $"{{0:{formatString}}}",
|
||||
enummember?.Value ?? parameterValue
|
||||
enumMember?.Value ?? parameterValue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче