use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorCode {
pub code: String,
pub severity: String,
pub location: String,
pub hint: String,
}
impl ErrorCode {
pub fn new(
code: impl Into<String>,
severity: impl Into<String>,
location: impl Into<String>,
hint: impl Into<String>,
) -> Self {
Self {
code: code.into(),
severity: severity.into(),
location: location.into(),
hint: hint.into(),
}
}
pub fn from_splice_code(
splice_code: SpliceErrorCode,
file: Option<&str>,
line: Option<usize>,
column: Option<usize>,
) -> Self {
let location = match (file, line, column) {
(Some(f), Some(l), Some(c)) => format!("{}:{}:{}", f, l, c),
(Some(f), Some(l), None) => format!("{}:{}", f, l),
(Some(f), None, Some(c)) => format!("{}:{}", f, c),
(Some(f), None, None) => f.to_string(),
(None, _, _) => "<unknown>".to_string(),
};
Self {
code: splice_code.code(),
severity: splice_code.severity(),
location,
hint: splice_code.hint(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Error,
Warning,
Note,
}
impl ErrorSeverity {
pub fn as_str(&self) -> &'static str {
match self {
ErrorSeverity::Error => "error",
ErrorSeverity::Warning => "warning",
ErrorSeverity::Note => "note",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpliceErrorCode {
SymbolNotFound,
AmbiguousSymbol,
ReferenceFailed,
AmbiguousReference,
ParseError,
InvalidUtf8,
InvalidSyntax,
InvalidSpan,
InvalidLineRange,
SpanOutOfBounds,
FileReadError,
FileWriteError,
FileNotFound,
FileExternallyModified,
RenameFailed,
AmbiguousSymbolAsWarning,
FileSkipped,
FileExternallyModifiedWarning,
PreVerificationFailed,
ParseValidationFailed,
CompilerValidationFailed,
InvalidPlanSchema,
PlanExecutionFailed,
InvalidBatchSchema,
GraphError,
DatabaseError,
ExecutionLogError,
ExecutionNotFound,
AnalyzerNotAvailable,
AnalyzerFailed,
MagellanError,
}
impl SpliceErrorCode {
pub fn code(&self) -> String {
match self {
SpliceErrorCode::SymbolNotFound => "SPL-E001".to_string(),
SpliceErrorCode::AmbiguousSymbol => "SPL-E002".to_string(),
SpliceErrorCode::ReferenceFailed => "SPL-E003".to_string(),
SpliceErrorCode::AmbiguousReference => "SPL-E004".to_string(),
SpliceErrorCode::ParseError => "SPL-E011".to_string(),
SpliceErrorCode::InvalidUtf8 => "SPL-E012".to_string(),
SpliceErrorCode::InvalidSyntax => "SPL-E013".to_string(),
SpliceErrorCode::InvalidSpan => "SPL-E021".to_string(),
SpliceErrorCode::InvalidLineRange => "SPL-E022".to_string(),
SpliceErrorCode::SpanOutOfBounds => "SPL-E023".to_string(),
SpliceErrorCode::FileReadError => "SPL-E031".to_string(),
SpliceErrorCode::FileWriteError => "SPL-E032".to_string(),
SpliceErrorCode::FileNotFound => "SPL-E033".to_string(),
SpliceErrorCode::FileExternallyModified => "SPL-E034".to_string(),
SpliceErrorCode::RenameFailed => "SPL-E040".to_string(),
SpliceErrorCode::AmbiguousSymbolAsWarning => "SPL-W001".to_string(),
SpliceErrorCode::FileSkipped => "SPL-W002".to_string(),
SpliceErrorCode::FileExternallyModifiedWarning => "SPL-W003".to_string(),
SpliceErrorCode::PreVerificationFailed => "SPL-E041".to_string(),
SpliceErrorCode::ParseValidationFailed => "SPL-E042".to_string(),
SpliceErrorCode::CompilerValidationFailed => "SPL-E043".to_string(),
SpliceErrorCode::InvalidPlanSchema => "SPL-E051".to_string(),
SpliceErrorCode::PlanExecutionFailed => "SPL-E052".to_string(),
SpliceErrorCode::InvalidBatchSchema => "SPL-E053".to_string(),
SpliceErrorCode::GraphError => "SPL-E061".to_string(),
SpliceErrorCode::DatabaseError => "SPL-E062".to_string(),
SpliceErrorCode::ExecutionLogError => "SPL-E071".to_string(),
SpliceErrorCode::ExecutionNotFound => "SPL-E072".to_string(),
SpliceErrorCode::AnalyzerNotAvailable => "SPL-E081".to_string(),
SpliceErrorCode::AnalyzerFailed => "SPL-E082".to_string(),
SpliceErrorCode::MagellanError => "SPL-E091".to_string(),
}
}
pub fn severity(&self) -> String {
match self {
SpliceErrorCode::SymbolNotFound
| SpliceErrorCode::ReferenceFailed
| SpliceErrorCode::ParseError
| SpliceErrorCode::InvalidUtf8
| SpliceErrorCode::InvalidSyntax
| SpliceErrorCode::InvalidSpan
| SpliceErrorCode::InvalidLineRange
| SpliceErrorCode::SpanOutOfBounds
| SpliceErrorCode::FileReadError
| SpliceErrorCode::FileWriteError
| SpliceErrorCode::FileNotFound
| SpliceErrorCode::PreVerificationFailed
| SpliceErrorCode::ParseValidationFailed
| SpliceErrorCode::CompilerValidationFailed
| SpliceErrorCode::InvalidPlanSchema
| SpliceErrorCode::PlanExecutionFailed
| SpliceErrorCode::InvalidBatchSchema
| SpliceErrorCode::GraphError
| SpliceErrorCode::DatabaseError
| SpliceErrorCode::ExecutionLogError
| SpliceErrorCode::ExecutionNotFound
| SpliceErrorCode::AnalyzerNotAvailable
| SpliceErrorCode::AnalyzerFailed
| SpliceErrorCode::MagellanError
| SpliceErrorCode::RenameFailed => "error".to_string(),
SpliceErrorCode::AmbiguousSymbol
| SpliceErrorCode::AmbiguousReference
| SpliceErrorCode::FileExternallyModified
| SpliceErrorCode::AmbiguousSymbolAsWarning
| SpliceErrorCode::FileSkipped
| SpliceErrorCode::FileExternallyModifiedWarning => "warning".to_string(),
}
}
pub fn hint(&self) -> String {
match self {
SpliceErrorCode::SymbolNotFound => "Check that the symbol name is spelled correctly and exists in the codebase. Use --file to specify the file if the symbol is defined in multiple files.".to_string(),
SpliceErrorCode::AmbiguousSymbol => "The symbol name is defined in multiple files. Use --file to specify which file to use.".to_string(),
SpliceErrorCode::ReferenceFailed => "Failed to locate symbol references. Ensure the codebase has been indexed and the symbol is accessible.".to_string(),
SpliceErrorCode::AmbiguousReference => "The reference could refer to multiple definitions. Qualify the reference with module/type information to resolve ambiguity.".to_string(),
SpliceErrorCode::ParseError => "Check file syntax and ensure it's valid source code for the detected language.".to_string(),
SpliceErrorCode::InvalidUtf8 => "The file contains invalid UTF-8 encoding. Ensure the file is saved as UTF-8.".to_string(),
SpliceErrorCode::InvalidSyntax => "Check the file syntax and fix any errors reported by the compiler.".to_string(),
SpliceErrorCode::InvalidSpan => "The byte range is invalid. Ensure start <= end and both are within file bounds.".to_string(),
SpliceErrorCode::InvalidLineRange => "The line range is invalid. Ensure line numbers are positive and within file bounds.".to_string(),
SpliceErrorCode::SpanOutOfBounds => "The span extends beyond the file. Check file hasn't been modified since indexing.".to_string(),
SpliceErrorCode::FileReadError => "Check file permissions and ensure the file exists and is readable.".to_string(),
SpliceErrorCode::FileWriteError => "Check disk space, file permissions, and ensure the file is not locked by another process.".to_string(),
SpliceErrorCode::FileNotFound => "The specified file does not exist. Check the file path.".to_string(),
SpliceErrorCode::FileExternallyModified => "The file was modified by another process. Re-index the codebase and retry.".to_string(),
SpliceErrorCode::RenameFailed => "Rename operation failed. Check that the symbol exists, has valid references, and the new name is not already in use.".to_string(),
SpliceErrorCode::AmbiguousSymbolAsWarning => "The symbol name is defined in multiple files. Use --file to specify which file to use.".to_string(),
SpliceErrorCode::FileSkipped => "File was skipped during ingestion. Check file type and permissions.".to_string(),
SpliceErrorCode::FileExternallyModifiedWarning => "The file was modified by another process. Changes may not be reflected.".to_string(),
SpliceErrorCode::PreVerificationFailed => "A pre-verification check failed. Review the check details and fix the reported issue.".to_string(),
SpliceErrorCode::ParseValidationFailed => "The file failed to parse after modification. Revert the change and fix the syntax error.".to_string(),
SpliceErrorCode::CompilerValidationFailed => "Compiler reported errors. Fix the compilation errors before proceeding.".to_string(),
SpliceErrorCode::InvalidPlanSchema => "The plan JSON schema is invalid. Check the plan file format.".to_string(),
SpliceErrorCode::PlanExecutionFailed => "A step in the plan failed. Review the error details and fix the issue.".to_string(),
SpliceErrorCode::InvalidBatchSchema => "The batch JSON schema is invalid. Check the batch file format.".to_string(),
SpliceErrorCode::GraphError => "The code graph database error. Check database permissions and integrity.".to_string(),
SpliceErrorCode::DatabaseError => "Database operation failed. Check database connection and permissions.".to_string(),
SpliceErrorCode::ExecutionLogError => "Failed to access execution log. Check log database permissions.".to_string(),
SpliceErrorCode::ExecutionNotFound => "Execution ID not found in log. Verify the execution ID is correct.".to_string(),
SpliceErrorCode::AnalyzerNotAvailable => "The requested analyzer is not available. Install the analyzer or use a different validation mode.".to_string(),
SpliceErrorCode::AnalyzerFailed => "The analyzer reported diagnostics. Fix the reported issues.".to_string(),
SpliceErrorCode::MagellanError => {
"Check that the Magellan database file exists and is readable. \
Try re-indexing the codebase with `splice ingest`.".to_string()
}
}
}
pub fn from_splice_error(error: &crate::SpliceError) -> Option<Self> {
match error {
crate::SpliceError::SymbolNotFound { .. } => Some(SpliceErrorCode::SymbolNotFound),
crate::SpliceError::AmbiguousSymbol { .. } => Some(SpliceErrorCode::AmbiguousSymbol),
crate::SpliceError::ReferenceFailed { .. } => Some(SpliceErrorCode::ReferenceFailed),
crate::SpliceError::AmbiguousReference { .. } => {
Some(SpliceErrorCode::AmbiguousReference)
}
crate::SpliceError::Parse { .. } => Some(SpliceErrorCode::ParseError),
crate::SpliceError::InvalidUtf8 { .. } => Some(SpliceErrorCode::InvalidUtf8),
crate::SpliceError::CompilerError(_) => Some(SpliceErrorCode::InvalidSyntax),
crate::SpliceError::InvalidSpan { .. } => Some(SpliceErrorCode::InvalidSpan),
crate::SpliceError::InvalidLineRange { .. } => Some(SpliceErrorCode::InvalidLineRange),
crate::SpliceError::FileExternallyModified { .. } => {
Some(SpliceErrorCode::FileExternallyModified)
}
crate::SpliceError::PreVerificationFailed { .. } => {
Some(SpliceErrorCode::PreVerificationFailed)
}
crate::SpliceError::ParseValidationFailed { .. } => {
Some(SpliceErrorCode::ParseValidationFailed)
}
crate::SpliceError::CompilerValidationFailed { .. } => {
Some(SpliceErrorCode::CompilerValidationFailed)
}
crate::SpliceError::InvalidPlanSchema { .. } => {
Some(SpliceErrorCode::InvalidPlanSchema)
}
crate::SpliceError::PlanExecutionFailed { .. } => {
Some(SpliceErrorCode::PlanExecutionFailed)
}
crate::SpliceError::InvalidBatchSchema { .. } => {
Some(SpliceErrorCode::InvalidBatchSchema)
}
crate::SpliceError::Graph(_) => Some(SpliceErrorCode::GraphError),
crate::SpliceError::ExecutionLogError { .. } => {
Some(SpliceErrorCode::ExecutionLogError)
}
crate::SpliceError::ExecutionNotFound { .. } => {
Some(SpliceErrorCode::ExecutionNotFound)
}
crate::SpliceError::AnalyzerNotAvailable { .. } => {
Some(SpliceErrorCode::AnalyzerNotAvailable)
}
crate::SpliceError::AnalyzerFailed { .. } => Some(SpliceErrorCode::AnalyzerFailed),
crate::SpliceError::Io { .. } | crate::SpliceError::IoContext { .. } => {
Some(SpliceErrorCode::FileReadError)
}
crate::SpliceError::InsufficientDiskSpace { .. } => {
Some(SpliceErrorCode::FileWriteError)
}
crate::SpliceError::QueryError { .. } => Some(SpliceErrorCode::DatabaseError),
crate::SpliceError::ExecutionRecordFailed { .. } => {
Some(SpliceErrorCode::ExecutionLogError)
}
crate::SpliceError::InvalidDateFormat { .. } => {
Some(SpliceErrorCode::InvalidPlanSchema)
}
crate::SpliceError::CargoCheckFailed { .. } => {
Some(SpliceErrorCode::CompilerValidationFailed)
}
crate::SpliceError::Magellan { .. } => Some(SpliceErrorCode::MagellanError),
crate::SpliceError::RenameFailed { .. } => Some(SpliceErrorCode::RenameFailed),
crate::SpliceError::InvalidBatchSpec { .. } => {
Some(SpliceErrorCode::InvalidBatchSchema)
}
crate::SpliceError::BatchOperationFailed { .. } => {
Some(SpliceErrorCode::PlanExecutionFailed)
}
crate::SpliceError::BrokenPipe
| crate::SpliceError::Utf8(_)
| crate::SpliceError::Other(_) => None,
}
}
}
pub fn get_error_explanation(code: &str) -> Option<&'static str> {
match code {
"SPL-E001" => Some(
r#"
Symbol Not Found (SPL-E001)
The specified symbol could not be found in the codebase.
POSSIBLE CAUSES:
- The symbol name is misspelled
- The symbol hasn't been ingested into the code graph
- The symbol exists in multiple files (use --file to disambiguate)
- The symbol is defined in a file that hasn't been indexed
WHAT TO DO:
1. Check the symbol name is spelled correctly
2. Run `splice ingest` to ensure the codebase is indexed
3. Use `splice query` to search for symbols by label
4. Use `splice delete --file <path>` to specify which file
5. Use `splice explain SPL-E002` for help with ambiguous symbols
RELATED: SPL-E002 (Ambiguous Symbol)
"#,
),
"SPL-E002" => Some(
r#"
Ambiguous Symbol (SPL-E002)
The symbol name exists in multiple files, making it ambiguous which one
to use without additional context.
POSSIBLE CAUSES:
- Common symbol names like `main`, `run`, `process` used in multiple files
- Multiple files define the same function/struct name
- File context was not specified for the operation
WHAT TO DO:
1. Use `--file <path>` to specify which file contains the symbol
2. Use `splice query --db <db> --label <label>` to list all symbols
3. Rename one of the conflicting symbols if appropriate
4. Use fully-qualified names if supported by the language
RELATED: SPL-E001 (Symbol Not Found)
"#,
),
"SPL-E003" => Some(
r#"
Reference Failed (SPL-E003)
Failed to locate symbol references in the codebase.
POSSIBLE CAUSES:
- The code graph is incomplete or outdated
- Reference edges haven't been created during ingestion
- The symbol exists but references weren't indexed
WHAT TO DO:
1. Re-run `splice ingest` to rebuild the code graph
2. Check that the source files haven't been modified since indexing
3. Use `splice log --operation-type ingest` to check ingestion status
4. Report this as a bug if the issue persists
RELATED: SPL-E061 (Graph Error)
"#,
),
"SPL-E004" => Some(
r#"
Ambiguous Reference (SPL-E004)
A reference could refer to multiple definitions, making it ambiguous
which one is being referenced.
POSSIBLE CAUSES:
- Multiple symbols with the same name in scope
- Import/use statements bring in conflicting names
- Type inference cannot determine which overload to use
WHAT TO DO:
1. Qualify the reference with module/type information
2. Use explicit imports instead of wildcards
3. Rename one of the conflicting symbols if appropriate
4. Check the error message for candidate definitions
RELATED: SPL-E002 (Ambiguous Symbol)
"#,
),
"SPL-E011" => Some(
r#"
Parse Error (SPL-E011)
Tree-sitter failed to parse the source file.
POSSIBLE CAUSES:
- Invalid syntax for the detected language
- Incomplete source file (e.g., missing closing brace)
- File encoding issues (should be UTF-8)
- Corrupted or truncated file
WHAT TO DO:
1. Check file syntax is valid for the language
2. Ensure the file is complete and properly formatted
3. Verify the file is saved as UTF-8 encoding
4. Use a language server or compiler to check for syntax errors
5. Re-ingest the file after fixing syntax
RELATED: SPL-E012 (Invalid UTF-8), SPL-E013 (Invalid Syntax)
"#,
),
"SPL-E012" => Some(
r#"
Invalid UTF-8 (SPL-E012)
The file contains invalid UTF-8 encoding.
POSSIBLE CAUSES:
- File was saved with a different encoding (e.g., Latin-1, UTF-16)
- File contains binary data or corruption
- File was transferred without preserving encoding
WHAT TO DO:
1. Convert the file to UTF-8 encoding
2. Use `file --mime-encoding <path>` to check current encoding
3. Use `iconv` or similar tool to convert encodings
4. Ensure your editor is configured to save files as UTF-8
RELATED: SPL-E011 (Parse Error)
"#,
),
"SPL-E013" => Some(
r#"
Invalid Syntax (SPL-E013)
The compiler reported a syntax error in the source file.
POSSIBLE CAUSES:
- Typo or mistake in the source code
- Incomplete statement or block
- Language feature used incorrectly
- Missing imports or dependencies
WHAT TO DO:
1. Check the file syntax and fix any errors reported by the compiler
2. Use the compiler's error messages for specific line/column information
3. Run `cargo check` (Rust) or equivalent for your language
4. Refer to language documentation for correct syntax
RELATED: SPL-E011 (Parse Error), SPL-E043 (Compiler Validation Failed)
"#,
),
"SPL-E021" => Some(
r#"
Invalid Span (SPL-E021)
The byte range specified is invalid.
POSSIBLE CAUSES:
- Start position is after end position
- Byte offsets are negative or extremely large
- Span was calculated incorrectly
WHAT TO DO:
1. Ensure start <= end for byte ranges
2. Check that byte offsets are within file bounds
3. Verify the span calculation logic
4. Use `splice query` to get valid spans for symbols
RELATED: SPL-E022 (Invalid Line Range), SPL-E023 (Span Out of Bounds)
"#,
),
"SPL-E022" => Some(
r#"
Invalid Line Range (SPL-E022)
The line range specified is invalid.
POSSIBLE CAUSES:
- Start line is after end line
- Line numbers are zero or negative
- Line numbers exceed file's total lines
WHAT TO DO:
1. Ensure line_start <= line_end
2. Use 1-based line numbering (first line is 1, not 0)
3. Check the file's total line count
4. Use `splice get` to retrieve valid line ranges
RELATED: SPL-E021 (Invalid Span), SPL-E023 (Span Out of Bounds)
"#,
),
"SPL-E023" => Some(
r#"
Span Out of Bounds (SPL-E023)
The span extends beyond the file's boundaries.
POSSIBLE CAUSES:
- File was modified since the span was calculated
- Byte offsets were calculated incorrectly
- File size has changed
WHAT TO DO:
1. Re-index the codebase with `splice ingest`
2. Check that the file hasn't been modified externally
3. Verify the file size matches expectations
4. Use checksums to detect file modifications
RELATED: SPL-E034 (File Externally Modified)
"#,
),
"SPL-E031" => Some(
r#"
File Read Error (SPL-E031)
Failed to read a file.
POSSIBLE CAUSES:
- File permissions prevent reading
- File is locked by another process
- File doesn't exist
- Insufficient system resources
WHAT TO DO:
1. Check file permissions with `ls -l <path>`
2. Ensure the file exists and is not locked
3. Check disk space and system resources
4. Verify the file path is correct
RELATED: SPL-E033 (File Not Found), SPL-E032 (File Write Error)
"#,
),
"SPL-E032" => Some(
r#"
File Write Error (SPL-E032)
Failed to write to a file.
POSSIBLE CAUSES:
- Insufficient disk space
- File permissions prevent writing
- File is locked by another process
- Directory doesn't exist
WHAT TO DO:
1. Check available disk space with `df -h`
2. Verify write permissions with `ls -ld <dir>`
3. Ensure the file is not open in another program
4. Create parent directories if needed
RELATED: SPL-E031 (File Read Error), SPL-E034 (File Externally Modified)
"#,
),
"SPL-E033" => Some(
r#"
File Not Found (SPL-E033)
The specified file does not exist.
POSSIBLE CAUSES:
- File path is incorrect or misspelled
- File was deleted or moved
- Relative path is used from wrong directory
WHAT TO DO:
1. Check the file path is correct
2. Use absolute paths if relative paths are problematic
3. Verify the file exists with `ls <path>`
4. Run `splice ingest` to re-index if file was moved
RELATED: SPL-E031 (File Read Error)
"#,
),
"SPL-E034" => Some(
r#"
File Externally Modified (SPL-E034)
The file was modified by another process since being indexed.
POSSIBLE CAUSES:
- File was edited in another editor or IDE
- Build process generated or modified the file
- Code formatter or linter modified the file
WHAT TO DO:
1. Re-index the codebase with `splice ingest`
2. Check for background processes modifying files
3. Use file checksums to detect modifications
4. Consider using file watchers to detect external changes
RELATED: SPL-E023 (Span Out of Bounds)
"#,
),
"SPL-E041" => Some(
r#"
Pre-Verification Failed (SPL-E041)
A pre-verification check failed before applying changes.
POSSIBLE CAUSES:
- Pre-condition checks detected an issue
- Validation gate found a blocking problem
- Resource constraints (disk space, etc.)
WHAT TO DO:
1. Review the specific check that failed
2. Fix the reported issue
3. Use `--skip-pre-verify` with caution (dangerous)
4. Check system resources if applicable
RELATED: SPL-E042 (Parse Validation Failed), SPL-E043 (Compiler Validation Failed)
"#,
),
"SPL-E042" => Some(
r#"
Parse Validation Failed (SPL-E042)
The file failed to parse after modification.
POSSIBLE CAUSES:
- Modification introduced a syntax error
- Incomplete replacement or deletion
- File was corrupted during write
WHAT TO DO:
1. Revert the change using `splice undo`
2. Check the modification for syntax errors
3. Use `--dry-run` to preview changes before applying
4. Ensure the replacement content is complete and valid
RELATED: SPL-E011 (Parse Error), SPL-E041 (Pre-Verification Failed)
"#,
),
"SPL-E043" => Some(
r#"
Compiler Validation Failed (SPL-E043)
Compiler reported errors after modification.
POSSIBLE CAUSES:
- Modification introduced compilation errors
- Type errors or missing imports
- Breaking changes to API usage
WHAT TO DO:
1. Check compiler output for specific errors
2. Fix compilation errors before proceeding
3. Use `--dry-run` to preview changes
4. Run compiler manually for detailed error messages
RELATED: SPL-E013 (Invalid Syntax), SPL-E081 (Analyzer Not Available)
"#,
),
"SPL-E051" => Some(
r#"
Invalid Plan Schema (SPL-E051)
The plan JSON schema is invalid.
POSSIBLE CAUSES:
- JSON syntax errors in plan file
- Missing required fields
- Invalid data types for fields
- Schema version mismatch
WHAT TO DO:
1. Validate JSON syntax with `jq . < plan.json`
2. Check the plan file format in documentation
3. Ensure all required fields are present
4. Verify schema version matches Splice version
RELATED: SPL-E053 (Invalid Batch Schema)
"#,
),
"SPL-E052" => Some(
r#"
Plan Execution Failed (SPL-E052)
A step in the plan failed during execution.
POSSIBLE CAUSES:
- One of the plan steps encountered an error
- Resource constraints during execution
- File system issues
WHAT TO DO:
1. Review the specific step that failed
2. Check the error message for details
3. Fix the underlying issue and re-run the plan
4. Use `--dry-run` to preview plan execution
RELATED: SPL-E051 (Invalid Plan Schema)
"#,
),
"SPL-E053" => Some(
r#"
Invalid Batch Schema (SPL-E053)
The batch JSON schema is invalid.
POSSIBLE CAUSES:
- JSON syntax errors in batch file
- Missing required fields
- Invalid data types for fields
- Incorrect array or object structure
WHAT TO DO:
1. Validate JSON syntax with `jq . < batch.json`
2. Check the batch file format in documentation
3. Ensure all required fields are present
4. Verify the file/replacement structure
RELATED: SPL-E051 (Invalid Plan Schema)
"#,
),
"SPL-E061" => Some(
r#"
Graph Error (SPL-E061)
The code graph database encountered an error.
POSSIBLE CAUSES:
- Database corruption
- Insufficient permissions
- Database locked by another process
- Incompatible database version
WHAT TO DO:
1. Check database permissions with `ls -l codegraph.db`
2. Ensure no other process is using the database
3. Try rebuilding the database with `splice ingest --force`
4. Check database version compatibility
RELATED: SPL-E062 (Database Error), SPL-E003 (Reference Failed)
"#,
),
"SPL-E062" => Some(
r#"
Database Error (SPL-E062)
A database operation failed.
POSSIBLE CAUSES:
- Query execution error
- Constraint violation
- Transaction failure
- Connection issues
WHAT TO DO:
1. Check database integrity
2. Rebuild the database if needed
3. Verify sufficient disk space
4. Check for concurrent access issues
RELATED: SPL-E061 (Graph Error)
"#,
),
"SPL-E071" => Some(
r#"
Execution Log Error (SPL-E071)
Failed to access the execution log database.
POSSIBLE CAUSES:
- Database permissions issue
- Execution log database corrupted
- Database locked by another process
WHAT TO DO:
1. Check execution log permissions
2. Ensure Splice has write access to log directory
3. Close other processes that might be using the log
4. Re-create the log database if corrupted
RELATED: SPL-E062 (Database Error)
"#,
),
"SPL-E072" => Some(
r#"
Execution Not Found (SPL-E072)
The specified execution ID was not found in the log.
POSSIBLE CAUSES:
- Execution ID is incorrect or misspelled
- Execution was logged to a different database
- Execution log was cleared or truncated
WHAT TO DO:
1. Verify the execution ID is correct
2. Use `splice log --stats` to list recent executions
3. Check the execution log database location
4. Re-run the operation if needed
RELATED: SPL-E071 (Execution Log Error)
"#,
),
"SPL-E081" => Some(
r#"
Analyzer Not Available (SPL-E081)
The requested analyzer is not available.
POSSIBLE CAUSES:
- rust-analyzer or other tool is not installed
- Analyzer is not in PATH
- Invalid analyzer path specified
WHAT TO DO:
1. Install the required analyzer (e.g., `rustup component add rust-analyzer`)
2. Ensure the analyzer is in your PATH
3. Use `--analyzer path` to specify explicit path
4. Use `--analyzer off` to disable analyzer validation
RELATED: SPL-E082 (Analyzer Failed)
"#,
),
"SPL-E082" => Some(
r#"
Analyzer Failed (SPL-E082)
The analyzer reported diagnostics.
POSSIBLE CAUSES:
- Code has issues detected by the analyzer
- Analyzer configuration problems
- False positives from analyzer
WHAT TO DO:
1. Review the analyzer diagnostics
2. Fix legitimate issues reported
3. Consider disabling analyzer for false positives
4. Update analyzer version if outdated
RELATED: SPL-E043 (Compiler Validation Failed), SPL-E081 (Analyzer Not Available)
"#,
),
"SPL-E091" => Some(
r#"
Magellan Error (SPL-E091)
An error occurred in the Magellan code graph integration.
POSSIBLE CAUSES:
- Database file is corrupted or incompatible
- Insufficient permissions to read the database
- Database file doesn't exist (need to run `splice ingest` first)
- Magellan internal error
WHAT TO DO:
1. Check that the database file exists: ls -l <db_path>
2. Verify file permissions: readable by current user
3. Try re-indexing: `splice ingest --force`
4. Check if Magellan version is compatible
RELATED: SPL-E061 (Graph Error), SPL-E031 (File Read Error)
"#,
),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_code_format() {
assert_eq!(SpliceErrorCode::SymbolNotFound.code(), "SPL-E001");
assert_eq!(SpliceErrorCode::AmbiguousSymbol.code(), "SPL-E002");
assert_eq!(SpliceErrorCode::ParseError.code(), "SPL-E011");
assert_eq!(SpliceErrorCode::InvalidSpan.code(), "SPL-E021");
assert_eq!(SpliceErrorCode::FileReadError.code(), "SPL-E031");
assert_eq!(SpliceErrorCode::PreVerificationFailed.code(), "SPL-E041");
assert_eq!(SpliceErrorCode::InvalidPlanSchema.code(), "SPL-E051");
assert_eq!(SpliceErrorCode::GraphError.code(), "SPL-E061");
assert_eq!(SpliceErrorCode::ExecutionLogError.code(), "SPL-E071");
assert_eq!(SpliceErrorCode::AnalyzerNotAvailable.code(), "SPL-E081");
}
#[test]
fn test_error_code_severity() {
assert_eq!(SpliceErrorCode::SymbolNotFound.severity(), "error");
assert_eq!(SpliceErrorCode::ParseError.severity(), "error");
assert_eq!(SpliceErrorCode::InvalidSpan.severity(), "error");
}
#[test]
fn test_error_code_has_hint() {
let code = SpliceErrorCode::SymbolNotFound;
let hint = code.hint();
assert!(!hint.is_empty());
assert!(hint.contains("symbol") || hint.contains("file"));
}
#[test]
fn test_error_code_from_splice_error() {
use crate::SpliceError;
let symbol_error = SpliceError::symbol_not_found("foo", None);
let code = SpliceErrorCode::from_splice_error(&symbol_error);
assert_eq!(code, Some(SpliceErrorCode::SymbolNotFound));
let ambiguous_error = SpliceError::AmbiguousSymbol {
name: "foo".to_string(),
files: vec!["a.rs".to_string(), "b.rs".to_string()],
};
let code = SpliceErrorCode::from_splice_error(&ambiguous_error);
assert_eq!(code, Some(SpliceErrorCode::AmbiguousSymbol));
}
#[test]
fn test_error_code_coverage() {
use crate::SpliceError;
use std::path::PathBuf;
let mut mapped_count = 0;
let error = SpliceError::symbol_not_found("test", None);
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::AmbiguousSymbol {
name: "foo".to_string(),
files: vec!["a.rs".to_string()],
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::ReferenceFailed {
name: "foo".to_string(),
reason: "test".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::AmbiguousReference {
name: "foo".to_string(),
file: "test.rs".to_string(),
line: 1,
col: 1,
candidates: vec!["a::foo".to_string()],
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::Parse {
file: PathBuf::from("test.rs"),
message: "test error".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let invalid_utf8 = std::str::from_utf8(b"\xff\xfe").unwrap_err();
let error = SpliceError::InvalidUtf8 {
file: PathBuf::from("test.rs"),
source: invalid_utf8,
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::CompilerError("syntax error".to_string());
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::InvalidSpan {
file: PathBuf::from("test.rs"),
start: 0,
end: 10,
file_size: 100,
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::InvalidLineRange {
file: PathBuf::from("test.rs"),
line_start: 1,
line_end: 10,
total_lines: 20,
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::FileExternallyModified {
file: "test.rs".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::Io {
path: PathBuf::from("test.rs"),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "test"),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::IoContext {
context: "test context".to_string(),
source: std::io::Error::new(std::io::ErrorKind::PermissionDenied, "test"),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::InsufficientDiskSpace {
needed: 1000,
available: 100,
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::PreVerificationFailed {
check: "test check".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::ParseValidationFailed {
file: PathBuf::from("test.rs"),
message: "validation failed".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::CompilerValidationFailed {
file: PathBuf::from("test.rs"),
language: "rust".to_string(),
diagnostics: vec![],
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::InvalidPlanSchema {
message: "invalid schema".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::PlanExecutionFailed {
step: 1,
error: "failed".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::InvalidBatchSchema {
message: "invalid batch".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::Graph(sqlitegraph::SqliteGraphError::connection("test error"));
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::ExecutionLogError {
message: "log error".to_string(),
source: None,
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::ExecutionNotFound {
execution_id: "test-id".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::ExecutionRecordFailed {
execution_id: "test-id".to_string(),
source: None,
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::AnalyzerNotAvailable {
mode: "path".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::AnalyzerFailed {
output: "failed".to_string(),
diagnostics: vec![],
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::QueryError {
message: "query failed".to_string(),
source: None,
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::InvalidDateFormat {
input: "invalid".to_string(),
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::CargoCheckFailed {
workspace: PathBuf::from("/workspace"),
output: "check failed".to_string(),
diagnostics: vec![],
};
assert!(SpliceErrorCode::from_splice_error(&error).is_some());
mapped_count += 1;
let error = SpliceError::BrokenPipe;
assert!(
SpliceErrorCode::from_splice_error(&error).is_none(),
"BrokenPipe should not have an error code (terminal state)"
);
let invalid_utf8 = std::str::from_utf8(b"\xff\xfe").unwrap_err();
let error = SpliceError::Utf8(invalid_utf8);
assert!(
SpliceErrorCode::from_splice_error(&error).is_none(),
"Utf8 variant should not have an error code (covered by InvalidUtf8)"
);
let error = SpliceError::Other("generic error".to_string());
assert!(
SpliceErrorCode::from_splice_error(&error).is_none(),
"Other variant should not have an error code (generic catchall)"
);
assert!(
mapped_count >= 22,
"Expected at least 22 error-level variants to be mapped, got {}",
mapped_count
);
println!("Total error-level variants mapped: {}", mapped_count);
}
#[test]
fn test_explain_command_generation() {
use crate::SpliceError;
let symbol_error = SpliceError::symbol_not_found("foo", None);
let payload = crate::cli::CliErrorPayload::from_error(&symbol_error);
assert!(payload.error.error_code.is_some());
assert!(payload.error.explain_command.is_some());
let explain_cmd = payload.error.explain_command.as_ref().unwrap();
assert_eq!(explain_cmd, "splice explain --code SPL-E001");
assert!(explain_cmd.contains("splice explain --code"));
let broken_pipe_error = SpliceError::BrokenPipe;
let payload = crate::cli::CliErrorPayload::from_error(&broken_pipe_error);
assert!(payload.error.error_code.is_none());
assert!(payload.error.explain_command.is_none());
let test_cases = vec![
(
SpliceError::Parse {
file: std::path::PathBuf::from("test.rs"),
message: "parse error".to_string(),
},
"SPL-E011",
),
(
SpliceError::InvalidSpan {
file: std::path::PathBuf::from("test.rs"),
start: 0,
end: 10,
file_size: 100,
},
"SPL-E021",
),
(
SpliceError::Io {
path: std::path::PathBuf::from("test.rs"),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "not found"),
},
"SPL-E031",
),
];
for (error, expected_code) in test_cases {
let payload = crate::cli::CliErrorPayload::from_error(&error);
assert!(payload.error.error_code.is_some());
assert!(payload.error.explain_command.is_some());
let explain_cmd = payload.error.explain_command.as_ref().unwrap();
let expected = format!("splice explain --code {}", expected_code);
assert_eq!(explain_cmd, &expected);
}
}
#[test]
fn test_error_code_construction() {
let code = ErrorCode::new(
"SPL-E001",
"error",
"src/main.rs:10:5",
"Check symbol spelling",
);
assert_eq!(code.code, "SPL-E001");
assert_eq!(code.severity, "error");
assert_eq!(code.location, "src/main.rs:10:5");
assert_eq!(code.hint, "Check symbol spelling");
}
#[test]
fn test_error_code_from_splice_code() {
let error_code = ErrorCode::from_splice_code(
SpliceErrorCode::SymbolNotFound,
Some("src/main.rs"),
Some(10),
Some(5),
);
assert_eq!(error_code.code, "SPL-E001");
assert_eq!(error_code.severity, "error");
assert_eq!(error_code.location, "src/main.rs:10:5");
assert!(!error_code.hint.is_empty());
}
#[test]
fn test_error_code_location_formats() {
let ec = ErrorCode::from_splice_code(
SpliceErrorCode::InvalidSpan,
Some("test.rs"),
Some(5),
Some(10),
);
assert_eq!(ec.location, "test.rs:5:10");
let ec = ErrorCode::from_splice_code(
SpliceErrorCode::FileReadError,
Some("test.rs"),
None,
None,
);
assert_eq!(ec.location, "test.rs");
let ec = ErrorCode::from_splice_code(SpliceErrorCode::GraphError, None, None, None);
assert_eq!(ec.location, "<unknown>");
}
#[test]
fn test_error_severity_as_str() {
assert_eq!(ErrorSeverity::Error.as_str(), "error");
assert_eq!(ErrorSeverity::Warning.as_str(), "warning");
assert_eq!(ErrorSeverity::Note.as_str(), "note");
}
#[test]
fn test_error_code_severity_error() {
assert_eq!(SpliceErrorCode::SymbolNotFound.severity(), "error");
assert_eq!(SpliceErrorCode::ReferenceFailed.severity(), "error");
assert_eq!(SpliceErrorCode::ParseError.severity(), "error");
assert_eq!(SpliceErrorCode::InvalidUtf8.severity(), "error");
assert_eq!(SpliceErrorCode::InvalidSyntax.severity(), "error");
assert_eq!(SpliceErrorCode::InvalidSpan.severity(), "error");
assert_eq!(SpliceErrorCode::InvalidLineRange.severity(), "error");
assert_eq!(SpliceErrorCode::SpanOutOfBounds.severity(), "error");
assert_eq!(SpliceErrorCode::FileReadError.severity(), "error");
assert_eq!(SpliceErrorCode::FileWriteError.severity(), "error");
assert_eq!(SpliceErrorCode::FileNotFound.severity(), "error");
assert_eq!(SpliceErrorCode::PreVerificationFailed.severity(), "error");
assert_eq!(SpliceErrorCode::ParseValidationFailed.severity(), "error");
assert_eq!(
SpliceErrorCode::CompilerValidationFailed.severity(),
"error"
);
assert_eq!(SpliceErrorCode::InvalidPlanSchema.severity(), "error");
assert_eq!(SpliceErrorCode::PlanExecutionFailed.severity(), "error");
assert_eq!(SpliceErrorCode::InvalidBatchSchema.severity(), "error");
assert_eq!(SpliceErrorCode::GraphError.severity(), "error");
assert_eq!(SpliceErrorCode::DatabaseError.severity(), "error");
assert_eq!(SpliceErrorCode::ExecutionLogError.severity(), "error");
assert_eq!(SpliceErrorCode::ExecutionNotFound.severity(), "error");
assert_eq!(SpliceErrorCode::AnalyzerNotAvailable.severity(), "error");
assert_eq!(SpliceErrorCode::AnalyzerFailed.severity(), "error");
}
#[test]
fn test_error_code_severity_warning() {
assert_eq!(SpliceErrorCode::AmbiguousSymbol.severity(), "warning");
assert_eq!(SpliceErrorCode::AmbiguousReference.severity(), "warning");
assert_eq!(
SpliceErrorCode::FileExternallyModified.severity(),
"warning"
);
assert_eq!(
SpliceErrorCode::AmbiguousSymbolAsWarning.severity(),
"warning"
);
assert_eq!(SpliceErrorCode::FileSkipped.severity(), "warning");
assert_eq!(
SpliceErrorCode::FileExternallyModifiedWarning.severity(),
"warning"
);
}
#[test]
fn test_error_code_all_have_severity() {
let all_codes = [
SpliceErrorCode::SymbolNotFound,
SpliceErrorCode::AmbiguousSymbol,
SpliceErrorCode::ReferenceFailed,
SpliceErrorCode::AmbiguousReference,
SpliceErrorCode::ParseError,
SpliceErrorCode::InvalidUtf8,
SpliceErrorCode::InvalidSyntax,
SpliceErrorCode::InvalidSpan,
SpliceErrorCode::InvalidLineRange,
SpliceErrorCode::SpanOutOfBounds,
SpliceErrorCode::FileReadError,
SpliceErrorCode::FileWriteError,
SpliceErrorCode::FileNotFound,
SpliceErrorCode::FileExternallyModified,
SpliceErrorCode::AmbiguousSymbolAsWarning,
SpliceErrorCode::FileSkipped,
SpliceErrorCode::FileExternallyModifiedWarning,
SpliceErrorCode::PreVerificationFailed,
SpliceErrorCode::ParseValidationFailed,
SpliceErrorCode::CompilerValidationFailed,
SpliceErrorCode::InvalidPlanSchema,
SpliceErrorCode::PlanExecutionFailed,
SpliceErrorCode::InvalidBatchSchema,
SpliceErrorCode::GraphError,
SpliceErrorCode::DatabaseError,
SpliceErrorCode::ExecutionLogError,
SpliceErrorCode::ExecutionNotFound,
SpliceErrorCode::AnalyzerNotAvailable,
SpliceErrorCode::AnalyzerFailed,
];
for code in all_codes.iter() {
let severity = code.severity();
assert!(
severity == "error" || severity == "warning" || severity == "note",
"Code {:?} has invalid severity: {}",
code,
severity
);
}
}
#[test]
fn test_warning_code_format() {
assert_eq!(SpliceErrorCode::AmbiguousSymbolAsWarning.code(), "SPL-W001");
assert_eq!(SpliceErrorCode::FileSkipped.code(), "SPL-W002");
assert_eq!(
SpliceErrorCode::FileExternallyModifiedWarning.code(),
"SPL-W003"
);
}
#[test]
fn test_warning_code_from_splice_code() {
let warning_code = ErrorCode::from_splice_code(
SpliceErrorCode::AmbiguousSymbolAsWarning,
Some("src/main.rs"),
Some(10),
Some(5),
);
assert_eq!(warning_code.code, "SPL-W001");
assert_eq!(warning_code.severity, "warning");
assert_eq!(warning_code.location, "src/main.rs:10:5");
assert!(!warning_code.hint.is_empty());
}
}