Skip to content

Commit

Permalink
Proposal: Add a TreatAsString field to QueryAttribute
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mark-pro committed Dec 27, 2024
1 parent 860a332 commit 0ef673e
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 4 deletions.
15 changes: 12 additions & 3 deletions Refit.Tests/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand Down Expand Up @@ -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<IDummyHttpApi>();
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);
Expand All @@ -3558,7 +3559,7 @@ public void TestNullableQueryStringParamsWithANull(string expectedQuery)
{
var fixture = new RequestBuilderImplementation<IDummyHttpApi>();
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);
Expand Down Expand Up @@ -3944,6 +3945,14 @@ public void ComplexQueryObjectWithDictionaryAndCustomFormatterProducesCorrectQue
}
}

public record Foo
{
public override string ToString()
{
return "foo";
}
}

static class RequestBuilderTestExtensions
{
public static Func<object[], HttpRequestMessage> BuildRequestFactoryForMethod(
Expand Down
6 changes: 6 additions & 0 deletions Refit/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,12 @@ public QueryAttribute(CollectionFormat collectionFormat)
CollectionFormat = collectionFormat;
}

/// <summary>
/// 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.
/// </summary>
public bool TreatAsString { get; set; }

/// <summary>
/// Used to customize the name of either the query parameter pair or of the form field when form encoding.
/// </summary>
Expand Down
13 changes: 12 additions & 1 deletion Refit/RequestBuilderImplementation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,18 @@ void AddQueryParameters(RestMethodInfoInternal restMethod, QueryAttribute? query
List<KeyValuePair<string, string?>> queryParamsToAdd, int i, RestMethodParameterInfo? parameterInfo)
{
var attr = queryAttribute ?? DefaultQueryAttribute;
if (DoNotConvertToQueryMap(param))
if (attr is { TreatAsString: true })
{
queryParamsToAdd.AddRange(
ParseQueryParameter(
param.ToString(),
restMethod.ParameterInfoArray[i],
restMethod.QueryParameterMap[i],
attr
)
);
}
else if (DoNotConvertToQueryMap(param))
{
queryParamsToAdd.AddRange(
ParseQueryParameter(
Expand Down

0 comments on commit 0ef673e

Please sign in to comment.