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 new_max_call_stack(details: KclErrorDetails) -> KclError {
148 KclError::MaxCallStack { details }
149 }
150
151 pub fn refactor(message: String) -> KclError {
152 KclError::Refactor {
153 details: KclErrorDetails {
154 source_ranges: Default::default(),
155 backtrace: Default::default(),
156 message,
157 },
158 }
159 }
160
161 pub fn new_engine(details: KclErrorDetails) -> KclError {
162 if details.message.eq_ignore_ascii_case("internal error") {
163 KclError::EngineInternal { details }
164 } else if is_retryable_engine_message(&details.message) {
165 KclError::EngineHangup {
166 details,
167 api_call_id: None,
168 }
169 } else {
170 KclError::Engine { details }
171 }
172 }
173
174 pub fn new_engine_hangup(details: KclErrorDetails, api_call_id: Option<String>) -> KclError {
175 KclError::EngineHangup { details, api_call_id }
176 }
177
178 pub fn new_lexical(details: KclErrorDetails) -> KclError {
179 KclError::Lexical { details }
180 }
181
182 pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
183 KclError::UndefinedValue { details, name }
184 }
185
186 pub fn new_type(details: KclErrorDetails) -> KclError {
187 KclError::Type { details }
188 }
189
190 pub fn new_user_defined(details: KclErrorDetails) -> KclError {
191 KclError::UserDefined { details }
192 }
193
194 pub fn is_undefined_value(&self) -> bool {
195 matches!(self, KclError::UndefinedValue { .. })
196 }
197
198 pub fn get_message(&self) -> String {
200 format!("{}: {}", self.error_type(), self.message())
201 }
202
203 pub fn error_type(&self) -> &'static str {
204 match self {
205 KclError::Lexical { .. } => "lexical",
206 KclError::Syntax { .. } => "syntax",
207 KclError::Semantic { .. } => "semantic",
208 KclError::ImportCycle { .. } => "import cycle",
209 KclError::Argument { .. } => "argument",
210 KclError::Type { .. } => "type",
211 KclError::UserDefined { .. } => "user-defined",
212 KclError::Io { .. } => "i/o",
213 KclError::Unexpected { .. } => "unexpected",
214 KclError::ValueAlreadyDefined { .. } => "value already defined",
215 KclError::UndefinedValue { .. } => "undefined value",
216 KclError::InvalidExpression { .. } => "invalid expression",
217 KclError::MaxCallStack { .. } => "max call stack",
218 KclError::Refactor { .. } => "refactor",
219 KclError::Engine { .. } => "engine",
220 KclError::EngineHangup { .. } => "engine hangup",
221 KclError::EngineInternal { .. } => "engine internal",
222 KclError::Internal { .. } => "internal",
223 }
224 }
225
226 pub fn source_ranges(&self) -> Vec<SourceRange> {
227 match &self {
228 KclError::Lexical { details: e } => e.source_ranges.clone(),
229 KclError::Syntax { details: e } => e.source_ranges.clone(),
230 KclError::Semantic { details: e } => e.source_ranges.clone(),
231 KclError::ImportCycle { details: e } => e.source_ranges.clone(),
232 KclError::Argument { details: e } => e.source_ranges.clone(),
233 KclError::Type { details: e } => e.source_ranges.clone(),
234 KclError::UserDefined { details: e } => e.source_ranges.clone(),
235 KclError::Io { details: e } => e.source_ranges.clone(),
236 KclError::Unexpected { details: e } => e.source_ranges.clone(),
237 KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
238 KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
239 KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
240 KclError::MaxCallStack { details: e } => e.source_ranges.clone(),
241 KclError::Refactor { details: e } => e.source_ranges.clone(),
242 KclError::Engine { details: e } => e.source_ranges.clone(),
243 KclError::EngineHangup { details: e, .. } => e.source_ranges.clone(),
244 KclError::EngineInternal { details: e } => e.source_ranges.clone(),
245 KclError::Internal { details: e } => e.source_ranges.clone(),
246 }
247 }
248
249 pub fn message(&self) -> &str {
251 match &self {
252 KclError::Lexical { details: e } => &e.message,
253 KclError::Syntax { details: e } => &e.message,
254 KclError::Semantic { details: e } => &e.message,
255 KclError::ImportCycle { details: e } => &e.message,
256 KclError::Argument { details: e } => &e.message,
257 KclError::Type { details: e } => &e.message,
258 KclError::UserDefined { details: e } => &e.message,
259 KclError::Io { details: e } => &e.message,
260 KclError::Unexpected { details: e } => &e.message,
261 KclError::ValueAlreadyDefined { details: e } => &e.message,
262 KclError::UndefinedValue { details: e, .. } => &e.message,
263 KclError::InvalidExpression { details: e } => &e.message,
264 KclError::MaxCallStack { details: e } => &e.message,
265 KclError::Refactor { details: e } => &e.message,
266 KclError::Engine { details: e } => &e.message,
267 KclError::EngineHangup { details: e, .. } => &e.message,
268 KclError::EngineInternal { details: e } => &e.message,
269 KclError::Internal { details: e } => &e.message,
270 }
271 }
272
273 pub fn backtrace(&self) -> Vec<BacktraceItem> {
274 match self {
275 KclError::Lexical { details: e }
276 | KclError::Syntax { details: e }
277 | KclError::Semantic { details: e }
278 | KclError::ImportCycle { details: e }
279 | KclError::Argument { details: e }
280 | KclError::Type { details: e }
281 | KclError::UserDefined { details: e }
282 | KclError::Io { details: e }
283 | KclError::Unexpected { details: e }
284 | KclError::ValueAlreadyDefined { details: e }
285 | KclError::UndefinedValue { details: e, .. }
286 | KclError::InvalidExpression { details: e }
287 | KclError::MaxCallStack { details: e }
288 | KclError::Refactor { details: e }
289 | KclError::Engine { details: e }
290 | KclError::EngineHangup { details: e, .. }
291 | KclError::EngineInternal { details: e }
292 | KclError::Internal { details: e } => e.backtrace.clone(),
293 }
294 }
295
296 pub fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
297 let mut new = self.clone();
298 match &mut new {
299 KclError::Lexical { details: e }
300 | KclError::Syntax { details: e }
301 | KclError::Semantic { details: e }
302 | KclError::ImportCycle { details: e }
303 | KclError::Argument { details: e }
304 | KclError::Type { details: e }
305 | KclError::UserDefined { details: e }
306 | KclError::Io { details: e }
307 | KclError::Unexpected { details: e }
308 | KclError::ValueAlreadyDefined { details: e }
309 | KclError::UndefinedValue { details: e, .. }
310 | KclError::InvalidExpression { details: e }
311 | KclError::MaxCallStack { details: e }
312 | KclError::Refactor { details: e }
313 | KclError::Engine { details: e }
314 | KclError::EngineHangup { details: e, .. }
315 | KclError::EngineInternal { details: e }
316 | KclError::Internal { details: e } => {
317 e.backtrace = source_ranges
318 .iter()
319 .map(|s| BacktraceItem {
320 source_range: *s,
321 fn_name: None,
322 })
323 .collect();
324 e.source_ranges = source_ranges;
325 }
326 }
327
328 new
329 }
330
331 pub fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
332 let mut new = self.clone();
333 match &mut new {
334 KclError::Lexical { details: e }
335 | KclError::Syntax { details: e }
336 | KclError::Semantic { details: e }
337 | KclError::ImportCycle { details: e }
338 | KclError::Argument { details: e }
339 | KclError::Type { details: e }
340 | KclError::UserDefined { details: e }
341 | KclError::Io { details: e }
342 | KclError::Unexpected { details: e }
343 | KclError::ValueAlreadyDefined { details: e }
344 | KclError::UndefinedValue { details: e, .. }
345 | KclError::InvalidExpression { details: e }
346 | KclError::MaxCallStack { details: e }
347 | KclError::Refactor { details: e }
348 | KclError::Engine { details: e }
349 | KclError::EngineHangup { details: e, .. }
350 | KclError::EngineInternal { details: e }
351 | KclError::Internal { details: e } => {
352 if let Some(item) = e.backtrace.last_mut() {
353 item.fn_name = last_fn_name;
354 }
355 e.backtrace.push(BacktraceItem {
356 source_range,
357 fn_name: None,
358 });
359 e.source_ranges.push(source_range);
360 }
361 }
362
363 new
364 }
365}
366
367#[derive(
368 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic, JsonSchema,
369)]
370#[serde(rename_all = "camelCase")]
371#[ts(export)]
372pub struct BacktraceItem {
373 pub source_range: SourceRange,
374 pub fn_name: Option<String>,
375}
376
377impl std::fmt::Display for BacktraceItem {
378 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379 if let Some(fn_name) = &self.fn_name {
380 write!(f, "{fn_name}: {:?}", self.source_range)
381 } else {
382 write!(f, "(fn): {:?}", self.source_range)
383 }
384 }
385}
386
387fn is_retryable_engine_message(message: &str) -> bool {
388 let message = message.to_ascii_lowercase();
390 RETRYABLE_ENGINE_MESSAGE_MARKER_SETS
391 .iter()
392 .any(|markers| markers.iter().all(|marker| message.contains(marker)))
393}
394
395impl From<KclError> for String {
398 fn from(error: KclError) -> Self {
399 serde_json::to_string(&error).unwrap()
400 }
401}
402
403impl From<CompilationIssue> for KclErrorDetails {
404 fn from(err: CompilationIssue) -> Self {
405 let backtrace = vec![BacktraceItem {
406 source_range: err.source_range,
407 fn_name: None,
408 }];
409 KclErrorDetails {
410 source_ranges: vec![err.source_range],
411 backtrace,
412 message: err.message,
413 }
414 }
415}
416
417#[cfg(feature = "pyo3")]
418impl From<pyo3::PyErr> for KclError {
419 fn from(error: pyo3::PyErr) -> Self {
420 KclError::new_internal(KclErrorDetails {
421 source_ranges: vec![],
422 backtrace: Default::default(),
423 message: error.to_string(),
424 })
425 }
426}
427
428#[cfg(feature = "pyo3")]
429impl From<KclError> for pyo3::PyErr {
430 fn from(error: KclError) -> Self {
431 pyo3::exceptions::PyException::new_err(error.to_string())
432 }
433}