PluginBench
Skill
Pass
Audit score 90

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-error
Claude Code
Cursor
Windsurf
Cline

How to use m13-domain-error

  1. 1.Identify who will see each error: end users, developers, or operations teams
  2. 2.Classify errors as transient (retryable) or permanent (fail-fast)
  3. 3.Determine what recovery is possible: retry, fallback, or graceful degradation
  4. 4.Design a typed error enum using thiserror with appropriate variants
  5. 5.Add context using anyhow::Context or structured logging for debugging
  6. 6.Implement retry logic with exponential backoff for transient errors only
  7. 7.Test error paths and verify user-facing messages are actionable

Use cases

Good for
  • 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
Who it's for
  • 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

When should I retry an error?

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.

How do I avoid exposing internal errors to users?

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.

What's the difference between user-facing and internal errors?

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).

Should I use string errors or typed errors?

Use typed errors with thiserror for structure and pattern matching. String errors lose type information and make it impossible to programmatically distinguish error kinds.

How do I add context to errors for debugging?

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 TypeAudienceRecoveryExample
User-facingEnd usersGuide actionInvalidEmail, NotFound
InternalDevelopersDebug infoDatabaseError, ParseError
SystemOps/SREMonitor/alertConnectionTimeout, RateLimited
TransientAutomationRetryNetworkError, ServiceUnavailable
PermanentHumanInvestigateConfigInvalid, 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)
QuestionTrace ToAsk
Retry policydomain-*What's acceptable latency for retry?
User experiencedomain-*What message should users see?
Compliancedomain-*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 PatternWhenImplementation
RetryTransient failuresexponential backoff
FallbackDegraded modecached/default value
Circuit BreakerCascading failuresfailsafe-rs
TimeoutSlow operationstokio::time::timeout
BulkheadIsolationseparate 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

MistakeWhy WrongBetter
Same error for allNo actionabilityCategorize by audience
Retry everythingWasted resourcesOnly transient errors
Infinite retryDoS selfMax attempts + backoff
Expose internal errorsSecurity riskUser-friendly messages
No contextHard to debug.context() everywhere

Anti-Patterns

Anti-PatternWhy BadBetter
String errorsNo structurethiserror types
panic! for recoverableBad UXResult with context
Ignore errorsSilent failuresLog or propagate
Box<dyn Error> everywhereLost type infothiserror
Error in happy pathPerformanceEarly validation

Related Skills

WhenSee
Error handling basicsm06-error-handling
Retry implementationm07-concurrency
Domain modelingm09-domain
User-facing APIsdomain-*