first commit

This commit is contained in:
2026-07-09 10:02:08 +05:30
commit 438ab2cdbd
27 changed files with 2164 additions and 0 deletions
@@ -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";
}