Skip to main content

kcl_lib/
errors.rs

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/// How did the KCL execution fail
28#[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/// How did the KCL execution fail, with extra state.
47#[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/// How did KCL client fail to connect to the engine
92#[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("internal error, please report to KittyCAD team: {details:?}")]
134    Internal { details: KclErrorDetails },
135}
136
137impl From<KclErrorWithOutputs> for KclError {
138    fn from(error: KclErrorWithOutputs) -> Self {
139        error.error
140    }
141}
142
143#[derive(Error, Debug, Serialize, ts_rs::TS, Clone, PartialEq)]
144#[error("{error}")]
145#[ts(export)]
146#[serde(rename_all = "camelCase")]
147pub struct KclErrorWithOutputs {
148    pub error: KclError,
149    pub non_fatal: Vec<CompilationError>,
150    /// Variables in the top-level of the root module. Note that functions will
151    /// have an invalid env ref.
152    pub variables: IndexMap<String, KclValue>,
153    #[cfg(feature = "artifact-graph")]
154    pub operations: Vec<Operation>,
155    // TODO: Remove this field.  Doing so breaks the ts-rs output for some
156    // reason.
157    #[cfg(feature = "artifact-graph")]
158    pub _artifact_commands: Vec<ArtifactCommand>,
159    #[cfg(feature = "artifact-graph")]
160    pub artifact_graph: ArtifactGraph,
161    #[cfg(feature = "artifact-graph")]
162    #[serde(skip)]
163    pub scene_objects: Vec<Object>,
164    #[cfg(feature = "artifact-graph")]
165    #[serde(skip)]
166    pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
167    #[cfg(feature = "artifact-graph")]
168    #[serde(skip)]
169    pub var_solutions: Vec<(SourceRange, Number)>,
170    pub scene_graph: Option<crate::front::SceneGraph>,
171    pub filenames: IndexMap<ModuleId, ModulePath>,
172    pub source_files: IndexMap<ModuleId, ModuleSource>,
173    pub default_planes: Option<DefaultPlanes>,
174}
175
176impl KclErrorWithOutputs {
177    #[allow(clippy::too_many_arguments)]
178    pub fn new(
179        error: KclError,
180        non_fatal: Vec<CompilationError>,
181        variables: IndexMap<String, KclValue>,
182        #[cfg(feature = "artifact-graph")] operations: Vec<Operation>,
183        #[cfg(feature = "artifact-graph")] artifact_commands: Vec<ArtifactCommand>,
184        #[cfg(feature = "artifact-graph")] artifact_graph: ArtifactGraph,
185        #[cfg(feature = "artifact-graph")] scene_objects: Vec<Object>,
186        #[cfg(feature = "artifact-graph")] source_range_to_object: BTreeMap<SourceRange, ObjectId>,
187        #[cfg(feature = "artifact-graph")] var_solutions: Vec<(SourceRange, Number)>,
188        filenames: IndexMap<ModuleId, ModulePath>,
189        source_files: IndexMap<ModuleId, ModuleSource>,
190        default_planes: Option<DefaultPlanes>,
191    ) -> Self {
192        Self {
193            error,
194            non_fatal,
195            variables,
196            #[cfg(feature = "artifact-graph")]
197            operations,
198            #[cfg(feature = "artifact-graph")]
199            _artifact_commands: artifact_commands,
200            #[cfg(feature = "artifact-graph")]
201            artifact_graph,
202            #[cfg(feature = "artifact-graph")]
203            scene_objects,
204            #[cfg(feature = "artifact-graph")]
205            source_range_to_object,
206            #[cfg(feature = "artifact-graph")]
207            var_solutions,
208            scene_graph: Default::default(),
209            filenames,
210            source_files,
211            default_planes,
212        }
213    }
214    pub fn no_outputs(error: KclError) -> Self {
215        Self {
216            error,
217            non_fatal: Default::default(),
218            variables: Default::default(),
219            #[cfg(feature = "artifact-graph")]
220            operations: Default::default(),
221            #[cfg(feature = "artifact-graph")]
222            _artifact_commands: Default::default(),
223            #[cfg(feature = "artifact-graph")]
224            artifact_graph: Default::default(),
225            #[cfg(feature = "artifact-graph")]
226            scene_objects: Default::default(),
227            #[cfg(feature = "artifact-graph")]
228            source_range_to_object: Default::default(),
229            #[cfg(feature = "artifact-graph")]
230            var_solutions: Default::default(),
231            scene_graph: Default::default(),
232            filenames: Default::default(),
233            source_files: Default::default(),
234            default_planes: Default::default(),
235        }
236    }
237    pub fn into_miette_report_with_outputs(self, code: &str) -> anyhow::Result<ReportWithOutputs> {
238        let mut source_ranges = self.error.source_ranges();
239
240        // Pop off the first source range to get the filename.
241        let first_source_range = source_ranges
242            .pop()
243            .ok_or_else(|| anyhow::anyhow!("No source ranges found"))?;
244
245        let source = self
246            .source_files
247            .get(&first_source_range.module_id())
248            .cloned()
249            .unwrap_or(ModuleSource {
250                source: code.to_string(),
251                path: self
252                    .filenames
253                    .get(&first_source_range.module_id())
254                    .cloned()
255                    .unwrap_or(ModulePath::Main),
256            });
257        let filename = source.path.to_string();
258        let kcl_source = source.source;
259
260        let mut related = Vec::new();
261        for source_range in source_ranges {
262            let module_id = source_range.module_id();
263            let source = self.source_files.get(&module_id).cloned().unwrap_or(ModuleSource {
264                source: code.to_string(),
265                path: self.filenames.get(&module_id).cloned().unwrap_or(ModulePath::Main),
266            });
267            let error = self.error.override_source_ranges(vec![source_range]);
268            let report = Report {
269                error,
270                kcl_source: source.source.to_string(),
271                filename: source.path.to_string(),
272            };
273            related.push(report);
274        }
275
276        Ok(ReportWithOutputs {
277            error: self,
278            kcl_source,
279            filename,
280            related,
281        })
282    }
283}
284
285impl IntoDiagnostic for KclErrorWithOutputs {
286    fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
287        let message = self.error.get_message();
288        let source_ranges = self.error.source_ranges();
289
290        source_ranges
291            .into_iter()
292            .map(|source_range| {
293                let source = self
294                    .source_files
295                    .get(&source_range.module_id())
296                    .cloned()
297                    .unwrap_or(ModuleSource {
298                        source: code.to_string(),
299                        path: self.filenames.get(&source_range.module_id()).unwrap().clone(),
300                    });
301                let mut filename = source.path.to_string();
302                if !filename.starts_with("file://") {
303                    filename = format!("file:///{}", filename.trim_start_matches("/"));
304                }
305
306                let related_information = if let Ok(uri) = url::Url::parse(&filename) {
307                    Some(vec![tower_lsp::lsp_types::DiagnosticRelatedInformation {
308                        location: tower_lsp::lsp_types::Location {
309                            uri,
310                            range: source_range.to_lsp_range(&source.source),
311                        },
312                        message: message.to_string(),
313                    }])
314                } else {
315                    None
316                };
317
318                Diagnostic {
319                    range: source_range.to_lsp_range(code),
320                    severity: Some(self.severity()),
321                    code: None,
322                    // TODO: this is neat we can pass a URL to a help page here for this specific error.
323                    code_description: None,
324                    source: Some("kcl".to_string()),
325                    related_information,
326                    message: message.clone(),
327                    tags: None,
328                    data: None,
329                }
330            })
331            .collect()
332    }
333
334    fn severity(&self) -> DiagnosticSeverity {
335        DiagnosticSeverity::ERROR
336    }
337}
338
339#[derive(thiserror::Error, Debug)]
340#[error("{}", self.error.error.get_message())]
341pub struct ReportWithOutputs {
342    pub error: KclErrorWithOutputs,
343    pub kcl_source: String,
344    pub filename: String,
345    pub related: Vec<Report>,
346}
347
348impl miette::Diagnostic for ReportWithOutputs {
349    fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
350        let family = match self.error.error {
351            KclError::Lexical { .. } => "Lexical",
352            KclError::Syntax { .. } => "Syntax",
353            KclError::Semantic { .. } => "Semantic",
354            KclError::ImportCycle { .. } => "ImportCycle",
355            KclError::Argument { .. } => "Argument",
356            KclError::Type { .. } => "Type",
357            KclError::Io { .. } => "I/O",
358            KclError::Unexpected { .. } => "Unexpected",
359            KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
360            KclError::UndefinedValue { .. } => "UndefinedValue",
361            KclError::InvalidExpression { .. } => "InvalidExpression",
362            KclError::MaxCallStack { .. } => "MaxCallStack",
363            KclError::Engine { .. } => "Engine",
364            KclError::Internal { .. } => "Internal",
365        };
366        let error_string = format!("KCL {family} error");
367        Some(Box::new(error_string))
368    }
369
370    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
371        Some(&self.kcl_source)
372    }
373
374    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
375        let iter = self
376            .error
377            .error
378            .source_ranges()
379            .into_iter()
380            .map(miette::SourceSpan::from)
381            .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
382        Some(Box::new(iter))
383    }
384
385    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
386        let iter = self.related.iter().map(|r| r as &dyn miette::Diagnostic);
387        Some(Box::new(iter))
388    }
389}
390
391#[derive(thiserror::Error, Debug)]
392#[error("{}", self.error.get_message())]
393pub struct Report {
394    pub error: KclError,
395    pub kcl_source: String,
396    pub filename: String,
397}
398
399impl miette::Diagnostic for Report {
400    fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
401        let family = match self.error {
402            KclError::Lexical { .. } => "Lexical",
403            KclError::Syntax { .. } => "Syntax",
404            KclError::Semantic { .. } => "Semantic",
405            KclError::ImportCycle { .. } => "ImportCycle",
406            KclError::Argument { .. } => "Argument",
407            KclError::Type { .. } => "Type",
408            KclError::Io { .. } => "I/O",
409            KclError::Unexpected { .. } => "Unexpected",
410            KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
411            KclError::UndefinedValue { .. } => "UndefinedValue",
412            KclError::InvalidExpression { .. } => "InvalidExpression",
413            KclError::MaxCallStack { .. } => "MaxCallStack",
414            KclError::Engine { .. } => "Engine",
415            KclError::Internal { .. } => "Internal",
416        };
417        let error_string = format!("KCL {family} error");
418        Some(Box::new(error_string))
419    }
420
421    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
422        Some(&self.kcl_source)
423    }
424
425    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
426        let iter = self
427            .error
428            .source_ranges()
429            .into_iter()
430            .map(miette::SourceSpan::from)
431            .map(|span| miette::LabeledSpan::new_with_span(Some(self.filename.to_string()), span));
432        Some(Box::new(iter))
433    }
434}
435
436impl KclErrorDetails {
437    pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
438        let backtrace = source_ranges
439            .iter()
440            .map(|s| BacktraceItem {
441                source_range: *s,
442                fn_name: None,
443            })
444            .collect();
445        KclErrorDetails {
446            source_ranges,
447            backtrace,
448            message,
449        }
450    }
451}
452
453impl KclError {
454    pub fn internal(message: String) -> KclError {
455        KclError::Internal {
456            details: KclErrorDetails {
457                source_ranges: Default::default(),
458                backtrace: Default::default(),
459                message,
460            },
461        }
462    }
463
464    pub fn new_internal(details: KclErrorDetails) -> KclError {
465        KclError::Internal { details }
466    }
467
468    pub fn new_import_cycle(details: KclErrorDetails) -> KclError {
469        KclError::ImportCycle { details }
470    }
471
472    pub fn new_argument(details: KclErrorDetails) -> KclError {
473        KclError::Argument { details }
474    }
475
476    pub fn new_semantic(details: KclErrorDetails) -> KclError {
477        KclError::Semantic { details }
478    }
479
480    pub fn new_value_already_defined(details: KclErrorDetails) -> KclError {
481        KclError::ValueAlreadyDefined { details }
482    }
483
484    pub fn new_syntax(details: KclErrorDetails) -> KclError {
485        KclError::Syntax { details }
486    }
487
488    pub fn new_io(details: KclErrorDetails) -> KclError {
489        KclError::Io { details }
490    }
491
492    pub fn new_invalid_expression(details: KclErrorDetails) -> KclError {
493        KclError::InvalidExpression { details }
494    }
495
496    pub fn new_engine(details: KclErrorDetails) -> KclError {
497        KclError::Engine { details }
498    }
499
500    pub fn new_lexical(details: KclErrorDetails) -> KclError {
501        KclError::Lexical { details }
502    }
503
504    pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
505        KclError::UndefinedValue { details, name }
506    }
507
508    pub fn new_type(details: KclErrorDetails) -> KclError {
509        KclError::Type { details }
510    }
511
512    /// Get the error message.
513    pub fn get_message(&self) -> String {
514        format!("{}: {}", self.error_type(), self.message())
515    }
516
517    pub fn error_type(&self) -> &'static str {
518        match self {
519            KclError::Lexical { .. } => "lexical",
520            KclError::Syntax { .. } => "syntax",
521            KclError::Semantic { .. } => "semantic",
522            KclError::ImportCycle { .. } => "import cycle",
523            KclError::Argument { .. } => "argument",
524            KclError::Type { .. } => "type",
525            KclError::Io { .. } => "i/o",
526            KclError::Unexpected { .. } => "unexpected",
527            KclError::ValueAlreadyDefined { .. } => "value already defined",
528            KclError::UndefinedValue { .. } => "undefined value",
529            KclError::InvalidExpression { .. } => "invalid expression",
530            KclError::MaxCallStack { .. } => "max call stack",
531            KclError::Engine { .. } => "engine",
532            KclError::Internal { .. } => "internal",
533        }
534    }
535
536    pub fn source_ranges(&self) -> Vec<SourceRange> {
537        match &self {
538            KclError::Lexical { details: e } => e.source_ranges.clone(),
539            KclError::Syntax { details: e } => e.source_ranges.clone(),
540            KclError::Semantic { details: e } => e.source_ranges.clone(),
541            KclError::ImportCycle { details: e } => e.source_ranges.clone(),
542            KclError::Argument { details: e } => e.source_ranges.clone(),
543            KclError::Type { details: e } => e.source_ranges.clone(),
544            KclError::Io { details: e } => e.source_ranges.clone(),
545            KclError::Unexpected { details: e } => e.source_ranges.clone(),
546            KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
547            KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
548            KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
549            KclError::MaxCallStack { details: e } => e.source_ranges.clone(),
550            KclError::Engine { details: e } => e.source_ranges.clone(),
551            KclError::Internal { details: e } => e.source_ranges.clone(),
552        }
553    }
554
555    /// Get the inner error message.
556    pub fn message(&self) -> &str {
557        match &self {
558            KclError::Lexical { details: e } => &e.message,
559            KclError::Syntax { details: e } => &e.message,
560            KclError::Semantic { details: e } => &e.message,
561            KclError::ImportCycle { details: e } => &e.message,
562            KclError::Argument { details: e } => &e.message,
563            KclError::Type { details: e } => &e.message,
564            KclError::Io { details: e } => &e.message,
565            KclError::Unexpected { details: e } => &e.message,
566            KclError::ValueAlreadyDefined { details: e } => &e.message,
567            KclError::UndefinedValue { details: e, .. } => &e.message,
568            KclError::InvalidExpression { details: e } => &e.message,
569            KclError::MaxCallStack { details: e } => &e.message,
570            KclError::Engine { details: e } => &e.message,
571            KclError::Internal { details: e } => &e.message,
572        }
573    }
574
575    pub fn backtrace(&self) -> Vec<BacktraceItem> {
576        match self {
577            KclError::Lexical { details: e }
578            | KclError::Syntax { details: e }
579            | KclError::Semantic { details: e }
580            | KclError::ImportCycle { details: e }
581            | KclError::Argument { details: e }
582            | KclError::Type { details: e }
583            | KclError::Io { details: e }
584            | KclError::Unexpected { details: e }
585            | KclError::ValueAlreadyDefined { details: e }
586            | KclError::UndefinedValue { details: e, .. }
587            | KclError::InvalidExpression { details: e }
588            | KclError::MaxCallStack { details: e }
589            | KclError::Engine { details: e }
590            | KclError::Internal { details: e } => e.backtrace.clone(),
591        }
592    }
593
594    pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
595        let mut new = self.clone();
596        match &mut new {
597            KclError::Lexical { details: e }
598            | KclError::Syntax { details: e }
599            | KclError::Semantic { details: e }
600            | KclError::ImportCycle { details: e }
601            | KclError::Argument { details: e }
602            | KclError::Type { details: e }
603            | KclError::Io { details: e }
604            | KclError::Unexpected { details: e }
605            | KclError::ValueAlreadyDefined { details: e }
606            | KclError::UndefinedValue { details: e, .. }
607            | KclError::InvalidExpression { details: e }
608            | KclError::MaxCallStack { details: e }
609            | KclError::Engine { details: e }
610            | KclError::Internal { details: e } => {
611                e.backtrace = source_ranges
612                    .iter()
613                    .map(|s| BacktraceItem {
614                        source_range: *s,
615                        fn_name: None,
616                    })
617                    .collect();
618                e.source_ranges = source_ranges;
619            }
620        }
621
622        new
623    }
624
625    pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
626        let mut new = self.clone();
627        match &mut new {
628            KclError::Lexical { details: e }
629            | KclError::Syntax { details: e }
630            | KclError::Semantic { details: e }
631            | KclError::ImportCycle { details: e }
632            | KclError::Argument { details: e }
633            | KclError::Type { details: e }
634            | KclError::Io { details: e }
635            | KclError::Unexpected { details: e }
636            | KclError::ValueAlreadyDefined { details: e }
637            | KclError::UndefinedValue { details: e, .. }
638            | KclError::InvalidExpression { details: e }
639            | KclError::MaxCallStack { details: e }
640            | KclError::Engine { details: e }
641            | KclError::Internal { details: e } => {
642                if let Some(item) = e.backtrace.last_mut() {
643                    item.fn_name = last_fn_name;
644                }
645                e.backtrace.push(BacktraceItem {
646                    source_range,
647                    fn_name: None,
648                });
649                e.source_ranges.push(source_range);
650            }
651        }
652
653        new
654    }
655}
656
657#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)]
658#[serde(rename_all = "camelCase")]
659#[ts(export)]
660pub struct BacktraceItem {
661    pub source_range: SourceRange,
662    pub fn_name: Option<String>,
663}
664
665impl std::fmt::Display for BacktraceItem {
666    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667        if let Some(fn_name) = &self.fn_name {
668            write!(f, "{fn_name}: {:?}", self.source_range)
669        } else {
670            write!(f, "(fn): {:?}", self.source_range)
671        }
672    }
673}
674
675impl IntoDiagnostic for KclError {
676    fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
677        let message = self.get_message();
678        let source_ranges = self.source_ranges();
679
680        // Limit to only errors in the top-level file.
681        let module_id = ModuleId::default();
682        let source_ranges = source_ranges
683            .iter()
684            .filter(|r| r.module_id() == module_id)
685            .collect::<Vec<_>>();
686
687        let mut diagnostics = Vec::new();
688        for source_range in &source_ranges {
689            diagnostics.push(Diagnostic {
690                range: source_range.to_lsp_range(code),
691                severity: Some(self.severity()),
692                code: None,
693                // TODO: this is neat we can pass a URL to a help page here for this specific error.
694                code_description: None,
695                source: Some("kcl".to_string()),
696                related_information: None,
697                message: message.clone(),
698                tags: None,
699                data: None,
700            });
701        }
702
703        diagnostics
704    }
705
706    fn severity(&self) -> DiagnosticSeverity {
707        DiagnosticSeverity::ERROR
708    }
709}
710
711/// This is different than to_string() in that it will serialize the Error
712/// the struct as JSON so we can deserialize it on the js side.
713impl From<KclError> for String {
714    fn from(error: KclError) -> Self {
715        serde_json::to_string(&error).unwrap()
716    }
717}
718
719impl From<String> for KclError {
720    fn from(error: String) -> Self {
721        serde_json::from_str(&error).unwrap()
722    }
723}
724
725#[cfg(feature = "pyo3")]
726impl From<pyo3::PyErr> for KclError {
727    fn from(error: pyo3::PyErr) -> Self {
728        KclError::new_internal(KclErrorDetails {
729            source_ranges: vec![],
730            backtrace: Default::default(),
731            message: error.to_string(),
732        })
733    }
734}
735
736#[cfg(feature = "pyo3")]
737impl From<KclError> for pyo3::PyErr {
738    fn from(error: KclError) -> Self {
739        pyo3::exceptions::PyException::new_err(error.to_string())
740    }
741}
742
743impl From<CompilationError> for KclErrorDetails {
744    fn from(err: CompilationError) -> Self {
745        let backtrace = vec![BacktraceItem {
746            source_range: err.source_range,
747            fn_name: None,
748        }];
749        KclErrorDetails {
750            source_ranges: vec![err.source_range],
751            backtrace,
752            message: err.message,
753        }
754    }
755}