1use schemars::JsonSchema;
2use serde::Deserialize;
3use serde::Serialize;
4use thiserror::Error;
5
6use crate::CompilationIssue;
7use crate::SourceRange;
8
9const RETRYABLE_ENGINE_MESSAGE_MARKER_SETS: &[&[&str]] = &[
10 &["modeling connection", "interrupted", "please reconnect"],
11 &["modeling connection", "heartbeats", "please reconnect"],
12];
13
14pub trait IsRetryable {
15 fn is_retryable(&self) -> bool;
18}
19
20#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, JsonSchema)]
21#[ts(export)]
22#[serde(tag = "kind", rename_all = "snake_case")]
23pub enum KclError {
24 #[error("lexical: {details:?}")]
25 Lexical { details: KclErrorDetails },
26 #[error("syntax: {details:?}")]
27 Syntax { details: KclErrorDetails },
28 #[error("semantic: {details:?}")]
29 Semantic { details: KclErrorDetails },
30 #[error("import cycle: {details:?}")]
31 ImportCycle { details: KclErrorDetails },
32 #[error("argument: {details:?}")]
33 Argument { details: KclErrorDetails },
34 #[error("type: {details:?}")]
35 Type { details: KclErrorDetails },
36 #[error("user-defined: {details:?}")]
37 UserDefined { details: KclErrorDetails },
38 #[error("i/o: {details:?}")]
39 Io { details: KclErrorDetails },
40 #[error("unexpected: {details:?}")]
41 Unexpected { details: KclErrorDetails },
42 #[error("value already defined: {details:?}")]
43 ValueAlreadyDefined { details: KclErrorDetails },
44 #[error("undefined value: {details:?}")]
45 UndefinedValue {
46 details: KclErrorDetails,
47 name: Option<String>,
48 },
49 #[error("invalid expression: {details:?}")]
50 InvalidExpression { details: KclErrorDetails },
51 #[error("max call stack size exceeded: {details:?}")]
52 MaxCallStack { details: KclErrorDetails },
53 #[error("refactor: {details:?}")]
54 Refactor { details: KclErrorDetails },
55 #[error("engine: {details:?}")]
56 Engine { details: KclErrorDetails },
57 #[error("engine hangup: {details:?}")]
58 EngineHangup {
59 details: KclErrorDetails,
60 api_call_id: Option<String>,
61 },
62 #[error("engine internal: {details:?}")]
63 EngineInternal { details: KclErrorDetails },
64 #[error("internal error, please report to KittyCAD team: {details:?}")]
65 Internal { details: KclErrorDetails },
66}
67
68impl IsRetryable for KclError {
69 fn is_retryable(&self) -> bool {
70 matches!(self, KclError::EngineHangup { .. } | KclError::EngineInternal { .. })
71 }
72}
73#[derive(
74 Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic, JsonSchema,
75)]
76#[serde(rename_all = "camelCase")]
77#[error("{message}")]
78#[ts(export)]
79pub struct KclErrorDetails {
80 #[label(collection, "Errors")]
81 pub source_ranges: Vec<SourceRange>,
82 pub backtrace: Vec<BacktraceItem>,
83 #[serde(rename = "msg")]
84 pub message: String,
85}
86
87impl KclErrorDetails {
88 pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
89 let backtrace = source_ranges
90 .iter()
91 .map(|s| BacktraceItem {
92 source_range: *s,
93 fn_name: None,
94 })
95 .collect();
96 KclErrorDetails {
97 source_ranges,
98 backtrace,
99 message,
100 }
101 }
102}
103
104impl KclError {
105 pub fn internal(message: String) -> KclError {
106 KclError::Internal {
107 details: KclErrorDetails {
108 source_ranges: Default::default(),
109 backtrace: Default::default(),
110 message,
111 },
112 }
113 }
114
115 pub fn new_internal(details: KclErrorDetails) -> KclError {
116 KclError::Internal { details }
117 }
118
119 pub fn new_import_cycle(details: KclErrorDetails) -> KclError {
120 KclError::ImportCycle { details }
121 }
122
123 pub fn new_argument(details: KclErrorDetails) -> KclError {
124 KclError::Argument { details }
125 }
126
127 pub fn new_semantic(details: KclErrorDetails) -> KclError {
128 KclError::Semantic { details }
129 }
130
131 pub fn new_value_already_defined(details: KclErrorDetails) -> KclError {
132 KclError::ValueAlreadyDefined { details }
133 }
134
135 pub fn new_syntax(details: KclErrorDetails) -> KclError {
136 KclError::Syntax { details }
137 }
138
139 pub fn new_io(details: KclErrorDetails) -> KclError {
140 KclError::Io { details }
141 }
142
143 pub fn new_invalid_expression(details: KclErrorDetails) -> KclError {
144 KclError::InvalidExpression { details }
145 }
146
147 pub fn refactor(message: String) -> KclError {
148 KclError::Refactor {
149 details: KclErrorDetails {
150 source_ranges: Default::default(),
151 backtrace: Default::default(),
152 message,
153 },
154 }
155 }
156
157 pub fn new_engine(details: KclErrorDetails) -> KclError {
158 if details.message.eq_ignore_ascii_case("internal error") {
159 KclError::EngineInternal { details }
160 } else if is_retryable_engine_message(&details.message) {
161 KclError::EngineHangup {
162 details,
163 api_call_id: None,
164 }
165 } else {
166 KclError::Engine { details }
167 }
168 }
169
170 pub fn new_engine_hangup(details: KclErrorDetails, api_call_id: Option<String>) -> KclError {
171 KclError::EngineHangup { details, api_call_id }
172 }
173
174 pub fn new_lexical(details: KclErrorDetails) -> KclError {
175 KclError::Lexical { details }
176 }
177
178 pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
179 KclError::UndefinedValue { details, name }
180 }
181
182 pub fn new_type(details: KclErrorDetails) -> KclError {
183 KclError::Type { details }
184 }
185
186 pub fn new_user_defined(details: KclErrorDetails) -> KclError {
187 KclError::UserDefined { details }
188 }
189
190 pub fn is_undefined_value(&self) -> bool {
191 matches!(self, KclError::UndefinedValue { .. })
192 }
193
194 pub fn get_message(&self) -> String {
196 format!("{}: {}", self.error_type(), self.message())
197 }
198
199 pub fn error_type(&self) -> &'static str {
200 match self {
201 KclError::Lexical { .. } => "lexical",
202 KclError::Syntax { .. } => "syntax",
203 KclError::Semantic { .. } => "semantic",
204 KclError::ImportCycle { .. } => "import cycle",
205 KclError::Argument { .. } => "argument",
206 KclError::Type { .. } => "type",
207 KclError::UserDefined { .. } => "user-defined",
208 KclError::Io { .. } => "i/o",
209 KclError::Unexpected { .. } => "unexpected",
210 KclError::ValueAlreadyDefined { .. } => "value already defined",
211 KclError::UndefinedValue { .. } => "undefined value",
212 KclError::InvalidExpression { .. } => "invalid expression",
213 KclError::MaxCallStack { .. } => "max call stack",
214 KclError::Refactor { .. } => "refactor",
215 KclError::Engine { .. } => "engine",
216 KclError::EngineHangup { .. } => "engine hangup",
217 KclError::EngineInternal { .. } => "engine internal",
218 KclError::Internal { .. } => "internal",
219 }
220 }
221
222 pub fn source_ranges(&self) -> Vec<SourceRange> {
223 match &self {
224 KclError::Lexical { details: e } => e.source_ranges.clone(),
225 KclError::Syntax { details: e } => e.source_ranges.clone(),
226 KclError::Semantic { details: e } => e.source_ranges.clone(),
227 KclError::ImportCycle { details: e } => e.source_ranges.clone(),
228 KclError::Argument { details: e } => e.source_ranges.clone(),
229 KclError::Type { details: e } => e.source_ranges.clone(),
230 KclError::UserDefined { details: e } => e.source_ranges.clone(),
231 KclError::Io { details: e } => e.source_ranges.clone(),
232 KclError::Unexpected { details: e } => e.source_ranges.clone(),
233 KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
234 KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
235 KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
236 KclError::MaxCallStack { details: e } => e.source_ranges.clone(),
237 KclError::Refactor { details: e } => e.source_ranges.clone(),
238 KclError::Engine { details: e } => e.source_ranges.clone(),
239 KclError::EngineHangup { details: e, .. } => e.source_ranges.clone(),
240 KclError::EngineInternal { details: e } => e.source_ranges.clone(),
241 KclError::Internal { details: e } => e.source_ranges.clone(),
242 }
243 }
244
245 pub fn message(&self) -> &str {
247 match &self {
248 KclError::Lexical { details: e } => &e.message,
249 KclError::Syntax { details: e } => &e.message,
250 KclError::Semantic { details: e } => &e.message,
251 KclError::ImportCycle { details: e } => &e.message,
252 KclError::Argument { details: e } => &e.message,
253 KclError::Type { details: e } => &e.message,
254 KclError::UserDefined { details: e } => &e.message,
255 KclError::Io { details: e } => &e.message,
256 KclError::Unexpected { details: e } => &e.message,
257 KclError::ValueAlreadyDefined { details: e } => &e.message,
258 KclError::UndefinedValue { details: e, .. } => &e.message,
259 KclError::InvalidExpression { details: e } => &e.message,
260 KclError::MaxCallStack { details: e } => &e.message,
261 KclError::Refactor { details: e } => &e.message,
262 KclError::Engine { details: e } => &e.message,
263 KclError::EngineHangup { details: e, .. } => &e.message,
264 KclError::EngineInternal { details: e } => &e.message,
265 KclError::Internal { details: e } => &e.message,
266 }
267 }
268
269 pub fn backtrace(&self) -> Vec<BacktraceItem> {
270 match self {
271 KclError::Lexical { details: e }
272 | KclError::Syntax { details: e }
273 | KclError::Semantic { details: e }
274 | KclError::ImportCycle { details: e }
275 | KclError::Argument { details: e }
276 | KclError::Type { details: e }
277 | KclError::UserDefined { details: e }
278 | KclError::Io { details: e }
279 | KclError::Unexpected { details: e }
280 | KclError::ValueAlreadyDefined { details: e }
281 | KclError::UndefinedValue { details: e, .. }
282 | KclError::InvalidExpression { details: e }
283 | KclError::MaxCallStack { details: e }
284 | KclError::Refactor { details: e }
285 | KclError::Engine { details: e }
286 | KclError::EngineHangup { details: e, .. }
287 | KclError::EngineInternal { details: e }
288 | KclError::Internal { details: e } => e.backtrace.clone(),
289 }
290 }
291
292 pub fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
293 let mut new = self.clone();
294 match &mut new {
295 KclError::Lexical { details: e }
296 | KclError::Syntax { details: e }
297 | KclError::Semantic { details: e }
298 | KclError::ImportCycle { details: e }
299 | KclError::Argument { details: e }
300 | KclError::Type { details: e }
301 | KclError::UserDefined { details: e }
302 | KclError::Io { details: e }
303 | KclError::Unexpected { details: e }
304 | KclError::ValueAlreadyDefined { details: e }
305 | KclError::UndefinedValue { details: e, .. }
306 | KclError::InvalidExpression { details: e }
307 | KclError::MaxCallStack { details: e }
308 | KclError::Refactor { details: e }
309 | KclError::Engine { details: e }
310 | KclError::EngineHangup { details: e, .. }
311 | KclError::EngineInternal { details: e }
312 | KclError::Internal { details: e } => {
313 e.backtrace = source_ranges
314 .iter()
315 .map(|s| BacktraceItem {
316 source_range: *s,
317 fn_name: None,
318 })
319 .collect();
320 e.source_ranges = source_ranges;
321 }
322 }
323
324 new
325 }
326
327 pub fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
328 let mut new = self.clone();
329 match &mut new {
330 KclError::Lexical { details: e }
331 | KclError::Syntax { details: e }
332 | KclError::Semantic { details: e }
333 | KclError::ImportCycle { details: e }
334 | KclError::Argument { details: e }
335 | KclError::Type { details: e }
336 | KclError::UserDefined { details: e }
337 | KclError::Io { details: e }
338 | KclError::Unexpected { details: e }
339 | KclError::ValueAlreadyDefined { details: e }
340 | KclError::UndefinedValue { details: e, .. }
341 | KclError::InvalidExpression { details: e }
342 | KclError::MaxCallStack { details: e }
343 | KclError::Refactor { details: e }
344 | KclError::Engine { details: e }
345 | KclError::EngineHangup { details: e, .. }
346 | KclError::EngineInternal { details: e }
347 | KclError::Internal { details: e } => {
348 if let Some(item) = e.backtrace.last_mut() {
349 item.fn_name = last_fn_name;
350 }
351 e.backtrace.push(BacktraceItem {
352 source_range,
353 fn_name: None,
354 });
355 e.source_ranges.push(source_range);
356 }
357 }
358
359 new
360 }
361}
362
363#[derive(
364 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic, JsonSchema,
365)]
366#[serde(rename_all = "camelCase")]
367#[ts(export)]
368pub struct BacktraceItem {
369 pub source_range: SourceRange,
370 pub fn_name: Option<String>,
371}
372
373impl std::fmt::Display for BacktraceItem {
374 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375 if let Some(fn_name) = &self.fn_name {
376 write!(f, "{fn_name}: {:?}", self.source_range)
377 } else {
378 write!(f, "(fn): {:?}", self.source_range)
379 }
380 }
381}
382
383fn is_retryable_engine_message(message: &str) -> bool {
384 let message = message.to_ascii_lowercase();
386 RETRYABLE_ENGINE_MESSAGE_MARKER_SETS
387 .iter()
388 .any(|markers| markers.iter().all(|marker| message.contains(marker)))
389}
390
391impl From<KclError> for String {
394 fn from(error: KclError) -> Self {
395 serde_json::to_string(&error).unwrap()
396 }
397}
398
399impl From<CompilationIssue> for KclErrorDetails {
400 fn from(err: CompilationIssue) -> Self {
401 let backtrace = vec![BacktraceItem {
402 source_range: err.source_range,
403 fn_name: None,
404 }];
405 KclErrorDetails {
406 source_ranges: vec![err.source_range],
407 backtrace,
408 message: err.message,
409 }
410 }
411}
412
413#[cfg(feature = "pyo3")]
414impl From<pyo3::PyErr> for KclError {
415 fn from(error: pyo3::PyErr) -> Self {
416 KclError::new_internal(KclErrorDetails {
417 source_ranges: vec![],
418 backtrace: Default::default(),
419 message: error.to_string(),
420 })
421 }
422}
423
424#[cfg(feature = "pyo3")]
425impl From<KclError> for pyo3::PyErr {
426 fn from(error: KclError) -> Self {
427 pyo3::exceptions::PyException::new_err(error.to_string())
428 }
429}