1#[cfg(feature = "artifact-graph")]
2use std::collections::BTreeMap;
3
4use indexmap::IndexMap;
5pub use kcl_error::{CompilationError, Severity, Suggestion, Tag};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
9
10use crate::{
11 ModuleId, SourceRange,
12 exec::KclValue,
13 execution::DefaultPlanes,
14 lsp::{IntoDiagnostic, ToLspRange},
15 modules::{ModulePath, ModuleSource},
16};
17#[cfg(feature = "artifact-graph")]
18use crate::{
19 execution::{ArtifactCommand, ArtifactGraph, Operation},
20 front::{Number, Object, ObjectId},
21};
22
23mod details;
24
25pub use details::KclErrorDetails;
26
27#[derive(thiserror::Error, Debug)]
29pub enum ExecError {
30 #[error("{0}")]
31 Kcl(#[from] Box<crate::KclErrorWithOutputs>),
32 #[error("Could not connect to engine: {0}")]
33 Connection(#[from] ConnectionError),
34 #[error("PNG snapshot could not be decoded: {0}")]
35 BadPng(String),
36 #[error("Bad export: {0}")]
37 BadExport(String),
38}
39
40impl From<KclErrorWithOutputs> for ExecError {
41 fn from(error: KclErrorWithOutputs) -> Self {
42 ExecError::Kcl(Box::new(error))
43 }
44}
45
46#[cfg_attr(target_arch = "wasm32", expect(dead_code))]
48#[derive(Debug)]
49pub struct ExecErrorWithState {
50 pub error: ExecError,
51 pub exec_state: Option<crate::execution::ExecState>,
52}
53
54impl ExecErrorWithState {
55 #[cfg_attr(target_arch = "wasm32", expect(dead_code))]
56 pub fn new(error: ExecError, exec_state: crate::execution::ExecState) -> Self {
57 Self {
58 error,
59 exec_state: Some(exec_state),
60 }
61 }
62}
63
64impl ExecError {
65 pub fn as_kcl_error(&self) -> Option<&crate::KclError> {
66 let ExecError::Kcl(k) = &self else {
67 return None;
68 };
69 Some(&k.error)
70 }
71}
72
73impl From<ExecError> for ExecErrorWithState {
74 fn from(error: ExecError) -> Self {
75 Self {
76 error,
77 exec_state: None,
78 }
79 }
80}
81
82impl From<ConnectionError> for ExecErrorWithState {
83 fn from(error: ConnectionError) -> Self {
84 Self {
85 error: error.into(),
86 exec_state: None,
87 }
88 }
89}
90
91#[derive(thiserror::Error, Debug)]
93pub enum ConnectionError {
94 #[error("Could not create a Zoo client: {0}")]
95 CouldNotMakeClient(anyhow::Error),
96 #[error("Could not establish connection to engine: {0}")]
97 Establishing(anyhow::Error),
98}
99
100#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
101#[ts(export)]
102#[serde(tag = "kind", rename_all = "snake_case")]
103pub enum KclError {
104 #[error("lexical: {details:?}")]
105 Lexical { details: KclErrorDetails },
106 #[error("syntax: {details:?}")]
107 Syntax { details: KclErrorDetails },
108 #[error("semantic: {details:?}")]
109 Semantic { details: KclErrorDetails },
110 #[error("import cycle: {details:?}")]
111 ImportCycle { details: KclErrorDetails },
112 #[error("argument: {details:?}")]
113 Argument { details: KclErrorDetails },
114 #[error("type: {details:?}")]
115 Type { details: KclErrorDetails },
116 #[error("i/o: {details:?}")]
117 Io { details: KclErrorDetails },
118 #[error("unexpected: {details:?}")]
119 Unexpected { details: KclErrorDetails },
120 #[error("value already defined: {details:?}")]
121 ValueAlreadyDefined { details: KclErrorDetails },
122 #[error("undefined value: {details:?}")]
123 UndefinedValue {
124 details: KclErrorDetails,
125 name: Option<String>,
126 },
127 #[error("invalid expression: {details:?}")]
128 InvalidExpression { details: KclErrorDetails },
129 #[error("max call stack size exceeded: {details:?}")]
130 MaxCallStack { details: KclErrorDetails },
131 #[error("engine: {details:?}")]
132 Engine { details: KclErrorDetails },
133 #[error("engine hangup: {details:?}")]
134 EngineHangup { details: KclErrorDetails },
135 #[error("engine internal: {details:?}")]
136 EngineInternal { details: KclErrorDetails },
137 #[error("internal error, please report to KittyCAD team: {details:?}")]
138 Internal { details: KclErrorDetails },
139}
140
141impl From<KclErrorWithOutputs> for KclError {
142 fn from(error: KclErrorWithOutputs) -> Self {
143 error.error
144 }
145}
146
147#[derive(Error, Debug, Serialize, ts_rs::TS, Clone, PartialEq)]
148#[error("{error}")]
149#[ts(export)]
150#[serde(rename_all = "camelCase")]
151pub struct KclErrorWithOutputs {
152 pub error: KclError,
153 pub non_fatal: Vec<CompilationError>,
154 pub variables: IndexMap<String, KclValue>,
157 #[cfg(feature = "artifact-graph")]
158 pub operations: Vec<Operation>,
159 #[cfg(feature = "artifact-graph")]
162 pub _artifact_commands: Vec<ArtifactCommand>,
163 #[cfg(feature = "artifact-graph")]
164 pub artifact_graph: ArtifactGraph,
165 #[cfg(feature = "artifact-graph")]
166 #[serde(skip)]
167 pub scene_objects: Vec<Object>,
168 #[cfg(feature = "artifact-graph")]
169 #[serde(skip)]
170 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
171 #[cfg(feature = "artifact-graph")]
172 #[serde(skip)]
173 pub var_solutions: Vec<(SourceRange, Number)>,
174 pub scene_graph: Option<crate::front::SceneGraph>,
175 pub filenames: IndexMap<ModuleId, ModulePath>,
176 pub source_files: IndexMap<ModuleId, ModuleSource>,
177 pub default_planes: Option<DefaultPlanes>,
178}
179
180impl KclErrorWithOutputs {
181 #[allow(clippy::too_many_arguments)]
182 pub fn new(
183 error: KclError,
184 non_fatal: Vec<CompilationError>,
185 variables: IndexMap<String, KclValue>,
186 #[cfg(feature = "artifact-graph")] operations: Vec<Operation>,
187 #[cfg(feature = "artifact-graph")] artifact_commands: Vec<ArtifactCommand>,
188 #[cfg(feature = "artifact-graph")] artifact_graph: ArtifactGraph,
189 #[cfg(feature = "artifact-graph")] scene_objects: Vec<Object>,
190 #[cfg(feature = "artifact-graph")] source_range_to_object: BTreeMap<SourceRange, ObjectId>,
191 #[cfg(feature = "artifact-graph")] var_solutions: Vec<(SourceRange, Number)>,
192 filenames: IndexMap<ModuleId, ModulePath>,
193 source_files: IndexMap<ModuleId, ModuleSource>,
194 default_planes: Option<DefaultPlanes>,
195 ) -> Self {
196 Self {
197 error,
198 non_fatal,
199 variables,
200 #[cfg(feature = "artifact-graph")]
201 operations,
202 #[cfg(feature = "artifact-graph")]
203 _artifact_commands: artifact_commands,
204 #[cfg(feature = "artifact-graph")]
205 artifact_graph,
206 #[cfg(feature = "artifact-graph")]
207 scene_objects,
208 #[cfg(feature = "artifact-graph")]
209 source_range_to_object,
210 #[cfg(feature = "artifact-graph")]
211 var_solutions,
212 scene_graph: Default::default(),
213 filenames,
214 source_files,
215 default_planes,
216 }
217 }
218 pub fn no_outputs(error: KclError) -> Self {
219 Self {
220 error,
221 non_fatal: Default::default(),
222 variables: Default::default(),
223 #[cfg(feature = "artifact-graph")]
224 operations: Default::default(),
225 #[cfg(feature = "artifact-graph")]
226 _artifact_commands: Default::default(),
227 #[cfg(feature = "artifact-graph")]
228 artifact_graph: Default::default(),
229 #[cfg(feature = "artifact-graph")]
230 scene_objects: Default::default(),
231 #[cfg(feature = "artifact-graph")]
232 source_range_to_object: Default::default(),
233 #[cfg(feature = "artifact-graph")]
234 var_solutions: Default::default(),
235 scene_graph: Default::default(),
236 filenames: Default::default(),
237 source_files: Default::default(),
238 default_planes: Default::default(),
239 }
240 }
241 pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
242 let mut source_ranges = self.error.source_ranges();
243
244 let first_source_range = source_ranges
246 .pop()
247 .ok_or_else(|| anyhow::anyhow!("No source ranges found"))?;
248
249 let source = self
250 .source_files
251 .get(&first_source_range.module_id())
252 .cloned()
253 .unwrap_or(ModuleSource {
254 source: code.to_string(),
255 path: self
256 .filenames
257 .get(&first_source_range.module_id())
258 .cloned()
259 .unwrap_or(ModulePath::Main),
260 });
261 let filename = source.path.to_string();
262 let kcl_source = source.source;
263
264 let mut related = Vec::new();
265 for source_range in source_ranges {
266 let module_id = source_range.module_id();
267 let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
268 source: code.to_string(),
269 path: self.filenames.get(&module_id).cloned().unwrap_or(ModulePath::Main),
270 });
271 let error = self.error.override_source_ranges(vec![source_range]);
272 let report = Report {
273 error,
274 kcl_source: source.source.to_string(),
275 filename: source.path.to_string(),
276 };
277 related.push(report);
278 }
279
280 Ok(ReportWithOutputs {
281 error: self,
282 kcl_source,
283 filename,
284 related,
285 })
286 }
287}
288
289impl IntoDiagnostic for KclErrorWithOutputs {
290 fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
291 let message = self.error.get_message();
292 let source_ranges = self.error.source_ranges();
293
294 source_ranges
295 .into_iter()
296 .map(|source_range| {
297 let source = self
298 .source_files
299 .get(&source_range.module_id())
300 .cloned()
301 .unwrap_or(ModuleSource {
302 source: code.to_string(),
303 path: self.filenames.get(&source_range.module_id()).unwrap().clone(),
304 });
305 let mut filename = source.path.to_string();
306 if !filename.starts_with("file://") {
307 filename = format!("file:///{}", filename.trim_start_matches("/"));
308 }
309
310 let related_information = if let Ok(uri) = url::Url::parse(&filename) {
311 Some(vec![tower_lsp::lsp_types::DiagnosticRelatedInformation {
312 location: tower_lsp::lsp_types::Location {
313 uri,
314 range: source_range.to_lsp_range(&source.source),
315 },
316 message: message.to_string(),
317 }])
318 } else {
319 None
320 };
321
322 Diagnostic {
323 range: source_range.to_lsp_range(code),
324 severity: Some(self.severity()),
325 code: None,
326 code_description: None,
328 source: Some("kcl".to_string()),
329 related_information,
330 message: message.clone(),
331 tags: None,
332 data: None,
333 }
334 })
335 .collect()
336 }
337
338 fn severity(&self) -> DiagnosticSeverity {
339 DiagnosticSeverity::ERROR
340 }
341}
342
343#[derive(thiserror::Error, Debug)]
344#[error("{}", self.error.error.get_message())]
345pub struct ReportWithOutputs {
346 pub error: KclErrorWithOutputs,
347 pub kcl_source: String,
348 pub filename: String,
349 pub related: Vec<Report>,
350}
351
352impl miette::Diagnostic for ReportWithOutputs {
353 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
354 let family = match self.error.error {
355 KclError::Lexical { .. } => "Lexical",
356 KclError::Syntax { .. } => "Syntax",
357 KclError::Semantic { .. } => "Semantic",
358 KclError::ImportCycle { .. } => "ImportCycle",
359 KclError::Argument { .. } => "Argument",
360 KclError::Type { .. } => "Type",
361 KclError::Io { .. } => "I/O",
362 KclError::Unexpected { .. } => "Unexpected",
363 KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
364 KclError::UndefinedValue { .. } => "UndefinedValue",
365 KclError::InvalidExpression { .. } => "InvalidExpression",
366 KclError::MaxCallStack { .. } => "MaxCallStack",
367 KclError::Engine { .. } => "Engine",
368 KclError::EngineHangup { .. } => "EngineHangup",
369 KclError::EngineInternal { .. } => "EngineInternal",
370 KclError::Internal { .. } => "Internal",
371 };
372 let error_string = format!("KCL {family} error");
373 Some(Box::new(error_string))
374 }
375
376 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
377 Some(&self.kcl_source)
378 }
379
380 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
381 let iter = self
382 .error
383 .error
384 .source_ranges()
385 .into_iter()
386 .map(miette::SourceSpan::from)
387 .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
388 Some(Box::new(iter))
389 }
390
391 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
392 let iter = self.related.iter().map(|r| r as &dyn miette::Diagnostic);
393 Some(Box::new(iter))
394 }
395}
396
397#[derive(thiserror::Error, Debug)]
398#[error("{}", self.error.get_message())]
399pub struct Report {
400 pub error: KclError,
401 pub kcl_source: String,
402 pub filename: String,
403}
404
405impl miette::Diagnostic for Report {
406 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
407 let family = match self.error {
408 KclError::Lexical { .. } => "Lexical",
409 KclError::Syntax { .. } => "Syntax",
410 KclError::Semantic { .. } => "Semantic",
411 KclError::ImportCycle { .. } => "ImportCycle",
412 KclError::Argument { .. } => "Argument",
413 KclError::Type { .. } => "Type",
414 KclError::Io { .. } => "I/O",
415 KclError::Unexpected { .. } => "Unexpected",
416 KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
417 KclError::UndefinedValue { .. } => "UndefinedValue",
418 KclError::InvalidExpression { .. } => "InvalidExpression",
419 KclError::MaxCallStack { .. } => "MaxCallStack",
420 KclError::Engine { .. } => "Engine",
421 KclError::EngineHangup { .. } => "EngineHangup",
422 KclError::EngineInternal { .. } => "EngineInternal",
423 KclError::Internal { .. } => "Internal",
424 };
425 let error_string = format!("KCL {family} error");
426 Some(Box::new(error_string))
427 }
428
429 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
430 Some(&self.kcl_source)
431 }
432
433 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
434 let iter = self
435 .error
436 .source_ranges()
437 .into_iter()
438 .map(miette::SourceSpan::from)
439 .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
440 Some(Box::new(iter))
441 }
442}
443
444impl KclErrorDetails {
445 pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
446 let backtrace = source_ranges
447 .iter()
448 .map(|s| BacktraceItem {
449 source_range: *s,
450 fn_name: None,
451 })
452 .collect();
453 KclErrorDetails {
454 source_ranges,
455 backtrace,
456 message,
457 }
458 }
459}
460
461impl KclError {
462 pub fn internal(message: String) -> KclError {
463 KclError::Internal {
464 details: KclErrorDetails {
465 source_ranges: Default::default(),
466 backtrace: Default::default(),
467 message,
468 },
469 }
470 }
471
472 pub fn new_internal(details: KclErrorDetails) -> KclError {
473 KclError::Internal { details }
474 }
475
476 pub fn new_import_cycle(details: KclErrorDetails) -> KclError {
477 KclError::ImportCycle { details }
478 }
479
480 pub fn new_argument(details: KclErrorDetails) -> KclError {
481 KclError::Argument { details }
482 }
483
484 pub fn new_semantic(details: KclErrorDetails) -> KclError {
485 KclError::Semantic { details }
486 }
487
488 pub fn new_value_already_defined(details: KclErrorDetails) -> KclError {
489 KclError::ValueAlreadyDefined { details }
490 }
491
492 pub fn new_syntax(details: KclErrorDetails) -> KclError {
493 KclError::Syntax { details }
494 }
495
496 pub fn new_io(details: KclErrorDetails) -> KclError {
497 KclError::Io { details }
498 }
499
500 pub fn new_invalid_expression(details: KclErrorDetails) -> KclError {
501 KclError::InvalidExpression { details }
502 }
503
504 pub fn new_engine(details: KclErrorDetails) -> KclError {
505 if details.message.eq_ignore_ascii_case("internal error") {
506 KclError::EngineInternal { details }
507 } else {
508 KclError::Engine { details }
509 }
510 }
511
512 pub fn new_engine_hangup(details: KclErrorDetails) -> KclError {
513 KclError::EngineHangup { details }
514 }
515
516 pub fn new_lexical(details: KclErrorDetails) -> KclError {
517 KclError::Lexical { details }
518 }
519
520 pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
521 KclError::UndefinedValue { details, name }
522 }
523
524 pub fn new_type(details: KclErrorDetails) -> KclError {
525 KclError::Type { details }
526 }
527
528 pub fn get_message(&self) -> String {
530 format!("{}: {}", self.error_type(), self.message())
531 }
532
533 pub fn error_type(&self) -> &'static str {
534 match self {
535 KclError::Lexical { .. } => "lexical",
536 KclError::Syntax { .. } => "syntax",
537 KclError::Semantic { .. } => "semantic",
538 KclError::ImportCycle { .. } => "import cycle",
539 KclError::Argument { .. } => "argument",
540 KclError::Type { .. } => "type",
541 KclError::Io { .. } => "i/o",
542 KclError::Unexpected { .. } => "unexpected",
543 KclError::ValueAlreadyDefined { .. } => "value already defined",
544 KclError::UndefinedValue { .. } => "undefined value",
545 KclError::InvalidExpression { .. } => "invalid expression",
546 KclError::MaxCallStack { .. } => "max call stack",
547 KclError::Engine { .. } => "engine",
548 KclError::EngineHangup { .. } => "engine hangup",
549 KclError::EngineInternal { .. } => "engine internal",
550 KclError::Internal { .. } => "internal",
551 }
552 }
553
554 pub fn source_ranges(&self) -> Vec<SourceRange> {
555 match &self {
556 KclError::Lexical { details: e } => e.source_ranges.clone(),
557 KclError::Syntax { details: e } => e.source_ranges.clone(),
558 KclError::Semantic { details: e } => e.source_ranges.clone(),
559 KclError::ImportCycle { details: e } => e.source_ranges.clone(),
560 KclError::Argument { details: e } => e.source_ranges.clone(),
561 KclError::Type { details: e } => e.source_ranges.clone(),
562 KclError::Io { details: e } => e.source_ranges.clone(),
563 KclError::Unexpected { details: e } => e.source_ranges.clone(),
564 KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
565 KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
566 KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
567 KclError::MaxCallStack { details: e } => e.source_ranges.clone(),
568 KclError::Engine { details: e } => e.source_ranges.clone(),
569 KclError::EngineHangup { details: e } => e.source_ranges.clone(),
570 KclError::EngineInternal { details: e } => e.source_ranges.clone(),
571 KclError::Internal { details: e } => e.source_ranges.clone(),
572 }
573 }
574
575 pub fn message(&self) -> &str {
577 match &self {
578 KclError::Lexical { details: e } => &e.message,
579 KclError::Syntax { details: e } => &e.message,
580 KclError::Semantic { details: e } => &e.message,
581 KclError::ImportCycle { details: e } => &e.message,
582 KclError::Argument { details: e } => &e.message,
583 KclError::Type { details: e } => &e.message,
584 KclError::Io { details: e } => &e.message,
585 KclError::Unexpected { details: e } => &e.message,
586 KclError::ValueAlreadyDefined { details: e } => &e.message,
587 KclError::UndefinedValue { details: e, .. } => &e.message,
588 KclError::InvalidExpression { details: e } => &e.message,
589 KclError::MaxCallStack { details: e } => &e.message,
590 KclError::Engine { details: e } => &e.message,
591 KclError::EngineHangup { details: e } => &e.message,
592 KclError::EngineInternal { details: e } => &e.message,
593 KclError::Internal { details: e } => &e.message,
594 }
595 }
596
597 pub fn backtrace(&self) -> Vec<BacktraceItem> {
598 match self {
599 KclError::Lexical { details: e }
600 | KclError::Syntax { details: e }
601 | KclError::Semantic { details: e }
602 | KclError::ImportCycle { details: e }
603 | KclError::Argument { details: e }
604 | KclError::Type { details: e }
605 | KclError::Io { details: e }
606 | KclError::Unexpected { details: e }
607 | KclError::ValueAlreadyDefined { details: e }
608 | KclError::UndefinedValue { details: e, .. }
609 | KclError::InvalidExpression { details: e }
610 | KclError::MaxCallStack { details: e }
611 | KclError::Engine { details: e }
612 | KclError::EngineHangup { details: e }
613 | KclError::EngineInternal { details: e }
614 | KclError::Internal { details: e } => e.backtrace.clone(),
615 }
616 }
617
618 pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
619 let mut new = self.clone();
620 match &mut new {
621 KclError::Lexical { details: e }
622 | KclError::Syntax { details: e }
623 | KclError::Semantic { details: e }
624 | KclError::ImportCycle { details: e }
625 | KclError::Argument { details: e }
626 | KclError::Type { details: e }
627 | KclError::Io { details: e }
628 | KclError::Unexpected { details: e }
629 | KclError::ValueAlreadyDefined { details: e }
630 | KclError::UndefinedValue { details: e, .. }
631 | KclError::InvalidExpression { details: e }
632 | KclError::MaxCallStack { details: e }
633 | KclError::Engine { details: e }
634 | KclError::EngineHangup { details: e }
635 | KclError::EngineInternal { details: e }
636 | KclError::Internal { details: e } => {
637 e.backtrace = source_ranges
638 .iter()
639 .map(|s| BacktraceItem {
640 source_range: *s,
641 fn_name: None,
642 })
643 .collect();
644 e.source_ranges = source_ranges;
645 }
646 }
647
648 new
649 }
650
651 pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
652 let mut new = self.clone();
653 match &mut new {
654 KclError::Lexical { details: e }
655 | KclError::Syntax { details: e }
656 | KclError::Semantic { details: e }
657 | KclError::ImportCycle { details: e }
658 | KclError::Argument { details: e }
659 | KclError::Type { details: e }
660 | KclError::Io { details: e }
661 | KclError::Unexpected { details: e }
662 | KclError::ValueAlreadyDefined { details: e }
663 | KclError::UndefinedValue { details: e, .. }
664 | KclError::InvalidExpression { details: e }
665 | KclError::MaxCallStack { details: e }
666 | KclError::Engine { details: e }
667 | KclError::EngineHangup { details: e }
668 | KclError::EngineInternal { details: e }
669 | KclError::Internal { details: e } => {
670 if let Some(item) = e.backtrace.last_mut() {
671 item.fn_name = last_fn_name;
672 }
673 e.backtrace.push(BacktraceItem {
674 source_range,
675 fn_name: None,
676 });
677 e.source_ranges.push(source_range);
678 }
679 }
680
681 new
682 }
683}
684
685#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)]
686#[serde(rename_all = "camelCase")]
687#[ts(export)]
688pub struct BacktraceItem {
689 pub source_range: SourceRange,
690 pub fn_name: Option<String>,
691}
692
693impl std::fmt::Display for BacktraceItem {
694 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695 if let Some(fn_name) = &self.fn_name {
696 write!(f, "{fn_name}: {:?}", self.source_range)
697 } else {
698 write!(f, "(fn): {:?}", self.source_range)
699 }
700 }
701}
702
703impl IntoDiagnostic for KclError {
704 fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
705 let message = self.get_message();
706 let source_ranges = self.source_ranges();
707
708 let module_id = ModuleId::default();
710 let source_ranges = source_ranges
711 .iter()
712 .filter(|r| r.module_id() == module_id)
713 .collect::<Vec<_>>();
714
715 let mut diagnostics = Vec::new();
716 for source_range in &source_ranges {
717 diagnostics.push(Diagnostic {
718 range: source_range.to_lsp_range(code),
719 severity: Some(self.severity()),
720 code: None,
721 code_description: None,
723 source: Some("kcl".to_string()),
724 related_information: None,
725 message: message.clone(),
726 tags: None,
727 data: None,
728 });
729 }
730
731 diagnostics
732 }
733
734 fn severity(&self) -> DiagnosticSeverity {
735 DiagnosticSeverity::ERROR
736 }
737}
738
739impl From<KclError> for String {
742 fn from(error: KclError) -> Self {
743 serde_json::to_string(&error).unwrap()
744 }
745}
746
747impl From<String> for KclError {
748 fn from(error: String) -> Self {
749 serde_json::from_str(&error).unwrap()
750 }
751}
752
753#[cfg(feature = "pyo3")]
754impl From<pyo3::PyErr> for KclError {
755 fn from(error: pyo3::PyErr) -> Self {
756 KclError::new_internal(KclErrorDetails {
757 source_ranges: vec![],
758 backtrace: Default::default(),
759 message: error.to_string(),
760 })
761 }
762}
763
764#[cfg(feature = "pyo3")]
765impl From<KclError> for pyo3::PyErr {
766 fn from(error: KclError) -> Self {
767 pyo3::exceptions::PyException::new_err(error.to_string())
768 }
769}
770
771impl From<CompilationError> for KclErrorDetails {
772 fn from(err: CompilationError) -> Self {
773 let backtrace = vec![BacktraceItem {
774 source_range: err.source_range,
775 fn_name: None,
776 }];
777 KclErrorDetails {
778 source_ranges: vec![err.source_range],
779 backtrace,
780 message: err.message,
781 }
782 }
783}