< Summary

Information
Class: Elsa.Bpmn.Interchange.Endpoints.Bpmn.BpmnErrorResponse
Assembly: Elsa.Bpmn.Interchange
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn.Interchange/Endpoints/Bpmn/BpmnErrorResponse.cs
Line coverage
100%
Covered lines: 13
Uncovered lines: 0
Coverable lines: 13
Total lines: 73
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_StatusCode()100%11100%
get_Message()100%11100%
get_Errors()100%11100%
get_Code()100%11100%
get_Data()100%11100%
Create(...)100%11100%
SendAsync(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn.Interchange/Endpoints/Bpmn/BpmnErrorResponse.cs

#LineLine coverage
 1using FastEndpoints;
 2using Microsoft.AspNetCore.Http;
 3
 4namespace Elsa.Bpmn.Interchange.Endpoints.Bpmn;
 5
 6/// <summary>
 7/// The error envelope every BPMN-specific refusal coded through <see cref="BpmnErrorCodes"/> is sent as.
 8/// </summary>
 9/// <remarks>
 10/// <para>
 11/// FastEndpoints has no way, in this deployment's configuration, to surface a
 12/// <see cref="FluentValidation.Results.ValidationFailure.ErrorCode"/> in the error response it builds by default:
 13/// that requires either its <c>ProblemDetails</c> response (which this deployment does not use — see
 14/// <c>Elsa.FastEndpointConfigurators.ElsaFastEndpointsConfigurator</c>) with its <c>IndicateErrorCode</c> flag set,
 15/// or replacing <c>Config.ErrOpts.ResponseBuilder</c>, which is a single, process-wide FastEndpoints setting — doing
 16/// so here would reshape every endpoint's error response in the process, not just these BPMN endpoints.
 17/// </para>
 18/// <para>
 19/// So these endpoints write this response themselves, sent through <see cref="SendAsync"/> instead of
 20/// FastEndpoints' <c>Send.ErrorsAsync</c>, keeping the same top-level shape FastEndpoints' default
 21/// <c>ErrorResponse</c> would have sent — <see cref="StatusCode"/>, <see cref="Message"/>, and an
 22/// <see cref="Errors"/> dictionary with the same <c>generalErrors</c> key <c>AddError(message)</c> groups under —
 23/// so a caller that only reads <c>message</c>/<c>errors</c> today, such as Elsa Studio's
 24/// <c>ValidationApiExceptionExtensions.GetValidationErrorsFromContent</c>, keeps seeing exactly what it saw before
 25/// this envelope's two additive members, <see cref="Code"/> and <see cref="Data"/>, existed.
 26/// </para>
 27/// </remarks>
 28internal sealed class BpmnErrorResponse
 29{
 30    /// <summary>
 31    /// The key FastEndpoints' own <c>AddError(message)</c> groups a message-only failure under — camelCased from
 32    /// its <c>Config.ErrOpts.GeneralErrorsField</c> default of <c>"GeneralErrors"</c>, which nothing in this
 33    /// deployment overrides (see <c>Elsa.FastEndpointConfigurators.ElsaFastEndpointsConfigurator</c>).
 34    /// </summary>
 35    private const string GeneralErrorsKey = "generalErrors";
 36
 37    /// <summary>The HTTP status code sent to the client.</summary>
 6438    public required int StatusCode { get; init; }
 39
 40    /// <summary>The same default message FastEndpoints' own <c>ErrorResponse</c> carries when nothing overrides it.</su
 4041    public string Message { get; init; } = "One or more errors occurred!";
 42
 43    /// <summary>The same shape FastEndpoints' own <c>ErrorResponse</c> builds from an endpoint's <c>AddError</c> calls.
 4844    public required IReadOnlyDictionary<string, IReadOnlyList<string>> Errors { get; init; }
 45
 46    /// <summary>The stable, machine-readable code identifying this refusal. See <see cref="BpmnErrorCodes"/>.</summary>
 4847    public required string Code { get; init; }
 48
 49    /// <summary>Structured data specific to <see cref="Code"/> (e.g. the missing capability names and element ids), or 
 4850    public object? Data { get; init; }
 51
 52    /// <summary>Builds the response for a single-message refusal, in the same shape <c>AddError(message)</c> would have
 2453    public static BpmnErrorResponse Create(string message, string code, int statusCode, object? data = null) => new()
 2454    {
 2455        StatusCode = statusCode,
 2456        Errors = new Dictionary<string, IReadOnlyList<string>> { [GeneralErrorsKey] = [message] },
 2457        Code = code,
 2458        Data = data
 2459    };
 60
 61    /// <summary>Sends <paramref name="response"/> with its own <see cref="StatusCode"/>.</summary>
 62    /// <remarks>
 63    /// Goes through <see cref="HttpResponse"/>'s own <c>SendAsync</c> extension rather than an endpoint's
 64    /// <c>Send.ErrorsAsync</c>/<c>Send.ResponseAsync</c>, since those build FastEndpoints' own <c>ErrorResponse</c>
 65    /// (see this type's remarks) or require the response type FastEndpoints generated for the calling endpoint's
 66    /// declared success response, neither of which fits an envelope with a <c>code</c> and a <c>data</c> member.
 67    /// <c>HttpResponse.SendAsync</c> still runs through <c>Config.SerOpts.ResponseSerializer</c> — the same
 68    /// <c>IApiSerializer</c>-backed serializer <c>Elsa.FastEndpointConfigurators.ElsaFastEndpointsConfigurator</c>
 69    /// configures for every other response — so this response's JSON casing matches the rest of the API.
 70    /// </remarks>
 71    public static Task SendAsync(HttpResponse httpResponse, BpmnErrorResponse response, CancellationToken cancellationTo
 1672        httpResponse.SendAsync(response, response.StatusCode, cancellation: cancellationToken);
 73}