From 0a749cdbfb6207995a0e7808bb1440117e92ca60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Fr=C3=B6hling?= Date: Sun, 29 Apr 2018 20:23:00 +0200 Subject: [PATCH] GH-87: Request handling improvements - Fix type in name of AuthorizationRefreshRequest - Add handler for authentication requests - Add handler for response errors - Add helper for reading response content streams --- .../AuthorizationRefreshRequest.cs | 2 +- .../Handler/AuthenticationRequestHandler.cs | 411 +++++++++++++ .../Handler/IAuthenticationRequestHandler.cs | 32 ++ .../Requests/Handler/RequestHandler.cs | 277 +-------- .../Requests/Handler/ResponseErrorHandler.cs | 538 ++++++++++++++++++ .../Handler/ResponseErrorParameters.cs | 36 ++ .../Requests/Handler/ResponseMessageHelper.cs | 17 + Source/Lib/TraktApiSharp/TraktApiSharp.csproj | 7 +- 8 files changed, 1049 insertions(+), 271 deletions(-) rename "Source/Lib/TraktApiSharp/Requests/Authentication/\303\201uthorizationRefreshRequest.cs" => Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequest.cs (55%) create mode 100644 Source/Lib/TraktApiSharp/Requests/Handler/AuthenticationRequestHandler.cs create mode 100644 Source/Lib/TraktApiSharp/Requests/Handler/IAuthenticationRequestHandler.cs create mode 100644 Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorHandler.cs create mode 100644 Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorParameters.cs create mode 100644 Source/Lib/TraktApiSharp/Requests/Handler/ResponseMessageHelper.cs diff --git "a/Source/Lib/TraktApiSharp/Requests/Authentication/\303\201uthorizationRefreshRequest.cs" b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequest.cs similarity index 55% rename from "Source/Lib/TraktApiSharp/Requests/Authentication/\303\201uthorizationRefreshRequest.cs" rename to Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequest.cs index c063c3311..cf43ec4b5 100644 --- "a/Source/Lib/TraktApiSharp/Requests/Authentication/\303\201uthorizationRefreshRequest.cs" +++ b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequest.cs @@ -2,7 +2,7 @@ { using Objects.Authentication; - internal sealed class ÁuthorizationRefreshRequest : AAuthorizationRequest + internal sealed class AuthorizationRefreshRequest : AAuthorizationRequest { public override string UriTemplate => "oauth/token"; } diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/AuthenticationRequestHandler.cs b/Source/Lib/TraktApiSharp/Requests/Handler/AuthenticationRequestHandler.cs new file mode 100644 index 000000000..3887ae444 --- /dev/null +++ b/Source/Lib/TraktApiSharp/Requests/Handler/AuthenticationRequestHandler.cs @@ -0,0 +1,411 @@ +namespace TraktApiSharp.Requests.Handler +{ + using Authentication; + using Core; + using Exceptions; + using Extensions; + using Interfaces; + using Interfaces.Base; + using Objects.Authentication; + using Objects.Authentication.Implementations; + using Objects.Basic; + using Objects.Json; + using Responses; + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Utils; + + internal sealed class AuthenticationRequestHandler : IAuthenticationRequestHandler + { + private readonly TraktClient _client; + private readonly RequestMessageBuilder _requestMessageBuilder; + private static IAuthenticationRequestHandler s_requestHandler; + + private AuthenticationRequestHandler(TraktClient client) + { + _client = client; + _requestMessageBuilder = new RequestMessageBuilder(_client); + } + + internal static IAuthenticationRequestHandler GetInstance(TraktClient client) + => s_requestHandler ?? (s_requestHandler = new AuthenticationRequestHandler(client)); + + public string CreateAuthorizationUrl(string clientId, string redirectUri, string state) + { + ValidateAuthorizationUrlArguments(clientId, redirectUri, state); + return BuildAuthorizationUrl(clientId, redirectUri, state); + } + + public string CreateAuthorizationUrlWithDefaultState(string clientId, string redirectUri) + { + string state = _client.Authentication.AntiForgeryToken; + return CreateAuthorizationUrl(clientId, redirectUri, state); + } + + public async Task>> CheckIfAuthorizationIsExpiredOrWasRevokedAsync(bool autoRefresh = false, CancellationToken cancellationToken = default) + { + if (_client.Authorization.IsExpired) + return new Pair>(true, new TraktResponse()); + + bool throwResponseExceptions = true; + + try + { + throwResponseExceptions = _client.Configuration.ThrowResponseExceptions; + _client.Configuration.ThrowResponseExceptions = true; + await _client.Sync.GetLastActivitiesAsync(cancellationToken).ConfigureAwait(false); + } + catch (TraktAuthorizationException) + { + if (!autoRefresh) + return new Pair>(true, new TraktResponse()); + + var request = new AuthorizationRefreshRequest + { + RequestBody = new AuthorizationRefreshRequestBody + { + ClientId = _client.ClientId, + ClientSecret = _client.ClientSecret, + RefreshToken = _client.Authorization.RefreshToken, + RedirectUri = _client.Authentication.RedirectUri + } + }; + + TraktResponse response = await RefreshAuthorizationAsync(request, cancellationToken).ConfigureAwait(false); + return new Pair>(response.IsSuccess, response); + } + finally + { + _client.Configuration.ThrowResponseExceptions = throwResponseExceptions; + } + + return new Pair>(true, new TraktResponse()); + } + + public async Task>> CheckIfAuthorizationIsExpiredOrWasRevokedAsync(ITraktAuthorization authorization, bool autoRefresh = false, CancellationToken cancellationToken = default) + { + if (authorization == null) + throw new ArgumentNullException(nameof(authorization)); + + ITraktAuthorization currentAuthorization = _client.Authorization; + _client.Authorization = authorization as TraktAuthorization; + + var result = new Pair>(true, new TraktResponse()); + + try + { + result = await CheckIfAuthorizationIsExpiredOrWasRevokedAsync(autoRefresh, cancellationToken).ConfigureAwait(false); + } + finally + { + _client.Authorization = currentAuthorization as TraktAuthorization; + } + + return result; + } + + public async Task CheckIfAccessTokenWasRevokedOrIsNotValidAsync(string accessToken, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(accessToken) || accessToken.ContainsSpace()) + throw new ArgumentException("access token must not be null, empty or contain any spaces", nameof(accessToken)); + + ITraktAuthorization currentAuthorization = _client.Authorization; + _client.Authorization = TraktAuthorization.CreateWith(accessToken); + + bool throwResponseExceptions = true; + + try + { + throwResponseExceptions = _client.Configuration.ThrowResponseExceptions; + _client.Configuration.ThrowResponseExceptions = true; + await _client.Sync.GetLastActivitiesAsync(cancellationToken).ConfigureAwait(false); + return false; + } + catch (TraktAuthorizationException) + { + return true; + } + finally + { + _client.Configuration.ThrowResponseExceptions = throwResponseExceptions; + _client.Authorization = currentAuthorization as TraktAuthorization; + } + } + + public async Task> GetDeviceAsync(DeviceRequest request, CancellationToken cancellationToken = default) + { + try + { + request.Validate(); + ExtendedHttpRequestMessage requestMessage = await _requestMessageBuilder.Reset(request).WithRequestBody(request.RequestBody).Build().ConfigureAwait(false); + HttpResponseMessage responseMessage = await _client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + + if (!responseMessage.IsSuccessStatusCode) + await ResponseErrorHandler.HandleErrorsAsync(requestMessage, responseMessage, isDeviceRequest: true, cancellationToken: cancellationToken).ConfigureAwait(false); + + DebugAsserter.AssertResponseMessageIsNotNull(responseMessage); + DebugAsserter.AssertHttpResponseCodeIsExpected(responseMessage.StatusCode, HttpStatusCode.NoContent, DebugAsserter.SINGLE_ITEM_RESPONSE_PRECONDITION_INVALID_STATUS_CODE); + + Stream responseContentStream = await ResponseMessageHelper.GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + IObjectJsonReader objectJsonReader = JsonFactoryContainer.CreateObjectReader(); + + ITraktDevice device = await objectJsonReader.ReadObjectAsync(responseContentStream, cancellationToken).ConfigureAwait(false); + TraktResponse response = device as TraktDevice; + + if (responseMessage.Headers != null) + ResponseHeaderParser.ParseResponseHeaderValues(response, responseMessage.Headers); + + _client.Authentication.Device = device as TraktDevice; + return response; + } + catch (Exception ex) + { + if (_client.Configuration.ThrowResponseExceptions) + throw; + + return new TraktResponse { IsSuccess = false, Exception = ex }; + } + } + + public Task> GetAuthorizationAsync(AuthorizationRequest request, CancellationToken cancellationToken = default) + => ExecuteAuthorizationRequestAsync(request, false, cancellationToken); + + public async Task> PollForAuthorizationAsync(AuthorizationPollRequest request, CancellationToken cancellationToken = default) + { + try + { + request.Validate(); + ExtendedHttpRequestMessage requestMessage = await _requestMessageBuilder.Reset(request).WithRequestBody(request.RequestBody).Build().ConfigureAwait(false); + HttpResponseMessage responseMessage; + Stream responseContentStream; + HttpStatusCode responseCode; + string reasonPhrase = string.Empty; + uint totalExpiredSeconds = 0; + ITraktDevice device = request.RequestBody.Device; + IObjectJsonReader objectJsonReader = JsonFactoryContainer.CreateObjectReader(); + + while (totalExpiredSeconds < device.ExpiresInSeconds) + { + responseMessage = await _client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + + responseCode = responseMessage.StatusCode; + reasonPhrase = responseMessage.ReasonPhrase; + + if (responseCode == HttpStatusCode.OK) // Success + { + responseContentStream = await ResponseMessageHelper.GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + ITraktAuthorization traktAuthorization = await objectJsonReader.ReadObjectAsync(responseContentStream, cancellationToken).ConfigureAwait(false); + TraktResponse response = traktAuthorization as TraktAuthorization; + + if (responseMessage.Headers != null) + ResponseHeaderParser.ParseResponseHeaderValues(response, responseMessage.Headers); + + _client.Authentication.Authorization = traktAuthorization as TraktAuthorization; + return response; + } + else if (responseCode == HttpStatusCode.BadRequest) // Pending + { + await Task.Delay((int)device.IntervalInMilliseconds).ConfigureAwait(false); + totalExpiredSeconds += device.IntervalInSeconds; + continue; + } + + await ResponseErrorHandler.HandleErrorsAsync(requestMessage, responseMessage, isInAuthorizationPolling: true, cancellationToken: cancellationToken).ConfigureAwait(false); + break; + } + + throw new TraktAuthenticationDeviceException("unknown exception") + { + RequestUrl = requestMessage.Url, + RequestBody = requestMessage.RequestBodyJson, + ServerReasonPhrase = reasonPhrase + }; + } + catch (Exception ex) + { + if (_client.Configuration.ThrowResponseExceptions) + throw; + + return new TraktResponse { IsSuccess = false, Exception = ex }; + } + } + + public async Task> RefreshAuthorizationAsync(AuthorizationRefreshRequest request, CancellationToken cancellationToken = default) + { + try + { + if (!_client.Authentication.IsAuthorized) + throw new TraktAuthorizationException("not authorized"); + } + catch (Exception ex) + { + if (_client.Configuration.ThrowResponseExceptions) + throw; + + return new TraktResponse { IsSuccess = false, Exception = ex }; + } + + return await ExecuteAuthorizationRequestAsync(request, true, cancellationToken).ConfigureAwait(false); + } + + public async Task RevokeAuthorizationAsync(AuthorizationRevokeRequest request, string clientId, CancellationToken cancellationToken = default) + { + try + { + if (!_client.Authentication.IsAuthorized) + throw new TraktAuthorizationException("not authorized"); + + if (string.IsNullOrEmpty(clientId) || clientId.ContainsSpace()) + throw new ArgumentException("client id not valid", nameof(clientId)); + + request.Validate(); + ExtendedHttpRequestMessage requestMessage = await _requestMessageBuilder.Reset(request).WithRequestBody(request.RequestBody).Build().ConfigureAwait(false); + //HttpClient httpClient = _httpClientProvider.GetHttpClient(clientId, request.RequestBody.AccessToken); + HttpResponseMessage responseMessage = await _client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + + if (!responseMessage.IsSuccessStatusCode) + { + if (responseMessage.StatusCode == HttpStatusCode.Unauthorized) + { + string responseContent = responseMessage.Content != null ? await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false) : string.Empty; + + throw new TraktAuthenticationException("error on revoking access token") + { + RequestUrl = requestMessage.Url, + RequestBody = requestMessage.RequestBodyJson, + Response = responseContent, + ServerReasonPhrase = responseMessage.ReasonPhrase + }; + } + + await ResponseErrorHandler.HandleErrorsAsync(requestMessage, responseMessage, isAuthorizationRevoke: true, cancellationToken: cancellationToken).ConfigureAwait(false); + } + else + { + _client.Authorization = TraktAuthorization.CreateWith(string.Empty, string.Empty); + } + + throw new TraktAuthenticationException("unknown exception") + { + RequestUrl = requestMessage.Url, + RequestBody = requestMessage.RequestBodyJson, + StatusCode = responseMessage.StatusCode, + ServerReasonPhrase = responseMessage.ReasonPhrase + }; + } + catch (Exception ex) + { + if (_client.Configuration.ThrowResponseExceptions) + throw; + + return new TraktNoContentResponse { IsSuccess = false, Exception = ex }; + } + } + + private async Task> ExecuteAuthorizationRequestAsync(IPostRequest request, bool isRefreshRequest, CancellationToken cancellationToken = default) where TRequestBodyType : IRequestBody + { + try + { + request.Validate(); + ExtendedHttpRequestMessage requestMessage = await _requestMessageBuilder.Reset(request).WithRequestBody(request.RequestBody).Build().ConfigureAwait(false); + HttpResponseMessage responseMessage = await _client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); + + HttpStatusCode responseCode = responseMessage.StatusCode; + Stream responseContentStream; + + if (responseCode == HttpStatusCode.OK) + { + responseContentStream = await ResponseMessageHelper.GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + IObjectJsonReader objectJsonReader = JsonFactoryContainer.CreateObjectReader(); + ITraktAuthorization traktAuthorization = await objectJsonReader.ReadObjectAsync(responseContentStream, cancellationToken).ConfigureAwait(false); + TraktResponse response = traktAuthorization as TraktAuthorization; + + if (responseMessage.Headers != null) + ResponseHeaderParser.ParseResponseHeaderValues(response, responseMessage.Headers); + + _client.Authentication.Authorization = traktAuthorization as TraktAuthorization; + return response; + } + else if (responseCode == HttpStatusCode.Unauthorized) // Invalid code + { + responseContentStream = await ResponseMessageHelper.GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + IObjectJsonReader objectJsonReader = JsonFactoryContainer.CreateObjectReader(); + ITraktError traktError = await objectJsonReader.ReadObjectAsync(responseContentStream, cancellationToken).ConfigureAwait(false); + + var errorMessage = traktError != null ? ($"error on {(isRefreshRequest ? "refreshing" : "retrieving")} oauth access token\nerror: {traktError.Error}\ndescription: {traktError.Description}") + : "unknown error"; + + throw new TraktAuthenticationOAuthException(errorMessage) + { + StatusCode = responseCode, + RequestUrl = requestMessage.Url, + RequestBody = requestMessage.RequestBodyJson, + ServerReasonPhrase = responseMessage.ReasonPhrase + }; + } + + await ResponseErrorHandler.HandleErrorsAsync(requestMessage, responseMessage, isAuthorizationRequest: true, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + if (_client.Configuration.ThrowResponseExceptions) + throw; + + return new TraktResponse { IsSuccess = false, Exception = ex }; + } + + return new TraktResponse(); + } + + private string CreateEncodedAuthorizationUri(string clientId, string redirectUri, string state = null) + { + var uriParams = new Dictionary + { + ["response_type"] = "code", + ["client_id"] = clientId, + ["redirect_uri"] = redirectUri + }; + + if (!string.IsNullOrEmpty(state)) + uriParams["state"] = state; + + var encodedUriContent = new FormUrlEncodedContent(uriParams); + string encodedUri = encodedUriContent.ReadAsStringAsync().Result; + + if (string.IsNullOrEmpty(encodedUri)) + throw new ArgumentException("authorization uri not valid"); + + return $"?{encodedUri}"; + } + + private string BuildAuthorizationUrl(string clientId, string redirectUri, string state = null) + { + string encodedUriParams = CreateEncodedAuthorizationUri(clientId, redirectUri, state); + bool isStagingUsed = _client.Configuration.UseSandboxEnvironment; + string baseUrl = isStagingUsed ? Constants.OAuthBaseAuthorizeStagingUrl : Constants.OAuthBaseAuthorizeUrl; + return $"{baseUrl}/{Constants.OAuthAuthorizeUri}{encodedUriParams}"; + } + + private void ValidateAuthorizationUrlArguments(string clientId, string redirectUri) + { + if (string.IsNullOrEmpty(clientId) || clientId.ContainsSpace()) + throw new ArgumentException("client id not valid", nameof(clientId)); + + if (string.IsNullOrEmpty(redirectUri) || redirectUri.ContainsSpace()) + throw new ArgumentException("redirect uri not valid", nameof(redirectUri)); + } + + private void ValidateAuthorizationUrlArguments(string clientId, string redirectUri, string state) + { + ValidateAuthorizationUrlArguments(clientId, redirectUri); + + if (string.IsNullOrEmpty(state) || state.ContainsSpace()) + throw new ArgumentException("state not valid", nameof(state)); + } + } +} diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/IAuthenticationRequestHandler.cs b/Source/Lib/TraktApiSharp/Requests/Handler/IAuthenticationRequestHandler.cs new file mode 100644 index 000000000..050d77fae --- /dev/null +++ b/Source/Lib/TraktApiSharp/Requests/Handler/IAuthenticationRequestHandler.cs @@ -0,0 +1,32 @@ +namespace TraktApiSharp.Requests.Handler +{ + using Authentication; + using Objects.Authentication; + using Responses; + using System.Threading; + using System.Threading.Tasks; + using Utils; + + internal interface IAuthenticationRequestHandler + { + string CreateAuthorizationUrl(string clientId, string redirectUri, string state); + + string CreateAuthorizationUrlWithDefaultState(string clientId, string redirectUri); + + Task>> CheckIfAuthorizationIsExpiredOrWasRevokedAsync(bool autoRefresh = false, CancellationToken cancellationToken = default); + + Task>> CheckIfAuthorizationIsExpiredOrWasRevokedAsync(ITraktAuthorization authorization, bool autoRefresh = false, CancellationToken cancellationToken = default); + + Task CheckIfAccessTokenWasRevokedOrIsNotValidAsync(string accessToken, CancellationToken cancellationToken = default); + + Task> GetDeviceAsync(DeviceRequest request, CancellationToken cancellationToken = default); + + Task> GetAuthorizationAsync(AuthorizationRequest request, CancellationToken cancellationToken = default); + + Task> PollForAuthorizationAsync(AuthorizationPollRequest request, CancellationToken cancellationToken = default); + + Task> RefreshAuthorizationAsync(AuthorizationRefreshRequest request, CancellationToken cancellationToken = default); + + Task RevokeAuthorizationAsync(AuthorizationRevokeRequest request, string clientId, CancellationToken cancellationToken = default); + } +} diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs b/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs index 2dc810126..27851e3f5 100644 --- a/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs +++ b/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs @@ -1,14 +1,10 @@ namespace TraktApiSharp.Requests.Handler { - using Base; using Checkins.OAuth; using Core; - using Exceptions; using Interfaces; using Interfaces.Base; - using Objects.Basic; using Objects.Json; - using Objects.Post.Checkins.Responses; using Responses; using System; using System.Collections.Generic; @@ -26,6 +22,7 @@ internal sealed class RequestHandler : IRequestHandler private readonly TraktClient _client; private readonly RequestMessageBuilder _requestMessageBuilder; + private static IRequestHandler s_requestHandler; internal RequestHandler(TraktClient client) { @@ -33,6 +30,9 @@ internal RequestHandler(TraktClient client) _requestMessageBuilder = new RequestMessageBuilder(_client); } + internal static IRequestHandler GetInstance(TraktClient client) + => s_requestHandler ?? (s_requestHandler = new RequestHandler(client)); + public async Task ExecuteNoContentRequestAsync(IRequest request, CancellationToken cancellationToken = default) { ValidateRequest(request); @@ -157,7 +157,7 @@ private async Task> QuerySingleItemAsync objectJsonReader = JsonFactoryContainer.CreateObjectReader(); DebugAsserter.AssertObjectJsonReaderIsNotNull(objectJsonReader); @@ -198,7 +198,7 @@ private async Task> QueryListAsync arrayJsonReader = JsonFactoryContainer.CreateArrayReader(); DebugAsserter.AssertArrayJsonReaderIsNotNull(arrayJsonReader); @@ -238,7 +238,7 @@ private async Task> QueryPagedListAsync responseMessage = await ExecuteRequestAsync(requestMessage, false, cancellationToken).ConfigureAwait(false); DebugAsserter.AssertResponseMessageIsNotNull(responseMessage); DebugAsserter.AssertHttpResponseCodeIsNotExpected(responseMessage.StatusCode, HttpStatusCode.NoContent, DebugAsserter.PAGED_LIST_RESPONSE_PRECONDITION_INVALID_STATUS_CODE); - Stream responseContentStream = await GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); + Stream responseContentStream = await ResponseMessageHelper.GetResponseContentStreamAsync(responseMessage).ConfigureAwait(false); DebugAsserter.AssertResponseContentStreamIsNotNull(responseContentStream); IArrayJsonReader arrayJsonReader = JsonFactoryContainer.CreateArrayReader(); DebugAsserter.AssertArrayJsonReaderIsNotNull(arrayJsonReader); @@ -274,14 +274,11 @@ private async Task ExecuteRequestAsync(ExtendedHttpRequestM HttpResponseMessage responseMessage = await _client.HttpClientProvider.GetHttpClient().SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); if (!responseMessage.IsSuccessStatusCode) - await ErrorHandlingAsync(responseMessage, requestMessage, isCheckinRequest, cancellationToken).ConfigureAwait(false); + await ResponseErrorHandler.HandleErrorsAsync(requestMessage, responseMessage, isCheckinRequest, cancellationToken: cancellationToken).ConfigureAwait(false); return responseMessage; } - private Task GetResponseContentStreamAsync(HttpResponseMessage response) - => response.Content != null ? response.Content.ReadAsStreamAsync() : Task.FromResult(default(Stream)); - private void ValidateRequest(IRequest request) { if (request == null) @@ -289,263 +286,5 @@ private void ValidateRequest(IRequest request) request.Validate(); } - - private async Task ErrorHandlingAsync(HttpResponseMessage response, ExtendedHttpRequestMessage requestMessage, bool isCheckinRequest = false, CancellationToken cancellationToken = default) - { - var responseContent = string.Empty; - - if (response.Content != null) - responseContent = await response.Content.ReadAsStringAsync(); - - HttpStatusCode code = response.StatusCode; - string url = requestMessage.Url; - string requestBodyJson = requestMessage.RequestBodyJson; - string reasonPhrase = response.ReasonPhrase; - - switch (code) - { - case HttpStatusCode.NotFound: - HandleNotFoundStatusCode(requestMessage, responseContent, url, requestBodyJson, reasonPhrase); - break; - case HttpStatusCode.BadRequest: - throw new TraktBadRequestException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case HttpStatusCode.Unauthorized: - throw new TraktAuthorizationException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case HttpStatusCode.Forbidden: - throw new TraktForbiddenException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case HttpStatusCode.MethodNotAllowed: - throw new TraktMethodNotFoundException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case HttpStatusCode.Conflict: - await HandleConflictStatusCode(isCheckinRequest, responseContent, url, requestBodyJson, reasonPhrase, cancellationToken); - break; - case HttpStatusCode.InternalServerError: - throw new TraktServerException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case HttpStatusCode.BadGateway: - throw new TraktBadGatewayException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case (HttpStatusCode)412: - throw new TraktPreconditionFailedException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case (HttpStatusCode)422: - throw new TraktValidationException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case (HttpStatusCode)429: - throw new TraktRateLimitException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case (HttpStatusCode)503: - case (HttpStatusCode)504: - throw new TraktServerUnavailableException("Service Unavailable - server overloaded (try again in 30s)") - { - RequestUrl = url, - RequestBody = requestBodyJson, - StatusCode = HttpStatusCode.ServiceUnavailable, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case (HttpStatusCode)520: - case (HttpStatusCode)521: - case (HttpStatusCode)522: - throw new TraktServerUnavailableException("Service Unavailable - Cloudflare error") - { - RequestUrl = url, - RequestBody = requestBodyJson, - StatusCode = HttpStatusCode.ServiceUnavailable, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - } - - await HandleUnknownError(responseContent, code, url, requestBodyJson, reasonPhrase, cancellationToken); - } - - private static void HandleNotFoundStatusCode(ExtendedHttpRequestMessage requestMessage, string responseContent, string url, string requestBodyJson, string reasonPhrase) - { - RequestObjectType? requestObjectType = requestMessage.RequestObjectType; - - if (requestObjectType.HasValue) - { - string objectId = requestMessage.ObjectId; - uint seasonNr = requestMessage.SeasonNumber ?? 0; - uint episodeNr = requestMessage.EpisodeNumber ?? 0; - - switch (requestObjectType.Value) - { - case RequestObjectType.Episodes: - throw new TraktEpisodeNotFoundException(objectId, seasonNr, episodeNr) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case RequestObjectType.Seasons: - throw new TraktSeasonNotFoundException(objectId, seasonNr) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case RequestObjectType.Shows: - throw new TraktShowNotFoundException(objectId) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case RequestObjectType.Movies: - throw new TraktMovieNotFoundException(objectId) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case RequestObjectType.People: - throw new TraktPersonNotFoundException(objectId) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case RequestObjectType.Comments: - throw new TraktCommentNotFoundException(objectId) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - case RequestObjectType.Lists: - throw new TraktListNotFoundException(objectId) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - default: - throw new TraktObjectNotFoundException(objectId) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - } - } - - throw new TraktNotFoundException($"Resource not found - Reason Phrase: {reasonPhrase}"); - } - - private static async Task HandleConflictStatusCode(bool isCheckinRequest, string responseContent, string url, string requestBodyJson, string reasonPhrase, CancellationToken cancellationToken = default) - { - if (isCheckinRequest) - { - ITraktCheckinPostErrorResponse errorResponse = null; - - if (!string.IsNullOrEmpty(responseContent)) - { - IObjectJsonReader errorResponseReader = JsonFactoryContainer.CreateObjectReader(); - errorResponse = await errorResponseReader.ReadObjectAsync(responseContent, cancellationToken); - } - - throw new TraktCheckinException("checkin is already in progress") - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase, - ExpiresAt = errorResponse?.ExpiresAt - }; - } - - throw new TraktConflictException() - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - } - - private static async Task HandleUnknownError(string responseContent, HttpStatusCode code, string url, string requestBodyJson, string reasonPhrase, CancellationToken cancellationToken = default) - { - ITraktError error = null; - - try - { - IObjectJsonReader errorReader = JsonFactoryContainer.CreateObjectReader(); - error = await errorReader.ReadObjectAsync(responseContent, cancellationToken); - } - catch (Exception ex) - { - throw new TraktException("json convert exception", ex); - } - - var errorMessage = (error == null || string.IsNullOrEmpty(error.Description)) - ? $"Trakt API error without content. Response status code was {(int)code}" - : error.Description; - - throw new TraktException(errorMessage) - { - RequestUrl = url, - RequestBody = requestBodyJson, - Response = responseContent, - ServerReasonPhrase = reasonPhrase - }; - } } } diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorHandler.cs b/Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorHandler.cs new file mode 100644 index 000000000..56f072574 --- /dev/null +++ b/Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorHandler.cs @@ -0,0 +1,538 @@ +namespace TraktApiSharp.Requests.Handler +{ + using Base; + using Exceptions; + using Objects.Basic; + using Objects.Json; + using Objects.Post.Checkins.Responses; + using System; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + internal static class ResponseErrorHandler + { + internal static async Task HandleErrorsAsync(ExtendedHttpRequestMessage requestMessage, HttpResponseMessage responseMessage, + bool isCheckinRequest = false, bool isDeviceRequest = false, bool isInAuthorizationPolling = false, + bool isAuthorizationRequest = false, bool isAuthorizationRevoke = false, + CancellationToken cancellationToken = default) + { + if (requestMessage == null) + throw new ArgumentNullException(nameof(requestMessage)); + + if (responseMessage == null) + throw new ArgumentNullException(nameof(responseMessage)); + + string responseContent = string.Empty; + + if (responseMessage.Content != null) + responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); + + var errorParameters = new ResponseErrorParameters + { + IsCheckinRequest = isCheckinRequest, + RequestBody = requestMessage.RequestBodyJson, + ResponseBody = responseContent ?? string.Empty, + ServerReasonPhrase = responseMessage.ReasonPhrase, + StatusCode = responseMessage.StatusCode, + Url = requestMessage.Url, + RequestObjectType = requestMessage.RequestObjectType ?? RequestObjectType.Unspecified, + ObjectId = requestMessage.ObjectId, + SeasonNumber = requestMessage.SeasonNumber ?? 0, + EpisodeNumber = requestMessage.EpisodeNumber ?? 0, + IsDeviceRequest = isDeviceRequest, + IsInAuthorizationPolling = isInAuthorizationPolling, + IsAuthorizationRequest = isAuthorizationRequest, + IsAuthorizationRevoke = isAuthorizationRevoke + }; + + await HandleErrorsAsync(errorParameters, cancellationToken).ConfigureAwait(false); + } + + private static async Task HandleErrorsAsync(ResponseErrorParameters errorParameters, CancellationToken cancellationToken = default) + { + switch (errorParameters.StatusCode) + { + case HttpStatusCode.NotFound: + HandleNotFoundError(errorParameters); + break; + case HttpStatusCode.Conflict: + await HandleConflictErrorAsync(errorParameters, cancellationToken).ConfigureAwait(false); + break; + case HttpStatusCode.BadRequest: + HandleBadRequestError(errorParameters); + break; + case HttpStatusCode.Unauthorized: + HandleUnauthorizedError(errorParameters); + break; + case HttpStatusCode.Forbidden: + HandleForbiddenError(errorParameters); + break; + case HttpStatusCode.MethodNotAllowed: + HandleMethodNotAllowedError(errorParameters); + break; + case HttpStatusCode.Gone: + HandleGoneError(errorParameters); + break; + case HttpStatusCode.InternalServerError: + HandleInternalServerError(errorParameters); + break; + case HttpStatusCode.BadGateway: + HandleBadGatewayError(errorParameters); + break; + case (HttpStatusCode)412: + HandlePreconditionError(errorParameters); + break; + case (HttpStatusCode)418: + HandleDeniedError(errorParameters); + break; + case (HttpStatusCode)422: + HandleValidationError(errorParameters); + break; + case (HttpStatusCode)429: + HandleRateLimitError(errorParameters); + break; + case (HttpStatusCode)503: + case (HttpStatusCode)504: + HandleServerOverloadedError(errorParameters); + break; + case (HttpStatusCode)520: + case (HttpStatusCode)521: + case (HttpStatusCode)522: + HandleCloudflareError(errorParameters); + break; + } + + await HandleUnknownErrorAsync(errorParameters, cancellationToken).ConfigureAwait(false); + } + + private static void HandleNotFoundError(ResponseErrorParameters errorParameters) + { + string requestUrl = errorParameters.Url; + string requestBody = errorParameters.RequestBody; + string responseBody = errorParameters.ResponseBody; + string reasonPhrase = errorParameters.ServerReasonPhrase; + RequestObjectType requestObjectType = errorParameters.RequestObjectType; + bool isDeviceRequest = errorParameters.IsDeviceRequest; + bool isInAuthorizationPolling = errorParameters.IsInAuthorizationPolling; + bool isAuthorizationRequest = errorParameters.IsAuthorizationRequest; + bool isAuthorizationRevoke = errorParameters.IsAuthorizationRevoke; + + if (isDeviceRequest || isInAuthorizationPolling) + { + throw new TraktAuthenticationDeviceException("Not Found - invalid device code") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + else if (isAuthorizationRequest) + { + throw new TraktAuthenticationOAuthException("Resource not found") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + else if (isAuthorizationRevoke) + { + throw new TraktAuthenticationException("Resource not found") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + else if (requestObjectType != RequestObjectType.Unspecified && !isDeviceRequest && !isInAuthorizationPolling && !isAuthorizationRequest && !isAuthorizationRevoke) + { + HandleNotFoundObjectError(errorParameters, requestUrl, requestBody, responseBody, reasonPhrase, requestObjectType); + } + + throw new TraktNotFoundException($"Resource not found - Reason Phrase: {reasonPhrase}") + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + + private static void HandleNotFoundObjectError(ResponseErrorParameters errorParameters, string requestUrl, string requestBody, string responseBody, string reasonPhrase, RequestObjectType requestObjectType) + { + string objectId = errorParameters.ObjectId; + uint seasonNr = errorParameters.SeasonNumber; + uint episodeNr = errorParameters.EpisodeNumber; + + switch (requestObjectType) + { + case RequestObjectType.Episodes: + throw new TraktEpisodeNotFoundException(objectId, seasonNr, episodeNr) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + case RequestObjectType.Seasons: + throw new TraktSeasonNotFoundException(objectId, seasonNr) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + case RequestObjectType.Shows: + throw new TraktShowNotFoundException(objectId) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + case RequestObjectType.Movies: + throw new TraktMovieNotFoundException(objectId) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + case RequestObjectType.People: + throw new TraktPersonNotFoundException(objectId) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + case RequestObjectType.Comments: + throw new TraktCommentNotFoundException(objectId) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + case RequestObjectType.Lists: + throw new TraktListNotFoundException(objectId) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + default: + throw new TraktObjectNotFoundException(objectId) + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + } + + private static async Task HandleConflictErrorAsync(ResponseErrorParameters errorParameters, CancellationToken cancellationToken = default) + { + string requestUrl = errorParameters.Url; + string requestBody = errorParameters.RequestBody; + string responseBody = errorParameters.ResponseBody; + string reasonPhrase = errorParameters.ServerReasonPhrase; + + if (errorParameters.IsCheckinRequest) + { + ITraktCheckinPostErrorResponse errorResponse = null; + + if (!string.IsNullOrEmpty(errorParameters.ResponseBody)) + { + IObjectJsonReader errorResponseReader = JsonFactoryContainer.CreateObjectReader(); + errorResponse = await errorResponseReader.ReadObjectAsync(errorParameters.ResponseBody, cancellationToken).ConfigureAwait(false); + } + + throw new TraktCheckinException("checkin is already in progress") + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase, + ExpiresAt = errorResponse?.ExpiresAt + }; + } + else if (errorParameters.IsInAuthorizationPolling) + { + // Authorization Polling - Already Used + throw new TraktAuthenticationDeviceException("Already Used - user already approved this code") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + + throw new TraktConflictException + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + + private static void HandleBadRequestError(ResponseErrorParameters errorParameters) + { + if (!errorParameters.IsInAuthorizationPolling) + { + throw new TraktBadRequestException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + } + + private static void HandleUnauthorizedError(ResponseErrorParameters errorParameters) + { + if (!errorParameters.IsAuthorizationRequest && !errorParameters.IsAuthorizationRevoke) + { + throw new TraktAuthorizationException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + } + + private static void HandleForbiddenError(ResponseErrorParameters errorParameters) + { + throw new TraktForbiddenException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static void HandleMethodNotAllowedError(ResponseErrorParameters errorParameters) + { + throw new TraktMethodNotFoundException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static void HandleGoneError(ResponseErrorParameters errorParameters) + { + if (errorParameters.IsInAuthorizationPolling) + { + // Authorization Polling - Expired + throw new TraktAuthenticationDeviceException("Expired - the tokens have expired, restart the process") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + } + + private static void HandleInternalServerError(ResponseErrorParameters errorParameters) + { + throw new TraktServerException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static void HandleBadGatewayError(ResponseErrorParameters errorParameters) + { + throw new TraktBadGatewayException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static void HandlePreconditionError(ResponseErrorParameters errorParameters) + { + throw new TraktPreconditionFailedException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static void HandleDeniedError(ResponseErrorParameters errorParameters) + { + if (errorParameters.IsInAuthorizationPolling) + { + // Authorization Polling - Denied + throw new TraktAuthenticationDeviceException("Denied - user explicitly denied this code") + { + StatusCode = (HttpStatusCode)418, + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + } + + private static void HandleValidationError(ResponseErrorParameters errorParameters) + { + throw new TraktValidationException + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static void HandleRateLimitError(ResponseErrorParameters errorParameters) + { + string requestUrl = errorParameters.Url; + string requestBody = errorParameters.RequestBody; + string responseBody = errorParameters.ResponseBody; + string reasonPhrase = errorParameters.ServerReasonPhrase; + + if (errorParameters.IsInAuthorizationPolling) + { + // Authorization Polling - Slow Down + throw new TraktAuthenticationDeviceException("Slow Down - your app is polling too quickly") + { + StatusCode = (HttpStatusCode)429, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + + throw new TraktRateLimitException + { + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + + private static void HandleServerOverloadedError(ResponseErrorParameters errorParameters) + { + throw new TraktServerUnavailableException("Service Unavailable - server overloaded (try again in 30s)") + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + StatusCode = HttpStatusCode.ServiceUnavailable, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static void HandleCloudflareError(ResponseErrorParameters errorParameters) + { + throw new TraktServerUnavailableException("Service Unavailable - Cloudflare error") + { + RequestUrl = errorParameters.Url, + RequestBody = errorParameters.RequestBody, + StatusCode = HttpStatusCode.ServiceUnavailable, + Response = errorParameters.ResponseBody, + ServerReasonPhrase = errorParameters.ServerReasonPhrase + }; + } + + private static async Task HandleUnknownErrorAsync(ResponseErrorParameters errorParameters, CancellationToken cancellationToken = default) + { + string requestUrl = errorParameters.Url; + string requestBody = errorParameters.RequestBody; + string responseBody = errorParameters.ResponseBody; + string reasonPhrase = errorParameters.ServerReasonPhrase; + + if (errorParameters.IsDeviceRequest || errorParameters.IsInAuthorizationPolling) + { + throw new TraktAuthenticationDeviceException("unknown exception") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + else if (errorParameters.IsAuthorizationRequest) + { + throw new TraktAuthenticationOAuthException("unknown exception") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + else if (errorParameters.IsAuthorizationRevoke) + { + throw new TraktAuthenticationException("unknown exception") + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + + ITraktError error = null; + + try + { + IObjectJsonReader errorReader = JsonFactoryContainer.CreateObjectReader(); + error = await errorReader.ReadObjectAsync(responseBody, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + throw new TraktException("json convert exception", ex); + } + + var errorMessage = (error == null || string.IsNullOrEmpty(error.Description)) + ? $"Trakt API error without content. Response status code was {(int)errorParameters.StatusCode}" + : error.Description; + + throw new TraktException(errorMessage) + { + StatusCode = errorParameters.StatusCode, + RequestUrl = requestUrl, + RequestBody = requestBody, + Response = responseBody, + ServerReasonPhrase = reasonPhrase + }; + } + } +} diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorParameters.cs b/Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorParameters.cs new file mode 100644 index 000000000..39b892cb7 --- /dev/null +++ b/Source/Lib/TraktApiSharp/Requests/Handler/ResponseErrorParameters.cs @@ -0,0 +1,36 @@ +namespace TraktApiSharp.Requests.Handler +{ + using Base; + using System.Net; + + internal sealed class ResponseErrorParameters + { + public string Url { get; set; } + + public string RequestBody { get; set; } + + public string ResponseBody { get; set; } + + public string ServerReasonPhrase { get; set; } + + public HttpStatusCode StatusCode { get; set; } + + public RequestObjectType RequestObjectType { get; set; } + + public string ObjectId { get; set; } + + public uint SeasonNumber { get; set; } + + public uint EpisodeNumber { get; set; } + + public bool IsCheckinRequest { get; set; } + + public bool IsDeviceRequest { get; set; } + + public bool IsInAuthorizationPolling { get; set; } + + public bool IsAuthorizationRequest { get; set; } + + public bool IsAuthorizationRevoke { get; set; } + } +} diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/ResponseMessageHelper.cs b/Source/Lib/TraktApiSharp/Requests/Handler/ResponseMessageHelper.cs new file mode 100644 index 000000000..6b3e1bece --- /dev/null +++ b/Source/Lib/TraktApiSharp/Requests/Handler/ResponseMessageHelper.cs @@ -0,0 +1,17 @@ +namespace TraktApiSharp.Requests.Handler +{ + using Core; + using System.IO; + using System.Net.Http; + using System.Threading.Tasks; + + internal static class ResponseMessageHelper + { + internal static async Task GetResponseContentStreamAsync(HttpResponseMessage responseMessage) + { + Stream responseContentStream = responseMessage.Content != null ? await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false) : default; + DebugAsserter.AssertResponseContentStreamIsNotNull(responseContentStream); + return responseContentStream; + } + } +} diff --git a/Source/Lib/TraktApiSharp/TraktApiSharp.csproj b/Source/Lib/TraktApiSharp/TraktApiSharp.csproj index 8a09027cb..d59db16e4 100644 --- a/Source/Lib/TraktApiSharp/TraktApiSharp.csproj +++ b/Source/Lib/TraktApiSharp/TraktApiSharp.csproj @@ -922,7 +922,7 @@ - + @@ -939,11 +939,16 @@ + + + + +