diff --git a/Refit.Tests/RequestBuilder.cs b/Refit.Tests/RequestBuilder.cs index d565c8cf5..bb4ae47e6 100644 --- a/Refit.Tests/RequestBuilder.cs +++ b/Refit.Tests/RequestBuilder.cs @@ -129,6 +129,8 @@ public class ComplexQueryObject public string TestAlias1 {get; set;} public string TestAlias2 {get; set;} + + public IEnumerable TestCollection { get; set; } } @@ -221,6 +223,19 @@ public void PostWithObjectQueryParameterHasCorrectQuerystring() Assert.Equal("/foo?test-query-alias=one&TestAlias2=two", uri.PathAndQuery); } + [Fact] + public void ObjectQueryParameterWithInnerCollectionHasCorrectQuerystring() + { + var fixture = new RequestBuilderImplementation(); + var factory = fixture.BuildRequestFactoryForMethod(nameof(IDummyHttpApi.ComplexTypeQueryWithInnerCollection)); + + var param = new ComplexQueryObject { TestCollection = new[] { 1, 2, 3 } }; + var output = factory(new object[] { param }); + var uri = new Uri(new Uri("http://api"), output.RequestUri); + + Assert.Equal("/foo?TestCollection=1%2C2%2C3", uri.PathAndQuery); + } + [Fact] public void MultipleQueryAttributesWithNulls() { @@ -709,6 +724,9 @@ Task> UploadFile(int companyId, [Post("/foo")] Task PostWithComplexTypeQuery([Query]ComplexQueryObject queryParams); + [Get("/foo")] + Task ComplexTypeQueryWithInnerCollection([Query]ComplexQueryObject queryParams); + [Get("/api/{obj.someProperty}")] Task QueryWithOptionalParametersPathBoundObject(PathBoundObject obj, [Query]string text = null, [Query]int? optionalId = null, [Query(CollectionFormat = CollectionFormat.Multi)]string[] filters = null); diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index 70068b3c8..8c426ef3d 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -592,45 +592,14 @@ Func> BuildRequestFactoryForMethod(RestMethod var attr = queryAttribute ?? new QueryAttribute(); if (DoNotConvertToQueryMap(param)) { - if (!(param is string) && param is IEnumerable paramValues) - { - switch (attr.CollectionFormat) - { - case CollectionFormat.Multi: - foreach (var paramValue in paramValues) - { - queryParamsToAdd.Add(new KeyValuePair( - restMethod.QueryParameterMap[i], - settings.UrlParameterFormatter.Format(paramValue, restMethod.ParameterInfoMap[i], restMethod.ParameterInfoMap[i].ParameterType))); - } - continue; - default: - var delimiter = attr.CollectionFormat == CollectionFormat.Ssv ? " " - : attr.CollectionFormat == CollectionFormat.Tsv ? "\t" - : attr.CollectionFormat == CollectionFormat.Pipes ? "|" - : ","; - - // Missing a "default" clause was preventing the collection from serializing at all, as it was hitting "continue" thus causing an off-by-one error - - var formattedValues = paramValues - .Cast() - .Select(v => settings.UrlParameterFormatter.Format(v, restMethod.ParameterInfoMap[i], restMethod.ParameterInfoMap[i].ParameterType)); - - queryParamsToAdd.Add(new KeyValuePair( - restMethod.QueryParameterMap[i], - string.Join(delimiter, formattedValues))); - continue; - } - } - - queryParamsToAdd.Add(new KeyValuePair(restMethod.QueryParameterMap[i], settings.UrlParameterFormatter.Format(param, restMethod.ParameterInfoMap[i], restMethod.ParameterInfoMap[i].ParameterType))); + queryParamsToAdd.AddRange(ParseQueryParameter(param, restMethod.ParameterInfoMap[i], restMethod.QueryParameterMap[i], attr)); } else { foreach (var kvp in BuildQueryMap(param, attr.Delimiter, parameterInfo)) { var path = !string.IsNullOrWhiteSpace(attr.Prefix) ? $"{attr.Prefix}{attr.Delimiter}{kvp.Key}" : kvp.Key; - queryParamsToAdd.Add(new KeyValuePair(path, settings.UrlParameterFormatter.Format(kvp.Value, restMethod.ParameterInfoMap[i], restMethod.ParameterInfoMap[i].ParameterType))); + queryParamsToAdd.AddRange(ParseQueryParameter(kvp.Value, restMethod.ParameterInfoMap[i], path, attr)); } } @@ -714,6 +683,44 @@ Func> BuildRequestFactoryForMethod(RestMethod }; } + IEnumerable> ParseQueryParameter(object param, ParameterInfo parameterInfo, string queryPath, QueryAttribute queryAttribute) + { + if (!(param is string) && param is IEnumerable paramValues) + { + switch (queryAttribute.CollectionFormat) + { + case CollectionFormat.Multi: + foreach (var paramValue in paramValues) + { + yield return new KeyValuePair( + queryPath, + settings.UrlParameterFormatter.Format(paramValue, parameterInfo, parameterInfo.ParameterType)); + } + + break; + + default: + var delimiter = queryAttribute.CollectionFormat == CollectionFormat.Ssv ? " " + : queryAttribute.CollectionFormat == CollectionFormat.Tsv ? "\t" + : queryAttribute.CollectionFormat == CollectionFormat.Pipes ? "|" + : ","; + + // Missing a "default" clause was preventing the collection from serializing at all, as it was hitting "continue" thus causing an off-by-one error + var formattedValues = paramValues + .Cast() + .Select(v => settings.UrlParameterFormatter.Format(v, parameterInfo, parameterInfo.ParameterType)); + + yield return new KeyValuePair(queryPath, string.Join(delimiter, formattedValues)); + + break; + } + } + else + { + yield return new KeyValuePair(queryPath, settings.UrlParameterFormatter.Format(param, parameterInfo, parameterInfo.ParameterType)); + } + } + Func> BuildRxFuncForMethod(RestMethodInfo restMethod) { var taskFunc = BuildCancellableTaskFuncForMethod(restMethod);