
M13 Domain Error
Design Rust domain error types with clear audiences, recovery strategies, and retry or degradation patterns before implementation hardens the wrong shape.
Overview
m13-domain-error is an agent skill for the Build phase that structures Rust domain errors by audience, recoverability, and resilience patterns before implementation.
Install
npx skills add https://github.com/zhanghandong/rust-skills --skill m13-domain-errorWhat is this skill?
- Five-way error categorization table: user-facing, internal, system, transient, and permanent
- Recovery matrix mapping retry with backoff, fallback, circuit breaker, and graceful degradation
- Design prompts for audience (user vs developer vs ops), recoverability, and debug context
- Guidance on user-facing vs internal error surfaces and error code design
- Keyword coverage for domain errors, resilience, backoff, and 领域错误 / 错误分类
- Five error category rows in the categorization table (user-facing through permanent)
Adoption & trust: 697 installs on skills.sh; 1.2k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Rust service mixes user messages, debug dumps, and retryable failures in one error type with no consistent recovery story.
Who is it for?
Solo builders designing a new Rust backend or refactoring error enums in services that need retries, fallbacks, or circuit breaking.
Skip if: Greenfield scripts with a single `main` and no multi-layer error boundaries, or teams that only need Clippy lints without domain modeling.
When should I use this skill?
Designing or refactoring Rust domain error handling; keywords include domain error, retry, fallback, circuit breaker, graceful degradation, and 领域错误.
What do I get? / Deliverables
You leave design with categorized domain errors, explicit recovery strategies, and context rules that map cleanly to handlers, logs, and alerts.
- Error categorization and audience mapping for the domain
- Recovery strategy choices (retry, fallback, degrade, alert) per error class
Recommended Skills
Journey fit
Build is where service boundaries and error enums are decided; this skill is Layer 2 design guidance before crates and handlers encode behavior. Backend subphase fits API and service error hierarchies, transient vs permanent classification, and ops-facing alerting semantics.
How it compares
Use this design skill before coding error types—not as a substitute for runtime observability stacks or generic exception loggers.
Common Questions / FAQ
Who is m13-domain-error for?
Indie Rust developers and small teams defining error hierarchies for APIs, workers, or CLIs where users, developers, and ops each need different detail.
When should I use m13-domain-error?
Use it during Build backend work when sketching modules, before major refactors of `thiserror`/`anyhow` usage, or when adding retry and degradation policies to existing domains.
Is m13-domain-error safe to install?
It is documentation-only design guidance with no runtime side effects; still review the Security Audits panel on this Prism page for the parent rust-skills package integrity.
SKILL.md
READMESKILL.md - M13 Domain Error
# 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: 1. **Who sees this error?** - End user → friendly message, actionable - Developer → detailed, debuggable - Ops → structured, alertable 2. **Can we recover?** - Transient → retry with backoff - Degradable → fallback value - Permanent → fail fast, alert 3. **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 ```rust #[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 ```rust 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 everythi