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