Documentation
¶
Overview ¶
Package errors provides structured error handling with hierarchical error codes.
This package defines the error taxonomy for AWF CLI, enabling machine-readable error identification, consistent error formatting, and programmatic error handling. It follows hexagonal architecture principles with zero infrastructure dependencies.
Architecture Role ¶
In the hexagonal architecture pattern:
- Domain layer defines error types and codes (this package)
- ErrorFormatter port interface for presentation concerns (domain/ports)
- Infrastructure layer implements formatters as adapters
- All layers use StructuredError for consistent error taxonomy
Core Types ¶
## Error Codes (codes.go)
Hierarchical error identifier system:
- ErrorCode: String type representing CATEGORY.SUBCATEGORY.SPECIFIC codes
- Category(): Extract top-level category (USER, WORKFLOW, EXECUTION, SYSTEM)
- Subcategory(): Extract middle classification (INPUT, VALIDATION, COMMAND, etc.)
- Specific(): Extract granular error identifier
Error code categories map to exit codes:
- USER.* → exit code 1 (bad input, missing file)
- WORKFLOW.* → exit code 2 (invalid state, cycles)
- EXECUTION.* → exit code 3 (command failed, timeout)
- SYSTEM.* → exit code 4 (I/O errors, permissions)
## Structured Error (structured_error.go)
Cross-cutting error type with taxonomy support:
- StructuredError: Error with Code, Message, Details, Cause, Timestamp
- Error(): Implements standard error interface
- Unwrap(): Supports error wrapping and errors.Is/As chains
- ExitCode(): Map error code to process exit code
## Error Catalog (catalog.go)
Error code documentation and resolution hints:
- ErrorInfo: Description, resolution guidance, related codes
- GetErrorInfo(): Lookup error metadata by code
- ListErrorCodes(): Enumerate all defined error codes
Error Code Taxonomy ¶
## USER Errors (exit code 1)
User-facing input and configuration errors:
- USER.INPUT.MISSING_FILE: Required file not found
- USER.INPUT.INVALID_FORMAT: File format validation failed
- USER.INPUT.VALIDATION_FAILED: Input parameter validation error
## WORKFLOW Errors (exit code 2)
Workflow definition and validation errors:
- WORKFLOW.PARSE.YAML_SYNTAX: YAML parsing error
- WORKFLOW.PARSE.UNKNOWN_FIELD: Unrecognized YAML field
- WORKFLOW.VALIDATION.CYCLE_DETECTED: State machine cycle detected
- WORKFLOW.VALIDATION.MISSING_STATE: Referenced state not defined
- WORKFLOW.VALIDATION.INVALID_TRANSITION: Malformed transition rule
## EXECUTION Errors (exit code 3)
Runtime execution failures:
- EXECUTION.COMMAND.FAILED: Shell command exited non-zero
- EXECUTION.COMMAND.TIMEOUT: Command exceeded timeout
- EXECUTION.PARALLEL.PARTIAL_FAILURE: Some parallel branches failed
## SYSTEM Errors (exit code 4)
Infrastructure and system-level failures:
- SYSTEM.IO.READ_FAILED: File read error
- SYSTEM.IO.WRITE_FAILED: File write error
- SYSTEM.IO.PERMISSION_DENIED: Insufficient permissions
Constructor Functions ¶
Convenience constructors for common patterns:
// Create with error code and message
err := errors.NewStructuredError(
errors.ErrorCodeUserInputMissingFile,
"workflow.yaml not found",
)
// Wrap existing error with code
err := errors.WrapError(
cause,
errors.ErrorCodeSystemIOReadFailed,
"failed to read config",
)
// Add contextual details
err := err.WithDetails(map[string]any{
"path": "/path/to/file",
"operation": "stat",
})
Usage Examples ¶
## Creating Structured Errors
Basic error with code and message:
func LoadWorkflow(path string) (*Workflow, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, errors.NewStructuredError(
errors.ErrorCodeUserInputMissingFile,
fmt.Sprintf("workflow file not found: %s", path),
)
}
// ...
}
## Wrapping Errors
Preserve cause while adding structure:
func SaveState(ctx *ExecutionContext) error {
data, err := json.Marshal(ctx)
if err != nil {
return errors.WrapError(
err,
errors.ErrorCodeSystemIOWriteFailed,
"failed to serialize state",
)
}
// ...
}
## Adding Details
Include debugging context:
err := errors.NewStructuredError(
errors.ErrorCodeExecutionCommandFailed,
"build command failed",
).WithDetails(map[string]any{
"command": "go build",
"exit_code": 2,
"step": "compile",
})
## Error Detection
Check for specific error codes:
if se, ok := err.(*errors.StructuredError); ok {
if se.Code.Category() == "USER" {
// User-facing error message
}
}
## Exit Code Mapping
Convert error to process exit code:
func main() {
if err := run(); err != nil {
if se, ok := err.(*errors.StructuredError); ok {
os.Exit(se.ExitCode())
}
os.Exit(1)
}
}
Design Principles ¶
## Hierarchical Structure
Three-level taxonomy enables:
- Broad categorization by error source (category)
- Mid-level grouping by error type (subcategory)
- Precise identification (specific)
Example: USER.INPUT.MISSING_FILE
- Category: USER (user-facing error)
- Subcategory: INPUT (input validation)
- Specific: MISSING_FILE (file not found)
## Machine-Readable
Error codes enable:
- Programmatic error handling in CI/CD
- Searchable error documentation
- Telemetry and error tracking
- Consistent error messages across formats
## Backward Compatibility
During migration:
- Exit codes preserved (1-4 mapping unchanged)
- String-based error detection still works
- Gradual migration without breaking changes
## Pure Domain
Zero infrastructure dependencies:
- stdlib only (errors, fmt, time, strings)
- No file I/O, HTTP, or external systems
- Presentation delegated to ErrorFormatter port
Related Packages ¶
- internal/domain/ports: ErrorFormatter port interface
- internal/domain/workflow: ValidationError for workflow validation
- internal/infrastructure/errors: Concrete formatter implementations
- internal/interfaces/cli: Error command and output integration
Index ¶
- Variables
- type CatalogEntry
- type ErrorCode
- type Hint
- type HintGenerator
- type StructuredError
- func NewExecutionError(code ErrorCode, message string, details map[string]any, cause error) *StructuredError
- func NewStructuredError(code ErrorCode, message string, details map[string]any, cause error) *StructuredError
- func NewSystemError(code ErrorCode, message string, details map[string]any, cause error) *StructuredError
- func NewUserError(code ErrorCode, message string, details map[string]any, cause error) *StructuredError
- func NewWorkflowError(code ErrorCode, message string, details map[string]any, cause error) *StructuredError
- func (e *StructuredError) As(target any) bool
- func (e *StructuredError) Error() string
- func (e *StructuredError) ExitCode() int
- func (e *StructuredError) Format(s fmt.State, verb rune)
- func (e *StructuredError) Is(target error) bool
- func (e *StructuredError) Unwrap() error
- func (e *StructuredError) WithDetails(additionalDetails map[string]any) *StructuredError
Constants ¶
This section is empty.
Variables ¶
var ErrorCatalog = map[ErrorCode]CatalogEntry{ ErrorCodeUserInputMissingFile: { Code: ErrorCodeUserInputMissingFile, Description: "The specified file was not found at the given path.", Resolution: "Verify the file path is correct and the file exists. Check for typos in the filename or path.", RelatedCodes: []ErrorCode{ErrorCodeUserInputInvalidFormat, ErrorCodeSystemIOReadFailed}, }, ErrorCodeUserInputInvalidFormat: { Code: ErrorCodeUserInputInvalidFormat, Description: "The file format does not match expected structure or contains invalid syntax.", Resolution: "Check the file format against the documentation. Ensure YAML syntax is valid if applicable.", RelatedCodes: []ErrorCode{ErrorCodeWorkflowParseYAMLSyntax, ErrorCodeUserInputValidationFailed}, }, ErrorCodeUserInputValidationFailed: { Code: ErrorCodeUserInputValidationFailed, Description: "Input parameter validation failed due to invalid or missing required values.", Resolution: "Review the command-line arguments and flags. Use --help for usage information.", RelatedCodes: []ErrorCode{ErrorCodeUserInputMissingFile, ErrorCodeUserInputInvalidFormat}, }, ErrorCodeWorkflowParseYAMLSyntax: { Code: ErrorCodeWorkflowParseYAMLSyntax, Description: "YAML parsing error due to syntax violation or malformed structure.", Resolution: "Validate YAML syntax using a YAML linter. Check for indentation errors, missing colons, or invalid characters.", RelatedCodes: []ErrorCode{ErrorCodeWorkflowParseUnknownField, ErrorCodeUserInputInvalidFormat}, }, ErrorCodeWorkflowParseUnknownField: { Code: ErrorCodeWorkflowParseUnknownField, Description: "The workflow definition contains an unrecognized field name.", Resolution: "Check the workflow schema documentation. Remove or rename the unrecognized field.", RelatedCodes: []ErrorCode{ErrorCodeWorkflowParseYAMLSyntax}, }, ErrorCodeWorkflowValidationCycleDetected: { Code: ErrorCodeWorkflowValidationCycleDetected, Description: "A cycle was detected in the workflow state machine transitions.", Resolution: "Review state transitions to identify and break the cycle. Ensure all paths lead to a terminal state.", RelatedCodes: []ErrorCode{ErrorCodeWorkflowValidationInvalidTransition, ErrorCodeWorkflowValidationMissingState}, }, ErrorCodeWorkflowValidationMissingState: { Code: ErrorCodeWorkflowValidationMissingState, Description: "A state referenced in a transition does not exist in the workflow definition.", Resolution: "Add the missing state definition or update the transition to reference an existing state.", RelatedCodes: []ErrorCode{ErrorCodeWorkflowValidationCycleDetected, ErrorCodeWorkflowValidationInvalidTransition}, }, ErrorCodeWorkflowValidationInvalidTransition: { Code: ErrorCodeWorkflowValidationInvalidTransition, Description: "A transition rule is malformed or violates state machine constraints.", Resolution: "Verify transition syntax. Check that source and target states are valid and transition logic is correct.", RelatedCodes: []ErrorCode{ErrorCodeWorkflowValidationMissingState, ErrorCodeWorkflowValidationCycleDetected}, }, ErrorCodeExecutionCommandFailed: { Code: ErrorCodeExecutionCommandFailed, Description: "A shell command executed during workflow execution exited with a non-zero status code.", Resolution: "Check command output for error details. Verify the command syntax and required dependencies are installed.", RelatedCodes: []ErrorCode{ErrorCodeExecutionCommandTimeout, ErrorCodeSystemIOPermissionDenied}, }, ErrorCodeExecutionCommandTimeout: { Code: ErrorCodeExecutionCommandTimeout, Description: "A command execution exceeded the configured timeout duration.", Resolution: "Increase the timeout value if the operation is expected to take longer, or optimize the command for faster execution.", RelatedCodes: []ErrorCode{ErrorCodeExecutionCommandFailed}, }, ErrorCodeExecutionParallelPartialFailure: { Code: ErrorCodeExecutionParallelPartialFailure, Description: "Some branches in a parallel execution block failed while others succeeded.", Resolution: "Review logs for failed branches. Fix underlying issues in failed steps or adjust parallel strategy.", RelatedCodes: []ErrorCode{ErrorCodeExecutionCommandFailed, ErrorCodeExecutionCommandTimeout}, }, ErrorCodeExecutionPluginDisabled: { Code: ErrorCodeExecutionPluginDisabled, Description: "An operation references a plugin that has been explicitly disabled.", Resolution: "Re-enable the plugin with 'awf plugin enable <name>' or remove the operation step from the workflow.", RelatedCodes: []ErrorCode{}, }, ErrorCodeSystemIOReadFailed: { Code: ErrorCodeSystemIOReadFailed, Description: "An I/O error occurred while attempting to read from a file or stream.", Resolution: "Check file permissions, disk space, and file system health. Verify the file is not locked by another process.", RelatedCodes: []ErrorCode{ErrorCodeSystemIOPermissionDenied, ErrorCodeUserInputMissingFile}, }, ErrorCodeSystemIOWriteFailed: { Code: ErrorCodeSystemIOWriteFailed, Description: "An I/O error occurred while attempting to write to a file or stream.", Resolution: "Check available disk space and write permissions. Verify the target directory exists and is writable.", RelatedCodes: []ErrorCode{ErrorCodeSystemIOPermissionDenied}, }, ErrorCodeSystemIOPermissionDenied: { Code: ErrorCodeSystemIOPermissionDenied, Description: "Insufficient permissions to access the requested file or directory.", Resolution: "Check file permissions with ls -l. Use chmod to grant necessary permissions or run with appropriate user privileges.", RelatedCodes: []ErrorCode{ErrorCodeSystemIOReadFailed, ErrorCodeSystemIOWriteFailed}, }, }
ErrorCatalog maps error codes to their documentation entries. Used by the `awf error <code>` CLI command for error code lookup.
Functions ¶
This section is empty.
Types ¶
type CatalogEntry ¶
type CatalogEntry struct {
// Code is the hierarchical error identifier.
Code ErrorCode
// Description explains what this error means in user-friendly terms.
Description string
// Resolution provides actionable guidance on how to fix the error.
Resolution string
// RelatedCodes lists other error codes commonly encountered with this error.
RelatedCodes []ErrorCode
}
CatalogEntry describes an error code with human-readable documentation, resolution guidance, and related error codes.
func GetCatalogEntry ¶
func GetCatalogEntry(code ErrorCode) (CatalogEntry, bool)
GetCatalogEntry retrieves the catalog entry for the given error code. Returns the entry and true if found, or an empty entry and false if not found.
type ErrorCode ¶
type ErrorCode string
ErrorCode represents a hierarchical error identifier in CATEGORY.SUBCATEGORY.SPECIFIC format. Error codes enable machine-readable error handling, consistent exit code mapping, and programmatic error detection across all architectural layers.
Format: CATEGORY.SUBCATEGORY.SPECIFIC
- CATEGORY: Top-level error classification (USER, WORKFLOW, EXECUTION, SYSTEM)
- SUBCATEGORY: Mid-level grouping by error type (INPUT, VALIDATION, COMMAND, IO)
- SPECIFIC: Precise error identifier (MISSING_FILE, CYCLE_DETECTED, etc.)
Example: USER.INPUT.MISSING_FILE
- Category(): "USER"
- Subcategory(): "INPUT"
- Specific(): "MISSING_FILE"
Error codes map to process exit codes:
- USER.* → exit code 1 (user-facing errors)
- WORKFLOW.* → exit code 2 (workflow definition errors)
- EXECUTION.* → exit code 3 (runtime execution errors)
- SYSTEM.* → exit code 4 (infrastructure errors)
const ( // ErrorCodeUserInputMissingFile indicates a required file was not found. ErrorCodeUserInputMissingFile ErrorCode = "USER.INPUT.MISSING_FILE" // ErrorCodeUserInputInvalidFormat indicates file format validation failed. ErrorCodeUserInputInvalidFormat ErrorCode = "USER.INPUT.INVALID_FORMAT" // ErrorCodeUserInputValidationFailed indicates input parameter validation error. ErrorCodeUserInputValidationFailed ErrorCode = "USER.INPUT.VALIDATION_FAILED" )
Error code constants for USER category (exit code 1). User-facing input and configuration errors.
const ( // ErrorCodeWorkflowParseYAMLSyntax indicates YAML parsing error. ErrorCodeWorkflowParseYAMLSyntax ErrorCode = "WORKFLOW.PARSE.YAML_SYNTAX" // ErrorCodeWorkflowParseUnknownField indicates an unrecognized YAML field. ErrorCodeWorkflowParseUnknownField ErrorCode = "WORKFLOW.PARSE.UNKNOWN_FIELD" // ErrorCodeWorkflowValidationCycleDetected indicates a state machine cycle. ErrorCodeWorkflowValidationCycleDetected ErrorCode = "WORKFLOW.VALIDATION.CYCLE_DETECTED" // ErrorCodeWorkflowValidationMissingState indicates a referenced state is not defined. ErrorCodeWorkflowValidationMissingState ErrorCode = "WORKFLOW.VALIDATION.MISSING_STATE" // ErrorCodeWorkflowValidationInvalidTransition indicates a malformed transition rule. ErrorCodeWorkflowValidationInvalidTransition ErrorCode = "WORKFLOW.VALIDATION.INVALID_TRANSITION" )
Error code constants for WORKFLOW category (exit code 2). Workflow definition parsing and validation errors.
const ( // ErrorCodeExecutionCommandFailed indicates shell command exited non-zero. ErrorCodeExecutionCommandFailed ErrorCode = "EXECUTION.COMMAND.FAILED" // ErrorCodeExecutionCommandTimeout indicates command exceeded timeout. ErrorCodeExecutionCommandTimeout ErrorCode = "EXECUTION.COMMAND.TIMEOUT" // ErrorCodeExecutionParallelPartialFailure indicates some parallel branches failed. ErrorCodeExecutionParallelPartialFailure ErrorCode = "EXECUTION.PARALLEL.PARTIAL_FAILURE" // ErrorCodeExecutionPluginDisabled indicates an operation references a disabled plugin. ErrorCodeExecutionPluginDisabled ErrorCode = "EXECUTION.PLUGIN.DISABLED" )
Error code constants for EXECUTION category (exit code 3). Runtime execution failures during workflow execution.
const ( // ErrorCodeSystemIOReadFailed indicates file read error. ErrorCodeSystemIOReadFailed ErrorCode = "SYSTEM.IO.READ_FAILED" // ErrorCodeSystemIOWriteFailed indicates file write error. ErrorCodeSystemIOWriteFailed ErrorCode = "SYSTEM.IO.WRITE_FAILED" // ErrorCodeSystemIOPermissionDenied indicates insufficient permissions. ErrorCodeSystemIOPermissionDenied ErrorCode = "SYSTEM.IO.PERMISSION_DENIED" )
Error code constants for SYSTEM category (exit code 4). Infrastructure and system-level failures.
func AllErrorCodes ¶
func AllErrorCodes() []ErrorCode
AllErrorCodes returns a sorted list of all defined error codes. Used by the `awf error` command to list all available error codes.
func (ErrorCode) Category ¶
Category extracts the top-level category from the error code. Returns empty string if the code format is invalid.
Examples:
- "USER.INPUT.MISSING_FILE" → "USER"
- "WORKFLOW.PARSE.YAML_SYNTAX" → "WORKFLOW"
- "INVALID" → ""
func (ErrorCode) ExitCode ¶
ExitCode maps the error code category to the corresponding process exit code. Returns:
- 1 for USER.* errors
- 2 for WORKFLOW.* errors
- 3 for EXECUTION.* errors
- 4 for SYSTEM.* errors
- 1 for invalid or unrecognized categories (default to user error)
func (ErrorCode) IsValid ¶
IsValid checks if the error code follows the required CATEGORY.SUBCATEGORY.SPECIFIC format. Returns true if the code has exactly three dot-separated parts, false otherwise.
func (ErrorCode) Specific ¶
Specific extracts the granular error identifier from the error code. Returns empty string if the code format is invalid.
Examples:
- "USER.INPUT.MISSING_FILE" → "MISSING_FILE"
- "WORKFLOW.PARSE.YAML_SYNTAX" → "YAML_SYNTAX"
- "USER.INPUT" → ""
func (ErrorCode) Subcategory ¶
Subcategory extracts the middle classification from the error code. Returns empty string if the code format is invalid.
Examples:
- "USER.INPUT.MISSING_FILE" → "INPUT"
- "WORKFLOW.PARSE.YAML_SYNTAX" → "PARSE"
- "USER" → ""
type Hint ¶
type Hint struct {
// Message is the actionable suggestion text.
// Should be a complete sentence without trailing punctuation (formatter adds styling).
Message string
}
Hint represents an actionable suggestion to help users resolve errors. Hints are context-aware messages displayed alongside error output to guide users toward successful resolution without requiring external documentation.
Design principles:
- Clear: Use plain language, avoid jargon
- Actionable: Suggest concrete steps, not vague advice
- Concise: One line when possible, max 2-3 lines
- Relevant: Generated from error context, not generic messages
Example hints:
- "Did you mean 'my-workflow.yaml'?" (Levenshtein suggestion)
- "Run 'awf list' to see available workflows"
- "Expected: 'version: 1.0'" (format guidance)
- "Check file permissions with 'ls -l /path/to/file'"
Hints are presentation-layer concerns and never stored in domain errors. Generated at render time by HintGenerator functions in the infrastructure layer.
type HintGenerator ¶
type HintGenerator func(err *StructuredError) []Hint
HintGenerator is a function that examines a StructuredError and generates zero or more actionable hints to help the user resolve the error.
Generators implement typed error detection using errors.As() to extract context from specific error types, then produce contextual suggestions.
Design:
- Pure functions: no side effects, no shared mutable state
- Return empty slice (not nil) when no hints available
- Order matters: most relevant hints first
- Limit: typically 1-3 hints per generator to avoid overwhelming users
Example generators:
- FileNotFoundHintGenerator: reads directory, suggests similar filenames
- YAMLSyntaxHintGenerator: extracts line/column from ParseError
- InvalidStateHintGenerator: uses Levenshtein to suggest closest state
- MissingInputHintGenerator: lists required inputs with examples
- CommandFailureHintGenerator: checks permissions, suggests verification
Thread safety: Generators must be safe for concurrent invocation. Stateless by design; context comes from StructuredError parameter.
type StructuredError ¶
type StructuredError struct {
// Code is the hierarchical error identifier (CATEGORY.SUBCATEGORY.SPECIFIC).
Code ErrorCode
// Message is the human-readable error description.
Message string
// Details contains additional structured context (e.g., file paths, field names).
// Optional. Use for machine-readable debugging information.
Details map[string]any
// Cause is the wrapped underlying error, if any.
// Optional. Enables error cause chains via Unwrap().
Cause error
// Timestamp records when the error was created.
// Used for telemetry, logging, and debugging temporal issues.
Timestamp time.Time
}
StructuredError represents a domain error with hierarchical error code, human-readable message, structured details, optional cause chain, and timestamp. Implements the error interface and supports error wrapping via Unwrap().
StructuredError enables:
- Machine-readable error codes for programmatic handling
- Exit code mapping via ErrorCode.ExitCode()
- Error cause chains for debugging
- Structured details (key-value pairs) for context
- Timestamp for telemetry and logging
Example:
err := NewStructuredError(
ErrorCodeUserInputMissingFile,
"workflow file not found",
map[string]any{"path": "/path/to/workflow.yaml"},
originalErr,
)
StructuredError is distinct from workflow.ValidationError:
- StructuredError: cross-cutting error taxonomy, all layers
- ValidationError: workflow-specific validation, has Level/Path fields
func NewExecutionError ¶
func NewStructuredError ¶
func NewSystemError ¶
func NewUserError ¶
func NewWorkflowError ¶
func (*StructuredError) As ¶
func (e *StructuredError) As(target any) bool
As implements error type assertion for errors.As(). Enables error chain traversal via errors.As().
func (*StructuredError) Error ¶
func (e *StructuredError) Error() string
func (*StructuredError) ExitCode ¶
func (e *StructuredError) ExitCode() int
ExitCode returns the process exit code for this error by delegating to ErrorCode.ExitCode(). Maps error categories to exit codes:
- USER.* → 1
- WORKFLOW.* → 2
- EXECUTION.* → 3
- SYSTEM.* → 4
func (*StructuredError) Format ¶
func (e *StructuredError) Format(s fmt.State, verb rune)
Format implements fmt.Formatter for custom error formatting. Supports:
- %s, %v: message only
- %+v: message with code and details
Example:
fmt.Printf("%+v", err) // "USER.INPUT.MISSING_FILE: workflow file not found (path=/workflow.yaml)"
func (*StructuredError) Is ¶
func (e *StructuredError) Is(target error) bool
Is implements error matching for errors.Is(). Returns true if the target is a StructuredError with the same ErrorCode.
func (*StructuredError) Unwrap ¶
func (e *StructuredError) Unwrap() error
Unwrap returns the underlying cause error, enabling error chain traversal. Returns nil if no cause is set.
func (*StructuredError) WithDetails ¶
func (e *StructuredError) WithDetails(additionalDetails map[string]any) *StructuredError
WithDetails returns a new StructuredError with the given details merged into the existing details. Useful for adding context as errors propagate up the call stack.
Example:
err := baseErr.WithDetails(map[string]any{"workflow_id": wf.ID})