nu_protocol/errors/shell_error/
mod.rs

1use super::chained_error::ChainedError;
2use crate::{
3    ConfigError, LabeledError, ParseError, Span, Spanned, Type, Value, ast::Operator,
4    engine::StateWorkingSet, format_cli_error, record,
5};
6use job::JobError;
7use miette::Diagnostic;
8use serde::{Deserialize, Serialize};
9use std::num::NonZeroI32;
10use thiserror::Error;
11
12pub mod bridge;
13pub mod io;
14pub mod job;
15pub mod location;
16pub mod network;
17
18/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
19/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
20/// and pass it into an error viewer to display to the user.
21#[derive(Debug, Clone, Error, Diagnostic, PartialEq)]
22pub enum ShellError {
23    /// One or more of the values have types not supported by the operator.
24    #[error("The '{op}' operator does not work on values of type '{unsupported}'.")]
25    #[diagnostic(code(nu::shell::operator_unsupported_type))]
26    OperatorUnsupportedType {
27        op: Operator,
28        unsupported: Type,
29        #[label = "does not support '{unsupported}'"]
30        op_span: Span,
31        #[label("{unsupported}")]
32        unsupported_span: Span,
33        #[help]
34        help: Option<&'static str>,
35    },
36
37    /// The operator supports the types of both values, but not the specific combination of their types.
38    #[error("Types '{lhs}' and '{rhs}' are not compatible for the '{op}' operator.")]
39    #[diagnostic(code(nu::shell::operator_incompatible_types))]
40    OperatorIncompatibleTypes {
41        op: Operator,
42        lhs: Type,
43        rhs: Type,
44        #[label = "does not operate between '{lhs}' and '{rhs}'"]
45        op_span: Span,
46        #[label("{lhs}")]
47        lhs_span: Span,
48        #[label("{rhs}")]
49        rhs_span: Span,
50        #[help]
51        help: Option<&'static str>,
52    },
53
54    /// An arithmetic operation's resulting value overflowed its possible size.
55    ///
56    /// ## Resolution
57    ///
58    /// Check the inputs to the operation and add guards for their sizes.
59    /// Integers are generally of size i64, floats are generally f64.
60    #[error("Operator overflow.")]
61    #[diagnostic(code(nu::shell::operator_overflow))]
62    OperatorOverflow {
63        msg: String,
64        #[label = "{msg}"]
65        span: Span,
66        #[help]
67        help: Option<String>,
68    },
69
70    /// The pipelined input into a command was not of the expected type. For example, it might
71    /// expect a string input, but received a table instead.
72    ///
73    /// ## Resolution
74    ///
75    /// Check the relevant pipeline and extract or convert values as needed.
76    #[error("Pipeline mismatch.")]
77    #[diagnostic(code(nu::shell::pipeline_mismatch))]
78    PipelineMismatch {
79        exp_input_type: String,
80        #[label("expected: {exp_input_type}")]
81        dst_span: Span,
82        #[label("value originates here")]
83        src_span: Span,
84    },
85
86    // TODO: properly unify
87    /// The pipelined input into a command was not of the expected type. For example, it might
88    /// expect a string input, but received a table instead.
89    ///
90    /// (duplicate of [`ShellError::PipelineMismatch`] that reports the observed type)
91    ///
92    /// ## Resolution
93    ///
94    /// Check the relevant pipeline and extract or convert values as needed.
95    #[error("Input type not supported.")]
96    #[diagnostic(code(nu::shell::only_supports_this_input_type))]
97    OnlySupportsThisInputType {
98        exp_input_type: String,
99        wrong_type: String,
100        #[label("only {exp_input_type} input data is supported")]
101        dst_span: Span,
102        #[label("input type: {wrong_type}")]
103        src_span: Span,
104    },
105
106    /// No input value was piped into the command.
107    ///
108    /// ## Resolution
109    ///
110    /// Only use this command to process values from a previous expression.
111    #[error("Pipeline empty.")]
112    #[diagnostic(code(nu::shell::pipeline_mismatch))]
113    PipelineEmpty {
114        #[label("no input value was piped in")]
115        dst_span: Span,
116    },
117
118    // TODO: remove non type error usages
119    /// A command received an argument of the wrong type.
120    ///
121    /// ## Resolution
122    ///
123    /// Convert the argument type before passing it in, or change the command to accept the type.
124    #[error("Type mismatch.")]
125    #[diagnostic(code(nu::shell::type_mismatch))]
126    TypeMismatch {
127        err_message: String,
128        #[label = "{err_message}"]
129        span: Span,
130    },
131
132    /// A value's type did not match the expected type.
133    ///
134    /// ## Resolution
135    ///
136    /// Convert the value to the correct type or provide a value of the correct type.
137    #[error("Type mismatch")]
138    #[diagnostic(code(nu::shell::type_mismatch))]
139    RuntimeTypeMismatch {
140        expected: Type,
141        actual: Type,
142        #[label = "expected {expected}, but got {actual}"]
143        span: Span,
144    },
145
146    /// A value had the correct type but is otherwise invalid.
147    ///
148    /// ## Resolution
149    ///
150    /// Ensure the value meets the criteria in the error message.
151    #[error("Invalid value")]
152    #[diagnostic(code(nu::shell::invalid_value))]
153    InvalidValue {
154        valid: String,
155        actual: String,
156        #[label = "expected {valid}, but got {actual}"]
157        span: Span,
158    },
159
160    /// A command received an argument with correct type but incorrect value.
161    ///
162    /// ## Resolution
163    ///
164    /// Correct the argument value before passing it in or change the command.
165    #[error("Incorrect value.")]
166    #[diagnostic(code(nu::shell::incorrect_value))]
167    IncorrectValue {
168        msg: String,
169        #[label = "{msg}"]
170        val_span: Span,
171        #[label = "encountered here"]
172        call_span: Span,
173    },
174
175    /// Invalid assignment left-hand side
176    ///
177    /// ## Resolution
178    ///
179    /// Assignment requires that you assign to a variable or variable cell path.
180    #[error("Assignment operations require a variable.")]
181    #[diagnostic(code(nu::shell::assignment_requires_variable))]
182    AssignmentRequiresVar {
183        #[label = "needs to be a variable"]
184        lhs_span: Span,
185    },
186
187    /// Invalid assignment left-hand side
188    ///
189    /// ## Resolution
190    ///
191    /// Assignment requires that you assign to a mutable variable or cell path.
192    #[error("Assignment to an immutable variable.")]
193    #[diagnostic(code(nu::shell::assignment_requires_mutable_variable))]
194    AssignmentRequiresMutableVar {
195        #[label = "needs to be a mutable variable"]
196        lhs_span: Span,
197    },
198
199    /// An operator was not recognized during evaluation.
200    ///
201    /// ## Resolution
202    ///
203    /// Did you write the correct operator?
204    #[error("Unknown operator: {op_token}.")]
205    #[diagnostic(code(nu::shell::unknown_operator))]
206    UnknownOperator {
207        op_token: String,
208        #[label = "unknown operator"]
209        span: Span,
210    },
211
212    /// An expected command parameter is missing.
213    ///
214    /// ## Resolution
215    ///
216    /// Add the expected parameter and try again.
217    #[error("Missing parameter: {param_name}.")]
218    #[diagnostic(code(nu::shell::missing_parameter))]
219    MissingParameter {
220        param_name: String,
221        #[label = "missing parameter: {param_name}"]
222        span: Span,
223    },
224
225    /// Two parameters conflict with each other or are otherwise mutually exclusive.
226    ///
227    /// ## Resolution
228    ///
229    /// Remove one of the parameters/options and try again.
230    #[error("Incompatible parameters.")]
231    #[diagnostic(code(nu::shell::incompatible_parameters))]
232    IncompatibleParameters {
233        left_message: String,
234        // Be cautious, as flags can share the same span, resulting in a panic (ex: `rm -pt`)
235        #[label("{left_message}")]
236        left_span: Span,
237        right_message: String,
238        #[label("{right_message}")]
239        right_span: Span,
240    },
241
242    /// There's some issue with number or matching of delimiters in an expression.
243    ///
244    /// ## Resolution
245    ///
246    /// Check your syntax for mismatched braces, RegExp syntax errors, etc, based on the specific error message.
247    #[error("Delimiter error")]
248    #[diagnostic(code(nu::shell::delimiter_error))]
249    DelimiterError {
250        msg: String,
251        #[label("{msg}")]
252        span: Span,
253    },
254
255    /// An operation received parameters with some sort of incompatibility
256    /// (for example, different number of rows in a table, incompatible column names, etc).
257    ///
258    /// ## Resolution
259    ///
260    /// Refer to the specific error message for details on what's incompatible and then fix your
261    /// inputs to make sure they match that way.
262    #[error("Incompatible parameters.")]
263    #[diagnostic(code(nu::shell::incompatible_parameters))]
264    IncompatibleParametersSingle {
265        msg: String,
266        #[label = "{msg}"]
267        span: Span,
268    },
269
270    /// You're trying to run an unsupported external command.
271    ///
272    /// ## Resolution
273    ///
274    /// Make sure there's an appropriate `run-external` declaration for this external command.
275    #[error("Running external commands not supported")]
276    #[diagnostic(code(nu::shell::external_commands))]
277    ExternalNotSupported {
278        #[label = "external not supported"]
279        span: Span,
280    },
281
282    // TODO: consider moving to a more generic error variant for invalid values
283    /// The given probability input is invalid. The probability must be between 0 and 1.
284    ///
285    /// ## Resolution
286    ///
287    /// Make sure the probability is between 0 and 1 and try again.
288    #[error("Invalid Probability.")]
289    #[diagnostic(code(nu::shell::invalid_probability))]
290    InvalidProbability {
291        #[label = "invalid probability: must be between 0 and 1"]
292        span: Span,
293    },
294
295    /// The first value in a `..` range must be compatible with the second one.
296    ///
297    /// ## Resolution
298    ///
299    /// Check to make sure both values are compatible, and that the values are enumerable in Nushell.
300    #[error("Invalid range {left_flank}..{right_flank}")]
301    #[diagnostic(code(nu::shell::invalid_range))]
302    InvalidRange {
303        left_flank: String,
304        right_flank: String,
305        #[label = "expected a valid range"]
306        span: Span,
307    },
308
309    /// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error.
310    ///
311    /// ## Resolution
312    ///
313    /// It is very likely that this is a bug. Please file an issue at <https://github.com/nushell/nushell/issues> with relevant information.
314    #[error("Nushell failed: {msg}.")]
315    #[diagnostic(
316        code(nu::shell::nushell_failed),
317        help(
318            "This shouldn't happen. Please file an issue: https://github.com/nushell/nushell/issues"
319        )
320    )]
321    // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable
322    NushellFailed { msg: String },
323
324    /// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error.
325    ///
326    /// ## Resolution
327    ///
328    /// It is very likely that this is a bug. Please file an issue at <https://github.com/nushell/nushell/issues> with relevant information.
329    #[error("Nushell failed: {msg}.")]
330    #[diagnostic(
331        code(nu::shell::nushell_failed_spanned),
332        help(
333            "This shouldn't happen. Please file an issue: https://github.com/nushell/nushell/issues"
334        )
335    )]
336    // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable
337    NushellFailedSpanned {
338        msg: String,
339        label: String,
340        #[label = "{label}"]
341        span: Span,
342    },
343
344    /// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error.
345    ///
346    /// ## Resolution
347    ///
348    /// It is very likely that this is a bug. Please file an issue at <https://github.com/nushell/nushell/issues> with relevant information.
349    #[error("Nushell failed: {msg}.")]
350    #[diagnostic(code(nu::shell::nushell_failed_help))]
351    // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable
352    NushellFailedHelp {
353        msg: String,
354        #[help]
355        help: String,
356    },
357
358    /// A referenced variable was not found at runtime.
359    ///
360    /// ## Resolution
361    ///
362    /// Check the variable name. Did you typo it? Did you forget to declare it? Is the casing right?
363    #[error("Variable not found")]
364    #[diagnostic(code(nu::shell::variable_not_found))]
365    VariableNotFoundAtRuntime {
366        #[label = "variable not found"]
367        span: Span,
368    },
369
370    /// A referenced environment variable was not found at runtime.
371    ///
372    /// ## Resolution
373    ///
374    /// Check the environment variable name. Did you typo it? Did you forget to declare it? Is the casing right?
375    #[error("Environment variable '{envvar_name}' not found")]
376    #[diagnostic(code(nu::shell::env_variable_not_found))]
377    EnvVarNotFoundAtRuntime {
378        envvar_name: String,
379        #[label = "environment variable not found"]
380        span: Span,
381    },
382
383    /// A referenced module was not found at runtime.
384    ///
385    /// ## Resolution
386    ///
387    /// Check the module name. Did you typo it? Did you forget to declare it? Is the casing right?
388    #[error("Module '{mod_name}' not found")]
389    #[diagnostic(code(nu::shell::module_not_found))]
390    ModuleNotFoundAtRuntime {
391        mod_name: String,
392        #[label = "module not found"]
393        span: Span,
394    },
395
396    /// A referenced overlay was not found at runtime.
397    ///
398    /// ## Resolution
399    ///
400    /// Check the overlay name. Did you typo it? Did you forget to declare it? Is the casing right?
401    #[error("Overlay '{overlay_name}' not found")]
402    #[diagnostic(code(nu::shell::overlay_not_found))]
403    OverlayNotFoundAtRuntime {
404        overlay_name: String,
405        #[label = "overlay not found"]
406        span: Span,
407    },
408
409    /// The given item was not found. This is a fairly generic error that depends on context.
410    ///
411    /// ## Resolution
412    ///
413    /// This error is triggered in various places, and simply signals that "something" was not found. Refer to the specific error message for further details.
414    #[error("Not found.")]
415    #[diagnostic(code(nu::parser::not_found))]
416    NotFound {
417        #[label = "did not find anything under this name"]
418        span: Span,
419    },
420
421    /// Failed to convert a value of one type into a different type.
422    ///
423    /// ## Resolution
424    ///
425    /// Not all values can be coerced this way. Check the supported type(s) and try again.
426    #[error("Can't convert to {to_type}.")]
427    #[diagnostic(code(nu::shell::cant_convert))]
428    CantConvert {
429        to_type: String,
430        from_type: String,
431        #[label("can't convert {from_type} to {to_type}")]
432        span: Span,
433        #[help]
434        help: Option<String>,
435    },
436
437    /// Failed to convert a value of one type into a different type by specifying a unit.
438    ///
439    /// ## Resolution
440    ///
441    /// Check that the provided value can be converted in the provided: only Durations can be converted to duration units, and only Filesize can be converted to filesize units.
442    #[error("Can't convert {from_type} to the specified unit.")]
443    #[diagnostic(code(nu::shell::cant_convert_value_to_unit))]
444    CantConvertToUnit {
445        to_type: String,
446        from_type: String,
447        #[label("can't convert {from_type} to {to_type}")]
448        span: Span,
449        #[label("conversion originates here")]
450        unit_span: Span,
451        #[help]
452        help: Option<String>,
453    },
454
455    /// An environment variable cannot be represented as a string.
456    ///
457    /// ## Resolution
458    ///
459    /// Not all types can be converted to environment variable values, which must be strings. Check the input type and try again.
460    #[error("'{envvar_name}' is not representable as a string.")]
461    #[diagnostic(
462            code(nu::shell::env_var_not_a_string),
463            help(
464                r#"The '{envvar_name}' environment variable must be a string or be convertible to a string.
465    Either make sure '{envvar_name}' is a string, or add a 'to_string' entry for it in ENV_CONVERSIONS."#
466            )
467        )]
468    EnvVarNotAString {
469        envvar_name: String,
470        #[label("value not representable as a string")]
471        span: Span,
472    },
473
474    /// This environment variable cannot be set manually.
475    ///
476    /// ## Resolution
477    ///
478    /// This environment variable is set automatically by Nushell and cannot not be set manually.
479    #[error("{envvar_name} cannot be set manually.")]
480    #[diagnostic(
481        code(nu::shell::automatic_env_var_set_manually),
482        help(
483            r#"The environment variable '{envvar_name}' is set automatically by Nushell and cannot be set manually."#
484        )
485    )]
486    AutomaticEnvVarSetManually {
487        envvar_name: String,
488        #[label("cannot set '{envvar_name}' manually")]
489        span: Span,
490    },
491
492    /// It is not possible to replace the entire environment at once
493    ///
494    /// ## Resolution
495    ///
496    /// Setting the entire environment is not allowed. Change environment variables individually
497    /// instead.
498    #[error("Cannot replace environment.")]
499    #[diagnostic(
500        code(nu::shell::cannot_replace_env),
501        help(r#"Assigning a value to '$env' is not allowed."#)
502    )]
503    CannotReplaceEnv {
504        #[label("setting '$env' not allowed")]
505        span: Span,
506    },
507
508    /// Division by zero is not a thing.
509    ///
510    /// ## Resolution
511    ///
512    /// Add a guard of some sort to check whether a denominator input to this division is zero, and branch off if that's the case.
513    #[error("Division by zero.")]
514    #[diagnostic(code(nu::shell::division_by_zero))]
515    DivisionByZero {
516        #[label("division by zero")]
517        span: Span,
518    },
519
520    /// An error happened while trying to create a range.
521    ///
522    /// This can happen in various unexpected situations, for example if the range would loop forever (as would be the case with a 0-increment).
523    ///
524    /// ## Resolution
525    ///
526    /// Check your range values to make sure they're countable and would not loop forever.
527    #[error("Can't convert range to countable values")]
528    #[diagnostic(code(nu::shell::range_to_countable))]
529    CannotCreateRange {
530        #[label = "can't convert to countable values"]
531        span: Span,
532    },
533
534    /// You attempted to access an index beyond the available length of a value.
535    ///
536    /// ## Resolution
537    ///
538    /// Check your lengths and try again.
539    #[error("Row number too large (max: {max_idx}).")]
540    #[diagnostic(code(nu::shell::access_beyond_end))]
541    AccessBeyondEnd {
542        max_idx: usize,
543        #[label = "index too large (max: {max_idx})"]
544        span: Span,
545    },
546
547    /// You attempted to insert data at a list position higher than the end.
548    ///
549    /// ## Resolution
550    ///
551    /// To insert data into a list, assign to the last used index + 1.
552    #[error("Inserted at wrong row number (should be {available_idx}).")]
553    #[diagnostic(code(nu::shell::access_beyond_end))]
554    InsertAfterNextFreeIndex {
555        available_idx: usize,
556        #[label = "can't insert at index (the next available index is {available_idx})"]
557        span: Span,
558    },
559
560    /// You attempted to access an index when it's empty.
561    ///
562    /// ## Resolution
563    ///
564    /// Check your lengths and try again.
565    #[error("Row number too large (empty content).")]
566    #[diagnostic(code(nu::shell::access_beyond_end))]
567    AccessEmptyContent {
568        #[label = "index too large (empty content)"]
569        span: Span,
570    },
571
572    // TODO: check to be taken over by `AccessBeyondEnd`
573    /// You attempted to access an index beyond the available length of a stream.
574    ///
575    /// ## Resolution
576    ///
577    /// Check your lengths and try again.
578    #[error("Row number too large.")]
579    #[diagnostic(code(nu::shell::access_beyond_end_of_stream))]
580    AccessBeyondEndOfStream {
581        #[label = "index too large"]
582        span: Span,
583    },
584
585    /// Tried to index into a type that does not support pathed access.
586    ///
587    /// ## Resolution
588    ///
589    /// Check your types. Only composite types can be pathed into.
590    #[error("Data cannot be accessed with a cell path")]
591    #[diagnostic(code(nu::shell::incompatible_path_access))]
592    IncompatiblePathAccess {
593        type_name: String,
594        #[label("{type_name} doesn't support cell paths")]
595        span: Span,
596    },
597
598    /// The requested column does not exist.
599    ///
600    /// ## Resolution
601    ///
602    /// Check the spelling of your column name. Did you forget to rename a column somewhere?
603    #[error("Cannot find column '{col_name}'")]
604    #[diagnostic(code(nu::shell::column_not_found))]
605    CantFindColumn {
606        col_name: String,
607        #[label = "cannot find column '{col_name}'"]
608        span: Option<Span>,
609        #[label = "value originates here"]
610        src_span: Span,
611    },
612
613    /// Attempted to insert a column into a table, but a column with that name already exists.
614    ///
615    /// ## Resolution
616    ///
617    /// Drop or rename the existing column (check `rename -h`) and try again.
618    #[error("Column already exists")]
619    #[diagnostic(code(nu::shell::column_already_exists))]
620    ColumnAlreadyExists {
621        col_name: String,
622        #[label = "column '{col_name}' already exists"]
623        span: Span,
624        #[label = "value originates here"]
625        src_span: Span,
626    },
627
628    /// The given operation can only be performed on lists.
629    ///
630    /// ## Resolution
631    ///
632    /// Check the input type to this command. Are you sure it's a list?
633    #[error("Not a list value")]
634    #[diagnostic(code(nu::shell::not_a_list))]
635    NotAList {
636        #[label = "value not a list"]
637        dst_span: Span,
638        #[label = "value originates here"]
639        src_span: Span,
640    },
641
642    /// Fields can only be defined once
643    ///
644    /// ## Resolution
645    ///
646    /// Check the record to ensure you aren't reusing the same field name
647    #[error("Record field or table column used twice: {col_name}")]
648    #[diagnostic(code(nu::shell::column_defined_twice))]
649    ColumnDefinedTwice {
650        col_name: String,
651        #[label = "field redefined here"]
652        second_use: Span,
653        #[label = "field first defined here"]
654        first_use: Span,
655    },
656
657    /// Attempted to create a record from different number of columns and values
658    ///
659    /// ## Resolution
660    ///
661    /// Check the record has the same number of columns as values
662    #[error("Attempted to create a record from different number of columns and values")]
663    #[diagnostic(code(nu::shell::record_cols_vals_mismatch))]
664    RecordColsValsMismatch {
665        #[label = "problematic value"]
666        bad_value: Span,
667        #[label = "attempted to create the record here"]
668        creation_site: Span,
669    },
670
671    /// Failed to detect columns
672    ///
673    /// ## Resolution
674    ///
675    /// Use `detect columns --guess` or `parse` instead
676    #[error("Failed to detect columns")]
677    #[diagnostic(code(nu::shell::failed_to_detect_columns))]
678    ColumnDetectionFailure {
679        #[label = "value coming from here"]
680        bad_value: Span,
681        #[label = "tried to detect columns here"]
682        failure_site: Span,
683    },
684
685    /// Attempted to us a relative range on an infinite stream
686    ///
687    /// ## Resolution
688    ///
689    /// Ensure that either the range is absolute or the stream has a known length.
690    #[error("Relative range values cannot be used with streams that don't have a known length")]
691    #[diagnostic(code(nu::shell::relative_range_on_infinite_stream))]
692    RelativeRangeOnInfiniteStream {
693        #[label = "Relative range values cannot be used with streams that don't have a known length"]
694        span: Span,
695    },
696
697    /// An error happened while performing an external command.
698    ///
699    /// ## Resolution
700    ///
701    /// This error is fairly generic. Refer to the specific error message for further details.
702    #[error("External command failed")]
703    #[diagnostic(code(nu::shell::external_command), help("{help}"))]
704    ExternalCommand {
705        label: String,
706        help: String,
707        #[label("{label}")]
708        span: Span,
709    },
710
711    /// An external command exited with a non-zero exit code.
712    ///
713    /// ## Resolution
714    ///
715    /// Check the external command's error message.
716    #[error("External command had a non-zero exit code")]
717    #[diagnostic(code(nu::shell::non_zero_exit_code))]
718    NonZeroExitCode {
719        exit_code: NonZeroI32,
720        #[label("exited with code {exit_code}")]
721        span: Span,
722    },
723
724    #[cfg(unix)]
725    /// An external command exited due to a signal.
726    ///
727    /// ## Resolution
728    ///
729    /// Check why the signal was sent or triggered.
730    #[error("External command was terminated by a signal")]
731    #[diagnostic(code(nu::shell::terminated_by_signal))]
732    TerminatedBySignal {
733        signal_name: String,
734        signal: i32,
735        #[label("terminated by {signal_name} ({signal})")]
736        span: Span,
737    },
738
739    #[cfg(unix)]
740    /// An external command core dumped.
741    ///
742    /// ## Resolution
743    ///
744    /// Check why the core dumped was triggered.
745    #[error("External command core dumped")]
746    #[diagnostic(code(nu::shell::core_dumped))]
747    CoreDumped {
748        signal_name: String,
749        signal: i32,
750        #[label("core dumped with {signal_name} ({signal})")]
751        span: Span,
752    },
753
754    /// An operation was attempted with an input unsupported for some reason.
755    ///
756    /// ## Resolution
757    ///
758    /// This error is fairly generic. Refer to the specific error message for further details.
759    #[error("Unsupported input")]
760    #[diagnostic(code(nu::shell::unsupported_input))]
761    UnsupportedInput {
762        msg: String,
763        input: String,
764        #[label("{msg}")]
765        msg_span: Span,
766        #[label("{input}")]
767        input_span: Span,
768    },
769
770    /// Failed to parse an input into a datetime value.
771    ///
772    /// ## Resolution
773    ///
774    /// Make sure your datetime input format is correct.
775    ///
776    /// For example, these are some valid formats:
777    ///
778    /// * "5 pm"
779    /// * "2020/12/4"
780    /// * "2020.12.04 22:10 +2"
781    /// * "2020-04-12 22:10:57 +02:00"
782    /// * "2020-04-12T22:10:57.213231+02:00"
783    /// * "Tue, 1 Jul 2003 10:52:37 +0200""#
784    #[error("Unable to parse datetime: [{msg}].")]
785    #[diagnostic(
786        code(nu::shell::datetime_parse_error),
787        help(
788            r#"Examples of supported inputs:
789 * "5 pm"
790 * "2020/12/4"
791 * "2020.12.04 22:10 +2"
792 * "2020-04-12 22:10:57 +02:00"
793 * "2020-04-12T22:10:57.213231+02:00"
794 * "Tue, 1 Jul 2003 10:52:37 +0200""#
795        )
796    )]
797    DatetimeParseError {
798        msg: String,
799        #[label("datetime parsing failed")]
800        span: Span,
801    },
802
803    /// A network operation failed.
804    ///
805    /// ## Resolution
806    ///
807    /// It's always DNS.
808    #[error("Network failure")]
809    #[diagnostic(code(nu::shell::network_failure))]
810    NetworkFailure {
811        msg: String,
812        #[label("{msg}")]
813        span: Span,
814    },
815
816    #[error(transparent)]
817    #[diagnostic(transparent)]
818    Network(#[from] network::NetworkError),
819
820    /// Help text for this command could not be found.
821    ///
822    /// ## Resolution
823    ///
824    /// Check the spelling for the requested command and try again. Are you sure it's defined and your configurations are loading correctly? Can you execute it?
825    #[error("Command not found")]
826    #[diagnostic(code(nu::shell::command_not_found))]
827    CommandNotFound {
828        #[label("command not found")]
829        span: Span,
830    },
831
832    /// This alias could not be found
833    ///
834    /// ## Resolution
835    ///
836    /// The alias does not exist in the current scope. It might exist in another scope or overlay or be hidden.
837    #[error("Alias not found")]
838    #[diagnostic(code(nu::shell::alias_not_found))]
839    AliasNotFound {
840        #[label("alias not found")]
841        span: Span,
842    },
843
844    /// The registered plugin data for a plugin is invalid.
845    ///
846    /// ## Resolution
847    ///
848    /// `plugin add` the plugin again to update the data, or remove it with `plugin rm`.
849    #[error("The registered plugin data for `{plugin_name}` is invalid")]
850    #[diagnostic(code(nu::shell::plugin_registry_data_invalid))]
851    PluginRegistryDataInvalid {
852        plugin_name: String,
853        #[label("plugin `{plugin_name}` loaded here")]
854        span: Option<Span>,
855        #[help(
856            "the format in the plugin registry file is not compatible with this version of Nushell.\n\nTry adding the plugin again with `{}`"
857        )]
858        add_command: String,
859    },
860
861    /// A plugin failed to load.
862    ///
863    /// ## Resolution
864    ///
865    /// This is a fairly generic error. Refer to the specific error message for further details.
866    #[error("Plugin failed to load: {msg}")]
867    #[diagnostic(code(nu::shell::plugin_failed_to_load))]
868    PluginFailedToLoad { msg: String },
869
870    /// A message from a plugin failed to encode.
871    ///
872    /// ## Resolution
873    ///
874    /// This is likely a bug with the plugin itself.
875    #[error("Plugin failed to encode: {msg}")]
876    #[diagnostic(code(nu::shell::plugin_failed_to_encode))]
877    PluginFailedToEncode { msg: String },
878
879    /// A message to a plugin failed to decode.
880    ///
881    /// ## Resolution
882    ///
883    /// This is either an issue with the inputs to a plugin (bad JSON?) or a bug in the plugin itself. Fix or report as appropriate.
884    #[error("Plugin failed to decode: {msg}")]
885    #[diagnostic(code(nu::shell::plugin_failed_to_decode))]
886    PluginFailedToDecode { msg: String },
887
888    /// A custom value cannot be sent to the given plugin.
889    ///
890    /// ## Resolution
891    ///
892    /// Custom values can only be used with the plugin they came from. Use a command from that
893    /// plugin instead.
894    #[error("Custom value `{name}` cannot be sent to plugin")]
895    #[diagnostic(code(nu::shell::custom_value_incorrect_for_plugin))]
896    CustomValueIncorrectForPlugin {
897        name: String,
898        #[label("the `{dest_plugin}` plugin does not support this kind of value")]
899        span: Span,
900        dest_plugin: String,
901        #[help("this value came from the `{}` plugin")]
902        src_plugin: Option<String>,
903    },
904
905    /// The plugin failed to encode a custom value.
906    ///
907    /// ## Resolution
908    ///
909    /// This is likely a bug with the plugin itself. The plugin may have tried to send a custom
910    /// value that is not serializable.
911    #[error("Custom value failed to encode")]
912    #[diagnostic(code(nu::shell::custom_value_failed_to_encode))]
913    CustomValueFailedToEncode {
914        msg: String,
915        #[label("{msg}")]
916        span: Span,
917    },
918
919    /// The plugin failed to encode a custom value.
920    ///
921    /// ## Resolution
922    ///
923    /// This may be a bug within the plugin, or the plugin may have been updated in between the
924    /// creation of the custom value and its use.
925    #[error("Custom value failed to decode")]
926    #[diagnostic(code(nu::shell::custom_value_failed_to_decode))]
927    #[diagnostic(help("the plugin may have been updated and no longer support this custom value"))]
928    CustomValueFailedToDecode {
929        msg: String,
930        #[label("{msg}")]
931        span: Span,
932    },
933
934    /// An I/O operation failed.
935    ///
936    /// ## Resolution
937    ///
938    /// This is the main I/O error, for further details check the error kind and additional context.
939    #[error(transparent)]
940    #[diagnostic(transparent)]
941    Io(#[from] io::IoError),
942
943    /// A name was not found. Did you mean a different name?
944    ///
945    /// ## Resolution
946    ///
947    /// The error message will suggest a possible match for what you meant.
948    #[error("Name not found")]
949    #[diagnostic(code(nu::shell::name_not_found))]
950    DidYouMean {
951        suggestion: String,
952        #[label("did you mean '{suggestion}'?")]
953        span: Span,
954    },
955
956    /// A name was not found. Did you mean a different name?
957    ///
958    /// ## Resolution
959    ///
960    /// The error message will suggest a possible match for what you meant.
961    #[error("{msg}")]
962    #[diagnostic(code(nu::shell::did_you_mean_custom))]
963    DidYouMeanCustom {
964        msg: String,
965        suggestion: String,
966        #[label("did you mean '{suggestion}'?")]
967        span: Span,
968    },
969
970    /// The given input must be valid UTF-8 for further processing.
971    ///
972    /// ## Resolution
973    ///
974    /// Check your input's encoding. Are there any funny characters/bytes?
975    #[error("Non-UTF8 string")]
976    #[diagnostic(
977        code(nu::parser::non_utf8),
978        help("see `decode` for handling character sets other than UTF-8")
979    )]
980    NonUtf8 {
981        #[label("non-UTF8 string")]
982        span: Span,
983    },
984
985    /// The given input must be valid UTF-8 for further processing.
986    ///
987    /// ## Resolution
988    ///
989    /// Check your input's encoding. Are there any funny characters/bytes?
990    #[error("Non-UTF8 string")]
991    #[diagnostic(
992        code(nu::parser::non_utf8_custom),
993        help("see `decode` for handling character sets other than UTF-8")
994    )]
995    NonUtf8Custom {
996        msg: String,
997        #[label("{msg}")]
998        span: Span,
999    },
1000
1001    /// Failed to update the config due to one or more errors.
1002    ///
1003    /// ## Resolution
1004    ///
1005    /// Refer to the error messages for specific details.
1006    #[error("Encountered {} error(s) when updating config", errors.len())]
1007    #[diagnostic(code(nu::shell::invalid_config))]
1008    InvalidConfig {
1009        #[related]
1010        errors: Vec<ConfigError>,
1011    },
1012
1013    /// A value was missing a required column.
1014    ///
1015    /// ## Resolution
1016    ///
1017    /// Make sure the value has the required column.
1018    #[error("Value is missing a required '{column}' column")]
1019    #[diagnostic(code(nu::shell::missing_required_column))]
1020    MissingRequiredColumn {
1021        column: &'static str,
1022        #[label("has no '{column}' column")]
1023        span: Span,
1024    },
1025
1026    /// Negative value passed when positive one is required.
1027    ///
1028    /// ## Resolution
1029    ///
1030    /// Guard against negative values or check your inputs.
1031    #[error("Negative value passed when positive one is required")]
1032    #[diagnostic(code(nu::shell::needs_positive_value))]
1033    NeedsPositiveValue {
1034        #[label("use a positive value")]
1035        span: Span,
1036    },
1037
1038    /// This is a generic error type used for different situations.
1039    #[error("{error}")]
1040    #[diagnostic()]
1041    GenericError {
1042        error: String,
1043        msg: String,
1044        #[label("{msg}")]
1045        span: Option<Span>,
1046        #[help]
1047        help: Option<String>,
1048        #[related]
1049        inner: Vec<ShellError>,
1050    },
1051
1052    /// This is a generic error type used for different situations.
1053    #[error("{error}")]
1054    #[diagnostic()]
1055    OutsideSpannedLabeledError {
1056        #[source_code]
1057        src: String,
1058        error: String,
1059        msg: String,
1060        #[label("{msg}")]
1061        span: Span,
1062    },
1063
1064    /// This is a generic error type used for user and plugin-generated errors.
1065    #[error(transparent)]
1066    #[diagnostic(transparent)]
1067    LabeledError(#[from] Box<super::LabeledError>),
1068
1069    /// Attempted to use a command that has been removed from Nushell.
1070    ///
1071    /// ## Resolution
1072    ///
1073    /// Check the help for the new suggested command and update your script accordingly.
1074    #[error("Removed command: {removed}")]
1075    #[diagnostic(code(nu::shell::removed_command))]
1076    RemovedCommand {
1077        removed: String,
1078        replacement: String,
1079        #[label("'{removed}' has been removed from Nushell. Please use '{replacement}' instead.")]
1080        span: Span,
1081    },
1082
1083    // It should be only used by commands accepts block, and accept inputs from pipeline.
1084    /// Failed to eval block with specific pipeline input.
1085    #[error("Eval block failed with pipeline input")]
1086    #[diagnostic(code(nu::shell::eval_block_with_input))]
1087    EvalBlockWithInput {
1088        #[label("source value")]
1089        span: Span,
1090        #[related]
1091        sources: Vec<ShellError>,
1092    },
1093
1094    /// Break event, which may become an error if used outside of a loop
1095    #[error("Break used outside of loop")]
1096    Break {
1097        #[label("used outside of loop")]
1098        span: Span,
1099    },
1100
1101    /// Continue event, which may become an error if used outside of a loop
1102    #[error("Continue used outside of loop")]
1103    Continue {
1104        #[label("used outside of loop")]
1105        span: Span,
1106    },
1107
1108    /// Return event, which may become an error if used outside of a custom command or closure
1109    #[error("Return used outside of custom command or closure")]
1110    Return {
1111        #[label("used outside of custom command or closure")]
1112        span: Span,
1113        value: Box<Value>,
1114    },
1115
1116    /// The code being executed called itself too many times.
1117    ///
1118    /// ## Resolution
1119    ///
1120    /// Adjust your Nu code to
1121    #[error("Recursion limit ({recursion_limit}) reached")]
1122    #[diagnostic(code(nu::shell::recursion_limit_reached))]
1123    RecursionLimitReached {
1124        recursion_limit: u64,
1125        #[label("This called itself too many times")]
1126        span: Option<Span>,
1127    },
1128
1129    /// Operation interrupted
1130    #[error("Operation interrupted")]
1131    Interrupted {
1132        #[label("This operation was interrupted")]
1133        span: Span,
1134    },
1135
1136    /// An attempt to use, as a match guard, an expression that
1137    /// does not resolve into a boolean
1138    #[error("Match guard not bool")]
1139    #[diagnostic(
1140        code(nu::shell::match_guard_not_bool),
1141        help("Match guards should evaluate to a boolean")
1142    )]
1143    MatchGuardNotBool {
1144        #[label("not a boolean expression")]
1145        span: Span,
1146    },
1147
1148    /// An attempt to run a command marked for constant evaluation lacking the const. eval.
1149    /// implementation.
1150    ///
1151    /// This is an internal Nushell error, please file an issue.
1152    #[error("Missing const eval implementation")]
1153    #[diagnostic(
1154        code(nu::shell::missing_const_eval_implementation),
1155        help(
1156            "The command lacks an implementation for constant evaluation. \
1157This is an internal Nushell error, please file an issue https://github.com/nushell/nushell/issues."
1158        )
1159    )]
1160    MissingConstEvalImpl {
1161        #[label("command lacks constant implementation")]
1162        span: Span,
1163    },
1164
1165    /// TODO: Get rid of this error by moving the check before evaluation
1166    ///
1167    /// Tried evaluating of a subexpression with parsing error
1168    ///
1169    /// ## Resolution
1170    ///
1171    /// Fix the parsing error first.
1172    #[error("Found parsing error in expression.")]
1173    #[diagnostic(
1174        code(nu::shell::parse_error_in_constant),
1175        help(
1176            "This expression is supposed to be evaluated into a constant, which means error-free."
1177        )
1178    )]
1179    ParseErrorInConstant {
1180        #[label("Parsing error detected in expression")]
1181        span: Span,
1182    },
1183
1184    /// Tried assigning non-constant value to a constant
1185    ///
1186    /// ## Resolution
1187    ///
1188    /// Only a subset of expressions are allowed to be assigned as a constant during parsing.
1189    #[error("Not a constant.")]
1190    #[diagnostic(
1191        code(nu::shell::not_a_constant),
1192        help(
1193            "Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally."
1194        )
1195    )]
1196    NotAConstant {
1197        #[label("Value is not a parse-time constant")]
1198        span: Span,
1199    },
1200
1201    // TODO: Update help text once custom const commands are supported
1202    /// Tried running a command that is not const-compatible
1203    ///
1204    /// ## Resolution
1205    ///
1206    /// Only a subset of builtin commands can run at parse time.
1207    #[error("Not a const command.")]
1208    #[diagnostic(
1209        code(nu::shell::not_a_const_command),
1210        help("Only a subset of builtin commands can run at parse time.")
1211    )]
1212    NotAConstCommand {
1213        #[label("This command cannot run at parse time.")]
1214        span: Span,
1215    },
1216
1217    /// Tried getting a help message at parse time.
1218    ///
1219    /// ## Resolution
1220    ///
1221    /// Help messages are not supported at parse time.
1222    #[error("Help message not a constant.")]
1223    #[diagnostic(
1224        code(nu::shell::not_a_const_help),
1225        help("Help messages are currently not supported to be constants.")
1226    )]
1227    NotAConstHelp {
1228        #[label("This command cannot run at parse time.")]
1229        span: Span,
1230    },
1231
1232    #[error("{deprecation_type} deprecated.")]
1233    #[diagnostic(code(nu::shell::deprecated), severity(Warning))]
1234    DeprecationWarning {
1235        deprecation_type: &'static str,
1236        suggestion: String,
1237        #[label("{suggestion}")]
1238        span: Span,
1239        #[help]
1240        help: Option<&'static str>,
1241    },
1242
1243    /// Invalid glob pattern
1244    ///
1245    /// ## Resolution
1246    ///
1247    /// Correct glob pattern
1248    #[error("Invalid glob pattern")]
1249    #[diagnostic(
1250        code(nu::shell::invalid_glob_pattern),
1251        help("Refer to xxx for help on nushell glob patterns.")
1252    )]
1253    InvalidGlobPattern {
1254        msg: String,
1255        #[label("{msg}")]
1256        span: Span,
1257    },
1258
1259    /// Invalid unit
1260    ///
1261    /// ## Resolution
1262    ///
1263    /// Correct unit
1264    #[error("Invalid unit")]
1265    #[diagnostic(
1266        code(nu::shell::invalid_unit),
1267        help("Supported units are: {supported_units}")
1268    )]
1269    InvalidUnit {
1270        supported_units: String,
1271        #[label("encountered here")]
1272        span: Span,
1273    },
1274
1275    /// Tried spreading a non-list inside a list or command call.
1276    ///
1277    /// ## Resolution
1278    ///
1279    /// Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading.
1280    #[error("Not a list")]
1281    #[diagnostic(
1282        code(nu::shell::cannot_spread_as_list),
1283        help(
1284            "Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading."
1285        )
1286    )]
1287    CannotSpreadAsList {
1288        #[label = "cannot spread value"]
1289        span: Span,
1290    },
1291
1292    /// Tried spreading a non-record inside a record.
1293    ///
1294    /// ## Resolution
1295    ///
1296    /// Only records can be spread inside records. Try converting the value to a record before spreading.
1297    #[error("Not a record")]
1298    #[diagnostic(
1299        code(nu::shell::cannot_spread_as_record),
1300        help(
1301            "Only records can be spread inside records. Try converting the value to a record before spreading."
1302        )
1303    )]
1304    CannotSpreadAsRecord {
1305        #[label = "cannot spread value"]
1306        span: Span,
1307    },
1308
1309    /// Lists are not automatically spread when calling external commands
1310    ///
1311    /// ## Resolution
1312    ///
1313    /// Use the spread operator (put a '...' before the argument)
1314    #[error("Lists are not automatically spread when calling external commands")]
1315    #[diagnostic(
1316        code(nu::shell::cannot_pass_list_to_external),
1317        help("Either convert the list to a string or use the spread operator, like so: ...{arg}")
1318    )]
1319    CannotPassListToExternal {
1320        arg: String,
1321        #[label = "Spread operator (...) is necessary to spread lists"]
1322        span: Span,
1323    },
1324
1325    /// Out of bounds.
1326    ///
1327    /// ## Resolution
1328    ///
1329    /// Make sure the range is within the bounds of the input.
1330    #[error(
1331        "The selected range {left_flank}..{right_flank} is out of the bounds of the provided input"
1332    )]
1333    #[diagnostic(code(nu::shell::out_of_bounds))]
1334    OutOfBounds {
1335        left_flank: String,
1336        right_flank: String,
1337        #[label = "byte index is not a char boundary or is out of bounds of the input"]
1338        span: Span,
1339    },
1340
1341    /// The config directory could not be found
1342    #[error("The config directory could not be found")]
1343    #[diagnostic(
1344        code(nu::shell::config_dir_not_found),
1345        help(
1346            r#"On Linux, this would be $XDG_CONFIG_HOME or $HOME/.config.
1347On MacOS, this would be `$HOME/Library/Application Support`.
1348On Windows, this would be %USERPROFILE%\AppData\Roaming"#
1349        )
1350    )]
1351    ConfigDirNotFound {
1352        #[label = "Could not find config directory"]
1353        span: Span,
1354    },
1355
1356    /// XDG_CONFIG_HOME was set to an invalid path
1357    #[error(
1358        "$env.XDG_CONFIG_HOME ({xdg}) is invalid, using default config directory instead: {default}"
1359    )]
1360    #[diagnostic(
1361        code(nu::shell::xdg_config_home_invalid),
1362        help("Set XDG_CONFIG_HOME to an absolute path, or set it to an empty string to ignore it")
1363    )]
1364    InvalidXdgConfig { xdg: String, default: String },
1365
1366    /// An unexpected error occurred during IR evaluation.
1367    ///
1368    /// ## Resolution
1369    ///
1370    /// This is most likely a correctness issue with the IR compiler or evaluator. Please file a
1371    /// bug with the minimum code needed to reproduce the issue, if possible.
1372    #[error("IR evaluation error: {msg}")]
1373    #[diagnostic(
1374        code(nu::shell::ir_eval_error),
1375        help(
1376            "this is a bug, please report it at https://github.com/nushell/nushell/issues/new along with the code you were running if able"
1377        )
1378    )]
1379    IrEvalError {
1380        msg: String,
1381        #[label = "while running this code"]
1382        span: Option<Span>,
1383    },
1384
1385    #[error("OS feature is disabled: {msg}")]
1386    #[diagnostic(
1387        code(nu::shell::os_disabled),
1388        help("You're probably running outside an OS like a browser, we cannot support this")
1389    )]
1390    DisabledOsSupport {
1391        msg: String,
1392        #[label = "while running this code"]
1393        span: Span,
1394    },
1395
1396    #[error(transparent)]
1397    #[diagnostic(transparent)]
1398    Job(#[from] JobError),
1399
1400    #[error(transparent)]
1401    #[diagnostic(transparent)]
1402    ChainedError(ChainedError),
1403}
1404
1405impl ShellError {
1406    pub fn external_exit_code(&self) -> Option<Spanned<i32>> {
1407        let (item, span) = match *self {
1408            Self::NonZeroExitCode { exit_code, span } => (exit_code.into(), span),
1409            #[cfg(unix)]
1410            Self::TerminatedBySignal { signal, span, .. }
1411            | Self::CoreDumped { signal, span, .. } => (-signal, span),
1412            _ => return None,
1413        };
1414        Some(Spanned { item, span })
1415    }
1416
1417    pub fn exit_code(&self) -> Option<i32> {
1418        match self {
1419            Self::Return { .. } | Self::Break { .. } | Self::Continue { .. } => None,
1420            _ => self.external_exit_code().map(|e| e.item).or(Some(1)),
1421        }
1422    }
1423
1424    pub fn into_value(self, working_set: &StateWorkingSet, span: Span) -> Value {
1425        let exit_code = self.external_exit_code();
1426
1427        let mut record = record! {
1428            "msg" => Value::string(self.to_string(), span),
1429            "debug" => Value::string(format!("{self:?}"), span),
1430            "raw" => Value::error(self.clone(), span),
1431            "rendered" => Value::string(format_cli_error(working_set, &self, Some("nu::shell::error")), span),
1432            "json" => Value::string(serde_json::to_string(&self).expect("Could not serialize error"), span),
1433        };
1434
1435        if let Some(code) = exit_code {
1436            record.push("exit_code", Value::int(code.item.into(), code.span));
1437        }
1438
1439        Value::record(record, span)
1440    }
1441
1442    // TODO: Implement as From trait
1443    pub fn wrap(self, working_set: &StateWorkingSet, span: Span) -> ParseError {
1444        let msg = format_cli_error(working_set, &self, None);
1445        ParseError::LabeledError(
1446            msg,
1447            "Encountered error during parse-time evaluation".into(),
1448            span,
1449        )
1450    }
1451
1452    /// Convert self error to a [`ShellError::ChainedError`] variant.
1453    pub fn into_chainned(self, span: Span) -> Self {
1454        match self {
1455            ShellError::ChainedError(inner) => {
1456                ShellError::ChainedError(ChainedError::new_chained(inner, span))
1457            }
1458            other => ShellError::ChainedError(ChainedError::new(other, span)),
1459        }
1460    }
1461}
1462
1463impl From<Box<dyn std::error::Error>> for ShellError {
1464    fn from(error: Box<dyn std::error::Error>) -> ShellError {
1465        ShellError::GenericError {
1466            error: format!("{error:?}"),
1467            msg: error.to_string(),
1468            span: None,
1469            help: None,
1470            inner: vec![],
1471        }
1472    }
1473}
1474
1475impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
1476    fn from(error: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
1477        ShellError::GenericError {
1478            error: format!("{error:?}"),
1479            msg: error.to_string(),
1480            span: None,
1481            help: None,
1482            inner: vec![],
1483        }
1484    }
1485}
1486
1487impl From<super::LabeledError> for ShellError {
1488    fn from(error: super::LabeledError) -> Self {
1489        ShellError::LabeledError(Box::new(error))
1490    }
1491}
1492
1493/// `ShellError` always serializes as [`LabeledError`].
1494impl Serialize for ShellError {
1495    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1496    where
1497        S: serde::Serializer,
1498    {
1499        LabeledError::from_diagnostic(self).serialize(serializer)
1500    }
1501}
1502
1503/// `ShellError` always deserializes as if it were [`LabeledError`], resulting in a
1504/// [`ShellError::LabeledError`] variant.
1505impl<'de> Deserialize<'de> for ShellError {
1506    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1507    where
1508        D: serde::Deserializer<'de>,
1509    {
1510        LabeledError::deserialize(deserializer).map(ShellError::from)
1511    }
1512}
1513
1514#[test]
1515fn shell_error_serialize_roundtrip() {
1516    // Ensure that we can serialize and deserialize `ShellError`, and check that it basically would
1517    // look the same
1518    let original_error = ShellError::CantConvert {
1519        span: Span::new(100, 200),
1520        to_type: "Foo".into(),
1521        from_type: "Bar".into(),
1522        help: Some("this is a test".into()),
1523    };
1524    println!("orig_error = {original_error:#?}");
1525
1526    let serialized =
1527        serde_json::to_string_pretty(&original_error).expect("serde_json::to_string_pretty failed");
1528    println!("serialized = {serialized}");
1529
1530    let deserialized: ShellError =
1531        serde_json::from_str(&serialized).expect("serde_json::from_str failed");
1532    println!("deserialized = {deserialized:#?}");
1533
1534    // We don't expect the deserialized error to be the same as the original error, but its miette
1535    // properties should be comparable
1536    assert_eq!(original_error.to_string(), deserialized.to_string());
1537
1538    assert_eq!(
1539        original_error.code().map(|c| c.to_string()),
1540        deserialized.code().map(|c| c.to_string())
1541    );
1542
1543    let orig_labels = original_error
1544        .labels()
1545        .into_iter()
1546        .flatten()
1547        .collect::<Vec<_>>();
1548    let deser_labels = deserialized
1549        .labels()
1550        .into_iter()
1551        .flatten()
1552        .collect::<Vec<_>>();
1553
1554    assert_eq!(orig_labels, deser_labels);
1555
1556    assert_eq!(
1557        original_error.help().map(|c| c.to_string()),
1558        deserialized.help().map(|c| c.to_string())
1559    );
1560}
1561
1562#[cfg(test)]
1563mod test {
1564    use super::*;
1565
1566    impl From<std::io::Error> for ShellError {
1567        fn from(_: std::io::Error) -> ShellError {
1568            unimplemented!(
1569                "This implementation is defined in the test module to ensure no other implementation exists."
1570            )
1571        }
1572    }
1573
1574    impl From<Spanned<std::io::Error>> for ShellError {
1575        fn from(_: Spanned<std::io::Error>) -> Self {
1576            unimplemented!(
1577                "This implementation is defined in the test module to ensure no other implementation exists."
1578            )
1579        }
1580    }
1581
1582    impl From<ShellError> for std::io::Error {
1583        fn from(_: ShellError) -> Self {
1584            unimplemented!(
1585                "This implementation is defined in the test module to ensure no other implementation exists."
1586            )
1587        }
1588    }
1589}