From 681b4daf68acdf971e138b5904aa8de9d688956b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B2=88=E6=98=9F=E7=B9=81?= Date: Wed, 18 Oct 2023 16:09:21 +0800 Subject: [PATCH] feat: support custom error for cqrs service agent --- .../ApiControllerBase.cs | 5 ++ .../CommandEndpointHandler.cs | 7 +- .../CqrsServiceAgent.cs | 82 ++++++++++--------- .../CommandResponseHandlerTests.cs | 10 ++- 4 files changed, 63 insertions(+), 41 deletions(-) diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs index a60a200..aa78382 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/ApiControllerBase.cs @@ -100,6 +100,11 @@ protected virtual IActionResult CustomErrorCommandResponseMap(CommandRes private IActionResult MapErrorCommandResponseToCqrsResponse(CommandResponse response) where TError : Enumeration { + if (response is { IsConcurrentError: true, LockAcquired: false }) + { + return StatusCode(429); + } + return BadRequest(response); } diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs index 359ba80..c5300f3 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.AspNetCore/CommandEndpointHandler.cs @@ -89,7 +89,12 @@ private IResult HandleErrorCommandResponse(CommandResponse response, HttpContext private static IResult HandleErrorCommandResponseWithCqrs(CommandResponse response) { - return Results.BadRequest(response); + if (response is { IsConcurrentError: true, LockAcquired: false }) + { + return Results.StatusCode(429); + } + + return Results.BadRequest((object)response); } private static IResult HandleErrorCommandResponseWithPlainText(CommandResponse response) diff --git a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs index abad998..ecb949e 100644 --- a/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs +++ b/src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs @@ -1,14 +1,32 @@ using System.Net; using System.Net.Http.Json; using Cnblogs.Architecture.Ddd.Cqrs.Abstractions; +using Cnblogs.Architecture.Ddd.Domain.Abstractions; using Cnblogs.Architecture.Ddd.Infrastructure.Abstractions; namespace Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent; +/// +/// Base Class for CQRS Service Agent. +/// +public abstract class CqrsServiceAgent : CqrsServiceAgent +{ + /// + /// Create a Cqrs service agent. + /// + /// The underlying http client. + protected CqrsServiceAgent(HttpClient httpClient) + : base(httpClient) + { + } +} + /// /// Service Agent for CQRS /// -public abstract class CqrsServiceAgent +/// The type of error for this service. +public abstract class CqrsServiceAgent + where TError : Enumeration { /// /// The underlying . @@ -30,7 +48,7 @@ protected CqrsServiceAgent(HttpClient httpClient) /// The url. /// Response type. /// The response. - public async Task> DeleteCommandAsync(string url) + public async Task> DeleteCommandAsync(string url) { var response = await HttpClient.DeleteAsync(url); return await HandleCommandResponseAsync(response); @@ -40,7 +58,7 @@ public async Task> DeleteCommandAs /// Execute a command with DELETE method. /// /// The route of the API. - public async Task> DeleteCommandAsync(string url) + public async Task> DeleteCommandAsync(string url) { var response = await HttpClient.DeleteAsync(url); return await HandleCommandResponseAsync(response); @@ -50,7 +68,7 @@ public async Task> DeleteCommandAsync(string /// Execute a command with POST method. /// /// The route of the API. - public async Task> PostCommandAsync(string url) + public async Task> PostCommandAsync(string url) { var response = await HttpClient.PostAsync(url, new StringContent(string.Empty)); return await HandleCommandResponseAsync(response); @@ -62,7 +80,7 @@ public async Task> PostCommandAsync(string ur /// The route of the API. /// The request body. /// The type of request body. - public async Task> PostCommandAsync(string url, TPayload payload) + public async Task> PostCommandAsync(string url, TPayload payload) { var response = await HttpClient.PostAsJsonAsync(url, payload); return await HandleCommandResponseAsync(response); @@ -76,7 +94,7 @@ public async Task> PostCommandAsync /// The type of response body. /// The type of request body. /// The response body. - public async Task> PostCommandAsync( + public async Task> PostCommandAsync( string url, TPayload payload) { @@ -88,7 +106,7 @@ public async Task> PostCommandAsyn /// Execute a command with PUT method and payload. /// /// The route of API. - public async Task> PutCommandAsync(string url) + public async Task> PutCommandAsync(string url) { var response = await HttpClient.PutAsync(url, new StringContent(string.Empty)); return await HandleCommandResponseAsync(response); @@ -101,7 +119,7 @@ public async Task> PutCommandAsync(string url /// The request body. /// The type of request body. /// The command response. - public async Task> PutCommandAsync(string url, TPayload payload) + public async Task> PutCommandAsync(string url, TPayload payload) { var response = await HttpClient.PutAsJsonAsync(url, payload); return await HandleCommandResponseAsync(response); @@ -115,7 +133,7 @@ public async Task> PutCommandAsync( /// The type of response body. /// The type of request body. /// The response body. - public async Task> PutCommandAsync( + public async Task> PutCommandAsync( string url, TPayload payload) { @@ -237,54 +255,44 @@ public async Task ListItemsAsync(string url) return await HttpClient.GetFromJsonAsync(url) ?? new TList(); } - private static async Task> HandleCommandResponseAsync( + private static async Task> HandleCommandResponseAsync( HttpResponseMessage httpResponseMessage) { - if (httpResponseMessage.IsSuccessStatusCode) + if (httpResponseMessage.StatusCode == HttpStatusCode.NoContent) + { + return CommandResponse.Success(); + } + + if (httpResponseMessage.StatusCode == HttpStatusCode.OK) { var result = await httpResponseMessage.Content.ReadFromJsonAsync(); - return CommandResponse.Success(result); + return CommandResponse.Success(result); } - var response = await httpResponseMessage.Content.ReadFromJsonAsync(); + var response = await httpResponseMessage.Content.ReadFromJsonAsync>(); if (response is null) { - return CommandResponse.Fail(ServiceAgentError.UnknownError); + throw new InvalidOperationException( + $"Could not deserialize error from response, response: {await httpResponseMessage.Content.ReadAsStringAsync()}"); } - return new CommandResponse - { - IsConcurrentError = response.IsConcurrentError, - IsValidationError = response.IsValidationError, - ErrorMessage = response.ErrorMessage, - LockAcquired = response.LockAcquired, - ValidationErrors = response.ValidationErrors, - ErrorCode = new ServiceAgentError(1, response.ErrorMessage) - }; + return response; } - private static async Task> HandleCommandResponseAsync( - HttpResponseMessage message) + private static async Task> HandleCommandResponseAsync(HttpResponseMessage message) { if (message.IsSuccessStatusCode) { - return CommandResponse.Success(); + return CommandResponse.Success(); } - var response = await message.Content.ReadFromJsonAsync(); + var response = await message.Content.ReadFromJsonAsync>(); if (response is null) { - return CommandResponse.Fail(ServiceAgentError.UnknownError); + throw new InvalidOperationException( + $"Could not deserialize error from response, response: {await message.Content.ReadAsStringAsync()}"); } - return new CommandResponse - { - IsConcurrentError = response.IsConcurrentError, - IsValidationError = response.IsValidationError, - ErrorMessage = response.ErrorMessage, - LockAcquired = response.LockAcquired, - ValidationErrors = response.ValidationErrors, - ErrorCode = new ServiceAgentError(1, response.ErrorMessage) - }; + return response; } } diff --git a/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs b/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs index e6cfe28..dfe5c10 100644 --- a/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs +++ b/test/Cnblogs.Architecture.IntegrationTests/CommandResponseHandlerTests.cs @@ -86,12 +86,14 @@ public async Task MinimalApi_HavingError_CommandResponseAsync(bool needValidatio var response = await client.PutAsJsonAsync( "/api/v1/strings/1", new UpdatePayload(needExecutionError, needValidationError)); - var commandResponse = await response.Content.ReadFromJsonAsync(); + var commandResponse = await response.Content.ReadFromJsonAsync>(); // Assert response.Should().HaveClientError(); commandResponse.Should().NotBeNull(); commandResponse!.IsSuccess().Should().BeFalse(); + commandResponse.Should().BeEquivalentTo(new { IsValidationError = needValidationError }); + (commandResponse.ErrorCode != null).Should().Be(needExecutionError); } [Theory] @@ -178,13 +180,15 @@ public async Task Mvc_HavingError_CommandResponseAsync(bool needValidationError, client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/cqrs")); var response = await client.PutAsJsonAsync( "/api/v1/mvc/strings/1", - new UpdatePayload(needValidationError, needExecutionError)); - var content = await response.Content.ReadFromJsonAsync(); + new UpdatePayload(needExecutionError, needValidationError)); + var content = await response.Content.ReadFromJsonAsync>(); // Assert response.Should().HaveClientError(); content.Should().NotBeNull(); content!.IsSuccess().Should().BeFalse(); + content.Should().BeEquivalentTo(new { IsValidationError = needValidationError }); + (content.ErrorCode != null).Should().Be(needExecutionError); } [Theory]