From d678c7a049c15e259df3852656613b3a377fc65c Mon Sep 17 00:00:00 2001 From: Oren Novotny Date: Fri, 19 Dec 2014 12:01:31 -0500 Subject: [PATCH 1/6] Add support for the HTTP PATCH method --- Refit/Attributes.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Refit/Attributes.cs b/Refit/Attributes.cs index 1d8539afb..e40232f8e 100644 --- a/Refit/Attributes.cs +++ b/Refit/Attributes.cs @@ -29,6 +29,17 @@ public override HttpMethod Method { } } + [AttributeUsage(AttributeTargets.Method)] + public class PatchAttribute : HttpMethodAttribute + { + public PatchAttribute(string path) : base(path) { } + + public override HttpMethod Method + { + get { return new HttpMethod("PATCH"); } + } + } + [AttributeUsage(AttributeTargets.Method)] public class PostAttribute : HttpMethodAttribute { From 6d50796b02da54b961ea11e2601b192a1eef13f5 Mon Sep 17 00:00:00 2001 From: Oren Novotny Date: Fri, 19 Dec 2014 12:59:36 -0500 Subject: [PATCH 2/6] Add support for detecting body param as per #89 --- Refit-Tests/RequestBuilder.cs | 84 +++++++++++++++++++++++++++ Refit/RequestBuilderImplementation.cs | 37 ++++++++++-- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/Refit-Tests/RequestBuilder.cs b/Refit-Tests/RequestBuilder.cs index 57daa5c21..d68b7157b 100644 --- a/Refit-Tests/RequestBuilder.cs +++ b/Refit-Tests/RequestBuilder.cs @@ -55,11 +55,95 @@ public interface IRestMethodInfoTests [Post("/foo/{id}")] string AsyncOnlyBuddy(int id); + + [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 06c5a4e59..0012b6ef3 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -320,6 +320,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) { @@ -343,7 +344,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); + BodyParameterInfo = findBodyParameter(parameterList, hma.Method); Headers = parseHeaders(methodInfo); HeaderParameterMap = buildHeaderParameterMap(parameterList); @@ -412,8 +413,14 @@ string getUrlNameForParameter(ParameterInfo paramInfo) return aliasAttr != null ? aliasAttr.Name : paramInfo.Name; } - Tuple findBodyParameter(List parameterList) + Tuple findBodyParameter(List parameterList, 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) @@ -423,12 +430,32 @@ Tuple findBodyParameter(List parame throw new ArgumentException("Only one parameter can be a Body parameter"); } + // #1, body attribute wins + if (bodyParams.Count == 1) + { + var ret = bodyParams[0]; + return Tuple.Create(ret.BodyAttribute.SerializationMethod, parameterList.IndexOf(ret.Parameter)); + } + if (bodyParams.Count == 0) { - return null; + + // 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(); + 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) From 1b4c227eab6c6cbc5c34018001b35ccc4d79102d Mon Sep 17 00:00:00 2001 From: Oren Novotny Date: Fri, 19 Dec 2014 18:06:16 -0500 Subject: [PATCH 3/6] remove redundant if and add more coments --- Refit/RequestBuilderImplementation.cs | 29 ++++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index 0012b6ef3..ee2001605 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -437,22 +437,23 @@ Tuple findBodyParameter(List parame return Tuple.Create(ret.BodyAttribute.SerializationMethod, parameterList.IndexOf(ret.Parameter)); } - if (bodyParams.Count == 0) { - - // see if we're a post/put/patch - if (method.Equals(HttpMethod.Post) || method.Equals(HttpMethod.Put) || method.Equals(patchMethod)) + // 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) { - var refParams = parameterList.Where(pi => !pi.ParameterType.GetTypeInfo().IsValueType && pi.ParameterType != typeof(string)).ToList(); - 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])); - } + 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])); } - } return null; From a4f8386a847657bb77e263ba7ac0e3b698420167 Mon Sep 17 00:00:00 2001 From: Oren Novotny Date: Sat, 20 Dec 2014 12:30:59 -0500 Subject: [PATCH 4/6] Fix formatting --- Refit/RequestBuilder.cs | 5 +- Refit/RequestBuilderImplementation.cs | 444 +++++++++++++------------- 2 files changed, 230 insertions(+), 219 deletions(-) diff --git a/Refit/RequestBuilder.cs b/Refit/RequestBuilder.cs index 942f3ed52..1c200ff94 100644 --- a/Refit/RequestBuilder.cs +++ b/Refit/RequestBuilder.cs @@ -19,12 +19,12 @@ interface IRequestBuilderFactory public static class RequestBuilder { static readonly IRequestBuilderFactory platformRequestBuilderFactory = new RequestBuilderFactory(); - + public static IRequestBuilder ForType(Type interfaceType, RefitSettings settings) { return platformRequestBuilderFactory.Create(interfaceType, settings); } - + public static IRequestBuilder ForType(Type interfaceType) { return platformRequestBuilderFactory.Create(interfaceType, null); @@ -51,4 +51,3 @@ public IRequestBuilder Create(Type interfaceType, RefitSettings settings = null) } #endif } - diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index ee2001605..c95f4587d 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -39,17 +39,20 @@ public RequestBuilderImplementation(Type targetInterface, RefitSettings refitSet targetType = targetInterface; interfaceHttpMethods = targetInterface.GetMethods() - .SelectMany(x => { - var attrs = x.GetCustomAttributes(true); - var hasHttpMethod = attrs.OfType().Any(); - if (!hasHttpMethod) return Enumerable.Empty(); - - return EnumerableEx.Return(new RestMethodInfo(targetInterface, x, settings)); - }) - .ToDictionary(k => k.Name, v => v); + .SelectMany(x => { + var attrs = x.GetCustomAttributes(true); + var hasHttpMethod = attrs.OfType().Any(); + if (!hasHttpMethod) { + return Enumerable.Empty(); + } + + return EnumerableEx.Return(new RestMethodInfo(targetInterface, x, settings)); + }) + .ToDictionary(k => k.Name, v => v); } - public IEnumerable InterfaceHttpMethods { + public IEnumerable InterfaceHttpMethods + { get { return interfaceHttpMethods.Keys; } } @@ -61,81 +64,81 @@ public Func BuildRequestFactoryForMethod(string me var restMethod = interfaceHttpMethods[methodName]; return paramList => { - var ret = new HttpRequestMessage() { - Method = restMethod.HttpMethod, - }; - - foreach (var header in restMethod.Headers) { - setHeader(ret, header.Key, header.Value); - } - - string urlTarget = (basePath == "/" ? String.Empty : basePath) + restMethod.RelativePath; - var queryParamsToAdd = new Dictionary(); - - for(int i=0; i < paramList.Length; i++) { - if (restMethod.ParameterMap.ContainsKey(i)) { - urlTarget = Regex.Replace( - urlTarget, - "{" + restMethod.ParameterMap[i] + "}", - settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]), - RegexOptions.IgnoreCase); - continue; - } - - if (restMethod.BodyParameterInfo != null && restMethod.BodyParameterInfo.Item2 == i) { - var streamParam = paramList[i] as Stream; - var stringParam = paramList[i] as string; - - if (streamParam != null) { - ret.Content = new StreamContent(streamParam); - } else if (stringParam != null) { - ret.Content = new StringContent(stringParam); - } else { - switch (restMethod.BodyParameterInfo.Item1) { - case BodySerializationMethod.UrlEncoded: - ret.Content = new FormUrlEncodedContent(new FormValueDictionary(paramList[i])); - break; - case BodySerializationMethod.Json: - ret.Content = new StringContent(JsonConvert.SerializeObject(paramList[i], settings.JsonSerializerSettings), Encoding.UTF8, "application/json"); - break; - } - } - - continue; - } - - - if (restMethod.HeaderParameterMap.ContainsKey(i)) { - setHeader(ret, restMethod.HeaderParameterMap[i], paramList[i]); - } else { - if (paramList[i] != null) { - queryParamsToAdd[restMethod.QueryParameterMap[i]] = settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]); - } - } - } - - // NB: The URI methods in .NET are dumb. Also, we do this - // UriBuilder business so that we preserve any hardcoded query - // parameters as well as add the parameterized ones. - var uri = new UriBuilder(new Uri(new Uri("http://api"), urlTarget)); - var query = HttpUtility.ParseQueryString(uri.Query ?? ""); - foreach(var kvp in queryParamsToAdd) { - query.Add(kvp.Key, kvp.Value); - } - - if (query.HasKeys()) { - var pairs = query.Keys.Cast().Select(x => HttpUtility.UrlEncode(x) + "=" + HttpUtility.UrlEncode(query[x])); - uri.Query = String.Join("&", pairs); - } else { - uri.Query = null; - } - - ret.RequestUri = new Uri(uri.Uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped), UriKind.Relative); - return ret; - }; + var ret = new HttpRequestMessage { + Method = restMethod.HttpMethod + }; + + foreach (var header in restMethod.Headers) { + setHeader(ret, header.Key, header.Value); + } + + var urlTarget = (basePath == "/" ? String.Empty : basePath) + restMethod.RelativePath; + var queryParamsToAdd = new Dictionary(); + + for (var i = 0; i < paramList.Length; i++) { + if (restMethod.ParameterMap.ContainsKey(i)) { + urlTarget = Regex.Replace( + urlTarget, + "{" + restMethod.ParameterMap[i] + "}", + settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]), + RegexOptions.IgnoreCase); + continue; + } + + if (restMethod.BodyParameterInfo != null && restMethod.BodyParameterInfo.Item2 == i) { + var streamParam = paramList[i] as Stream; + var stringParam = paramList[i] as string; + + if (streamParam != null) { + ret.Content = new StreamContent(streamParam); + } else if (stringParam != null) { + ret.Content = new StringContent(stringParam); + } else { + switch (restMethod.BodyParameterInfo.Item1) { + case BodySerializationMethod.UrlEncoded: + ret.Content = new FormUrlEncodedContent(new FormValueDictionary(paramList[i])); + break; + case BodySerializationMethod.Json: + ret.Content = new StringContent(JsonConvert.SerializeObject(paramList[i], settings.JsonSerializerSettings), Encoding.UTF8, "application/json"); + break; + } + } + + continue; + } + + + if (restMethod.HeaderParameterMap.ContainsKey(i)) { + setHeader(ret, restMethod.HeaderParameterMap[i], paramList[i]); + } else { + if (paramList[i] != null) { + queryParamsToAdd[restMethod.QueryParameterMap[i]] = settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]); + } + } + } + + // NB: The URI methods in .NET are dumb. Also, we do this + // UriBuilder business so that we preserve any hardcoded query + // parameters as well as add the parameterized ones. + var uri = new UriBuilder(new Uri(new Uri("http://api"), urlTarget)); + var query = HttpUtility.ParseQueryString(uri.Query ?? ""); + foreach (var kvp in queryParamsToAdd) { + query.Add(kvp.Key, kvp.Value); + } + + if (query.HasKeys()) { + var pairs = query.Keys.Cast().Select(x => HttpUtility.UrlEncode(x) + "=" + HttpUtility.UrlEncode(query[x])); + uri.Query = String.Join("&", pairs); + } else { + uri.Query = null; + } + + ret.RequestUri = new Uri(uri.Uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped), UriKind.Relative); + return ret; + }; } - void setHeader(HttpRequestMessage request, string name, object value) + void setHeader(HttpRequestMessage request, string name, object value) { // Clear any existing version of this header we may have set, because // we want to allow removal/redefinition of headers. @@ -149,7 +152,9 @@ void setHeader(HttpRequestMessage request, string name, object value) request.Content.Headers.Remove(name); } - if (value == null) return; + if (value == null) { + return; + } var s = value.ToString(); request.Headers.TryAddWithoutValidation(name, s); @@ -169,41 +174,37 @@ public Func BuildRestResultFuncForMethod(string me if (restMethod.ReturnType == typeof(Task)) { return buildVoidTaskFuncForMethod(restMethod); - } else if (restMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { + } + if (restMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { // NB: This jacked up reflection code is here because it's // difficult to upcast Task to an arbitrary T, especially // if you need to AOT everything, so we need to reflectively // invoke buildTaskFuncForMethod. var taskFuncMi = GetType().GetMethod("buildTaskFuncForMethod", BindingFlags.NonPublic | BindingFlags.Instance); var taskFunc = (MulticastDelegate)taskFuncMi.MakeGenericMethod(restMethod.SerializedReturnType) - .Invoke(this, new[] { restMethod }); - - return (client, args) => { - return taskFunc.DynamicInvoke(new object[] { client, args } ); - }; - } else { - // Same deal - var rxFuncMi = GetType().GetMethod("buildRxFuncForMethod", BindingFlags.NonPublic | BindingFlags.Instance); - var rxFunc = (MulticastDelegate)rxFuncMi.MakeGenericMethod(restMethod.SerializedReturnType) - .Invoke(this, new[] { restMethod }); - - return (client, args) => { - return rxFunc.DynamicInvoke(new object[] { client, args }); - }; + .Invoke(this, new[] { restMethod }); + + return (client, args) => { return taskFunc.DynamicInvoke(client, args); }; } + // Same deal + var rxFuncMi = GetType().GetMethod("buildRxFuncForMethod", BindingFlags.NonPublic | BindingFlags.Instance); + var rxFunc = (MulticastDelegate)rxFuncMi.MakeGenericMethod(restMethod.SerializedReturnType) + .Invoke(this, new[] { restMethod }); + + return (client, args) => { return rxFunc.DynamicInvoke(client, args); }; } Func buildVoidTaskFuncForMethod(RestMethodInfo restMethod) - { + { return async (client, paramList) => { - var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); - var rq = factory(paramList); - var resp = await client.SendAsync(rq); - - if (!resp.IsSuccessStatusCode) { - throw await ApiException.Create(resp, settings); - } - }; + var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); + var rq = factory(paramList); + var resp = await client.SendAsync(rq); + + if (!resp.IsSuccessStatusCode) { + throw await ApiException.Create(resp, settings); + } + }; } Func> buildTaskFuncForMethod(RestMethodInfo restMethod) @@ -215,49 +216,49 @@ Func> buildTaskFuncForMethod(RestMethodInfo res Func> buildCancellableTaskFuncForMethod(RestMethodInfo restMethod) { return async (client, ct, paramList) => { - var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); - var rq = factory(paramList); - - var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct); - - if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { - // NB: This double-casting manual-boxing hate crime is the only way to make - // this work without a 'class' generic constraint. It could blow up at runtime - // and would be A Bad Idea if we hadn't already vetted the return type. - return (T)(object)resp; - } - - if (!resp.IsSuccessStatusCode) { - throw await ApiException.Create(resp, restMethod.RefitSettings); - } - - var ms = new MemoryStream(); - var fromStream = await resp.Content.ReadAsStreamAsync(); - await fromStream.CopyToAsync(ms, 4096, ct); - - var bytes = ms.ToArray(); - var content = Encoding.UTF8.GetString(bytes, 0, bytes.Length); - if (restMethod.SerializedReturnType == typeof(string)) { - return (T)(object)content; - } - - return JsonConvert.DeserializeObject(content, settings.JsonSerializerSettings); - }; + var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); + var rq = factory(paramList); + + var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct); + + if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { + // NB: This double-casting manual-boxing hate crime is the only way to make + // this work without a 'class' generic constraint. It could blow up at runtime + // and would be A Bad Idea if we hadn't already vetted the return type. + return (T)(object)resp; + } + + if (!resp.IsSuccessStatusCode) { + throw await ApiException.Create(resp, restMethod.RefitSettings); + } + + var ms = new MemoryStream(); + var fromStream = await resp.Content.ReadAsStreamAsync(); + await fromStream.CopyToAsync(ms, 4096, ct); + + var bytes = ms.ToArray(); + var content = Encoding.UTF8.GetString(bytes, 0, bytes.Length); + if (restMethod.SerializedReturnType == typeof(string)) { + return (T)(object)content; + } + + return JsonConvert.DeserializeObject(content, settings.JsonSerializerSettings); + }; } Func> buildRxFuncForMethod(RestMethodInfo restMethod) { var taskFunc = buildCancellableTaskFuncForMethod(restMethod); - return (client, paramList) => - new TaskToObservable(ct => taskFunc(client, ct, paramList)); + return (client, paramList) => + new TaskToObservable(ct => taskFunc(client, ct, paramList)); } class TaskToObservable : IObservable { - Func> taskFactory; + readonly Func> taskFactory; - public TaskToObservable(Func> taskFactory) + public TaskToObservable(Func> taskFactory) { this.taskFactory = taskFactory; } @@ -266,21 +267,23 @@ public IDisposable Subscribe(IObserver observer) { var cts = new CancellationTokenSource(); taskFactory(cts.Token).ContinueWith(t => { - if (cts.IsCancellationRequested) return; - - if (t.Exception != null) { - observer.OnError(t.Exception.InnerExceptions.First()); - return; - } - - try { - observer.OnNext(t.Result); - } catch (Exception ex) { - observer.OnError(ex); - } - - observer.OnCompleted(); - }); + if (cts.IsCancellationRequested) { + return; + } + + if (t.Exception != null) { + observer.OnError(t.Exception.InnerExceptions.First()); + return; + } + + try { + observer.OnNext(t.Result); + } catch (Exception ex) { + observer.OnError(ex); + } + + observer.OnCompleted(); + }); return new AnonymousDisposable(cts.Cancel); } @@ -330,8 +333,8 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings MethodInfo = methodInfo; var hma = methodInfo.GetCustomAttributes(true) - .OfType() - .First(); + .OfType() + .First(); HttpMethod = hma.Method; RelativePath = hma.Path; @@ -341,7 +344,10 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings var parameterList = methodInfo.GetParameters().ToList(); ParameterInfoMap = parameterList - .Select((parameter, index) => new { index, parameter }) + .Select((parameter, index) => new { + index, + parameter + }) .ToDictionary(x => x.index, x => x.parameter); ParameterMap = buildParameterMap(RelativePath, parameterList); BodyParameterInfo = findBodyParameter(parameterList, hma.Method); @@ -350,7 +356,7 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings HeaderParameterMap = buildHeaderParameterMap(parameterList); QueryParameterMap = new Dictionary(); - for (int i=0; i < parameterList.Count; i++) { + for (var i = 0; i < parameterList.Count; i++) { if (ParameterMap.ContainsKey(i) || HeaderParameterMap.ContainsKey(i) || (BodyParameterInfo != null && BodyParameterInfo.Item2 == i)) { continue; } @@ -359,10 +365,11 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings } } - void verifyUrlPathIsSane(string relativePath) + void verifyUrlPathIsSane(string relativePath) { - if (relativePath == "") + if (relativePath == "") { return; + } if (!relativePath.StartsWith("/")) { goto bogusPath; @@ -375,7 +382,7 @@ void verifyUrlPathIsSane(string relativePath) return; - bogusPath: + bogusPath: throw new ArgumentException("URL path must be of the form '/foo/bar/baz'"); } @@ -384,8 +391,8 @@ Dictionary buildParameterMap(string relativePath, List(); var parameterizedParts = relativePath.Split('/', '?') - .SelectMany(x => parameterRegex.Matches(x).Cast()) - .ToList(); + .SelectMany(x => parameterRegex.Matches(x).Cast()) + .ToList(); if (parameterizedParts.Count == 0) { return ret; @@ -408,21 +415,23 @@ Dictionary buildParameterMap(string relativePath, List() - .FirstOrDefault(); + .OfType() + .FirstOrDefault(); return aliasAttr != null ? aliasAttr.Name : paramInfo.Name; } Tuple findBodyParameter(List parameterList, 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() }) + .Select(x => new { + Parameter = x, + BodyAttribute = x.GetCustomAttributes(true).OfType().FirstOrDefault() + }) .Where(x => x.BodyAttribute != null) .ToList(); @@ -431,27 +440,22 @@ Tuple findBodyParameter(List parame } // #1, body attribute wins - if (bodyParams.Count == 1) - { + 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)) - { + // 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) - { + if (refParams.Count > 1) { throw new ArgumentException("Multiple complex types found. Specify one parameter as the body using BodyAttribute"); } - else if (refParams.Count == 1) - { + if (refParams.Count == 1) { return Tuple.Create(BodySerializationMethod.Json, parameterList.IndexOf(refParams[0])); } } @@ -459,45 +463,47 @@ Tuple findBodyParameter(List parame return null; } - Dictionary parseHeaders(MethodInfo methodInfo) + Dictionary parseHeaders(MethodInfo methodInfo) { var ret = new Dictionary(); var declaringTypeAttributes = methodInfo.DeclaringType != null - ? methodInfo.DeclaringType.GetCustomAttributes(true) - : new Attribute[0]; + ? methodInfo.DeclaringType.GetCustomAttributes(true) + : new Attribute[0]; // Headers set on the declaring type have to come first, // so headers set on the method can replace them. Switching // the order here will break stuff. var headers = declaringTypeAttributes.Concat(methodInfo.GetCustomAttributes(true)) - .OfType() - .SelectMany(ha => ha.Headers); + .OfType() + .SelectMany(ha => ha.Headers); foreach (var header in headers) { - if (string.IsNullOrWhiteSpace(header)) continue; + if (string.IsNullOrWhiteSpace(header)) { + continue; + } - // NB: Silverlight doesn't have an overload for String.Split() - // with a count parameter, but header values can contain - // ':' so we have to re-join all but the first part to get the - // value. + // NB: Silverlight doesn't have an overload for String.Split() + // with a count parameter, but header values can contain + // ':' so we have to re-join all but the first part to get the + // value. var parts = header.Split(':'); - ret[parts[0].Trim()] = parts.Length > 1 ? - String.Join(":", parts.Skip(1)).Trim() : null; + ret[parts[0].Trim()] = parts.Length > 1 ? + String.Join(":", parts.Skip(1)).Trim() : null; } return ret; } - Dictionary buildHeaderParameterMap(List parameterList) + Dictionary buildHeaderParameterMap(List parameterList) { var ret = new Dictionary(); - for (int i = 0; i < parameterList.Count; i++) { + for (var i = 0; i < parameterList.Count; i++) { var header = parameterList[i].GetCustomAttributes(true) - .OfType() - .Select(ha => ha.Header) - .FirstOrDefault(); + .OfType() + .Select(ha => ha.Header) + .FirstOrDefault(); if (!string.IsNullOrWhiteSpace(header)) { ret[i] = header.Trim(); @@ -510,7 +516,7 @@ Dictionary buildHeaderParameterMap(List parameterLis void determineReturnTypeInfo(MethodInfo methodInfo) { if (methodInfo.ReturnType.IsGenericType() == false) { - if (methodInfo.ReturnType != typeof (Task)) { + if (methodInfo.ReturnType != typeof(Task)) { goto bogusMethod; } @@ -528,7 +534,7 @@ void determineReturnTypeInfo(MethodInfo methodInfo) SerializedReturnType = methodInfo.ReturnType.GetGenericArguments()[0]; return; - bogusMethod: + bogusMethod: throw new ArgumentException("All REST Methods must return either Task or IObservable"); } } @@ -543,13 +549,15 @@ public class ApiException : Exception public string Content { get; private set; } - public bool HasContent { + public bool HasContent + { get { return !String.IsNullOrWhiteSpace(Content); } } - public RefitSettings RefitSettings{get;set;} - ApiException(HttpStatusCode statusCode, string reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings = null) : - base(createMessage(statusCode, reasonPhrase)) + public RefitSettings RefitSettings { get; set; } + + ApiException(HttpStatusCode statusCode, string reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings = null) : + base(createMessage(statusCode, reasonPhrase)) { StatusCode = statusCode; ReasonPhrase = reasonPhrase; @@ -559,17 +567,19 @@ public bool HasContent { public T GetContentAs() { - return HasContent ? - JsonConvert.DeserializeObject(Content, RefitSettings.JsonSerializerSettings) : - default(T); + return HasContent ? + JsonConvert.DeserializeObject(Content, RefitSettings.JsonSerializerSettings) : + default(T); } - public static async Task Create(HttpResponseMessage response, RefitSettings refitSettings = null) + public static async Task Create(HttpResponseMessage response, RefitSettings refitSettings = null) { var exception = new ApiException(response.StatusCode, response.ReasonPhrase, response.Headers, refitSettings); - if (response.Content == null) return exception; - + if (response.Content == null) { + return exception; + } + try { exception.ContentHeaders = response.Content.Headers; exception.Content = await response.Content.ReadAsStringAsync(); @@ -579,7 +589,7 @@ public static async Task Create(HttpResponseMessage response, Refi // so we want to make sure we don't throw another one // that hides the real error. } - + return exception; } @@ -594,16 +604,18 @@ class FormValueDictionary : Dictionary static readonly Dictionary propertyCache = new Dictionary(); - public FormValueDictionary(object source) + public FormValueDictionary(object source) { - if (source == null) return; + if (source == null) { + return; + } var dictionary = source as IDictionary; if (dictionary != null) { foreach (var key in dictionary.Keys) { Add(key.ToString(), string.Format("{0}", dictionary[key])); } - + return; } @@ -620,18 +632,18 @@ public FormValueDictionary(object source) } } - PropertyInfo[] getProperties(Type type) + PropertyInfo[] getProperties(Type type) { return type.GetProperties() - .Where(p => p.CanRead) - .ToArray(); + .Where(p => p.CanRead) + .ToArray(); } string getFieldNameForProperty(PropertyInfo propertyInfo) { var aliasAttr = propertyInfo.GetCustomAttributes(true) - .OfType() - .FirstOrDefault(); + .OfType() + .FirstOrDefault(); return aliasAttr != null ? aliasAttr.Name : propertyInfo.Name; } } From 8e9e622cf77c71fe7c75ced0d329b12fb118606b Mon Sep 17 00:00:00 2001 From: Oren Novotny Date: Thu, 16 Apr 2015 10:59:56 -0400 Subject: [PATCH 5/6] Revert "Fix formatting" This reverts commit a4f8386a847657bb77e263ba7ac0e3b698420167. --- Refit/RequestBuilder.cs | 5 +- Refit/RequestBuilderImplementation.cs | 444 +++++++++++++------------- 2 files changed, 219 insertions(+), 230 deletions(-) diff --git a/Refit/RequestBuilder.cs b/Refit/RequestBuilder.cs index 1c200ff94..942f3ed52 100644 --- a/Refit/RequestBuilder.cs +++ b/Refit/RequestBuilder.cs @@ -19,12 +19,12 @@ interface IRequestBuilderFactory public static class RequestBuilder { static readonly IRequestBuilderFactory platformRequestBuilderFactory = new RequestBuilderFactory(); - + public static IRequestBuilder ForType(Type interfaceType, RefitSettings settings) { return platformRequestBuilderFactory.Create(interfaceType, settings); } - + public static IRequestBuilder ForType(Type interfaceType) { return platformRequestBuilderFactory.Create(interfaceType, null); @@ -51,3 +51,4 @@ public IRequestBuilder Create(Type interfaceType, RefitSettings settings = null) } #endif } + diff --git a/Refit/RequestBuilderImplementation.cs b/Refit/RequestBuilderImplementation.cs index c95f4587d..ee2001605 100644 --- a/Refit/RequestBuilderImplementation.cs +++ b/Refit/RequestBuilderImplementation.cs @@ -39,20 +39,17 @@ public RequestBuilderImplementation(Type targetInterface, RefitSettings refitSet targetType = targetInterface; interfaceHttpMethods = targetInterface.GetMethods() - .SelectMany(x => { - var attrs = x.GetCustomAttributes(true); - var hasHttpMethod = attrs.OfType().Any(); - if (!hasHttpMethod) { - return Enumerable.Empty(); - } - - return EnumerableEx.Return(new RestMethodInfo(targetInterface, x, settings)); - }) - .ToDictionary(k => k.Name, v => v); + .SelectMany(x => { + var attrs = x.GetCustomAttributes(true); + var hasHttpMethod = attrs.OfType().Any(); + if (!hasHttpMethod) return Enumerable.Empty(); + + return EnumerableEx.Return(new RestMethodInfo(targetInterface, x, settings)); + }) + .ToDictionary(k => k.Name, v => v); } - public IEnumerable InterfaceHttpMethods - { + public IEnumerable InterfaceHttpMethods { get { return interfaceHttpMethods.Keys; } } @@ -64,81 +61,81 @@ public Func BuildRequestFactoryForMethod(string me var restMethod = interfaceHttpMethods[methodName]; return paramList => { - var ret = new HttpRequestMessage { - Method = restMethod.HttpMethod - }; - - foreach (var header in restMethod.Headers) { - setHeader(ret, header.Key, header.Value); - } - - var urlTarget = (basePath == "/" ? String.Empty : basePath) + restMethod.RelativePath; - var queryParamsToAdd = new Dictionary(); - - for (var i = 0; i < paramList.Length; i++) { - if (restMethod.ParameterMap.ContainsKey(i)) { - urlTarget = Regex.Replace( - urlTarget, - "{" + restMethod.ParameterMap[i] + "}", - settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]), - RegexOptions.IgnoreCase); - continue; - } - - if (restMethod.BodyParameterInfo != null && restMethod.BodyParameterInfo.Item2 == i) { - var streamParam = paramList[i] as Stream; - var stringParam = paramList[i] as string; - - if (streamParam != null) { - ret.Content = new StreamContent(streamParam); - } else if (stringParam != null) { - ret.Content = new StringContent(stringParam); - } else { - switch (restMethod.BodyParameterInfo.Item1) { - case BodySerializationMethod.UrlEncoded: - ret.Content = new FormUrlEncodedContent(new FormValueDictionary(paramList[i])); - break; - case BodySerializationMethod.Json: - ret.Content = new StringContent(JsonConvert.SerializeObject(paramList[i], settings.JsonSerializerSettings), Encoding.UTF8, "application/json"); - break; - } - } - - continue; - } - - - if (restMethod.HeaderParameterMap.ContainsKey(i)) { - setHeader(ret, restMethod.HeaderParameterMap[i], paramList[i]); - } else { - if (paramList[i] != null) { - queryParamsToAdd[restMethod.QueryParameterMap[i]] = settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]); - } - } - } - - // NB: The URI methods in .NET are dumb. Also, we do this - // UriBuilder business so that we preserve any hardcoded query - // parameters as well as add the parameterized ones. - var uri = new UriBuilder(new Uri(new Uri("http://api"), urlTarget)); - var query = HttpUtility.ParseQueryString(uri.Query ?? ""); - foreach (var kvp in queryParamsToAdd) { - query.Add(kvp.Key, kvp.Value); - } - - if (query.HasKeys()) { - var pairs = query.Keys.Cast().Select(x => HttpUtility.UrlEncode(x) + "=" + HttpUtility.UrlEncode(query[x])); - uri.Query = String.Join("&", pairs); - } else { - uri.Query = null; - } - - ret.RequestUri = new Uri(uri.Uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped), UriKind.Relative); - return ret; - }; + var ret = new HttpRequestMessage() { + Method = restMethod.HttpMethod, + }; + + foreach (var header in restMethod.Headers) { + setHeader(ret, header.Key, header.Value); + } + + string urlTarget = (basePath == "/" ? String.Empty : basePath) + restMethod.RelativePath; + var queryParamsToAdd = new Dictionary(); + + for(int i=0; i < paramList.Length; i++) { + if (restMethod.ParameterMap.ContainsKey(i)) { + urlTarget = Regex.Replace( + urlTarget, + "{" + restMethod.ParameterMap[i] + "}", + settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]), + RegexOptions.IgnoreCase); + continue; + } + + if (restMethod.BodyParameterInfo != null && restMethod.BodyParameterInfo.Item2 == i) { + var streamParam = paramList[i] as Stream; + var stringParam = paramList[i] as string; + + if (streamParam != null) { + ret.Content = new StreamContent(streamParam); + } else if (stringParam != null) { + ret.Content = new StringContent(stringParam); + } else { + switch (restMethod.BodyParameterInfo.Item1) { + case BodySerializationMethod.UrlEncoded: + ret.Content = new FormUrlEncodedContent(new FormValueDictionary(paramList[i])); + break; + case BodySerializationMethod.Json: + ret.Content = new StringContent(JsonConvert.SerializeObject(paramList[i], settings.JsonSerializerSettings), Encoding.UTF8, "application/json"); + break; + } + } + + continue; + } + + + if (restMethod.HeaderParameterMap.ContainsKey(i)) { + setHeader(ret, restMethod.HeaderParameterMap[i], paramList[i]); + } else { + if (paramList[i] != null) { + queryParamsToAdd[restMethod.QueryParameterMap[i]] = settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]); + } + } + } + + // NB: The URI methods in .NET are dumb. Also, we do this + // UriBuilder business so that we preserve any hardcoded query + // parameters as well as add the parameterized ones. + var uri = new UriBuilder(new Uri(new Uri("http://api"), urlTarget)); + var query = HttpUtility.ParseQueryString(uri.Query ?? ""); + foreach(var kvp in queryParamsToAdd) { + query.Add(kvp.Key, kvp.Value); + } + + if (query.HasKeys()) { + var pairs = query.Keys.Cast().Select(x => HttpUtility.UrlEncode(x) + "=" + HttpUtility.UrlEncode(query[x])); + uri.Query = String.Join("&", pairs); + } else { + uri.Query = null; + } + + ret.RequestUri = new Uri(uri.Uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped), UriKind.Relative); + return ret; + }; } - void setHeader(HttpRequestMessage request, string name, object value) + void setHeader(HttpRequestMessage request, string name, object value) { // Clear any existing version of this header we may have set, because // we want to allow removal/redefinition of headers. @@ -152,9 +149,7 @@ void setHeader(HttpRequestMessage request, string name, object value) request.Content.Headers.Remove(name); } - if (value == null) { - return; - } + if (value == null) return; var s = value.ToString(); request.Headers.TryAddWithoutValidation(name, s); @@ -174,37 +169,41 @@ public Func BuildRestResultFuncForMethod(string me if (restMethod.ReturnType == typeof(Task)) { return buildVoidTaskFuncForMethod(restMethod); - } - if (restMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { + } else if (restMethod.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { // NB: This jacked up reflection code is here because it's // difficult to upcast Task to an arbitrary T, especially // if you need to AOT everything, so we need to reflectively // invoke buildTaskFuncForMethod. var taskFuncMi = GetType().GetMethod("buildTaskFuncForMethod", BindingFlags.NonPublic | BindingFlags.Instance); var taskFunc = (MulticastDelegate)taskFuncMi.MakeGenericMethod(restMethod.SerializedReturnType) - .Invoke(this, new[] { restMethod }); - - return (client, args) => { return taskFunc.DynamicInvoke(client, args); }; + .Invoke(this, new[] { restMethod }); + + return (client, args) => { + return taskFunc.DynamicInvoke(new object[] { client, args } ); + }; + } else { + // Same deal + var rxFuncMi = GetType().GetMethod("buildRxFuncForMethod", BindingFlags.NonPublic | BindingFlags.Instance); + var rxFunc = (MulticastDelegate)rxFuncMi.MakeGenericMethod(restMethod.SerializedReturnType) + .Invoke(this, new[] { restMethod }); + + return (client, args) => { + return rxFunc.DynamicInvoke(new object[] { client, args }); + }; } - // Same deal - var rxFuncMi = GetType().GetMethod("buildRxFuncForMethod", BindingFlags.NonPublic | BindingFlags.Instance); - var rxFunc = (MulticastDelegate)rxFuncMi.MakeGenericMethod(restMethod.SerializedReturnType) - .Invoke(this, new[] { restMethod }); - - return (client, args) => { return rxFunc.DynamicInvoke(client, args); }; } Func buildVoidTaskFuncForMethod(RestMethodInfo restMethod) - { + { return async (client, paramList) => { - var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); - var rq = factory(paramList); - var resp = await client.SendAsync(rq); - - if (!resp.IsSuccessStatusCode) { - throw await ApiException.Create(resp, settings); - } - }; + var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); + var rq = factory(paramList); + var resp = await client.SendAsync(rq); + + if (!resp.IsSuccessStatusCode) { + throw await ApiException.Create(resp, settings); + } + }; } Func> buildTaskFuncForMethod(RestMethodInfo restMethod) @@ -216,49 +215,49 @@ Func> buildTaskFuncForMethod(RestMethodInfo res Func> buildCancellableTaskFuncForMethod(RestMethodInfo restMethod) { return async (client, ct, paramList) => { - var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); - var rq = factory(paramList); - - var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct); - - if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { - // NB: This double-casting manual-boxing hate crime is the only way to make - // this work without a 'class' generic constraint. It could blow up at runtime - // and would be A Bad Idea if we hadn't already vetted the return type. - return (T)(object)resp; - } - - if (!resp.IsSuccessStatusCode) { - throw await ApiException.Create(resp, restMethod.RefitSettings); - } - - var ms = new MemoryStream(); - var fromStream = await resp.Content.ReadAsStreamAsync(); - await fromStream.CopyToAsync(ms, 4096, ct); - - var bytes = ms.ToArray(); - var content = Encoding.UTF8.GetString(bytes, 0, bytes.Length); - if (restMethod.SerializedReturnType == typeof(string)) { - return (T)(object)content; - } - - return JsonConvert.DeserializeObject(content, settings.JsonSerializerSettings); - }; + var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); + var rq = factory(paramList); + + var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct); + + if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { + // NB: This double-casting manual-boxing hate crime is the only way to make + // this work without a 'class' generic constraint. It could blow up at runtime + // and would be A Bad Idea if we hadn't already vetted the return type. + return (T)(object)resp; + } + + if (!resp.IsSuccessStatusCode) { + throw await ApiException.Create(resp, restMethod.RefitSettings); + } + + var ms = new MemoryStream(); + var fromStream = await resp.Content.ReadAsStreamAsync(); + await fromStream.CopyToAsync(ms, 4096, ct); + + var bytes = ms.ToArray(); + var content = Encoding.UTF8.GetString(bytes, 0, bytes.Length); + if (restMethod.SerializedReturnType == typeof(string)) { + return (T)(object)content; + } + + return JsonConvert.DeserializeObject(content, settings.JsonSerializerSettings); + }; } Func> buildRxFuncForMethod(RestMethodInfo restMethod) { var taskFunc = buildCancellableTaskFuncForMethod(restMethod); - return (client, paramList) => - new TaskToObservable(ct => taskFunc(client, ct, paramList)); + return (client, paramList) => + new TaskToObservable(ct => taskFunc(client, ct, paramList)); } class TaskToObservable : IObservable { - readonly Func> taskFactory; + Func> taskFactory; - public TaskToObservable(Func> taskFactory) + public TaskToObservable(Func> taskFactory) { this.taskFactory = taskFactory; } @@ -267,23 +266,21 @@ public IDisposable Subscribe(IObserver observer) { var cts = new CancellationTokenSource(); taskFactory(cts.Token).ContinueWith(t => { - if (cts.IsCancellationRequested) { - return; - } - - if (t.Exception != null) { - observer.OnError(t.Exception.InnerExceptions.First()); - return; - } - - try { - observer.OnNext(t.Result); - } catch (Exception ex) { - observer.OnError(ex); - } - - observer.OnCompleted(); - }); + if (cts.IsCancellationRequested) return; + + if (t.Exception != null) { + observer.OnError(t.Exception.InnerExceptions.First()); + return; + } + + try { + observer.OnNext(t.Result); + } catch (Exception ex) { + observer.OnError(ex); + } + + observer.OnCompleted(); + }); return new AnonymousDisposable(cts.Cancel); } @@ -333,8 +330,8 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings MethodInfo = methodInfo; var hma = methodInfo.GetCustomAttributes(true) - .OfType() - .First(); + .OfType() + .First(); HttpMethod = hma.Method; RelativePath = hma.Path; @@ -344,10 +341,7 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings var parameterList = methodInfo.GetParameters().ToList(); ParameterInfoMap = parameterList - .Select((parameter, index) => new { - index, - parameter - }) + .Select((parameter, index) => new { index, parameter }) .ToDictionary(x => x.index, x => x.parameter); ParameterMap = buildParameterMap(RelativePath, parameterList); BodyParameterInfo = findBodyParameter(parameterList, hma.Method); @@ -356,7 +350,7 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings HeaderParameterMap = buildHeaderParameterMap(parameterList); QueryParameterMap = new Dictionary(); - for (var i = 0; i < parameterList.Count; i++) { + for (int i=0; i < parameterList.Count; i++) { if (ParameterMap.ContainsKey(i) || HeaderParameterMap.ContainsKey(i) || (BodyParameterInfo != null && BodyParameterInfo.Item2 == i)) { continue; } @@ -365,11 +359,10 @@ public RestMethodInfo(Type targetInterface, MethodInfo methodInfo, RefitSettings } } - void verifyUrlPathIsSane(string relativePath) + void verifyUrlPathIsSane(string relativePath) { - if (relativePath == "") { + if (relativePath == "") return; - } if (!relativePath.StartsWith("/")) { goto bogusPath; @@ -382,7 +375,7 @@ void verifyUrlPathIsSane(string relativePath) return; - bogusPath: + bogusPath: throw new ArgumentException("URL path must be of the form '/foo/bar/baz'"); } @@ -391,8 +384,8 @@ Dictionary buildParameterMap(string relativePath, List(); var parameterizedParts = relativePath.Split('/', '?') - .SelectMany(x => parameterRegex.Matches(x).Cast()) - .ToList(); + .SelectMany(x => parameterRegex.Matches(x).Cast()) + .ToList(); if (parameterizedParts.Count == 0) { return ret; @@ -415,23 +408,21 @@ Dictionary buildParameterMap(string relativePath, List() - .FirstOrDefault(); + .OfType() + .FirstOrDefault(); return aliasAttr != null ? aliasAttr.Name : paramInfo.Name; } Tuple findBodyParameter(List parameterList, 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() - }) + .Select(x => new { Parameter = x, BodyAttribute = x.GetCustomAttributes(true).OfType().FirstOrDefault() }) .Where(x => x.BodyAttribute != null) .ToList(); @@ -440,22 +431,27 @@ Tuple findBodyParameter(List parame } // #1, body attribute wins - if (bodyParams.Count == 1) { + 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(); + 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) { + if (refParams.Count > 1) + { throw new ArgumentException("Multiple complex types found. Specify one parameter as the body using BodyAttribute"); } - if (refParams.Count == 1) { + else if (refParams.Count == 1) + { return Tuple.Create(BodySerializationMethod.Json, parameterList.IndexOf(refParams[0])); } } @@ -463,47 +459,45 @@ Tuple findBodyParameter(List parame return null; } - Dictionary parseHeaders(MethodInfo methodInfo) + Dictionary parseHeaders(MethodInfo methodInfo) { var ret = new Dictionary(); var declaringTypeAttributes = methodInfo.DeclaringType != null - ? methodInfo.DeclaringType.GetCustomAttributes(true) - : new Attribute[0]; + ? methodInfo.DeclaringType.GetCustomAttributes(true) + : new Attribute[0]; // Headers set on the declaring type have to come first, // so headers set on the method can replace them. Switching // the order here will break stuff. var headers = declaringTypeAttributes.Concat(methodInfo.GetCustomAttributes(true)) - .OfType() - .SelectMany(ha => ha.Headers); + .OfType() + .SelectMany(ha => ha.Headers); foreach (var header in headers) { - if (string.IsNullOrWhiteSpace(header)) { - continue; - } + if (string.IsNullOrWhiteSpace(header)) continue; - // NB: Silverlight doesn't have an overload for String.Split() - // with a count parameter, but header values can contain - // ':' so we have to re-join all but the first part to get the - // value. + // NB: Silverlight doesn't have an overload for String.Split() + // with a count parameter, but header values can contain + // ':' so we have to re-join all but the first part to get the + // value. var parts = header.Split(':'); - ret[parts[0].Trim()] = parts.Length > 1 ? - String.Join(":", parts.Skip(1)).Trim() : null; + ret[parts[0].Trim()] = parts.Length > 1 ? + String.Join(":", parts.Skip(1)).Trim() : null; } return ret; } - Dictionary buildHeaderParameterMap(List parameterList) + Dictionary buildHeaderParameterMap(List parameterList) { var ret = new Dictionary(); - for (var i = 0; i < parameterList.Count; i++) { + for (int i = 0; i < parameterList.Count; i++) { var header = parameterList[i].GetCustomAttributes(true) - .OfType() - .Select(ha => ha.Header) - .FirstOrDefault(); + .OfType() + .Select(ha => ha.Header) + .FirstOrDefault(); if (!string.IsNullOrWhiteSpace(header)) { ret[i] = header.Trim(); @@ -516,7 +510,7 @@ Dictionary buildHeaderParameterMap(List parameterLis void determineReturnTypeInfo(MethodInfo methodInfo) { if (methodInfo.ReturnType.IsGenericType() == false) { - if (methodInfo.ReturnType != typeof(Task)) { + if (methodInfo.ReturnType != typeof (Task)) { goto bogusMethod; } @@ -534,7 +528,7 @@ void determineReturnTypeInfo(MethodInfo methodInfo) SerializedReturnType = methodInfo.ReturnType.GetGenericArguments()[0]; return; - bogusMethod: + bogusMethod: throw new ArgumentException("All REST Methods must return either Task or IObservable"); } } @@ -549,15 +543,13 @@ public class ApiException : Exception public string Content { get; private set; } - public bool HasContent - { + public bool HasContent { get { return !String.IsNullOrWhiteSpace(Content); } } + public RefitSettings RefitSettings{get;set;} - public RefitSettings RefitSettings { get; set; } - - ApiException(HttpStatusCode statusCode, string reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings = null) : - base(createMessage(statusCode, reasonPhrase)) + ApiException(HttpStatusCode statusCode, string reasonPhrase, HttpResponseHeaders headers, RefitSettings refitSettings = null) : + base(createMessage(statusCode, reasonPhrase)) { StatusCode = statusCode; ReasonPhrase = reasonPhrase; @@ -567,19 +559,17 @@ public bool HasContent public T GetContentAs() { - return HasContent ? - JsonConvert.DeserializeObject(Content, RefitSettings.JsonSerializerSettings) : - default(T); + return HasContent ? + JsonConvert.DeserializeObject(Content, RefitSettings.JsonSerializerSettings) : + default(T); } - public static async Task Create(HttpResponseMessage response, RefitSettings refitSettings = null) + public static async Task Create(HttpResponseMessage response, RefitSettings refitSettings = null) { var exception = new ApiException(response.StatusCode, response.ReasonPhrase, response.Headers, refitSettings); - if (response.Content == null) { - return exception; - } - + if (response.Content == null) return exception; + try { exception.ContentHeaders = response.Content.Headers; exception.Content = await response.Content.ReadAsStringAsync(); @@ -589,7 +579,7 @@ public static async Task Create(HttpResponseMessage response, Refi // so we want to make sure we don't throw another one // that hides the real error. } - + return exception; } @@ -604,18 +594,16 @@ class FormValueDictionary : Dictionary static readonly Dictionary propertyCache = new Dictionary(); - public FormValueDictionary(object source) + public FormValueDictionary(object source) { - if (source == null) { - return; - } + if (source == null) return; var dictionary = source as IDictionary; if (dictionary != null) { foreach (var key in dictionary.Keys) { Add(key.ToString(), string.Format("{0}", dictionary[key])); } - + return; } @@ -632,18 +620,18 @@ public FormValueDictionary(object source) } } - PropertyInfo[] getProperties(Type type) + PropertyInfo[] getProperties(Type type) { return type.GetProperties() - .Where(p => p.CanRead) - .ToArray(); + .Where(p => p.CanRead) + .ToArray(); } string getFieldNameForProperty(PropertyInfo propertyInfo) { var aliasAttr = propertyInfo.GetCustomAttributes(true) - .OfType() - .FirstOrDefault(); + .OfType() + .FirstOrDefault(); return aliasAttr != null ? aliasAttr.Name : propertyInfo.Name; } } From b4bab5a7ec4a791e6f8cbf97868fc5e3b98a4dd4 Mon Sep 17 00:00:00 2001 From: Oren Novotny Date: Thu, 16 Apr 2015 11:04:18 -0400 Subject: [PATCH 6/6] Revert "Add support for the HTTP PATCH method" This reverts commit d678c7a049c15e259df3852656613b3a377fc65c. --- Refit/Attributes.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Refit/Attributes.cs b/Refit/Attributes.cs index e40232f8e..1d8539afb 100644 --- a/Refit/Attributes.cs +++ b/Refit/Attributes.cs @@ -29,17 +29,6 @@ public override HttpMethod Method { } } - [AttributeUsage(AttributeTargets.Method)] - public class PatchAttribute : HttpMethodAttribute - { - public PatchAttribute(string path) : base(path) { } - - public override HttpMethod Method - { - get { return new HttpMethod("PATCH"); } - } - } - [AttributeUsage(AttributeTargets.Method)] public class PostAttribute : HttpMethodAttribute {