1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, Error)]
10pub enum Error {
11 #[error("parse error: {0}")]
13 Parse(String),
14
15 #[error("cyclic dependency detected: {0}")]
17 CyclicDependency(String),
18
19 #[error("cell not found: {0}")]
21 CellNotFound(String),
22
23 #[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 #[error("failed to load library: {0}")]
32 LibraryLoad(#[from] libloading::Error),
33
34 #[error("serialization error: {0}")]
36 Serialization(String),
37
38 #[error("deserialization error: {0}")]
40 Deserialization(String),
41
42 #[error("schema evolution error: {0}")]
44 SchemaEvolution(String),
45
46 #[error("IO error: {0}")]
48 Io(#[from] std::io::Error),
49
50 #[error("IPC error: {0}")]
52 Ipc(String),
53
54 #[error("toolchain error: {0}")]
56 Toolchain(String),
57
58 #[error("execution error: {0}")]
60 Execution(String),
61
62 #[error("execution aborted")]
64 Aborted,
65
66 #[error("invalid operation: {0}")]
68 InvalidOperation(String),
69}
70
71impl Error {
72 pub fn recovery_hint(&self) -> Option<String> {
76 match self {
77 Error::CyclicDependency(msg) => {
78 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 Error::Parse(_) | Error::LibraryLoad(_) | Error::Serialization(_) |
156 Error::Aborted | Error::InvalidOperation(_) => None,
157 }
158 }
159
160 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}