From 48cc2d7f614e08fcc4bcd9d879cae3d4aa14f455 Mon Sep 17 00:00:00 2001 From: mark-pro <20671988+mark-pro@users.noreply.github.com> Date: Fri, 27 Dec 2024 00:03:44 -0500 Subject: [PATCH] Proposal: Add a `TreatAsString` field to `QueryAttribute` The proposal arises from the need to pass custom types or structs which might represent types with rules and conversions. For example: ```csharp record readonly struct ZipCode(ushort ZipCode) { public override string ToString() => ZipCode.ToString(); } ``` To solve the problem the proposal seeks to add a field to the QueryAttribute called `TreatAsString`. The new field is used when the query string is created and simply calls the `ToString()` function on a type. There is a workaround that is not documented. The current workaround is to implement `IFormattable` on the type, which feels redundant if `ToString()` is already implemented on a custom type. --- Refit.Tests/RequestBuilder.cs | 15 ++++++++++++--- Refit/Attributes.cs | 6 ++++++ Refit/RequestBuilderImplementation.cs | 13 ++++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Refit.Tests/RequestBuilder.cs b/Refit.Tests/RequestBuilder.cs index f0477c5aa..4c06cb9da 100644 --- a/Refit.Tests/RequestBuilder.cs +++ b/Refit.Tests/RequestBuilder.cs @@ -2044,6 +2044,7 @@ Task QueryWithOptionalParameters( int id, [Query] string text = null, [Query] int? optionalId = null, + [Query(TreatAsString = true)] Foo foo = null, [Query(CollectionFormat = CollectionFormat.Multi)] string[] filters = null ); @@ -3541,12 +3542,12 @@ string expectedQuery } [Theory] - [InlineData("/api/123?text=title&optionalId=999&filters=A&filters=B")] + [InlineData("/api/123?text=title&optionalId=999&foo=foo&filters=A&filters=B")] public void TestNullableQueryStringParams(string expectedQuery) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("QueryWithOptionalParameters"); - var output = factory(new object[] { 123, "title", 999, new string[] { "A", "B" } }); + var output = factory(new object[] { 123, "title", 999, new Foo(), new string[] { "A", "B" } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); @@ -3558,7 +3559,7 @@ public void TestNullableQueryStringParamsWithANull(string expectedQuery) { var fixture = new RequestBuilderImplementation(); var factory = fixture.BuildRequestFactoryForMethod("QueryWithOptionalParameters"); - var output = factory(new object[] { 123, "title", null, new string[] { "A", "B" } }); + var output = factory(new object[] { 123, "title", null, null, new string[] { "A", "B" } }); var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal(expectedQuery, uri.PathAndQuery); @@ -3944,6 +3945,14 @@ public void ComplexQueryObjectWithDictionaryAndCustomFormatterProducesCorrectQue } } + public record Foo + { + public override string ToString() + { + return "foo"; + } + } + static class RequestBuilderTestExtensions { public static Func BuildRequestFactoryForMethod( diff --git a/Refit/Attributes.cs b/Refit/Attributes.cs index 5d7d95efb..f7d779e12 100644 --- a/Refit/Attributes.cs +++ b/Refit/Attributes.cs @@ -465,6 +465,12 @@ public QueryAttribute(CollectionFormat collectionFormat) CollectionFormat = collectionFormat; } + /// + /// Used to specify that the value should be treated as a string. + /// Set to true if you want to call ToString() on the object before adding it to the query string. + /// + public bool TreatAsString { get; set; } + /// /// Used to customize the name of either the query parameter pair or of the form field when form encoding. /// diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index eec31a9f9..1c30ecf85 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -946,7 +946,18 @@ void AddQueryParameters(RestMethodInfoInternal restMethod, QueryAttribute? query List> queryParamsToAdd, int i, RestMethodParameterInfo? parameterInfo) { var attr = queryAttribute ?? DefaultQueryAttribute; - if (DoNotConvertToQueryMap(param)) + if (attr.TreatAsString) + { + queryParamsToAdd.AddRange( + ParseQueryParameter( + param.ToString(), + restMethod.ParameterInfoArray[i], + restMethod.QueryParameterMap[i], + attr + ) + ); + } + else if (DoNotConvertToQueryMap(param)) { queryParamsToAdd.AddRange( ParseQueryParameter(