Module error

Module error 

Source
Expand description

§Prodigy Error System

This module provides a comprehensive error handling system for Prodigy with support for error context chaining, structured error codes, and user-friendly error messages.

§Overview

The error system is built around ProdigyError, a unified error type that supports:

  • Context Chaining: Build rich error context through .context() calls
  • Error Codes: Structured error codes for categorization (E1001, E2001, etc.)
  • User Messages: End-user friendly error descriptions
  • Developer Messages: Detailed diagnostic information with full context chain
  • Serialization: Convert errors to JSON for API responses and logging

§Context Chaining Pattern

The core pattern is to add context at Effect boundaries - points where your code transitions between different layers of abstraction or performs I/O operations.

§Basic Usage

The following examples are illustrative patterns showing how to structure error handling. They reference hypothetical types (Config, read_file) to demonstrate the pattern.

use prodigy::error::{ProdigyError, ErrorExt};

fn read_config(path: &str) -> Result<Config, ProdigyError> {
    // Effect boundary: file I/O
    let content = std::fs::read_to_string(path)
        .map_err(ProdigyError::from)
        .context("Failed to read configuration file")?;

    // Effect boundary: parsing
    let config: Config = serde_json::from_str(&content)
        .map_err(ProdigyError::from)
        .context("Failed to parse configuration JSON")?;

    Ok(config)
}

fn load_application_config() -> Result<Config, ProdigyError> {
    // Effect boundary: calling lower-level function
    read_config("config.json")
        .context("Failed to load application configuration")?
}

This creates a context chain like:

Failed to load application configuration
  └─ Failed to read configuration file
     └─ No such file or directory (os error 2)

§Effect Boundaries

Add .context() calls at these boundaries:

  1. I/O Operations

    std::fs::write(path, data)
        .map_err(ProdigyError::from)
        .context(format!("Failed to write to {}", path))?;
  2. External Calls

    subprocess.execute()
        .context("Failed to execute git command")?;
  3. Layer Transitions

    storage.save_checkpoint(checkpoint)
        .context("Failed to persist workflow checkpoint")?;
  4. Error Propagation

    validate_workflow(&workflow)
        .context(format!("Validation failed for workflow '{}'", workflow.name))?;

§Advanced Patterns

Dynamic Context with Closures:

work_items.iter()
    .map(|item| {
        process_item(item)
            .with_context(|| format!("Failed to process item {}", item.id))
    })
    .collect::<Result<Vec<_>, _>>()?;

Context with Location Tracking:

use prodigy::error::ProdigyError;

fn critical_operation() -> Result<(), ProdigyError> {
    do_something()
        .map_err(ProdigyError::from)
        .context_at("In critical_operation")?;
    Ok(())
}

§Error Construction

Use the helper functions in helpers::common for creating errors:

use prodigy::error::helpers::common;
use std::path::PathBuf;

// Configuration file not found
let err = common::config_not_found("/etc/prodigy/config.yml");

// Storage I/O errors
let path = Some(PathBuf::from("/var/lib/prodigy/checkpoint.json"));
let err = common::storage_io_error(path, "read");

// Command not found
let err = common::command_not_found("git");

// Execution timeout
let err = common::execution_timeout("long_running_command", 30);

// Session not found
let err = common::session_not_found("session-123");

// Workflow validation failed
let err = common::workflow_validation_failed("deploy", "missing required field");

§Displaying Errors

Errors support multiple display formats:

User Message (end-user friendly):

use prodigy::error::ProdigyError;

let error = ProdigyError::config("Invalid configuration file");
let msg = error.user_message();
assert!(msg.contains("Configuration problem"));

Developer Message (full diagnostic info):

use prodigy::error::ProdigyError;

let error = ProdigyError::storage("File not found")
    .context("Loading configuration")
    .context("Starting application");

let dev_msg = error.developer_message();
assert!(dev_msg.contains("File not found"));
assert!(dev_msg.contains("Context chain"));

§Serialization

Convert errors to JSON for APIs and logging:

use prodigy::error::{ProdigyError, SerializableError};

let error = ProdigyError::execution("Command failed")
    .context("Running workflow");

let serializable = SerializableError::from(&error);
assert_eq!(serializable.kind, "Execution");

// Or use convenience methods
let json_string = error.to_json_string();
assert!(json_string.contains("Execution"));

§Migration Guide

To add context to existing error handling (illustrative pattern):

Before:

let data = read_file(path)?;

After:

let data = read_file(path)
    .context(format!("Failed to read file at {}", path))?;

For a real example, see the working tests above.

See the migration guide in docs/specs/ for comprehensive examples.

Re-exports§

pub use codes::describe_error_code;
pub use codes::ErrorCode;
pub use helpers::common;
pub use helpers::ErrorExt;
pub use serialization::SerializableError;

Modules§

codes
helpers
serialization

Structs§

ErrorContext
Error context entry

Enums§

ProdigyError
The unified error type for the entire Prodigy application

Type Aliases§

AppResult
Type alias for application Results (using anyhow for flexibility)
LibResult
Type alias for library Results (same as Result for now)
Result
Type alias for Results using ProdigyError