venus_core/
error.rs

1//! Error types for venus-core.
2
3use thiserror::Error;
4
5/// Result type for venus-core operations.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// Errors that can occur in venus-core.
9#[derive(Debug, Error)]
10pub enum Error {
11    /// Failed to parse notebook source.
12    #[error("parse error: {0}")]
13    Parse(String),
14
15    /// Cyclic dependency detected in the cell graph.
16    #[error("cyclic dependency detected: {0}")]
17    CyclicDependency(String),
18
19    /// Cell not found.
20    #[error("cell not found: {0}")]
21    CellNotFound(String),
22
23    /// Compilation failed.
24    #[error("compilation failed{}: {message}", cell_id.as_ref().map(|id| format!(" for cell {}", id)).unwrap_or_default())]
25    Compilation {
26        cell_id: Option<String>,
27        message: String,
28    },
29
30    /// Failed to load dynamic library.
31    #[error("failed to load library: {0}")]
32    LibraryLoad(#[from] libloading::Error),
33
34    /// Serialization error.
35    #[error("serialization error: {0}")]
36    Serialization(String),
37
38    /// Deserialization error.
39    #[error("deserialization error: {0}")]
40    Deserialization(String),
41
42    /// Schema evolution error (incompatible type change).
43    #[error("schema evolution error: {0}")]
44    SchemaEvolution(String),
45
46    /// IO error.
47    #[error("IO error: {0}")]
48    Io(#[from] std::io::Error),
49
50    /// IPC communication error with worker process.
51    #[error("IPC error: {0}")]
52    Ipc(String),
53
54    /// Toolchain error.
55    #[error("toolchain error: {0}")]
56    Toolchain(String),
57
58    /// Execution error.
59    #[error("execution error: {0}")]
60    Execution(String),
61
62    /// Execution was aborted by user request.
63    #[error("execution aborted")]
64    Aborted,
65
66    /// Invalid operation (e.g., moving first cell up).
67    #[error("invalid operation: {0}")]
68    InvalidOperation(String),
69}
70
71impl Error {
72    /// Get a recovery suggestion for this error, if available.
73    ///
74    /// Returns a user-friendly hint on how to fix the error.
75    pub fn recovery_hint(&self) -> Option<String> {
76        match self {
77            Error::CyclicDependency(msg) => {
78                // Extract cycle path from error message if possible
79                if msg.contains("→") {
80                    Some("Remove one of the dependency edges in the cycle to break it. For example, if A → B → C → A, you could remove the dependency from C back to A.".to_string())
81                } else {
82                    Some("Review your cell dependencies and remove circular references.".to_string())
83                }
84            }
85            Error::CellNotFound(msg) => {
86                if msg.contains("depends on") {
87                    Some("Check that the cell name matches exactly (case-sensitive). If the cell was renamed, update all dependencies that reference it.".to_string())
88                } else {
89                    Some("Verify the cell name is spelled correctly and the cell exists in your notebook.".to_string())
90                }
91            }
92            Error::Compilation { message, .. } => {
93                if message.contains("type mismatch") || message.contains("expected") {
94                    Some("Check that parameter types match the output types of dependency cells. Use '&Type' for borrowed references, not 'Type'.".to_string())
95                } else if message.contains("cannot find") {
96                    Some("Ensure all required types and functions are imported. You may need to add dependencies to the notebook header.".to_string())
97                } else {
98                    Some("Run with RUST_LOG=venus=debug for detailed compiler output. Fix the compilation errors in your cell code.".to_string())
99                }
100            }
101            Error::Deserialization(msg) => {
102                if msg.contains("type mismatch") || msg.contains("check dependency types") {
103                    Some("The cell's parameter types don't match the actual output types from dependencies. Ensure parameter types exactly match what the dependency cells return.".to_string())
104                } else {
105                    Some("Check that your data structures have proper rkyv serialization derives: #[derive(Archive, RkyvSerialize, RkyvDeserialize)]".to_string())
106                }
107            }
108            Error::SchemaEvolution(msg) => {
109                if msg.contains("breaking change") || msg.contains("incompatible") {
110                    Some("You've changed a type definition in a way that's incompatible with cached data. Clean the cache with: rm -rf .venus/cache".to_string())
111                } else {
112                    Some("Type definitions have changed. Try cleaning the cache directory: rm -rf .venus/cache".to_string())
113                }
114            }
115            Error::Toolchain(msg) => {
116                if msg.contains("rustc") || msg.contains("not found") {
117                    Some("Install Rust from https://rustup.rs if not already installed. Ensure 'rustc' is in your PATH.".to_string())
118                } else if msg.contains("cranelift") {
119                    Some("Cranelift backend is optional. Venus will fall back to standard rustc compilation.".to_string())
120                } else {
121                    Some("Verify your Rust installation with: rustc --version".to_string())
122                }
123            }
124            Error::Execution(msg) => {
125                if msg.contains("deserialize") || msg.contains("type") {
126                    Some("Run with RUST_LOG=venus=debug to see detailed error information. Check that cell parameter types match dependency output types.".to_string())
127                } else if msg.contains("panicked") {
128                    Some("Check your cell code for unwrap() calls on None/Err values, array out-of-bounds access, or other panic sources. Add proper error handling.".to_string())
129                } else {
130                    None
131                }
132            }
133            Error::Io(io_err) => {
134                match io_err.kind() {
135                    std::io::ErrorKind::NotFound => {
136                        Some("Verify the file path is correct and the file exists.".to_string())
137                    }
138                    std::io::ErrorKind::PermissionDenied => {
139                        Some("Check file permissions. You may need to make the file readable/writable or run with appropriate permissions.".to_string())
140                    }
141                    std::io::ErrorKind::AlreadyExists => {
142                        Some("The file already exists. Delete the existing file or choose a different name.".to_string())
143                    }
144                    _ => None,
145                }
146            }
147            Error::Ipc(msg) => {
148                if msg.contains("timeout") || msg.contains("disconnected") {
149                    Some("The worker process may have crashed. Try cleaning the build directory: rm -rf .venus/build".to_string())
150                } else {
151                    None
152                }
153            }
154            // These errors are self-explanatory or context-specific
155            Error::Parse(_) | Error::LibraryLoad(_) | Error::Serialization(_) |
156            Error::Aborted | Error::InvalidOperation(_) => None,
157        }
158    }
159
160    /// Format the error with its recovery hint, if available.
161    ///
162    /// This is useful for displaying errors to users with actionable guidance.
163    pub fn with_hint(&self) -> String {
164        let base_msg = self.to_string();
165        match self.recovery_hint() {
166            Some(hint) => format!("{}\n\nšŸ’” Hint: {}", base_msg, hint),
167            None => base_msg,
168        }
169    }
170}