Skip to content
Back to skills

Error Handling And Result Modeling

ASecurity

Playbook for modeling and propagating errors in backend services — typed result/error envelopes, failure classification, HTTP status mapping, error translation at layer boundaries, and client-safe vs internal error separation. Prevents exception-driven spaghetti and accidental information leakage.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentstypescriptpythongosqldebuggingbackend

Works with

  • cli

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill error-handling-and-result-modeling --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Error Handling And Result Modeling?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Error Handling And Result Modeling
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-error-handling-and-result-modeling/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-error-handling-and-result-modeling)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: error-handling-and-result-modeling
description: "Playbook for modeling and propagating errors in backend services — typed result/error envelopes, failure classification, HTTP status mapping, error translation at layer boundaries, and client-safe vs internal error separation. Prevents exception-driven spaghetti and accidental information leakage."
---

# Error Handling and Result Modeling

## 1. Classify Before You Handle

Every failure belongs to exactly one category. The category drives the handling strategy.

| Category | Meaning | Retry? | Expose to client? |
|---|---|---|---|
| **Validation** | Caller sent bad input | No | Yes — full detail |
| **Not found** | Requested resource absent | No | Yes — safe message |
| **Conflict** | State mismatch (optimistic lock, duplicate) | No | Yes — safe message |
| **Unauthorized / Forbidden** | Auth failure | No | Minimal detail |
| **Downstream** | Dependency failed or timed out | Yes (idempotent) | Opaque `service_unavailable` |
| **Bug / unexpected** | Unhandled code path | No | Opaque `internal_error` |

## 2. Typed Result Envelope (the Pattern)

Avoid throwing exceptions across use-case/service boundaries. Return a typed result:

```typescript
// TypeScript example — adapt to your language
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
```

```python
# Python example using dataclasses
@dataclass
class Ok(Generic[T]):
    value: T

@dataclass
class Err(Generic[E]):
    error: E

Result = Ok[T] | Err[E]
```

Rules:
1. Use-case methods return `Result<DomainValue, DomainError>` — never throw for expected failures.
2. Reserve exceptions for **truly unexpected** paths (bugs, unrecoverable state).
3. Domain errors are value objects that carry enough context for the handler to decide — not raw exception messages.

## 3. Layer Boundary Translation

```
HTTP handler
    └── Use-case / service  (returns Result<T, DomainError>)
            └── Repository / client  (returns Result<T, InfraError>)
```

**At each boundary, translate inward-facing errors to the layer's vocabulary:**

```
InfraError.DbConnectionFailed → DomainError.ServiceUnavailable
InfraError.UniqueConstraintViolated → DomainError.Conflict(entity, key)
```

The HTTP handler is the only layer that maps `DomainError` → HTTP status + JSON body.

## 4. HTTP Status Mapping Table

| DomainError category | HTTP status | When |
|---|---|---|
| Validation | 400 | Bad request body/params |
| Not found | 404 | Resource absent |
| Conflict | 409 | Duplicate / stale update |
| Unauthorized | 401 | Missing / invalid credential |
| Forbidden | 403 | Authenticated but not allowed |
| Downstream | 503 | Dependency unreachable |
| Bug / unexpected | 500 | Anything else |

## 5. Client-Safe Error Body

```json
{
  "error": {
    "code": "PAYMENT_METHOD_EXPIRED",
    "message": "The payment method has expired.",
    "details": [{ "field": "card.expiry", "issue": "past_expiry" }]
  }
}
```

**Rules:**
- `code` is a stable machine-readable slug — don't change it after release.
- `message` is English prose safe to display (no stack traces, no internal state).
- `details` is optional; present only for validation errors with field-level specifics.
- Never include stack traces, SQL errors, or internal service names in 4xx/5xx responses.

## 6. Logging Strategy

| Error category | Log level | What to include |
|---|---|---|
| Validation / not found | DEBUG or omit | High-volume, expected — log at trace if needed |
| Downstream (retried, eventually ok) | WARN | Attempt count, dependency name, duration |
| Downstream (exhausted) | ERROR | Full context, trace ID, retries attempted |
| Bug / unexpected | ERROR + alert | Stack trace, request ID, all context |

## Pitfalls

- Throwing exceptions for validation failures — forces callers into try/catch chains and hides intent.
- Returning `{ error: null, data: null }` — ambiguous; force the caller to pick a branch.
- Using HTTP 500 for validation errors — clients can't distinguish "your fault" from "our fault".
- Leaking `psycopg2.OperationalError: ...` or `ECONNREFUSED` in the response body — exposes topology.
- Re-throwing the same raw exception across layers — the HTTP handler ends up doing domain logic.
- Not logging the trace/correlation ID — makes cross-service debugging impossible.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…