From 530da0aae362a3fc848ff5358fc13ad8f9c9a6e6 Mon Sep 17 00:00:00 2001 From: Andre Date: Fri, 18 Mar 2022 16:00:35 +0000 Subject: [PATCH 1/4] error handling --- src/Castle.Sdk/Actions/Authenticate.cs | 12 ++++++ src/Castle.Sdk/Actions/Filter.cs | 12 ++++++ src/Castle.Sdk/Actions/Risk.cs | 12 ++++++ src/Castle.Sdk/Castle.Sdk.xml | 15 ++++++++ .../Infrastructure/ExceptionGuard.cs | 4 ++ .../CastleInvalidParametersException.cs | 22 +++++++++++ .../Exceptions/CastleInvalidTokenException.cs | 22 +++++++++++ .../Exceptions/CastleNotFoundException.cs | 22 +++++++++++ .../Extensions/HttpResponseExtensions.cs | 37 ++++++++++++++++++- 9 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs create mode 100644 src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs create mode 100644 src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs diff --git a/src/Castle.Sdk/Actions/Authenticate.cs b/src/Castle.Sdk/Actions/Authenticate.cs index e921415..eaa202d 100644 --- a/src/Castle.Sdk/Actions/Authenticate.cs +++ b/src/Castle.Sdk/Actions/Authenticate.cs @@ -23,6 +23,10 @@ public static async Task Execute( { return await send(); } + catch (CastleNotFoundException e) + { + throw e; + } catch (Exception e) { logger.Warn(() => "Failover, " + e); @@ -47,6 +51,14 @@ private static Verdict CreateFailoverResponse(ActionType strategy, string reason private static Verdict CreateFailoverResponse(ActionType strategy, Exception exception) { + if (exception is CastleInvalidTokenException) + { + return CreateFailoverResponse(ActionType.Deny, exception.Message); + } + if (exception is CastleInvalidParametersException) + { + return CreateFailoverResponse(strategy, exception.Message); + } return CreateFailoverResponse(strategy, exception is CastleTimeoutException ? "timeout" : "server error"); } } diff --git a/src/Castle.Sdk/Actions/Filter.cs b/src/Castle.Sdk/Actions/Filter.cs index c6c61ac..b5d916c 100644 --- a/src/Castle.Sdk/Actions/Filter.cs +++ b/src/Castle.Sdk/Actions/Filter.cs @@ -19,6 +19,10 @@ public static async Task Execute( { return await send(); } + catch (CastleNotFoundException e) + { + throw e; + } catch (Exception e) { logger.Warn(() => "Failover, " + e); @@ -43,6 +47,14 @@ private static RiskResponse CreateFailoverResponse(ActionType strategy, string r private static RiskResponse CreateFailoverResponse(ActionType strategy, Exception exception) { + if (exception is CastleInvalidTokenException) + { + return CreateFailoverResponse(ActionType.Deny, exception.Message); + } + if (exception is CastleInvalidParametersException) + { + return CreateFailoverResponse(strategy, exception.Message); + } return CreateFailoverResponse(strategy, exception is CastleTimeoutException ? "timeout" : "server error"); } } diff --git a/src/Castle.Sdk/Actions/Risk.cs b/src/Castle.Sdk/Actions/Risk.cs index 8f59deb..a6d2898 100644 --- a/src/Castle.Sdk/Actions/Risk.cs +++ b/src/Castle.Sdk/Actions/Risk.cs @@ -19,6 +19,10 @@ public static async Task Execute( { return await send(); } + catch (CastleNotFoundException e) + { + throw e; + } catch (Exception e) { logger.Warn(() => "Failover, " + e); @@ -43,6 +47,14 @@ private static RiskResponse CreateFailoverResponse(ActionType strategy, string r private static RiskResponse CreateFailoverResponse(ActionType strategy, Exception exception) { + if (exception is CastleInvalidTokenException) + { + return CreateFailoverResponse(ActionType.Deny, exception.Message); + } + if (exception is CastleInvalidParametersException) + { + return CreateFailoverResponse(strategy, exception.Message); + } return CreateFailoverResponse(strategy, exception is CastleTimeoutException ? "timeout" : "server error"); } } diff --git a/src/Castle.Sdk/Castle.Sdk.xml b/src/Castle.Sdk/Castle.Sdk.xml index d35bcc1..ad20866 100644 --- a/src/Castle.Sdk/Castle.Sdk.xml +++ b/src/Castle.Sdk/Castle.Sdk.xml @@ -126,6 +126,21 @@ Exception for internal SDK errors, which must not escape to the outside + + + Exception for Unprocessable Entity + + + + + Exception for invalid request token + + + + + Exception for route not found + + Provides consistent internal interfaces for requests with no response diff --git a/src/Castle.Sdk/Infrastructure/ExceptionGuard.cs b/src/Castle.Sdk/Infrastructure/ExceptionGuard.cs index ea02c53..1a67937 100644 --- a/src/Castle.Sdk/Infrastructure/ExceptionGuard.cs +++ b/src/Castle.Sdk/Infrastructure/ExceptionGuard.cs @@ -19,6 +19,10 @@ public static async Task Try( { throw; } + catch (CastleNotFoundException e) + { + throw e; + } catch (Exception e) { logger.Error(e.ToString); diff --git a/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs new file mode 100644 index 0000000..47aca56 --- /dev/null +++ b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs @@ -0,0 +1,22 @@ +using System; +using System.Net; + +namespace Castle.Infrastructure.Exceptions +{ + /// + /// Exception for internal SDK errors, which must not escape to the outside + /// + internal class CastleInvalidParametersException : Exception + { + public CastleInvalidParametersException(string message, string requestUri, HttpStatusCode? httpStatusCode = null) + : base(message) + { + HttpStatusCode = httpStatusCode; + RequestUri = requestUri; + } + + public HttpStatusCode? HttpStatusCode { get; set; } + + public string RequestUri { get; set; } + } +} diff --git a/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs new file mode 100644 index 0000000..4eac5c4 --- /dev/null +++ b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs @@ -0,0 +1,22 @@ +using System; +using System.Net; + +namespace Castle.Infrastructure.Exceptions +{ + /// + /// Exception for internal SDK errors, which must not escape to the outside + /// + internal class CastleInvalidTokenException : Exception + { + public CastleInvalidTokenException(string message, string requestUri, HttpStatusCode? httpStatusCode = null) + : base(message) + { + HttpStatusCode = httpStatusCode; + RequestUri = requestUri; + } + + public HttpStatusCode? HttpStatusCode { get; set; } + + public string RequestUri { get; set; } + } +} diff --git a/src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs b/src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs new file mode 100644 index 0000000..b549876 --- /dev/null +++ b/src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs @@ -0,0 +1,22 @@ +using System; +using System.Net; + +namespace Castle.Infrastructure.Exceptions +{ + /// + /// Exception for internal SDK errors, which must not escape to the outside + /// + internal class CastleNotFoundException : Exception + { + public CastleNotFoundException(string message, string requestUri, HttpStatusCode? httpStatusCode = null) + : base(message) + { + HttpStatusCode = httpStatusCode; + RequestUri = requestUri; + } + + public HttpStatusCode? HttpStatusCode { get; set; } + + public string RequestUri { get; set; } + } +} diff --git a/src/Castle.Sdk/Infrastructure/Extensions/HttpResponseExtensions.cs b/src/Castle.Sdk/Infrastructure/Extensions/HttpResponseExtensions.cs index 97c1c74..b38da9e 100644 --- a/src/Castle.Sdk/Infrastructure/Extensions/HttpResponseExtensions.cs +++ b/src/Castle.Sdk/Infrastructure/Extensions/HttpResponseExtensions.cs @@ -1,14 +1,47 @@ -using System.Net.Http; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text.Json; using System.Threading.Tasks; using Castle.Infrastructure.Exceptions; +using Castle.Infrastructure.Json; namespace Castle.Infrastructure.Extensions { internal static class HttpResponseExtensions { - public static async Task ToCastleException(this HttpResponseMessage message, string requestUri) + public static async Task ToCastleException(this HttpResponseMessage message, string requestUri) { + + if (message.StatusCode == HttpStatusCode.NotFound) + { + throw new CastleNotFoundException("Not Found", requestUri, message.StatusCode); + } var content = await message.Content.ReadAsStringAsync(); + try + { + var parsedContent = JsonForCastle.DeserializeObject>(content); + if (parsedContent["type"] != null) + { + if (parsedContent["type"] == "invalid_request_token") + { + return new CastleInvalidTokenException(parsedContent["message"], requestUri, message.StatusCode); + } + return new CastleInvalidParametersException(parsedContent["message"], requestUri, message.StatusCode); + } + } + catch (JsonException) + { + return new CastleInternalException(content, requestUri, message.StatusCode); + } + catch (Exception) + { + return new CastleInvalidParametersException(content, requestUri, message.StatusCode); + } + + + return new CastleInternalException(content, requestUri, message.StatusCode); } } From afeeaf8abcfb7a686b178bb174364c5d5ad181f1 Mon Sep 17 00:00:00 2001 From: Andre Date: Fri, 18 Mar 2022 16:11:00 +0000 Subject: [PATCH 2/4] changed xml, and tests --- .../Exceptions/CastleInvalidParametersException.cs | 2 +- .../Infrastructure/Exceptions/CastleInvalidTokenException.cs | 2 +- .../Infrastructure/Exceptions/CastleNotFoundException.cs | 2 +- src/Tests/Sending/When_converting_http_messages.cs | 3 --- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs index 47aca56..24859da 100644 --- a/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs +++ b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidParametersException.cs @@ -4,7 +4,7 @@ namespace Castle.Infrastructure.Exceptions { /// - /// Exception for internal SDK errors, which must not escape to the outside + /// Exception for Unprocessable Entity /// internal class CastleInvalidParametersException : Exception { diff --git a/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs index 4eac5c4..17fdaac 100644 --- a/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs +++ b/src/Castle.Sdk/Infrastructure/Exceptions/CastleInvalidTokenException.cs @@ -4,7 +4,7 @@ namespace Castle.Infrastructure.Exceptions { /// - /// Exception for internal SDK errors, which must not escape to the outside + /// Exception for Invalid request token /// internal class CastleInvalidTokenException : Exception { diff --git a/src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs b/src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs index b549876..391b0f6 100644 --- a/src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs +++ b/src/Castle.Sdk/Infrastructure/Exceptions/CastleNotFoundException.cs @@ -4,7 +4,7 @@ namespace Castle.Infrastructure.Exceptions { /// - /// Exception for internal SDK errors, which must not escape to the outside + /// Exception for route not found /// internal class CastleNotFoundException : Exception { diff --git a/src/Tests/Sending/When_converting_http_messages.cs b/src/Tests/Sending/When_converting_http_messages.cs index c458945..906e26a 100644 --- a/src/Tests/Sending/When_converting_http_messages.cs +++ b/src/Tests/Sending/When_converting_http_messages.cs @@ -24,9 +24,6 @@ public void Should_create_stringcontent_from_payload(object payload) public async Task Should_create_exception_from_httpresponse(HttpResponseMessage response, string uri) { var result = await response.ToCastleException(uri); - - result.HttpStatusCode.Should().Be(response.StatusCode); - result.RequestUri.Should().Be(uri); result.Message.Should().Be(await response.Content.ReadAsStringAsync()); } } From 52b13ba052882ed3969c8876b0efb3ec94c143be Mon Sep 17 00:00:00 2001 From: Andre Date: Fri, 18 Mar 2022 16:14:58 +0000 Subject: [PATCH 3/4] fixed tests --- src/Castle.Sdk/Castle.Sdk.xml | 2 +- src/Tests/Sending/When_sending_requests.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Castle.Sdk/Castle.Sdk.xml b/src/Castle.Sdk/Castle.Sdk.xml index ad20866..38f23ef 100644 --- a/src/Castle.Sdk/Castle.Sdk.xml +++ b/src/Castle.Sdk/Castle.Sdk.xml @@ -133,7 +133,7 @@ - Exception for invalid request token + Exception for Invalid request token diff --git a/src/Tests/Sending/When_sending_requests.cs b/src/Tests/Sending/When_sending_requests.cs index fa5d4ff..f3574f9 100644 --- a/src/Tests/Sending/When_sending_requests.cs +++ b/src/Tests/Sending/When_sending_requests.cs @@ -47,7 +47,7 @@ public async Task Should_log_response_message( .Received() .Info(Arg.Is>(func => func() .StartsWith("Response"))); - } + } } [Theory, AutoFakeData(typeof(HttpMessageHandlerSuccessCustomization))] @@ -62,7 +62,7 @@ public async Task Should_return_response_if_success_code( { var result = await testMethod(sut); result.Should().BeOfType(); - } + } } [Theory, AutoFakeData(typeof(HttpMessageHandlerFailureCustomization))] @@ -76,8 +76,8 @@ public void Should_throw_castle_exception_if_not_success_code( foreach (var testMethod in TestMethods) { Func act = async () => await testMethod(sut); - act.Should().Throw(); - } + act.Should().Throw(); + } } [Theory, AutoFakeData(typeof(HttpMessageHandlerCancelledCustomization))] @@ -92,7 +92,7 @@ public void Should_throw_timeout_exception_if_operation_cancelled( { Func act = async () => await testMethod(sut); act.Should().Throw(); - } + } } private static readonly Func>[] TestMethods = From 9da311196846da6123a34e86eac823502cca53db Mon Sep 17 00:00:00 2001 From: Andre Date: Fri, 18 Mar 2022 16:29:50 +0000 Subject: [PATCH 4/4] more tests --- src/Tests/Actions/When_sending_filter.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Tests/Actions/When_sending_filter.cs b/src/Tests/Actions/When_sending_filter.cs index bcee07e..c11bfd7 100644 --- a/src/Tests/Actions/When_sending_filter.cs +++ b/src/Tests/Actions/When_sending_filter.cs @@ -75,5 +75,19 @@ public async Task Should_throw_exception_if_failing_over_with_no_strategy( await act.Should().ThrowAsync(); } + [Theory, AutoFakeData] + public async Task Should_throw_exception_if_route_not_found( + CastleConfiguration configuration) + { + configuration.FailOverStrategy = ActionType.None; + var logger = Substitute.For(); + + Task Send() => throw new CastleNotFoundException("Not Found", "someurl", System.Net.HttpStatusCode.NotFound); + + Func act = async () => await Filter.Execute(Send, configuration, logger); + + await act.Should().ThrowAsync(); + } + } }