multiAI Summary Pending

error-conversion-guide

Guides users on error conversion patterns, From trait implementations, and the ? operator. Activates when users need to convert between error types or handle multiple error types in a function.

231 stars

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/error-conversion-guide/SKILL.md --create-dirs "https://raw.githubusercontent.com/aiskillstore/marketplace/main/skills/emillindfors/error-conversion-guide/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/error-conversion-guide/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How error-conversion-guide Compares

Feature / Agenterror-conversion-guideStandard Approach
Platform SupportmultiLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Guides users on error conversion patterns, From trait implementations, and the ? operator. Activates when users need to convert between error types or handle multiple error types in a function.

Which AI agents support this skill?

This skill is compatible with multi.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Error Conversion Guide Skill

You are an expert at Rust error conversion patterns. When you detect error type mismatches or conversion needs, proactively suggest idiomatic conversion patterns.

## When to Activate

Activate this skill when you notice:
- Multiple error types in a single function
- Manual error conversion with `map_err`
- Type mismatch errors with the `?` operator
- Questions about From/Into traits for errors
- Need to combine different error types

## Error Conversion Patterns

### Pattern 1: Automatic Conversion with #[from]

**What to Look For**:
- Manual `map_err` calls that could be automatic
- Repetitive error conversions

**Before**:
```rust
#[derive(Debug)]
pub enum AppError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
}

fn process() -> Result<i32, AppError> {
    let content = std::fs::read_to_string("data.txt")
        .map_err(|e| AppError::Io(e))?;  // ❌ Manual conversion

    let num = content.trim().parse::<i32>()
        .map_err(|e| AppError::Parse(e))?;  // ❌ Manual conversion

    Ok(num)
}
```

**After**:
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("IO error")]
    Io(#[from] std::io::Error),  // ✅ Automatic From impl

    #[error("Parse error")]
    Parse(#[from] std::num::ParseIntError),  // ✅ Automatic From impl
}

fn process() -> Result<i32, AppError> {
    let content = std::fs::read_to_string("data.txt")?;  // ✅ Auto-converts
    let num = content.trim().parse::<i32>()?;  // ✅ Auto-converts
    Ok(num)
}
```

**Suggestion Template**:
```
Use #[from] in your error enum to enable automatic conversion:

#[derive(Error, Debug)]
pub enum AppError {
    #[error("IO error")]
    Io(#[from] std::io::Error),
}

This implements From<std::io::Error> for AppError, allowing ? to automatically convert.
```

### Pattern 2: Manual From Implementation

**What to Look For**:
- Custom error types that need conversion
- Complex conversion logic

**Pattern**:
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Database error: {message}")]
    Database { message: String },

    #[error("Validation error: {0}")]
    Validation(String),
}

// Manual From for custom conversion logic
impl From<sqlx::Error> for AppError {
    fn from(err: sqlx::Error) -> Self {
        AppError::Database {
            message: format!("Database operation failed: {}", err),
        }
    }
}

// Convert with context
impl From<validator::ValidationErrors> for AppError {
    fn from(err: validator::ValidationErrors) -> Self {
        let messages: Vec<String> = err
            .field_errors()
            .iter()
            .map(|(field, errors)| {
                format!("{}: {:?}", field, errors)
            })
            .collect();

        AppError::Validation(messages.join(", "))
    }
}
```

**Suggestion Template**:
```
When you need custom conversion logic, implement From manually:

impl From<SourceError> for AppError {
    fn from(err: SourceError) -> Self {
        AppError::Variant {
            field: extract_info(&err),
        }
    }
}
```

### Pattern 3: Converting Between Result Types

**What to Look For**:
- Calling functions with different error types
- Need to unify error types

**Pattern**:
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ServiceError {
    #[error("Repository error")]
    Repository(#[from] RepositoryError),

    #[error("External API error")]
    Api(#[from] ApiError),

    #[error("Validation error")]
    Validation(#[from] ValidationError),
}

// All these errors convert automatically
fn service_operation() -> Result<Data, ServiceError> {
    // Returns Result<_, RepositoryError>
    let data = repository.fetch()?;  // ✅ Auto-converts to ServiceError

    // Returns Result<_, ValidationError>
    validate(&data)?;  // ✅ Auto-converts to ServiceError

    // Returns Result<_, ApiError>
    let enriched = api_client.enrich(data)?;  // ✅ Auto-converts to ServiceError

    Ok(enriched)
}
```

**Suggestion Template**:
```
Create a unified error type that can convert from all the errors you need:

#[derive(Error, Debug)]
pub enum UnifiedError {
    #[error("Database error")]
    Database(#[from] DbError),

    #[error("Network error")]
    Network(#[from] NetworkError),
}

fn operation() -> Result<(), UnifiedError> {
    db_operation()?;  // Auto-converts
    network_operation()?;  // Auto-converts
    Ok(())
}
```

### Pattern 4: map_err for One-Off Conversions

**What to Look For**:
- Single conversion that doesn't justify From impl
- Adding context during conversion

**Pattern**:
```rust
use anyhow::Context;

fn process(id: &str) -> anyhow::Result<Data> {
    // One-off conversion with context
    let config = load_config()
        .map_err(|e| anyhow::anyhow!("Failed to load config: {}", e))?;

    // Better: use context
    let config = load_config()
        .context("Failed to load config")?;

    // map_err for type conversion without From impl
    let data = fetch_data(id)
        .map_err(|e| format!("Fetch failed for {}: {}", id, e))?;

    Ok(data)
}
```

**When to Use**:
- One-off conversions
- Adding context to specific call sites
- Converting to types that don't have From impl

**Suggestion Template**:
```
For one-off conversions or adding context, use map_err or anyhow's context:

// With map_err
operation().map_err(|e| MyError::Custom(format!("Failed: {}", e)))?;

// With anyhow (preferred)
operation().context("Operation failed")?;
```

### Pattern 5: Error Type Aliases

**What to Look For**:
- Repetitive Result<T, MyError> types
- Complex error type signatures

**Pattern**:
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("IO error")]
    Io(#[from] std::io::Error),

    #[error("Parse error")]
    Parse(#[from] serde_json::Error),
}

// Type alias for cleaner signatures
pub type Result<T> = std::result::Result<T, AppError>;

// Now use it everywhere
pub fn load_config() -> Result<Config> {  // ✅ Clean
    let bytes = std::fs::read("config.json")?;
    let config = serde_json::from_slice(&bytes)?;
    Ok(config)
}

// Instead of
pub fn load_config_verbose() -> std::result::Result<Config, AppError> {  // ❌ Verbose
    // ...
}
```

**Suggestion Template**:
```
Create a type alias for your Result type:

pub type Result<T> = std::result::Result<T, AppError>;

Then use it in function signatures:

pub fn operation() -> Result<Data> {
    Ok(data)
}
```

### Pattern 6: Boxing Errors

**What to Look For**:
- Need for dynamic error types
- Functions that can return multiple error types

**Pattern**:
```rust
// For libraries that need flexibility
type BoxError = Box<dyn std::error::Error + Send + Sync>;

fn flexible_function() -> Result<Data, BoxError> {
    let data1 = io_operation()?;  // std::io::Error auto-boxes
    let data2 = parse_operation()?;  // ParseError auto-boxes
    Ok(combine(data1, data2))
}

// Or use anyhow for applications
use anyhow::Result;

fn application_function() -> Result<Data> {
    let data1 = io_operation()?;
    let data2 = parse_operation()?;
    Ok(combine(data1, data2))
}
```

**Trade-offs**:
- ✅ Flexible: Can return any error type
- ✅ Simple: No need to define custom error enum
- ❌ Dynamic: Error type not known at compile time
- ❌ Harder to match on specific errors

**Suggestion Template**:
```
For flexible error handling, use Box<dyn Error> or anyhow:

// Libraries: Box<dyn Error>
type BoxError = Box<dyn std::error::Error + Send + Sync>;
fn operation() -> Result<T, BoxError> { ... }

// Applications: anyhow
use anyhow::Result;
fn operation() -> Result<T> { ... }
```

### Pattern 7: Layered Error Conversion

**What to Look For**:
- Multi-layer architecture (domain, infra, app)
- Need for error boundary between layers

**Pattern**:
```rust
use thiserror::Error;

// Infrastructure layer
#[derive(Error, Debug)]
pub enum InfraError {
    #[error("Database error")]
    Database(#[from] sqlx::Error),

    #[error("HTTP error")]
    Http(#[from] reqwest::Error),
}

// Domain layer (doesn't know about infrastructure)
#[derive(Error, Debug)]
pub enum DomainError {
    #[error("User not found: {0}")]
    UserNotFound(String),

    #[error("Invalid data: {0}")]
    InvalidData(String),
}

// Application layer unifies both
#[derive(Error, Debug)]
pub enum AppError {
    #[error("Domain error: {0}")]
    Domain(#[from] DomainError),

    #[error("Infrastructure error: {0}")]
    Infra(#[from] InfraError),
}

// Infrastructure to Domain conversion (at boundary)
impl From<InfraError> for DomainError {
    fn from(err: InfraError) -> Self {
        match err {
            InfraError::Database(e) if e.to_string().contains("not found") => {
                DomainError::UserNotFound("User not found in database".to_string())
            }
            _ => DomainError::InvalidData("Data access failed".to_string()),
        }
    }
}
```

**Suggestion Template**:
```
For layered architectures, convert errors at layer boundaries:

// Infrastructure → Domain conversion
impl From<InfraError> for DomainError {
    fn from(err: InfraError) -> Self {
        // Convert infrastructure concepts to domain concepts
        match err {
            InfraError::NotFound => DomainError::EntityNotFound,
            _ => DomainError::InfrastructureFailed,
        }
    }
}
```

## Advanced Patterns

### Pattern 8: Multiple Error Sources with Custom Logic

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ProcessError {
    #[error("Stage 1 failed: {0}")]
    Stage1(String),

    #[error("Stage 2 failed: {0}")]
    Stage2(String),
}

// Custom conversion with different variants
impl From<Stage1Error> for ProcessError {
    fn from(err: Stage1Error) -> Self {
        ProcessError::Stage1(err.to_string())
    }
}

impl From<Stage2Error> for ProcessError {
    fn from(err: Stage2Error) -> Self {
        ProcessError::Stage2(err.to_string())
    }
}

fn process() -> Result<(), ProcessError> {
    stage1()?;  // Converts to ProcessError::Stage1
    stage2()?;  // Converts to ProcessError::Stage2
    Ok(())
}
```

### Pattern 9: Fallible Conversion

```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConversionError {
    #[error("Incompatible error type: {0}")]
    Incompatible(String),
}

// TryFrom for fallible conversion
impl TryFrom<ExternalError> for MyError {
    type Error = ConversionError;

    fn try_from(err: ExternalError) -> Result<Self, Self::Error> {
        match err.code() {
            404 => Ok(MyError::NotFound),
            500 => Ok(MyError::Internal),
            _ => Err(ConversionError::Incompatible(
                format!("Unknown error code: {}", err.code())
            )),
        }
    }
}
```

## Common Mistakes

### Mistake 1: Multiple #[from] for Same Type

```rust
// ❌ BAD: Can't have two #[from] for same type
#[derive(Error, Debug)]
pub enum MyError {
    #[error("First")]
    First(#[from] std::io::Error),

    #[error("Second")]
    Second(#[from] std::io::Error),  // ❌ Conflict!
}

// ✅ GOOD: Use #[source] and manual construction
#[derive(Error, Debug)]
pub enum MyError {
    #[error("Read failed")]
    ReadFailed(#[source] std::io::Error),

    #[error("Write failed")]
    WriteFailed(#[source] std::io::Error),
}

// Construct manually with context
let err = MyError::ReadFailed(io_err);
```

### Mistake 2: Losing Error Information

```rust
// ❌ BAD: Converts to String, loses error chain
operation().map_err(|e| MyError::Failed(e.to_string()))?;

// ✅ GOOD: Preserves error chain
operation().map_err(|e| MyError::Failed(e))?;
// Or use #[from]
```

### Mistake 3: Not Using ? When Available

```rust
// ❌ BAD: Manual error handling
let result = match operation() {
    Ok(val) => val,
    Err(e) => return Err(e.into()),
};

// ✅ GOOD: Use ?
let result = operation()?;
```

## Decision Guide

**Use #[from] when**:
- Single error source type
- No need for additional context
- Want automatic conversion

**Use #[source] when**:
- Multiple variants for same source type
- Need to add context (like field names)
- Want manual construction

**Use map_err when**:
- One-off conversion
- Adding context to specific call
- Converting to types without From impl

**Use anyhow when**:
- Application-level code
- Need flexibility
- Want easy context addition

**Use thiserror when**:
- Library code
- Want specific error types
- Consumers need to match on errors

## Your Approach

1. **Detect**: Identify error conversion needs or type mismatches
2. **Analyze**: Determine the best conversion pattern
3. **Suggest**: Provide specific implementation
4. **Explain**: Why this pattern is appropriate

## Communication Style

- Explain the trade-offs between different approaches
- Suggest #[from] as the default, map_err as fallback
- Point out when error information is being lost
- Recommend type aliases for cleaner code

When you detect error conversion issues, immediately suggest the most appropriate pattern and show how to implement it.