Skip to content
bitzorcas
EN

Concept

Error handling

Global error handling — RFC 9457 ProblemDetails via GlobalExceptionHandler, Result\<T\> domain error pattern, and structured error responses.

Last updated

BitzOrcas uses a dual error handling strategy: Result<T> for domain-level failures and GlobalExceptionHandler for HTTP boundary translation to RFC 9457 ProblemDetails.

Result<T> pattern

Domain services return Result<T> instead of throwing exceptions:

public Result<Note> CreateNote(string title, string content)
{
if (string.IsNullOrWhiteSpace(title))
return Result<Note>.Failure(Error.Validation("Note.Title", "Title is required"));
if (content.Length > 10000)
return Result<Note>.Failure(Error.Validation("Note.Content", "Content too long"));
var note = new Note(title, content);
return Result<Note>.Success(note);
}

Error types

public enum ErrorType
{
Validation, // 400 — Input validation failure
NotFound, // 404 — Resource not found
Conflict, // 409 — Duplicate/optimistic concurrency conflict
Unauthorized, // 401 — Authentication required
Forbidden, // 403 — Insufficient permissions
Failure, // 500 — Domain/business rule failure
Infrastructure // 503 — External dependency failure
}

Error structure

public readonly record struct Error
{
public ErrorType Type { get; }
public string Code { get; } // e.g., "Note.NotFound"
public string Description { get; }
}

GlobalExceptionHandler

Catches unhandled exceptions at the HTTP boundary and converts to ProblemDetails:

public class GlobalExceptionHandler : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
// Maps exception types to ProblemDetails
}
}

ProblemDetails response format

All error responses follow RFC 9457 with BitzOrcas extensions:

{
"type": "https://docs.bitzsoft.com/problems/not-found",
"title": "Not Found",
"status": 404,
"detail": "Note with ID 'abc123' was not found.",
"instance": "/api/notes/abc123",
"traceId": "00-abc123def456-...",
"correlationId": "corr-789",
"requestId": "req-012",
"errorCode": "Note.NotFound"
}

Standard extensions

ExtensionSourceDescription
traceIdActivity.CurrentDistributed trace identifier
correlationIdCorrelationIdMiddlewareRequest correlation ID
requestIdHttpContextPer-request identifier
errorCodeError.CodeMachine-readable error code
retryAfterRate limiterSeconds until retry allowed (429 only)

Result to HTTP translation

In Minimal API endpoints:

app.MapPost("/api/notes", async (
CreateNoteCommand cmd, IMediator mediator) =>
{
var result = await mediator.Send(cmd);
return result.Match(
onSuccess: value => Results.Created($"/api/notes/{value.Id}", value),
onFailure: error => error.ToProblemDetails()
);
});

Validation errors

The ValidationPipelineBehavior catches IRequestRule failures and returns structured validation errors:

{
"type": "https://docs.bitzsoft.com/problems/validation",
"title": "Validation Failed",
"status": 400,
"errors": {
"Title": ["Title is required"],
"Content": ["Content must not exceed 10000 characters"]
}
}

Rate limit errors (429)

{
"type": "https://docs.bitzsoft.com/problems/rate-limit-exceeded",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded.",
"retryAfter": 60,
"traceId": "...",
"correlationId": "...",
"errorCode": "RateLimit.Exceeded"
}