nu_protocol/errors/shell_error/
mod.rs

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