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:
-
I/O Operations
ⓘstd::fs::write(path, data) .map_err(ProdigyError::from) .context(format!("Failed to write to {}", path))?; -
External Calls
ⓘsubprocess.execute() .context("Failed to execute git command")?; -
Layer Transitions
ⓘstorage.save_checkpoint(checkpoint) .context("Failed to persist workflow checkpoint")?; -
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§
Structs§
- Error
Context - Error context entry
Enums§
- Prodigy
Error - The unified error type for the entire Prodigy application