first commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
namespace ERPCore.System.Errors;
|
||||
|
||||
/// <summary>
|
||||
/// Base for domain / business-rule violations. Carries a stable <see cref="Code"/>
|
||||
/// (see the error catalog in docs/11-BACKEND-PHASE1.md) and an HTTP
|
||||
/// <see cref="StatusCode"/>; mapped to RFC 7807 ProblemDetails by
|
||||
/// <see cref="DomainExceptionHandler"/>.
|
||||
/// </summary>
|
||||
public class DomainException : Exception
|
||||
{
|
||||
public string Code { get; }
|
||||
public int StatusCode { get; }
|
||||
|
||||
public DomainException(string code, string message, int statusCode = 400)
|
||||
: base(message)
|
||||
{
|
||||
Code = code;
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>404 — a referenced resource does not exist.</summary>
|
||||
public sealed class NotFoundException : DomainException
|
||||
{
|
||||
public NotFoundException(string message) : base(ErrorCodes.NotFound, message, 404) { }
|
||||
}
|
||||
|
||||
/// <summary>409 — the request conflicts with current server state.</summary>
|
||||
public sealed class ConflictException : DomainException
|
||||
{
|
||||
public ConflictException(string message) : base(ErrorCodes.Conflict, message, 409) { }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ERPCore.System.Errors;
|
||||
|
||||
/// <summary>
|
||||
/// Translates <see cref="DomainException"/> into an RFC 7807 ProblemDetails
|
||||
/// response, preserving the stable domain <c>code</c> as an extension. Non-domain
|
||||
/// exceptions are left unhandled so the framework returns a 500 ProblemDetails.
|
||||
/// Registered via AddExceptionHandler and invoked by UseExceptionHandler.
|
||||
/// </summary>
|
||||
public sealed class DomainExceptionHandler : IExceptionHandler
|
||||
{
|
||||
private readonly IProblemDetailsService _problemDetails;
|
||||
|
||||
public DomainExceptionHandler(IProblemDetailsService problemDetails)
|
||||
=> _problemDetails = problemDetails;
|
||||
|
||||
public async ValueTask<bool> TryHandleAsync(
|
||||
HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
|
||||
{
|
||||
if (exception is not DomainException domain)
|
||||
return false;
|
||||
|
||||
httpContext.Response.StatusCode = domain.StatusCode;
|
||||
|
||||
return await _problemDetails.TryWriteAsync(new ProblemDetailsContext
|
||||
{
|
||||
HttpContext = httpContext,
|
||||
Exception = domain,
|
||||
ProblemDetails = new ProblemDetails
|
||||
{
|
||||
Status = domain.StatusCode,
|
||||
Title = "Domain rule violation",
|
||||
Detail = domain.Message,
|
||||
Extensions = { ["code"] = domain.Code }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace ERPCore.System.Errors;
|
||||
|
||||
/// <summary>
|
||||
/// Stable domain error codes carried in the <c>code</c> extension of RFC 7807
|
||||
/// ProblemDetails responses. The authoritative catalog lives in
|
||||
/// docs/11-BACKEND-PHASE1.md; add codes here as endpoints are implemented so
|
||||
/// the two stay in sync.
|
||||
/// </summary>
|
||||
public static class ErrorCodes
|
||||
{
|
||||
public const string Validation = "validation_error";
|
||||
public const string NotFound = "not_found";
|
||||
public const string Conflict = "conflict";
|
||||
}
|
||||
Reference in New Issue
Block a user