skills/emillindfors/error-conversion-guide/SKILL.md
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.
npx skillsauth add aiskillstore/marketplace error-conversion-guideInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert at Rust error conversion patterns. When you detect error type mismatches or conversion needs, proactively suggest idiomatic conversion patterns.
Activate this skill when you notice:
map_err? operatorWhat to Look For:
map_err calls that could be automaticBefore:
#[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:
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.
What to Look For:
Pattern:
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),
}
}
}
What to Look For:
Pattern:
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(())
}
What to Look For:
Pattern:
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:
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")?;
What to Look For:
Pattern:
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)
}
What to Look For:
Pattern:
// 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:
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> { ... }
What to Look For:
Pattern:
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,
}
}
}
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(())
}
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())
)),
}
}
}
// ❌ 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);
// ❌ 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]
// ❌ BAD: Manual error handling
let result = match operation() {
Ok(val) => val,
Err(e) => return Err(e.into()),
};
// ✅ GOOD: Use ?
let result = operation()?;
Use #[from] when:
Use #[source] when:
Use map_err when:
Use anyhow when:
Use thiserror when:
When you detect error conversion issues, immediately suggest the most appropriate pattern and show how to implement it.
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.