diff --git a/Refit.Tests/RequestBuilder.cs b/Refit.Tests/RequestBuilder.cs index 444ba208d..2f46be40c 100644 --- a/Refit.Tests/RequestBuilder.cs +++ b/Refit.Tests/RequestBuilder.cs @@ -63,46 +63,123 @@ public interface IRestMethodInfoTests [Get("/foo")] Task FetchSomeStuffWithDynamicHeaderQueryParamAndArrayQueryParam([Header("Authorization")] string authorization, int id, [Query(CollectionFormat.Multi)] string[] someArray, [Property("SomeProperty")] object someValue); + #region [HeaderCollection] interface methods + [Get("/foo/bar/{id}")] [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json")] Task FetchSomeStuffWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers); + [Put("/foo/bar/{id}")] + Task PutSomeStuffWithCustomHeaderCollection(int id, [Body] object body, [HeaderCollection] IDictionary headers); + [Post("/foo/bar/{id}")] Task PostSomeStuffWithCustomHeaderCollection(int id, [Body] object body, [HeaderCollection] IDictionary headers); + [Patch("/foo/bar/{id}")] + Task PatchSomeStuffWithCustomHeaderCollection(int id, [Body] object body, [HeaderCollection] IDictionary headers); + + [Put("/foo/bar/{id}")] + Task PutSomeStuffWithoutBodyAndCustomHeaderCollection(int id, [HeaderCollection] IDictionary headers); + + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithoutBodyAndCustomHeaderCollection(int id, [HeaderCollection] IDictionary headers); + + [Patch("/foo/bar/{id}")] + Task PatchSomeStuffWithoutBodyAndCustomHeaderCollection(int id, [HeaderCollection] IDictionary headers); + + [Put("/foo/bar/{id}")] + Task PutSomeStuffWithInferredBodyAndWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers, object inferredBody); + + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithInferredBodyAndWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers, object inferredBody); + + [Patch("/foo/bar/{id}")] + Task PatchSomeStuffWithInferredBodyAndWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers, object inferredBody); + [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicHeaderCollectionAndAuthorize(int id, [Authorize] string value, [HeaderCollection] IDictionary headers); + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithDynamicHeaderCollectionAndAuthorize(int id, [Authorize] string value, [HeaderCollection] IDictionary headers); + [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader(int id, [Header("Authorization")] string value, [HeaderCollection] IDictionary headers); + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithDynamicHeaderCollectionAndDynamicHeader(int id, [Header("Authorization")] string value, [HeaderCollection] IDictionary headers); + [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeaderOrderFlipped(int id, [HeaderCollection] IDictionary headers, [Header("Authorization")] string value); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection([Header("X-PathMember")] int id, [HeaderCollection] IDictionary headers); + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection([Header("X-PathMember")] int id, [HeaderCollection] IDictionary headers); + [Get("/foo/bar/{id}")] Task FetchSomeStuffWithHeaderCollection(int id, [HeaderCollection] IDictionary headers, int baz); + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithHeaderCollection(int id, [HeaderCollection] IDictionary headers, int baz); + [Get("/foo/bar")] Task FetchSomeStuffWithDuplicateHeaderCollection([HeaderCollection] IDictionary headers, [HeaderCollection] IDictionary headers2); + [Post("/foo/bar")] + Task PostSomeStuffWithDuplicateHeaderCollection([HeaderCollection] IDictionary headers, [HeaderCollection] IDictionary headers2); + [Get("/foo")] Task FetchSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam([HeaderCollection] IDictionary headers, int id, [Query(CollectionFormat.Multi)] string[] someArray, [Property("SomeProperty")] object someValue); + [Post("/foo")] + Task PostSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam([HeaderCollection] IDictionary headers, int id, [Query(CollectionFormat.Multi)] string[] someArray, [Property("SomeProperty")] object someValue); + [Get("/foo")] Task FetchSomeStuffWithHeaderCollectionOfUnsupportedType([HeaderCollection] string headers); + [Post("/foo")] + Task PostSomeStuffWithHeaderCollectionOfUnsupportedType([HeaderCollection] string headers); + + #endregion + + #region [Property] interface methods + [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someValue); + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithDynamicRequestProperty(int id, [Body] object body, [Property("SomeProperty")] object someValue); + + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithDynamicRequestProperties(int id, [Body] object body, [Property("SomeProperty")] object someValue, [Property("SomeOtherProperty")] object someOtherValue); + + [Put("/foo/bar/{id}")] + Task PutSomeStuffWithoutBodyAndWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someValue); + + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithoutBodyAndWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someValue); + + [Patch("/foo/bar/{id}")] + Task PatchSomeStuffWithoutBodyAndWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someValue); + + [Put("/foo/bar/{id}")] + Task PutSomeStuffWithInferredBodyAndWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someValue, object inferredBody); + + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithInferredBodyAndWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someValue, object inferredBody); + + [Patch("/foo/bar/{id}")] + Task PatchSomeStuffWithInferredBodyAndWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someValue, object inferredBody); + [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestPropertyWithDuplicateKey(int id, [Property("SomeProperty")] object someValue1, [Property("SomeProperty")] object someValue2); [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestPropertyWithoutKey(int id, [Property] object someValue, [Property("")] object someOtherValue); + #endregion + [Post("/foo/{id}")] Task OhYeahValueTypes(int id, [Body(buffered: true)] int whatever); @@ -226,24 +303,27 @@ public void ManyComplexTypes() Assert.Equal(1, fixture.BodyParameterInfo.Item3); } - [Fact] - public void DefaultBodyParameterDetectedForPost() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.PutWithBodyDetected))] + [InlineData(nameof(IRestMethodInfoTests.PostWithBodyDetected))] + [InlineData(nameof(IRestMethodInfoTests.PatchWithBodyDetected))] + public void DefaultBodyParameterDetected(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.PostWithBodyDetected))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); Assert.Empty(fixture.QueryParameterMap); Assert.NotNull(fixture.BodyParameterInfo); } [Fact] - public void DefaultBodyParameterDetectedForPut() + public void DefaultBodyParameterNotDetectedForGet() { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.PutWithBodyDetected))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.GetWithBodyDetected))); - Assert.Empty(fixture.QueryParameterMap); - Assert.NotNull(fixture.BodyParameterInfo); + Assert.Single(fixture.QueryParameterMap); + Assert.Null(fixture.BodyParameterInfo); } [Fact] @@ -377,26 +457,6 @@ public void MultipleQueryAttributesWithNulls() Assert.Equal(3, fixtureParams.QueryParameterMap.Count); } - [Fact] - public void DefaultBodyParameterDetectedForPatch() - { - var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.PatchWithBodyDetected))); - - Assert.Empty(fixture.QueryParameterMap); - Assert.NotNull(fixture.BodyParameterInfo); - } - - [Fact] - public void DefaultBodyParameterNotDetectedForGet() - { - var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.GetWithBodyDetected))); - - Assert.Single(fixture.QueryParameterMap); - Assert.Null(fixture.BodyParameterInfo); - } - [Fact] public void GarbagePathsShouldThrow() { @@ -591,6 +651,8 @@ public void DynamicHeadersShouldWork() Assert.Equal(2, fixture.Headers.Count); } + #region [HeaderCollection] Tests + [Fact] public void DynamicHeaderCollectionShouldWork() { @@ -617,11 +679,14 @@ public void DynamicHeaderCollectionShouldWork() Assert.True(fixture.HeaderCollectionParameterMap.Contains(1)); } - [Fact] - public void DynamicHeaderCollectionShouldWorkWithBody() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.PutSomeStuffWithCustomHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithCustomHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PatchSomeStuffWithCustomHeaderCollection))] + public void DynamicHeaderCollectionShouldWorkWithBody(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.PostSomeStuffWithCustomHeaderCollection))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); @@ -634,11 +699,54 @@ public void DynamicHeaderCollectionShouldWorkWithBody() Assert.True(fixture.HeaderCollectionParameterMap.Contains(2)); } - [Fact] - public void DynamicHeaderCollectionShouldWorkWithAuthorize() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.PutSomeStuffWithoutBodyAndCustomHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithoutBodyAndCustomHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PatchSomeStuffWithoutBodyAndCustomHeaderCollection))] + public void DynamicHeaderCollectionShouldWorkWithoutBody(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollectionAndAuthorize))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); + Assert.Equal("id", fixture.ParameterMap[0].Name); + Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); + Assert.Empty(fixture.QueryParameterMap); + Assert.Empty(fixture.HeaderParameterMap); + Assert.Empty(fixture.PropertyParameterMap); + Assert.Null(fixture.BodyParameterInfo); + Assert.Null(fixture.AuthorizeParameterInfo); + + Assert.Equal(1, fixture.HeaderCollectionParameterMap.Count); + Assert.True(fixture.HeaderCollectionParameterMap.Contains(1)); + } + + [Theory] + [InlineData(nameof(IRestMethodInfoTests.PutSomeStuffWithInferredBodyAndWithDynamicHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithInferredBodyAndWithDynamicHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PatchSomeStuffWithInferredBodyAndWithDynamicHeaderCollection))] + public void DynamicHeaderCollectionShouldWorkWithInferredBody(string interfaceMethodName) + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); + Assert.Equal("id", fixture.ParameterMap[0].Name); + Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); + Assert.Empty(fixture.QueryParameterMap); + Assert.Empty(fixture.HeaderParameterMap); + Assert.Empty(fixture.PropertyParameterMap); + Assert.NotNull(fixture.BodyParameterInfo); + Assert.Null(fixture.AuthorizeParameterInfo); + + Assert.Equal(1, fixture.HeaderCollectionParameterMap.Count); + Assert.True(fixture.HeaderCollectionParameterMap.Contains(1)); + Assert.Equal(2, fixture.BodyParameterInfo.Item3); + } + + [Theory] + [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollectionAndAuthorize))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithDynamicHeaderCollectionAndAuthorize))] + public void DynamicHeaderCollectionShouldWorkWithAuthorize(string interfaceMethodName) + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); @@ -651,11 +759,13 @@ public void DynamicHeaderCollectionShouldWorkWithAuthorize() Assert.True(fixture.HeaderCollectionParameterMap.Contains(2)); } - [Fact] - public void DynamicHeaderCollectionShouldWorkWithDynamicHeader() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithDynamicHeaderCollectionAndDynamicHeader))] + public void DynamicHeaderCollectionShouldWorkWithDynamicHeader(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); @@ -683,11 +793,13 @@ public void DynamicHeaderCollectionShouldWorkWithDynamicHeader() Assert.True(fixture.HeaderCollectionParameterMap.Contains(1)); } - [Fact] - public void DynamicHeaderCollectionShouldWorkWithPathMemberDynamicHeader() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection))] + public void DynamicHeaderCollectionShouldWorkWithPathMemberDynamicHeader(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithPathMemberInCustomHeaderAndDynamicHeaderCollection))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Empty(fixture.QueryParameterMap); @@ -701,11 +813,13 @@ public void DynamicHeaderCollectionShouldWorkWithPathMemberDynamicHeader() Assert.True(fixture.HeaderCollectionParameterMap.Contains(1)); } - [Fact] - public void DynamicHeaderCollectionInMiddleOfParamsShouldWork() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithHeaderCollection))] + public void DynamicHeaderCollectionInMiddleOfParamsShouldWork(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollection))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); Assert.Equal("id", fixture.ParameterMap[0].Name); Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); Assert.Null(fixture.AuthorizeParameterInfo); @@ -717,19 +831,23 @@ public void DynamicHeaderCollectionInMiddleOfParamsShouldWork() Assert.True(fixture.HeaderCollectionParameterMap.Contains(1)); } - [Fact] - public void DynamicHeaderCollectionShouldOnlyAllowOne() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithDuplicateHeaderCollection))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithDuplicateHeaderCollection))] + public void DynamicHeaderCollectionShouldOnlyAllowOne(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - Assert.Throws(() => new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithDuplicateHeaderCollection)))); + Assert.Throws(() => new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName))); } - [Fact] - public void DynamicHeaderCollectionShouldWorkWithProperty() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam))] + public void DynamicHeaderCollectionShouldWorkWithProperty(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollectionQueryParamAndArrayQueryParam))); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); Assert.Null(fixture.BodyParameterInfo); Assert.Null(fixture.AuthorizeParameterInfo); @@ -743,13 +861,19 @@ public void DynamicHeaderCollectionShouldWorkWithProperty() Assert.True(fixture.HeaderCollectionParameterMap.Contains(0)); } - [Fact] - public void DynamicHeaderCollectionShouldOnlyWorkWithSupportedSemantics() + [Theory] + [InlineData(nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollectionOfUnsupportedType))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithHeaderCollectionOfUnsupportedType))] + public void DynamicHeaderCollectionShouldOnlyWorkWithSupportedSemantics(string interfaceMethodName) { var input = typeof(IRestMethodInfoTests); - Assert.Throws(() => new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.FetchSomeStuffWithHeaderCollectionOfUnsupportedType)))); + Assert.Throws(() => new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName))); } + #endregion + + #region [Property] Tests + [Fact] public void DynamicRequestPropertiesShouldWork() { @@ -764,6 +888,79 @@ public void DynamicRequestPropertiesShouldWork() Assert.Equal("SomeProperty", fixture.PropertyParameterMap[1]); } + [Fact] + public void DynamicRequestPropertyShouldWorkWithBody() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.PostSomeStuffWithDynamicRequestProperty))); + Assert.Equal("id", fixture.ParameterMap[0].Name); + Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); + Assert.Empty(fixture.QueryParameterMap); + Assert.Empty(fixture.HeaderParameterMap); + Assert.NotNull(fixture.BodyParameterInfo); + Assert.Null(fixture.AuthorizeParameterInfo); + Assert.Empty(fixture.HeaderCollectionParameterMap); + + Assert.Equal("SomeProperty", fixture.PropertyParameterMap[2]); + } + + [Fact] + public void DynamicRequestPropertiesShouldWorkWithBody() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == nameof(IRestMethodInfoTests.PostSomeStuffWithDynamicRequestProperties))); + Assert.Equal("id", fixture.ParameterMap[0].Name); + Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); + Assert.Empty(fixture.QueryParameterMap); + Assert.Empty(fixture.HeaderParameterMap); + Assert.NotNull(fixture.BodyParameterInfo); + Assert.Null(fixture.AuthorizeParameterInfo); + Assert.Empty(fixture.HeaderCollectionParameterMap); + + Assert.Equal("SomeProperty", fixture.PropertyParameterMap[2]); + Assert.Equal("SomeOtherProperty", fixture.PropertyParameterMap[3]); + } + + + [Theory] + [InlineData(nameof(IRestMethodInfoTests.PutSomeStuffWithoutBodyAndWithDynamicRequestProperty))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithoutBodyAndWithDynamicRequestProperty))] + [InlineData(nameof(IRestMethodInfoTests.PatchSomeStuffWithoutBodyAndWithDynamicRequestProperty))] + public void DynamicRequestPropertyShouldWorkWithoutBody(string interfaceMethodName) + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); + Assert.Equal("id", fixture.ParameterMap[0].Name); + Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); + Assert.Empty(fixture.QueryParameterMap); + Assert.Empty(fixture.HeaderParameterMap); + Assert.Null(fixture.BodyParameterInfo); + Assert.Null(fixture.AuthorizeParameterInfo); + Assert.Empty(fixture.HeaderCollectionParameterMap); + + Assert.Equal("SomeProperty", fixture.PropertyParameterMap[1]); + } + + [Theory] + [InlineData(nameof(IRestMethodInfoTests.PutSomeStuffWithInferredBodyAndWithDynamicRequestProperty))] + [InlineData(nameof(IRestMethodInfoTests.PostSomeStuffWithInferredBodyAndWithDynamicRequestProperty))] + [InlineData(nameof(IRestMethodInfoTests.PatchSomeStuffWithInferredBodyAndWithDynamicRequestProperty))] + public void DynamicRequestPropertyShouldWorkWithInferredBody(string interfaceMethodName) + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == interfaceMethodName)); + Assert.Equal("id", fixture.ParameterMap[0].Name); + Assert.Equal(ParameterType.Normal, fixture.ParameterMap[0].Type); + Assert.Empty(fixture.QueryParameterMap); + Assert.Empty(fixture.HeaderParameterMap); + Assert.NotNull(fixture.BodyParameterInfo); + Assert.Null(fixture.AuthorizeParameterInfo); + Assert.Empty(fixture.HeaderCollectionParameterMap); + + Assert.Equal("SomeProperty", fixture.PropertyParameterMap[1]); + Assert.Equal(2, fixture.BodyParameterInfo.Item3); + } + [Fact] public void DynamicRequestPropertiesWithoutKeysShouldDefaultKeyToParameterName() { @@ -794,6 +991,8 @@ public void DynamicRequestPropertiesWithDuplicateKeysDontBlowUp() Assert.Equal("SomeProperty", fixture.PropertyParameterMap[2]); } + #endregion + [Fact] public void ValueTypesDontBlowUpBuffered() { @@ -972,6 +1171,22 @@ public interface IDummyHttpApi [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json")] Task FetchSomeStuffWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers); + [Delete("/foo/bar/{id}")] + [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json")] + Task DeleteSomeStuffWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers); + + [Put("/foo/bar/{id}")] + [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json")] + Task PutSomeStuffWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers); + + [Post("/foo/bar/{id}")] + [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json")] + Task PostSomeStuffWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers); + + [Patch("/foo/bar/{id}")] + [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==", "Accept: application/json")] + Task PatchSomeStuffWithDynamicHeaderCollection(int id, [HeaderCollection] IDictionary headers); + [Get("/foo/bar/{id}")] [Headers("Authorization: SRSLY aHR0cDovL2kuaW1ndXIuY29tL0NGRzJaLmdpZg==")] Task FetchSomeStuffWithDynamicHeaderCollectionAndDynamicHeader(int id, [Header("Authorization")] string value, [HeaderCollection] IDictionary headers); @@ -983,6 +1198,18 @@ public interface IDummyHttpApi [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someProperty); + [Delete("/foo/bar/{id}")] + Task DeleteSomeStuffWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someProperty); + + [Put("/foo/bar/{id}")] + Task PutSomeStuffWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someProperty); + + [Post("/foo/bar/{id}")] + Task PostSomeStuffWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someProperty); + + [Patch("/foo/bar/{id}")] + Task PatchSomeStuffWithDynamicRequestProperty(int id, [Property("SomeProperty")] object someProperty); + [Get("/foo/bar/{id}")] Task FetchSomeStuffWithDynamicRequestPropertyWithDuplicateKey(int id, [Property("SomeProperty")] object someValue1, [Property("SomeProperty")] object someValue2); @@ -1148,7 +1375,6 @@ interface ICancellableMethods Task GetWithCancellationAndReturn(CancellationToken token = default); } - public enum FooWithEnumMember { A, @@ -1509,7 +1735,7 @@ public void ParametersShouldBePutAsExplicitQueryString() var factory = fixture.BuildRequestFactoryForMethod(nameof(IDummyHttpApi.QueryWithExplicitParameters)); var output = factory(new object[] { "value1", "value2" }); - var uri = new Uri(new Uri("http://api"), output.RequestUri); + var uri = new Uri(new Uri("http://api"), output.RequestUri); Assert.Equal("/query?q2=value2&q1=value1", uri.PathAndQuery); } @@ -1733,8 +1959,13 @@ public void AddCustomHeadersToRequestHeadersOnly() Assert.False(output.Content.Headers.Contains("X-Emoji"), "Content headers include X-Emoji header"); } - [Fact] - public void HeaderCollectionShouldBeInHeaders() + [Theory] + [InlineData(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.DeleteSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.PutSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.PostSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.PatchSomeStuffWithDynamicHeaderCollection))] + public void HeaderCollectionShouldBeInHeaders(string interfaceMethodName) { var headerCollection = new Dictionary { @@ -1743,7 +1974,7 @@ public void HeaderCollectionShouldBeInHeaders() }; var fixture = new RequestBuilderImplementation(); - var factory = fixture.BuildRequestFactoryForMethod(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollection)); + var factory = fixture.BuildRequestFactoryForMethod(interfaceMethodName); var output = factory(new object[] { 6, headerCollection }); Assert.True(output.Headers.Contains("User-Agent"), "Headers include User-Agent header"); @@ -1786,11 +2017,16 @@ public void LastWriteWinsWhenHeaderCollectionAndDynamicHeader() Assert.Equal(authHeader, output.Headers.GetValues("Authorization").First()); } - [Fact] - public void NullHeaderCollectionDoesntBlowUp() + [Theory] + [InlineData(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.DeleteSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.PutSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.PostSomeStuffWithDynamicHeaderCollection))] + [InlineData(nameof(IDummyHttpApi.PatchSomeStuffWithDynamicHeaderCollection))] + public void NullHeaderCollectionDoesntBlowUp(string interfaceMethodName) { var fixture = new RequestBuilderImplementation(); - var factory = fixture.BuildRequestFactoryForMethod(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicHeaderCollection)); + var factory = fixture.BuildRequestFactoryForMethod(interfaceMethodName); var output = factory(new object[] { 6, null }); Assert.True(output.Headers.Contains("User-Agent"), "Headers include User-Agent header"); @@ -1823,12 +2059,17 @@ public void HeaderCollectionCanUnsetHeaders() Assert.Equal("", output.Headers.GetValues("Authorization").First()); } - [Fact] - public void DynamicRequestPropertiesShouldBeInProperties() + [Theory] + [InlineData(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicRequestProperty))] + [InlineData(nameof(IDummyHttpApi.DeleteSomeStuffWithDynamicRequestProperty))] + [InlineData(nameof(IDummyHttpApi.PutSomeStuffWithDynamicRequestProperty))] + [InlineData(nameof(IDummyHttpApi.PostSomeStuffWithDynamicRequestProperty))] + [InlineData(nameof(IDummyHttpApi.PatchSomeStuffWithDynamicRequestProperty))] + public void DynamicRequestPropertiesShouldBeInProperties(string interfaceMethodName) { var someProperty = new object(); var fixture = new RequestBuilderImplementation(); - var factory = fixture.BuildRequestFactoryForMethod(nameof(IDummyHttpApi.FetchSomeStuffWithDynamicRequestProperty)); + var factory = fixture.BuildRequestFactoryForMethod(interfaceMethodName); var output = factory(new object[] { 6, someProperty }); #if NET5_0_OR_GREATER @@ -1860,7 +2101,7 @@ public void InterfaceTypeShouldBeInProperties() Assert.NotEmpty(output.Properties); Assert.Equal(typeof(IContainAandB), output.Properties[HttpRequestMessageOptions.InterfaceType]); #pragma warning restore CS0618 // Type or member is obsolete - + } [Fact] diff --git a/Refit/RestMethodInfo.cs b/Refit/RestMethodInfo.cs index 276e69bf5..f7e764b36 100644 --- a/Refit/RestMethodInfo.cs +++ b/Refit/RestMethodInfo.cs @@ -361,8 +361,14 @@ static string GetAttachmentNameForParameter(ParameterInfo paramInfo) } // see if we're a post/put/patch - // BH: explicitly skip [Query]-denoted params - var refParams = parameterList.Where(pi => !pi.ParameterType.GetTypeInfo().IsValueType && pi.ParameterType != typeof(string) && pi.GetCustomAttribute() == null).ToList(); + // explicitly skip [Query], [HeaderCollection], and [Property]-denoted params + var refParams = parameterList.Where(pi => + !pi.ParameterType.GetTypeInfo().IsValueType && + pi.ParameterType != typeof(string) && + pi.GetCustomAttribute() == null && + pi.GetCustomAttribute() == null && + pi.GetCustomAttribute() == null) + .ToList(); // Check for rule #3 if (refParams.Count > 1)