Skip to content

Commit 4557279

Browse files
authored
Improve UrlFormatter (#135)
I noticed that `UrlFormatter.CreateString()` does not prefer custom `IUrlParameter.GetString()` implementations when a type as well implements `IList`/`IEnumerable`. Besides that, the PR contains the following additional improvements: - Recursively call `CreateString()` for collection items - Remove duplicate enum member handling - Makes sure that `CreateString()` never returns `null` (there is no value in distinguishing between `null` and `string.Empty` here)
1 parent af894be commit 4557279

File tree

1 file changed

+20
-38
lines changed

1 file changed

+20
-38
lines changed

src/Elastic.Transport/Requests/UrlFormatter.cs

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44

55
using System;
66
using System.Collections;
7-
using System.Runtime.Serialization;
7+
using System.Globalization;
88
using System.Text;
9+
910
using Elastic.Transport.Extensions;
1011

1112
namespace Elastic.Transport;
1213

1314
/// <summary>
1415
/// A formatter that can utilize <see cref="ITransportConfiguration" /> to resolve <see cref="IUrlParameter" />'s passed
15-
/// as format arguments. It also handles known string representations for e.g bool/Enums/IEnumerable.
16+
/// as format arguments. It also handles known string representations for e.g. bool/Enums/IEnumerable.
1617
/// </summary>
1718
public sealed class UrlFormatter : IFormatProvider, ICustomFormatter
1819
{
@@ -39,26 +40,32 @@ public string Format(string? format, object? arg, IFormatProvider? formatProvide
3940
public object? GetFormat(Type formatType) => formatType == typeof(ICustomFormatter) ? this : null;
4041

4142
/// <inheritdoc cref="CreateString(object, ITransportConfiguration)"/>
42-
public string? CreateString(object? value) => CreateString(value, _settings);
43+
public string CreateString(object? value) => CreateString(value, _settings);
4344

4445
/// <summary> Creates a query string representation for <paramref name="value"/> </summary>
45-
public static string? CreateString(object? value, ITransportConfiguration settings) =>
46+
public static string CreateString(object? value, ITransportConfiguration settings) =>
4647
value switch
4748
{
48-
null => null,
49+
null => string.Empty,
4950
string s => s,
5051
string[] ss => string.Join(",", ss),
5152
Enum e => e.GetStringValue(),
5253
bool b => b ? "true" : "false",
5354
DateTimeOffset offset => offset.ToString("o"),
5455
TimeSpan timeSpan => timeSpan.ToTimeUnit(),
56+
// Custom `IUrlParameter.GetString()` implementations should take precedence over collection
57+
// specializations.
58+
IUrlParameter urlParam => urlParam.GetString(settings),
5559
// Special handling to support non-zero based arrays
5660
Array pns => CreateStringFromArray(pns, settings),
5761
// Performance optimization for directly indexable collections
5862
IList pns => CreateStringFromIList(pns, settings),
5963
// Generic implementation for all other collections
6064
IEnumerable pns => CreateStringFromIEnumerable(pns, settings),
61-
_ => ResolveUrlParameterOrDefault(value, settings)
65+
// Generic implementation for `IFormattable` types
66+
IFormattable f => f.ToString(null, CultureInfo.InvariantCulture),
67+
// Last resort fallback
68+
_ => value.ToString() ?? string.Empty
6269
};
6370

6471
private static string CreateStringFromArray(Array value, ITransportConfiguration settings)
@@ -67,8 +74,9 @@ private static string CreateStringFromArray(Array value, ITransportConfiguration
6774
{
6875
case 0:
6976
return string.Empty;
77+
7078
case 1:
71-
return ResolveUrlParameterOrDefault(value.GetValue(value.GetLowerBound(0)), settings);
79+
return CreateString(value.GetValue(value.GetLowerBound(0)), settings);
7280
}
7381

7482
var sb = new StringBuilder();
@@ -78,7 +86,7 @@ private static string CreateStringFromArray(Array value, ITransportConfiguration
7886
if (sb.Length != 0)
7987
sb.Append(',');
8088

81-
sb.Append(ResolveUrlParameterOrDefault(value.GetValue(i), settings));
89+
sb.Append(CreateString(value.GetValue(i), settings));
8290
}
8391

8492
return sb.ToString();
@@ -90,8 +98,9 @@ private static string CreateStringFromIList(IList value, ITransportConfiguration
9098
{
9199
case 0:
92100
return string.Empty;
101+
93102
case 1:
94-
return ResolveUrlParameterOrDefault(value[0], settings);
103+
return CreateString(value[0], settings);
95104
}
96105

97106
var sb = new StringBuilder();
@@ -101,7 +110,7 @@ private static string CreateStringFromIList(IList value, ITransportConfiguration
101110
if (sb.Length != 0)
102111
sb.Append(',');
103112

104-
sb.Append(ResolveUrlParameterOrDefault(value[i], settings));
113+
sb.Append(CreateString(value[i], settings));
105114
}
106115

107116
return sb.ToString();
@@ -116,36 +125,9 @@ private static string CreateStringFromIEnumerable(IEnumerable value, ITransportC
116125
if (sb.Length != 0)
117126
sb.Append(',');
118127

119-
sb.Append(ResolveUrlParameterOrDefault(v, settings));
128+
sb.Append(CreateString(v, settings));
120129
}
121130

122131
return sb.ToString();
123132
}
124-
125-
private static string ResolveUrlParameterOrDefault(object? value, ITransportConfiguration settings) =>
126-
value switch
127-
{
128-
null => string.Empty,
129-
IUrlParameter urlParam => urlParam.GetString(settings),
130-
_ => GetEnumMemberName(value) ?? value.ToString() ?? string.Empty
131-
};
132-
133-
private static string? GetEnumMemberName(object value)
134-
{
135-
var type = value.GetType();
136-
if (!type.IsEnum)
137-
return null;
138-
139-
var name = Enum.GetName(type, value);
140-
if (name is null)
141-
return null;
142-
143-
var field = type.GetField(name);
144-
if (field is null)
145-
return null;
146-
147-
return Attribute.GetCustomAttribute(field, typeof(EnumMemberAttribute)) is EnumMemberAttribute attribute
148-
? attribute.Value
149-
: null;
150-
}
151133
}

0 commit comments

Comments
 (0)