Skip to content

Commit 08db456

Browse files
authored
feat(query): supporting value per key transform (#93)
1 parent caccd49 commit 08db456

File tree

5 files changed

+69
-10
lines changed

5 files changed

+69
-10
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
<!-- ## [4.2.0](https://github.com/sketch7/FluentlyHttpClient/compare/4.1.0...4.2.0) (2024-10-11) -->
66

7+
## [4.1.1](https://github.com/sketch7/FluentlyHttpClient/compare/4.1.0...4.1.1) (2025-07-24)
8+
9+
### Features
10+
11+
- **transform value:** format value per key
712

813
## [4.1.0](https://github.com/sketch7/FluentlyHttpClient/compare/4.0.0...4.1.0) (2024-10-11)
914

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sketch7/fluently-http-client",
3-
"version": "4.1.0",
3+
"version": "4.1.1",
44
"versionSuffix": "",
55
"scripts": {
66
"pack": "bash ./tools/pack.sh",

src/FluentlyHttpClient/Utils/QueryStringOptions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,15 @@ public record QueryStringOptions
4848
/// Gets or sets the function to format a collection item. This will allow you to manipulate the value.
4949
/// </summary>
5050
public Func<object, string>? CollectionItemFormatter { get; set; }
51+
public Dictionary<string, Func<object, string>>? CollectionValuePerKeyItemFormatter { get; set; }
5152

5253
/// <summary>
5354
/// Gets or sets the function to format the key e.g. lowercase.
5455
/// </summary>
5556
internal Func<string, string> KeyFormatter { get; set; } = DefaultKeyFormatter;
5657

5758
internal Func<object, string>? ValueFormatter { get; set; }
59+
internal Dictionary<string, Func<object, string>>? ValuePerKeyFormatter { get; set; }
5860

5961
internal Func<string, string> ValueEncoder { get; set; } = DefaultValueEncoder;
6062

@@ -98,6 +100,18 @@ public QueryStringOptions WithValueFormatter(Func<object, string> configure)
98100
return this;
99101
}
100102

103+
/// <summary>
104+
/// Gets or sets the function to format a value per key. This will allow you to manipulate the value by key
105+
/// </summary>
106+
/// <param name="configure"></param>
107+
/// <returns></returns>
108+
public QueryStringOptions WithValuePerKeyFormatter(Dictionary<string, Func<object, string>> configure)
109+
{
110+
ValuePerKeyFormatter = configure;
111+
CollectionValuePerKeyItemFormatter = configure;
112+
return this;
113+
}
114+
101115
/// <summary>
102116
/// Gets or sets the function to encode a value. This will allow you to encode the value. e.g. UrlEncode.
103117
/// </summary>

src/FluentlyHttpClient/Utils/QueryStringUtils.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static string ToQueryString<TKey, TValue>(this IDictionary<TKey, TValue>?
3535

3636
if (item.Value is string)
3737
{
38-
qs = AddQueryString(key, FormatValue(item.Value), qs, options.ValueEncoder);
38+
qs = AddQueryString(key, FormatValue(key, item.Value), qs, options.ValueEncoder);
3939
continue;
4040
}
4141

@@ -45,15 +45,23 @@ public static string ToQueryString<TKey, TValue>(this IDictionary<TKey, TValue>?
4545
qs = BuildCollectionQueryString(key, values, qs, options);
4646
break;
4747
default:
48-
qs = AddQueryString(key, FormatValue(item.Value), qs, options.ValueEncoder);
48+
qs = AddQueryString(key, FormatValue(key, item.Value), qs, options.ValueEncoder);
4949
break;
5050
}
5151
}
5252

5353
return qs;
5454

55-
string FormatValue(object value)
56-
=> options.ValueFormatter == null ? value.ToString()! : options.ValueFormatter(value);
55+
string FormatValue(object param, object value)
56+
{
57+
if (options.ValueFormatter != null)
58+
return options.ValueFormatter(value);
59+
60+
if (options.ValuePerKeyFormatter != null && param is string && options.ValuePerKeyFormatter.TryGetValue((param as string)!, out var transform))
61+
return transform(value);
62+
63+
return value.ToString()!;
64+
}
5765
}
5866

5967
/// <summary>
@@ -76,13 +84,18 @@ private static string BuildCollectionQueryString(string key, IEnumerable values,
7684
? key
7785
: options.CollectionKeyFormatter(key);
7886

87+
var collectionFormatter = options.CollectionItemFormatter;
88+
89+
if (collectionFormatter == null && options.CollectionValuePerKeyItemFormatter?.TryGetValue(key, out var formatter) is true)
90+
collectionFormatter = formatter;
91+
7992
switch (options.CollectionMode)
8093
{
8194
case QueryStringCollectionMode.KeyPerValue:
8295
foreach (var value in values)
8396
{
84-
var valueStr = options.CollectionItemFormatter != null
85-
? options.CollectionItemFormatter(value)
97+
var valueStr = collectionFormatter != null
98+
? collectionFormatter(value)
8699
: value.ToString();
87100
qs = AddQueryString(key, valueStr, qs, options.ValueEncoder);
88101
}
@@ -91,8 +104,8 @@ private static string BuildCollectionQueryString(string key, IEnumerable values,
91104
var index = 0;
92105
foreach (var value in values)
93106
{
94-
var valueStr = options.CollectionItemFormatter != null
95-
? options.CollectionItemFormatter(value)
107+
var valueStr = collectionFormatter != null
108+
? collectionFormatter(value)
96109
: value.ToString();
97110
if (index == 0)
98111
qs = AddQueryString(key, valueStr, qs, options.ValueEncoder);

test/FluentHttpRequestBuilderTest.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,13 +286,40 @@ public void WithQueryParamsOptions_MultipleCallsShouldKeepConfiguring()
286286
var request = builder.WithUri("/org/sketch7/heroes")
287287
.WithQueryParamsOptions(opts => opts.CollectionMode = QueryStringCollectionMode.CommaSeparated)
288288
.WithQueryParamsOptions(opts => opts.WithKeyFormatter(s => s.ToUpper()))
289+
.WithQueryParamsOptions(opts => opts.WithValuePerKeyFormatter(new Dictionary<string, Func<object, string>>()
290+
{
291+
["POWERS"] = val => (val is string valStr) ? valStr.ToUpper() : null!
292+
}))
289293
.WithQueryParams(new
290294
{
291295
Roles = new List<string> { "warrior", "assassin" },
296+
Powers = new List<string> { "medium", "high" }
292297
}
293298
).Build();
294299

295-
Assert.Equal("/org/sketch7/heroes?ROLES=warrior,assassin", request.Uri.ToString());
300+
Assert.Equal("/org/sketch7/heroes?ROLES=warrior,assassin&POWERS=MEDIUM,HIGH", request.Uri.ToString());
301+
}
302+
303+
[Fact]
304+
public void WithQueryParamsOptions_ValuePerKeyTransform()
305+
{
306+
var builder = GetNewRequestBuilder();
307+
var request = builder.WithUri("/org/sketch7/heroes")
308+
.WithQueryParamsOptions(opts => opts.WithValuePerKeyFormatter(new Dictionary<string, Func<object, string>>()
309+
{
310+
["power"] = val => (val is string valStr) ? valStr.ToUpper() : null!,
311+
["dateTime"] = val => (val is DateTime valStr) ? valStr.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") : null!
312+
}))
313+
.WithQueryParamsOptions(opts => opts.WithValueEncoder(x => x))
314+
.WithQueryParams(new
315+
{
316+
Role = "warrior",
317+
Power = "medium",
318+
DateTime = new DateTime(2025, 1, 1)
319+
}
320+
).Build();
321+
322+
Assert.Equal("/org/sketch7/heroes?role=warrior&power=MEDIUM&dateTime=2025-01-01T00:00:00Z", request.Uri.ToString());
296323
}
297324
}
298325

0 commit comments

Comments
 (0)