diff --git a/CHANGELOG.md b/CHANGELOG.md index a366ef8..f1576c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ +## [4.1.1](https://github.com/sketch7/FluentlyHttpClient/compare/4.1.0...4.1.1) (2025-07-24) + +### Features + +- **transform value:** format value per key ## [4.1.0](https://github.com/sketch7/FluentlyHttpClient/compare/4.0.0...4.1.0) (2024-10-11) diff --git a/package.json b/package.json index 1c79430..c789d2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sketch7/fluently-http-client", - "version": "4.1.0", + "version": "4.1.1", "versionSuffix": "", "scripts": { "pack": "bash ./tools/pack.sh", diff --git a/src/FluentlyHttpClient/Utils/QueryStringOptions.cs b/src/FluentlyHttpClient/Utils/QueryStringOptions.cs index 6e92775..05d50f1 100644 --- a/src/FluentlyHttpClient/Utils/QueryStringOptions.cs +++ b/src/FluentlyHttpClient/Utils/QueryStringOptions.cs @@ -48,6 +48,7 @@ public record QueryStringOptions /// Gets or sets the function to format a collection item. This will allow you to manipulate the value. /// public Func? CollectionItemFormatter { get; set; } + public Dictionary>? CollectionValuePerKeyItemFormatter { get; set; } /// /// Gets or sets the function to format the key e.g. lowercase. @@ -55,6 +56,7 @@ public record QueryStringOptions internal Func KeyFormatter { get; set; } = DefaultKeyFormatter; internal Func? ValueFormatter { get; set; } + internal Dictionary>? ValuePerKeyFormatter { get; set; } internal Func ValueEncoder { get; set; } = DefaultValueEncoder; @@ -98,6 +100,18 @@ public QueryStringOptions WithValueFormatter(Func configure) return this; } + /// + /// Gets or sets the function to format a value per key. This will allow you to manipulate the value by key + /// + /// + /// + public QueryStringOptions WithValuePerKeyFormatter(Dictionary> configure) + { + ValuePerKeyFormatter = configure; + CollectionValuePerKeyItemFormatter = configure; + return this; + } + /// /// Gets or sets the function to encode a value. This will allow you to encode the value. e.g. UrlEncode. /// diff --git a/src/FluentlyHttpClient/Utils/QueryStringUtils.cs b/src/FluentlyHttpClient/Utils/QueryStringUtils.cs index 1fd3e6b..b5d463b 100644 --- a/src/FluentlyHttpClient/Utils/QueryStringUtils.cs +++ b/src/FluentlyHttpClient/Utils/QueryStringUtils.cs @@ -35,7 +35,7 @@ public static string ToQueryString(this IDictionary? if (item.Value is string) { - qs = AddQueryString(key, FormatValue(item.Value), qs, options.ValueEncoder); + qs = AddQueryString(key, FormatValue(key, item.Value), qs, options.ValueEncoder); continue; } @@ -45,15 +45,23 @@ public static string ToQueryString(this IDictionary? qs = BuildCollectionQueryString(key, values, qs, options); break; default: - qs = AddQueryString(key, FormatValue(item.Value), qs, options.ValueEncoder); + qs = AddQueryString(key, FormatValue(key, item.Value), qs, options.ValueEncoder); break; } } return qs; - string FormatValue(object value) - => options.ValueFormatter == null ? value.ToString()! : options.ValueFormatter(value); + string FormatValue(object param, object value) + { + if (options.ValueFormatter != null) + return options.ValueFormatter(value); + + if (options.ValuePerKeyFormatter != null && param is string && options.ValuePerKeyFormatter.TryGetValue((param as string)!, out var transform)) + return transform(value); + + return value.ToString()!; + } } /// @@ -76,13 +84,18 @@ private static string BuildCollectionQueryString(string key, IEnumerable values, ? key : options.CollectionKeyFormatter(key); + var collectionFormatter = options.CollectionItemFormatter; + + if (collectionFormatter == null && options.CollectionValuePerKeyItemFormatter?.TryGetValue(key, out var formatter) is true) + collectionFormatter = formatter; + switch (options.CollectionMode) { case QueryStringCollectionMode.KeyPerValue: foreach (var value in values) { - var valueStr = options.CollectionItemFormatter != null - ? options.CollectionItemFormatter(value) + var valueStr = collectionFormatter != null + ? collectionFormatter(value) : value.ToString(); qs = AddQueryString(key, valueStr, qs, options.ValueEncoder); } @@ -91,8 +104,8 @@ private static string BuildCollectionQueryString(string key, IEnumerable values, var index = 0; foreach (var value in values) { - var valueStr = options.CollectionItemFormatter != null - ? options.CollectionItemFormatter(value) + var valueStr = collectionFormatter != null + ? collectionFormatter(value) : value.ToString(); if (index == 0) qs = AddQueryString(key, valueStr, qs, options.ValueEncoder); diff --git a/test/FluentHttpRequestBuilderTest.cs b/test/FluentHttpRequestBuilderTest.cs index e8c2b6c..5e5f40a 100644 --- a/test/FluentHttpRequestBuilderTest.cs +++ b/test/FluentHttpRequestBuilderTest.cs @@ -286,13 +286,40 @@ public void WithQueryParamsOptions_MultipleCallsShouldKeepConfiguring() var request = builder.WithUri("/org/sketch7/heroes") .WithQueryParamsOptions(opts => opts.CollectionMode = QueryStringCollectionMode.CommaSeparated) .WithQueryParamsOptions(opts => opts.WithKeyFormatter(s => s.ToUpper())) + .WithQueryParamsOptions(opts => opts.WithValuePerKeyFormatter(new Dictionary>() + { + ["POWERS"] = val => (val is string valStr) ? valStr.ToUpper() : null! + })) .WithQueryParams(new { Roles = new List { "warrior", "assassin" }, + Powers = new List { "medium", "high" } } ).Build(); - Assert.Equal("/org/sketch7/heroes?ROLES=warrior,assassin", request.Uri.ToString()); + Assert.Equal("/org/sketch7/heroes?ROLES=warrior,assassin&POWERS=MEDIUM,HIGH", request.Uri.ToString()); + } + + [Fact] + public void WithQueryParamsOptions_ValuePerKeyTransform() + { + var builder = GetNewRequestBuilder(); + var request = builder.WithUri("/org/sketch7/heroes") + .WithQueryParamsOptions(opts => opts.WithValuePerKeyFormatter(new Dictionary>() + { + ["power"] = val => (val is string valStr) ? valStr.ToUpper() : null!, + ["dateTime"] = val => (val is DateTime valStr) ? valStr.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") : null! + })) + .WithQueryParamsOptions(opts => opts.WithValueEncoder(x => x)) + .WithQueryParams(new + { + Role = "warrior", + Power = "medium", + DateTime = new DateTime(2025, 1, 1) + } + ).Build(); + + Assert.Equal("/org/sketch7/heroes?role=warrior&power=MEDIUM&dateTime=2025-01-01T00:00:00Z", request.Uri.ToString()); } }