m13-domain-error
actionbook/rust-skills
Design domain error handling with categorization, recovery strategies, and resilience patterns.
What is m13-domain-error?
This skill guides you through designing error types and recovery strategies for domain-driven applications. Use it when deciding how errors should be categorized (user-facing vs. internal), whether they're retryable, and what context is needed for debugging and recovery.
- Categorize errors by audience (end users, developers, ops) and recoverability (transient vs. permanent)
- Design error hierarchies using thiserror with typed, structured errors instead of strings
- Implement retry patterns with exponential backoff for transient failures
- Apply recovery strategies: retry, fallback values, circuit breakers, and graceful degradation
- Determine what context and metadata each error type needs for debugging and monitoring
- Distinguish between user-facing messages and internal debug information
How to install m13-domain-error
npx skills add https://github.com/actionbook/rust-skills --skill m13-domain-errorHow to use m13-domain-error
- 1.Identify who will see each error: end users, developers, or operations teams
- 2.Classify errors as transient (retryable) or permanent (fail-fast)
- 3.Determine what recovery is possible: retry, fallback, or graceful degradation
- 4.Design a typed error enum using thiserror with appropriate variants
- 5.Add context using anyhow::Context or structured logging for debugging
- 6.Implement retry logic with exponential backoff for transient errors only
- 7.Test error paths and verify user-facing messages are actionable
Use cases
- Designing payment or transaction error handling with appropriate retry policies and user messaging
- Building resilient API clients that distinguish between temporary network failures and permanent errors
- Creating domain-specific error types for financial, healthcare, or compliance-critical systems
- Implementing circuit breakers to prevent cascading failures in microservices
- Structuring error responses for REST/GraphQL APIs with actionable user guidance
- Backend engineers designing error handling strategies
- Library authors creating typed error types for consumers
- Systems engineers building resilient, fault-tolerant services
- Teams implementing domain-driven design with proper error semantics
m13-domain-error FAQ
Only for transient errors (network timeouts, service unavailable, rate limits). Use exponential backoff with a maximum number of attempts and max delay to avoid self-inflicted DoS.
Categorize errors by audience: user-facing errors get friendly, actionable messages; internal errors get detailed context for developers. Map internal errors to generic user messages.
User-facing errors guide end users toward action (e.g., 'Invalid email format'); internal errors provide developers with debugging details (e.g., database connection stack trace).
Use typed errors with thiserror for structure and pattern matching. String errors lose type information and make it impossible to programmatically distinguish error kinds.
Use anyhow::Context to attach call chain information, request IDs, and input data. Combine with structured logging (tracing) to include error fields in logs.
Full instructions (SKILL.md)
Source of truth, from actionbook/rust-skills.
name: m13-domain-error description: "Use when designing domain error handling. Keywords: domain error, error categorization, recovery strategy, retry, fallback, domain error hierarchy, user-facing vs internal errors, error code design, circuit breaker, graceful degradation, resilience, error context, backoff, retry with backoff, error recovery, transient vs permanent error, 领域错误, 错误分类, 恢复策略, 重试, 熔断器, 优雅降级" user-invocable: false
Domain Error Strategy
Layer 2: Design Choices
Core Question
Who needs to handle this error, and how should they recover?
Before designing error types:
- Is this user-facing or internal?
- Is recovery possible?
- What context is needed for debugging?
Error Categorization
| Error Type | Audience | Recovery | Example |
|---|---|---|---|
| User-facing | End users | Guide action | InvalidEmail, NotFound |
| Internal | Developers | Debug info | DatabaseError, ParseError |
| System | Ops/SRE | Monitor/alert | ConnectionTimeout, RateLimited |
| Transient | Automation | Retry | NetworkError, ServiceUnavailable |
| Permanent | Human | Investigate | ConfigInvalid, DataCorrupted |
Thinking Prompt
Before designing error types:
-
Who sees this error?
- End user → friendly message, actionable
- Developer → detailed, debuggable
- Ops → structured, alertable
-
Can we recover?
- Transient → retry with backoff
- Degradable → fallback value
- Permanent → fail fast, alert
-
What context is needed?
- Call chain → anyhow::Context
- Request ID → structured logging
- Input data → error payload
Trace Up ↑
To domain constraints (Layer 3):
"How should I handle payment failures?"
↑ Ask: What are the business rules for retries?
↑ Check: domain-fintech (transaction requirements)
↑ Check: SLA (availability requirements)
| Question | Trace To | Ask |
|---|---|---|
| Retry policy | domain-* | What's acceptable latency for retry? |
| User experience | domain-* | What message should users see? |
| Compliance | domain-* | What must be logged for audit? |
Trace Down ↓
To implementation (Layer 1):
"Need typed errors"
↓ m06-error-handling: thiserror for library
↓ m04-zero-cost: Error enum design
"Need error context"
↓ m06-error-handling: anyhow::Context
↓ Logging: tracing with fields
"Need retry logic"
↓ m07-concurrency: async retry patterns
↓ Crates: tokio-retry, backoff
Quick Reference
| Recovery Pattern | When | Implementation |
|---|---|---|
| Retry | Transient failures | exponential backoff |
| Fallback | Degraded mode | cached/default value |
| Circuit Breaker | Cascading failures | failsafe-rs |
| Timeout | Slow operations | tokio::time::timeout |
| Bulkhead | Isolation | separate thread pools |
Error Hierarchy
#[derive(thiserror::Error, Debug)]
pub enum AppError {
// User-facing
#[error("Invalid input: {0}")]
Validation(String),
// Transient (retryable)
#[error("Service temporarily unavailable")]
ServiceUnavailable(#[source] reqwest::Error),
// Internal (log details, show generic)
#[error("Internal error")]
Internal(#[source] anyhow::Error),
}
impl AppError {
pub fn is_retryable(&self) -> bool {
matches!(self, Self::ServiceUnavailable(_))
}
}
Retry Pattern
use tokio_retry::{Retry, strategy::ExponentialBackoff};
async fn with_retry<F, T, E>(f: F) -> Result<T, E>
where
F: Fn() -> impl Future<Output = Result<T, E>>,
E: std::fmt::Debug,
{
let strategy = ExponentialBackoff::from_millis(100)
.max_delay(Duration::from_secs(10))
.take(5);
Retry::spawn(strategy, || f()).await
}
Common Mistakes
| Mistake | Why Wrong | Better |
|---|---|---|
| Same error for all | No actionability | Categorize by audience |
| Retry everything | Wasted resources | Only transient errors |
| Infinite retry | DoS self | Max attempts + backoff |
| Expose internal errors | Security risk | User-friendly messages |
| No context | Hard to debug | .context() everywhere |
Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|---|---|---|
| String errors | No structure | thiserror types |
| panic! for recoverable | Bad UX | Result with context |
| Ignore errors | Silent failures | Log or propagate |
| Box<dyn Error> everywhere | Lost type info | thiserror |
| Error in happy path | Performance | Early validation |
Related Skills
| When | See |
|---|---|
| Error handling basics | m06-error-handling |
| Retry implementation | m07-concurrency |
| Domain modeling | m09-domain |
| User-facing APIs | domain-* |
Related skills
More from actionbook/rust-skills and the wider catalog.

m14-mental-model
Mental models and analogies for understanding Rust ownership, borrowing, and core concepts.

m15-anti-pattern
Identify and refactor Rust code anti-patterns during review.

meta-cognition-parallel
Three-layer parallel meta-cognition analysis for Rust questions—language mechanics, design choices, and domain constraints.

rust-call-graph
Visualize Rust function call graphs and trace caller/callee relationships using LSP.

rust-code-navigator
Navigate Rust code with LSP: go to definition, find references, and explore symbols efficiently.

rust-daily
Fetch Rust community news and updates filtered by time range (daily, weekly, or monthly).