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
| Extension | Source | Description |
|---|---|---|
traceId | Activity.Current | Distributed trace identifier |
correlationId | CorrelationIdMiddleware | Request correlation ID |
requestId | HttpContext | Per-request identifier |
errorCode | Error.Code | Machine-readable error code |
retryAfter | Rate limiter | Seconds 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"}