diff --git a/Refit-Tests/RequestBuilder.cs b/Refit-Tests/RequestBuilder.cs index 09f2f7fcb..e4380e8b1 100644 --- a/Refit-Tests/RequestBuilder.cs +++ b/Refit-Tests/RequestBuilder.cs @@ -59,11 +59,96 @@ public interface IRestMethodInfoTests [Patch("/foo/{id}")] IObservable PatchSomething(int id, [Body] string someAttribute); + + + [Post("/foo")] + Task PostWithBodyDetected(Dictionary theData); + + [Get("/foo")] + Task GetWithBodyDetected(Dictionary theData); + + [Put("/foo")] + Task PutWithBodyDetected(Dictionary theData); + + [Patch("/foo")] + Task PatchWithBodyDetected(Dictionary theData); + + [Post("/foo")] + Task TooManyComplexTypes(Dictionary theData, Dictionary theData1); + + [Post("/foo")] + Task ManyComplexTypes(Dictionary theData, [Body] Dictionary theData1); } [TestFixture] public class RestMethodInfoTests { + + [Test] + public void TooManyComplexTypesThrows() + { + var input = typeof(IRestMethodInfoTests); + + Assert.Throws(() => + { + var fixture = new RestMethodInfo(input, input.GetMethods() + .First(x => x.Name == "TooManyComplexTypes")); + + }); + + } + + [Test] + public void ManyComplexTypes() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == "ManyComplexTypes")); + + Assert.AreEqual(1, fixture.QueryParameterMap.Count); + Assert.IsNotNull(fixture.BodyParameterInfo); + Assert.AreEqual(1, fixture.BodyParameterInfo.Item2); + } + + [Test] + public void DefaultBodyParameterDetectedForPost() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == "PostWithBodyDetected")); + + Assert.AreEqual(0, fixture.QueryParameterMap.Count); + Assert.IsNotNull(fixture.BodyParameterInfo); + } + + [Test] + public void DefaultBodyParameterDetectedForPut() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == "PutWithBodyDetected")); + + Assert.AreEqual(0, fixture.QueryParameterMap.Count); + Assert.IsNotNull(fixture.BodyParameterInfo); + } + + [Test] + public void DefaultBodyParameterDetectedForPatch() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == "PatchWithBodyDetected")); + + Assert.AreEqual(0, fixture.QueryParameterMap.Count); + Assert.IsNotNull(fixture.BodyParameterInfo); + } + + [Test] + public void DefaultBodyParameterNotDetectedForGet() + { + var input = typeof(IRestMethodInfoTests); + var fixture = new RestMethodInfo(input, input.GetMethods().First(x => x.Name == "GetWithBodyDetected")); + + Assert.AreEqual(1, fixture.QueryParameterMap.Count); + Assert.IsNull(fixture.BodyParameterInfo); + } + [Test] public void GarbagePathsShouldThrow() { diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index 106dd9361..d21e83f1e 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -126,7 +126,7 @@ public Func BuildRequestFactoryForMethod(string me // for anything that fell through to here, if this is not // a multipart method, add the parameter to the query string if (!restMethod.IsMultipart) { - queryParamsToAdd[restMethod.QueryParameterMap[i]] = settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]); + queryParamsToAdd[restMethod.QueryParameterMap[i]] = settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]); continue; } @@ -402,6 +402,7 @@ public class RestMethodInfo public RefitSettings RefitSettings { get; set; } static readonly Regex parameterRegex = new Regex(@"{(.*?)}"); + static readonly HttpMethod patchMethod = new HttpMethod("PATCH"); public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings refitSettings = null) { @@ -429,7 +430,7 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings .Select((parameter, index) => new { index, parameter }) .ToDictionary(x => x.index, x => x.parameter); ParameterMap = buildParameterMap(RelativePath, parameterList); - BodyParameterInfo = findBodyParameter(parameterList, IsMultipart); + BodyParameterInfo = findBodyParameter(parameterList, IsMultipart, hma.Method); Headers = parseHeaders(methodInfo); HeaderParameterMap = buildHeaderParameterMap(parameterList); @@ -521,8 +522,14 @@ string getAttachmentNameForParameter(ParameterInfo paramInfo) return nameAttr != null ? nameAttr.Name : null; } - Tuple findBodyParameter(List parameterList, bool isMultipart) + Tuple findBodyParameter(List parameterList, bool isMultipart, HttpMethod method) { + + // The body parameter is found using the following logic / order of precedence: + // 1) [Body] attribute + // 2) POST/PUT/PATCH: Reference type other than string + // 3) If there are two reference types other than string, without the body attribute, throw + var bodyParams = parameterList .Select(x => new { Parameter = x, BodyAttribute = x.GetCustomAttributes(true).OfType().FirstOrDefault() }) .Where(x => x.BodyAttribute != null) @@ -536,12 +543,33 @@ Tuple findBodyParameter(List parame throw new ArgumentException("Only one parameter can be a Body parameter"); } - if (bodyParams.Count == 0) { - return null; + // #1, body attribute wins + if (bodyParams.Count == 1) + { + var ret = bodyParams[0]; + return Tuple.Create(ret.BodyAttribute.SerializationMethod, parameterList.IndexOf(ret.Parameter)); + } + + // No body attribute found, check rule #2 + + // see if we're a post/put/patch + if (method.Equals(HttpMethod.Post) || method.Equals(HttpMethod.Put) || method.Equals(patchMethod)) + { + + var refParams = parameterList.Where(pi => !pi.ParameterType.GetTypeInfo().IsValueType && pi.ParameterType != typeof(string)).ToList(); + + // Check for rule #3 + if (refParams.Count > 1) + { + throw new ArgumentException("Multiple complex types found. Specify one parameter as the body using BodyAttribute"); + } + else if (refParams.Count == 1) + { + return Tuple.Create(BodySerializationMethod.Json, parameterList.IndexOf(refParams[0])); + } } - var ret = bodyParams[0]; - return Tuple.Create(ret.BodyAttribute.SerializationMethod, parameterList.IndexOf(ret.Parameter)); + return null; } Dictionary parseHeaders(MethodInfo methodInfo)