Skip to content

Commit

Permalink
Merge pull request #161 from cnblogs/support-custom-error-for-cqrs-se…
Browse files Browse the repository at this point in the history
…rvice-agent

feat: support custom error for cqrs service agent
  • Loading branch information
ikesnowy authored Oct 18, 2023
2 parents 9d5a9c2 + 681b4da commit f723775
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ protected virtual IActionResult CustomErrorCommandResponseMap<TError>(CommandRes
private IActionResult MapErrorCommandResponseToCqrsResponse<TError>(CommandResponse<TError> response)
where TError : Enumeration
{
if (response is { IsConcurrentError: true, LockAcquired: false })
{
return StatusCode(429);
}

return BadRequest(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
82 changes: 45 additions & 37 deletions src/Cnblogs.Architecture.Ddd.Cqrs.ServiceAgent/CqrsServiceAgent.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Base Class for CQRS Service Agent.
/// </summary>
public abstract class CqrsServiceAgent : CqrsServiceAgent<ServiceAgentError>
{
/// <summary>
/// Create a Cqrs service agent.
/// </summary>
/// <param name="httpClient">The underlying http client.</param>
protected CqrsServiceAgent(HttpClient httpClient)
: base(httpClient)
{
}
}

/// <summary>
/// Service Agent for CQRS
/// </summary>
public abstract class CqrsServiceAgent
/// <typeparam name="TError">The type of error for this service.</typeparam>
public abstract class CqrsServiceAgent<TError>
where TError : Enumeration
{
/// <summary>
/// The underlying <see cref="HttpClient"/>.
Expand All @@ -30,7 +48,7 @@ protected CqrsServiceAgent(HttpClient httpClient)
/// <param name="url">The url.</param>
/// <typeparam name="TResponse">Response type.</typeparam>
/// <returns>The response.</returns>
public async Task<CommandResponse<TResponse, ServiceAgentError>> DeleteCommandAsync<TResponse>(string url)
public async Task<CommandResponse<TResponse, TError>> DeleteCommandAsync<TResponse>(string url)
{
var response = await HttpClient.DeleteAsync(url);
return await HandleCommandResponseAsync<TResponse>(response);
Expand All @@ -40,7 +58,7 @@ public async Task<CommandResponse<TResponse, ServiceAgentError>> DeleteCommandAs
/// Execute a command with DELETE method.
/// </summary>
/// <param name="url">The route of the API.</param>
public async Task<CommandResponse<ServiceAgentError>> DeleteCommandAsync(string url)
public async Task<CommandResponse<TError>> DeleteCommandAsync(string url)
{
var response = await HttpClient.DeleteAsync(url);
return await HandleCommandResponseAsync(response);
Expand All @@ -50,7 +68,7 @@ public async Task<CommandResponse<ServiceAgentError>> DeleteCommandAsync(string
/// Execute a command with POST method.
/// </summary>
/// <param name="url">The route of the API.</param>
public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync(string url)
public async Task<CommandResponse<TError>> PostCommandAsync(string url)
{
var response = await HttpClient.PostAsync(url, new StringContent(string.Empty));
return await HandleCommandResponseAsync(response);
Expand All @@ -62,7 +80,7 @@ public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync(string ur
/// <param name="url">The route of the API.</param>
/// <param name="payload">The request body.</param>
/// <typeparam name="TPayload">The type of request body.</typeparam>
public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync<TPayload>(string url, TPayload payload)
public async Task<CommandResponse<TError>> PostCommandAsync<TPayload>(string url, TPayload payload)
{
var response = await HttpClient.PostAsJsonAsync(url, payload);
return await HandleCommandResponseAsync(response);
Expand All @@ -76,7 +94,7 @@ public async Task<CommandResponse<ServiceAgentError>> PostCommandAsync<TPayload>
/// <typeparam name="TResponse">The type of response body.</typeparam>
/// <typeparam name="TPayload">The type of request body.</typeparam>
/// <returns>The response body.</returns>
public async Task<CommandResponse<TResponse, ServiceAgentError>> PostCommandAsync<TResponse, TPayload>(
public async Task<CommandResponse<TResponse, TError>> PostCommandAsync<TResponse, TPayload>(
string url,
TPayload payload)
{
Expand All @@ -88,7 +106,7 @@ public async Task<CommandResponse<TResponse, ServiceAgentError>> PostCommandAsyn
/// Execute a command with PUT method and payload.
/// </summary>
/// <param name="url">The route of API.</param>
public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync(string url)
public async Task<CommandResponse<TError>> PutCommandAsync(string url)
{
var response = await HttpClient.PutAsync(url, new StringContent(string.Empty));
return await HandleCommandResponseAsync(response);
Expand All @@ -101,7 +119,7 @@ public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync(string url
/// <param name="payload">The request body.</param>
/// <typeparam name="TPayload">The type of request body.</typeparam>
/// <returns>The command response.</returns>
public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync<TPayload>(string url, TPayload payload)
public async Task<CommandResponse<TError>> PutCommandAsync<TPayload>(string url, TPayload payload)
{
var response = await HttpClient.PutAsJsonAsync(url, payload);
return await HandleCommandResponseAsync(response);
Expand All @@ -115,7 +133,7 @@ public async Task<CommandResponse<ServiceAgentError>> PutCommandAsync<TPayload>(
/// <typeparam name="TResponse">The type of response body.</typeparam>
/// <typeparam name="TPayload">The type of request body.</typeparam>
/// <returns>The response body.</returns>
public async Task<CommandResponse<TResponse, ServiceAgentError>> PutCommandAsync<TResponse, TPayload>(
public async Task<CommandResponse<TResponse, TError>> PutCommandAsync<TResponse, TPayload>(
string url,
TPayload payload)
{
Expand Down Expand Up @@ -237,54 +255,44 @@ public async Task<TList> ListItemsAsync<TList>(string url)
return await HttpClient.GetFromJsonAsync<TList>(url) ?? new TList();
}

private static async Task<CommandResponse<TResponse, ServiceAgentError>> HandleCommandResponseAsync<TResponse>(
private static async Task<CommandResponse<TResponse, TError>> HandleCommandResponseAsync<TResponse>(
HttpResponseMessage httpResponseMessage)
{
if (httpResponseMessage.IsSuccessStatusCode)
if (httpResponseMessage.StatusCode == HttpStatusCode.NoContent)
{
return CommandResponse<TResponse, TError>.Success();
}

if (httpResponseMessage.StatusCode == HttpStatusCode.OK)
{
var result = await httpResponseMessage.Content.ReadFromJsonAsync<TResponse>();
return CommandResponse<TResponse, ServiceAgentError>.Success(result);
return CommandResponse<TResponse, TError>.Success(result);
}

var response = await httpResponseMessage.Content.ReadFromJsonAsync<CommandResponse>();
var response = await httpResponseMessage.Content.ReadFromJsonAsync<CommandResponse<TResponse, TError>>();
if (response is null)
{
return CommandResponse<TResponse, ServiceAgentError>.Fail(ServiceAgentError.UnknownError);
throw new InvalidOperationException(
$"Could not deserialize error from response, response: {await httpResponseMessage.Content.ReadAsStringAsync()}");
}

return new CommandResponse<TResponse, ServiceAgentError>
{
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<CommandResponse<ServiceAgentError>> HandleCommandResponseAsync(
HttpResponseMessage message)
private static async Task<CommandResponse<TError>> HandleCommandResponseAsync(HttpResponseMessage message)
{
if (message.IsSuccessStatusCode)
{
return CommandResponse<ServiceAgentError>.Success();
return CommandResponse<TError>.Success();
}

var response = await message.Content.ReadFromJsonAsync<CommandResponse>();
var response = await message.Content.ReadFromJsonAsync<CommandResponse<TError>>();
if (response is null)
{
return CommandResponse<ServiceAgentError>.Fail(ServiceAgentError.UnknownError);
throw new InvalidOperationException(
$"Could not deserialize error from response, response: {await message.Content.ReadAsStringAsync()}");
}

return new CommandResponse<ServiceAgentError>
{
IsConcurrentError = response.IsConcurrentError,
IsValidationError = response.IsValidationError,
ErrorMessage = response.ErrorMessage,
LockAcquired = response.LockAcquired,
ValidationErrors = response.ValidationErrors,
ErrorCode = new ServiceAgentError(1, response.ErrorMessage)
};
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<CommandResponse>();
var commandResponse = await response.Content.ReadFromJsonAsync<CommandResponse<TestError>>();

// Assert
response.Should().HaveClientError();
commandResponse.Should().NotBeNull();
commandResponse!.IsSuccess().Should().BeFalse();
commandResponse.Should().BeEquivalentTo(new { IsValidationError = needValidationError });
(commandResponse.ErrorCode != null).Should().Be(needExecutionError);
}

[Theory]
Expand Down Expand Up @@ -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<CommandResponse>();
new UpdatePayload(needExecutionError, needValidationError));
var content = await response.Content.ReadFromJsonAsync<CommandResponse<TestError>>();

// Assert
response.Should().HaveClientError();
content.Should().NotBeNull();
content!.IsSuccess().Should().BeFalse();
content.Should().BeEquivalentTo(new { IsValidationError = needValidationError });
(content.ErrorCode != null).Should().Be(needExecutionError);
}

[Theory]
Expand Down

0 comments on commit f723775

Please sign in to comment.