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    /// Attempted to us a relative range on an infinite stream
671    ///
672    /// ## Resolution
673    ///
674    /// Ensure that either the range is absolute or the stream has a known length.
675    #[error("Relative range values cannot be used with streams that don't have a known length")]
676    #[diagnostic(code(nu::shell::relative_range_on_infinite_stream))]
677    RelativeRangeOnInfiniteStream {
678        #[label = "Relative range values cannot be used with streams that don't have a known length"]
679        span: Span,
680    },
681
682    /// An error happened while performing an external command.
683    ///
684    /// ## Resolution
685    ///
686    /// This error is fairly generic. Refer to the specific error message for further details.
687    #[error("External command failed")]
688    #[diagnostic(code(nu::shell::external_command), help("{help}"))]
689    ExternalCommand {
690        label: String,
691        help: String,
692        #[label("{label}")]
693        span: Span,
694    },
695
696    /// An external command exited with a non-zero exit code.
697    ///
698    /// ## Resolution
699    ///
700    /// Check the external command's error message.
701    #[error("External command had a non-zero exit code")]
702    #[diagnostic(code(nu::shell::non_zero_exit_code))]
703    NonZeroExitCode {
704        exit_code: NonZeroI32,
705        #[label("exited with code {exit_code}")]
706        span: Span,
707    },
708
709    #[cfg(unix)]
710    /// An external command exited due to a signal.
711    ///
712    /// ## Resolution
713    ///
714    /// Check why the signal was sent or triggered.
715    #[error("External command was terminated by a signal")]
716    #[diagnostic(code(nu::shell::terminated_by_signal))]
717    TerminatedBySignal {
718        signal_name: String,
719        signal: i32,
720        #[label("terminated by {signal_name} ({signal})")]
721        span: Span,
722    },
723
724    #[cfg(unix)]
725    /// An external command core dumped.
726    ///
727    /// ## Resolution
728    ///
729    /// Check why the core dumped was triggered.
730    #[error("External command core dumped")]
731    #[diagnostic(code(nu::shell::core_dumped))]
732    CoreDumped {
733        signal_name: String,
734        signal: i32,
735        #[label("core dumped with {signal_name} ({signal})")]
736        span: Span,
737    },
738
739    /// An operation was attempted with an input unsupported for some reason.
740    ///
741    /// ## Resolution
742    ///
743    /// This error is fairly generic. Refer to the specific error message for further details.
744    #[error("Unsupported input")]
745    #[diagnostic(code(nu::shell::unsupported_input))]
746    UnsupportedInput {
747        msg: String,
748        input: String,
749        #[label("{msg}")]
750        msg_span: Span,
751        #[label("{input}")]
752        input_span: Span,
753    },
754
755    /// Failed to parse an input into a datetime value.
756    ///
757    /// ## Resolution
758    ///
759    /// Make sure your datetime input format is correct.
760    ///
761    /// For example, these are some valid formats:
762    ///
763    /// * "5 pm"
764    /// * "2020/12/4"
765    /// * "2020.12.04 22:10 +2"
766    /// * "2020-04-12 22:10:57 +02:00"
767    /// * "2020-04-12T22:10:57.213231+02:00"
768    /// * "Tue, 1 Jul 2003 10:52:37 +0200""#
769    #[error("Unable to parse datetime: [{msg}].")]
770    #[diagnostic(
771        code(nu::shell::datetime_parse_error),
772        help(
773            r#"Examples of supported inputs:
774 * "5 pm"
775 * "2020/12/4"
776 * "2020.12.04 22:10 +2"
777 * "2020-04-12 22:10:57 +02:00"
778 * "2020-04-12T22:10:57.213231+02:00"
779 * "Tue, 1 Jul 2003 10:52:37 +0200""#
780        )
781    )]
782    DatetimeParseError {
783        msg: String,
784        #[label("datetime parsing failed")]
785        span: Span,
786    },
787
788    /// A network operation failed.
789    ///
790    /// ## Resolution
791    ///
792    /// It's always DNS.
793    #[error("Network failure")]
794    #[diagnostic(code(nu::shell::network_failure))]
795    NetworkFailure {
796        msg: String,
797        #[label("{msg}")]
798        span: Span,
799    },
800
801    /// Help text for this command could not be found.
802    ///
803    /// ## Resolution
804    ///
805    /// 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?
806    #[error("Command not found")]
807    #[diagnostic(code(nu::shell::command_not_found))]
808    CommandNotFound {
809        #[label("command not found")]
810        span: Span,
811    },
812
813    /// This alias could not be found
814    ///
815    /// ## Resolution
816    ///
817    /// The alias does not exist in the current scope. It might exist in another scope or overlay or be hidden.
818    #[error("Alias not found")]
819    #[diagnostic(code(nu::shell::alias_not_found))]
820    AliasNotFound {
821        #[label("alias not found")]
822        span: Span,
823    },
824
825    /// The registered plugin data for a plugin is invalid.
826    ///
827    /// ## Resolution
828    ///
829    /// `plugin add` the plugin again to update the data, or remove it with `plugin rm`.
830    #[error("The registered plugin data for `{plugin_name}` is invalid")]
831    #[diagnostic(code(nu::shell::plugin_registry_data_invalid))]
832    PluginRegistryDataInvalid {
833        plugin_name: String,
834        #[label("plugin `{plugin_name}` loaded here")]
835        span: Option<Span>,
836        #[help(
837            "the format in the plugin registry file is not compatible with this version of Nushell.\n\nTry adding the plugin again with `{}`"
838        )]
839        add_command: String,
840    },
841
842    /// A plugin failed to load.
843    ///
844    /// ## Resolution
845    ///
846    /// This is a fairly generic error. Refer to the specific error message for further details.
847    #[error("Plugin failed to load: {msg}")]
848    #[diagnostic(code(nu::shell::plugin_failed_to_load))]
849    PluginFailedToLoad { msg: String },
850
851    /// A message from a plugin failed to encode.
852    ///
853    /// ## Resolution
854    ///
855    /// This is likely a bug with the plugin itself.
856    #[error("Plugin failed to encode: {msg}")]
857    #[diagnostic(code(nu::shell::plugin_failed_to_encode))]
858    PluginFailedToEncode { msg: String },
859
860    /// A message to a plugin failed to decode.
861    ///
862    /// ## Resolution
863    ///
864    /// 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.
865    #[error("Plugin failed to decode: {msg}")]
866    #[diagnostic(code(nu::shell::plugin_failed_to_decode))]
867    PluginFailedToDecode { msg: String },
868
869    /// A custom value cannot be sent to the given plugin.
870    ///
871    /// ## Resolution
872    ///
873    /// Custom values can only be used with the plugin they came from. Use a command from that
874    /// plugin instead.
875    #[error("Custom value `{name}` cannot be sent to plugin")]
876    #[diagnostic(code(nu::shell::custom_value_incorrect_for_plugin))]
877    CustomValueIncorrectForPlugin {
878        name: String,
879        #[label("the `{dest_plugin}` plugin does not support this kind of value")]
880        span: Span,
881        dest_plugin: String,
882        #[help("this value came from the `{}` plugin")]
883        src_plugin: Option<String>,
884    },
885
886    /// The plugin failed to encode a custom value.
887    ///
888    /// ## Resolution
889    ///
890    /// This is likely a bug with the plugin itself. The plugin may have tried to send a custom
891    /// value that is not serializable.
892    #[error("Custom value failed to encode")]
893    #[diagnostic(code(nu::shell::custom_value_failed_to_encode))]
894    CustomValueFailedToEncode {
895        msg: String,
896        #[label("{msg}")]
897        span: Span,
898    },
899
900    /// The plugin failed to encode a custom value.
901    ///
902    /// ## Resolution
903    ///
904    /// This may be a bug within the plugin, or the plugin may have been updated in between the
905    /// creation of the custom value and its use.
906    #[error("Custom value failed to decode")]
907    #[diagnostic(code(nu::shell::custom_value_failed_to_decode))]
908    #[diagnostic(help("the plugin may have been updated and no longer support this custom value"))]
909    CustomValueFailedToDecode {
910        msg: String,
911        #[label("{msg}")]
912        span: Span,
913    },
914
915    /// An I/O operation failed.
916    ///
917    /// ## Resolution
918    ///
919    /// This is the main I/O error, for further details check the error kind and additional context.
920    #[error(transparent)]
921    #[diagnostic(transparent)]
922    Io(#[from] io::IoError),
923
924    /// A name was not found. Did you mean a different name?
925    ///
926    /// ## Resolution
927    ///
928    /// The error message will suggest a possible match for what you meant.
929    #[error("Name not found")]
930    #[diagnostic(code(nu::shell::name_not_found))]
931    DidYouMean {
932        suggestion: String,
933        #[label("did you mean '{suggestion}'?")]
934        span: Span,
935    },
936
937    /// A name was not found. Did you mean a different name?
938    ///
939    /// ## Resolution
940    ///
941    /// The error message will suggest a possible match for what you meant.
942    #[error("{msg}")]
943    #[diagnostic(code(nu::shell::did_you_mean_custom))]
944    DidYouMeanCustom {
945        msg: String,
946        suggestion: String,
947        #[label("did you mean '{suggestion}'?")]
948        span: Span,
949    },
950
951    /// The given input must be valid UTF-8 for further processing.
952    ///
953    /// ## Resolution
954    ///
955    /// Check your input's encoding. Are there any funny characters/bytes?
956    #[error("Non-UTF8 string")]
957    #[diagnostic(
958        code(nu::parser::non_utf8),
959        help("see `decode` for handling character sets other than UTF-8")
960    )]
961    NonUtf8 {
962        #[label("non-UTF8 string")]
963        span: Span,
964    },
965
966    /// The given input must be valid UTF-8 for further processing.
967    ///
968    /// ## Resolution
969    ///
970    /// Check your input's encoding. Are there any funny characters/bytes?
971    #[error("Non-UTF8 string")]
972    #[diagnostic(
973        code(nu::parser::non_utf8_custom),
974        help("see `decode` for handling character sets other than UTF-8")
975    )]
976    NonUtf8Custom {
977        msg: String,
978        #[label("{msg}")]
979        span: Span,
980    },
981
982    /// Failed to update the config due to one or more errors.
983    ///
984    /// ## Resolution
985    ///
986    /// Refer to the error messages for specific details.
987    #[error("Encountered {} error(s) when updating config", errors.len())]
988    #[diagnostic(code(nu::shell::invalid_config))]
989    InvalidConfig {
990        #[related]
991        errors: Vec<ConfigError>,
992    },
993
994    /// A value was missing a required column.
995    ///
996    /// ## Resolution
997    ///
998    /// Make sure the value has the required column.
999    #[error("Value is missing a required '{column}' column")]
1000    #[diagnostic(code(nu::shell::missing_required_column))]
1001    MissingRequiredColumn {
1002        column: &'static str,
1003        #[label("has no '{column}' column")]
1004        span: Span,
1005    },
1006
1007    /// Negative value passed when positive one is required.
1008    ///
1009    /// ## Resolution
1010    ///
1011    /// Guard against negative values or check your inputs.
1012    #[error("Negative value passed when positive one is required")]
1013    #[diagnostic(code(nu::shell::needs_positive_value))]
1014    NeedsPositiveValue {
1015        #[label("use a positive value")]
1016        span: Span,
1017    },
1018
1019    /// This is a generic error type used for different situations.
1020    #[error("{error}")]
1021    #[diagnostic()]
1022    GenericError {
1023        error: String,
1024        msg: String,
1025        #[label("{msg}")]
1026        span: Option<Span>,
1027        #[help]
1028        help: Option<String>,
1029        #[related]
1030        inner: Vec<ShellError>,
1031    },
1032
1033    /// This is a generic error type used for different situations.
1034    #[error("{error}")]
1035    #[diagnostic()]
1036    OutsideSpannedLabeledError {
1037        #[source_code]
1038        src: String,
1039        error: String,
1040        msg: String,
1041        #[label("{msg}")]
1042        span: Span,
1043    },
1044
1045    /// This is a generic error type used for user and plugin-generated errors.
1046    #[error(transparent)]
1047    #[diagnostic(transparent)]
1048    LabeledError(#[from] Box<super::LabeledError>),
1049
1050    /// Attempted to use a command that has been removed from Nushell.
1051    ///
1052    /// ## Resolution
1053    ///
1054    /// Check the help for the new suggested command and update your script accordingly.
1055    #[error("Removed command: {removed}")]
1056    #[diagnostic(code(nu::shell::removed_command))]
1057    RemovedCommand {
1058        removed: String,
1059        replacement: String,
1060        #[label("'{removed}' has been removed from Nushell. Please use '{replacement}' instead.")]
1061        span: Span,
1062    },
1063
1064    // It should be only used by commands accepts block, and accept inputs from pipeline.
1065    /// Failed to eval block with specific pipeline input.
1066    #[error("Eval block failed with pipeline input")]
1067    #[diagnostic(code(nu::shell::eval_block_with_input))]
1068    EvalBlockWithInput {
1069        #[label("source value")]
1070        span: Span,
1071        #[related]
1072        sources: Vec<ShellError>,
1073    },
1074
1075    /// Break event, which may become an error if used outside of a loop
1076    #[error("Break used outside of loop")]
1077    Break {
1078        #[label("used outside of loop")]
1079        span: Span,
1080    },
1081
1082    /// Continue event, which may become an error if used outside of a loop
1083    #[error("Continue used outside of loop")]
1084    Continue {
1085        #[label("used outside of loop")]
1086        span: Span,
1087    },
1088
1089    /// Return event, which may become an error if used outside of a custom command or closure
1090    #[error("Return used outside of custom command or closure")]
1091    Return {
1092        #[label("used outside of custom command or closure")]
1093        span: Span,
1094        value: Box<Value>,
1095    },
1096
1097    /// The code being executed called itself too many times.
1098    ///
1099    /// ## Resolution
1100    ///
1101    /// Adjust your Nu code to
1102    #[error("Recursion limit ({recursion_limit}) reached")]
1103    #[diagnostic(code(nu::shell::recursion_limit_reached))]
1104    RecursionLimitReached {
1105        recursion_limit: u64,
1106        #[label("This called itself too many times")]
1107        span: Option<Span>,
1108    },
1109
1110    /// Operation interrupted
1111    #[error("Operation interrupted")]
1112    Interrupted {
1113        #[label("This operation was interrupted")]
1114        span: Span,
1115    },
1116
1117    /// Operation interrupted by user
1118    #[error("Operation interrupted by user")]
1119    InterruptedByUser {
1120        #[label("This operation was interrupted")]
1121        span: Option<Span>,
1122    },
1123
1124    /// An attempt to use, as a match guard, an expression that
1125    /// does not resolve into a boolean
1126    #[error("Match guard not bool")]
1127    #[diagnostic(
1128        code(nu::shell::match_guard_not_bool),
1129        help("Match guards should evaluate to a boolean")
1130    )]
1131    MatchGuardNotBool {
1132        #[label("not a boolean expression")]
1133        span: Span,
1134    },
1135
1136    /// An attempt to run a command marked for constant evaluation lacking the const. eval.
1137    /// implementation.
1138    ///
1139    /// This is an internal Nushell error, please file an issue.
1140    #[error("Missing const eval implementation")]
1141    #[diagnostic(
1142        code(nu::shell::missing_const_eval_implementation),
1143        help(
1144            "The command lacks an implementation for constant evaluation. \
1145This is an internal Nushell error, please file an issue https://github.com/nushell/nushell/issues."
1146        )
1147    )]
1148    MissingConstEvalImpl {
1149        #[label("command lacks constant implementation")]
1150        span: Span,
1151    },
1152
1153    /// TODO: Get rid of this error by moving the check before evaluation
1154    ///
1155    /// Tried evaluating of a subexpression with parsing error
1156    ///
1157    /// ## Resolution
1158    ///
1159    /// Fix the parsing error first.
1160    #[error("Found parsing error in expression.")]
1161    #[diagnostic(
1162        code(nu::shell::parse_error_in_constant),
1163        help(
1164            "This expression is supposed to be evaluated into a constant, which means error-free."
1165        )
1166    )]
1167    ParseErrorInConstant {
1168        #[label("Parsing error detected in expression")]
1169        span: Span,
1170    },
1171
1172    /// Tried assigning non-constant value to a constant
1173    ///
1174    /// ## Resolution
1175    ///
1176    /// Only a subset of expressions are allowed to be assigned as a constant during parsing.
1177    #[error("Not a constant.")]
1178    #[diagnostic(
1179        code(nu::shell::not_a_constant),
1180        help(
1181            "Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally."
1182        )
1183    )]
1184    NotAConstant {
1185        #[label("Value is not a parse-time constant")]
1186        span: Span,
1187    },
1188
1189    /// Tried running a command that is not const-compatible
1190    ///
1191    /// ## Resolution
1192    ///
1193    /// Only a subset of builtin commands, and custom commands built only from those commands, can
1194    /// run at parse time.
1195    #[error("Not a const command.")]
1196    #[diagnostic(
1197        code(nu::shell::not_a_const_command),
1198        help(
1199            "Only a subset of builtin commands, and custom commands built only from those commands, can run at parse time."
1200        )
1201    )]
1202    NotAConstCommand {
1203        #[label("This command cannot run at parse time.")]
1204        span: Span,
1205    },
1206
1207    /// Tried getting a help message at parse time.
1208    ///
1209    /// ## Resolution
1210    ///
1211    /// Help messages are not supported at parse time.
1212    #[error("Help message not a constant.")]
1213    #[diagnostic(
1214        code(nu::shell::not_a_const_help),
1215        help("Help messages are currently not supported to be constants.")
1216    )]
1217    NotAConstHelp {
1218        #[label("This command cannot run at parse time.")]
1219        span: Span,
1220    },
1221
1222    #[error("{deprecation_type} deprecated.")]
1223    #[diagnostic(code(nu::shell::deprecated), severity(Warning))]
1224    DeprecationWarning {
1225        deprecation_type: &'static str,
1226        suggestion: String,
1227        #[label("{suggestion}")]
1228        span: Span,
1229        #[help]
1230        help: Option<&'static str>,
1231    },
1232
1233    /// Invalid glob pattern
1234    ///
1235    /// ## Resolution
1236    ///
1237    /// Correct glob pattern
1238    #[error("Invalid glob pattern")]
1239    #[diagnostic(
1240        code(nu::shell::invalid_glob_pattern),
1241        help("Refer to xxx for help on nushell glob patterns.")
1242    )]
1243    InvalidGlobPattern {
1244        msg: String,
1245        #[label("{msg}")]
1246        span: Span,
1247    },
1248
1249    /// Invalid unit
1250    ///
1251    /// ## Resolution
1252    ///
1253    /// Correct unit
1254    #[error("Invalid unit")]
1255    #[diagnostic(
1256        code(nu::shell::invalid_unit),
1257        help("Supported units are: {supported_units}")
1258    )]
1259    InvalidUnit {
1260        supported_units: String,
1261        #[label("encountered here")]
1262        span: Span,
1263    },
1264
1265    /// Tried spreading a non-list inside a list or command call.
1266    ///
1267    /// ## Resolution
1268    ///
1269    /// Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading.
1270    #[error("Not a list")]
1271    #[diagnostic(
1272        code(nu::shell::cannot_spread_as_list),
1273        help(
1274            "Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading."
1275        )
1276    )]
1277    CannotSpreadAsList {
1278        #[label = "cannot spread value"]
1279        span: Span,
1280    },
1281
1282    /// Tried spreading a non-record inside a record.
1283    ///
1284    /// ## Resolution
1285    ///
1286    /// Only records can be spread inside records. Try converting the value to a record before spreading.
1287    #[error("Not a record")]
1288    #[diagnostic(
1289        code(nu::shell::cannot_spread_as_record),
1290        help(
1291            "Only records can be spread inside records. Try converting the value to a record before spreading."
1292        )
1293    )]
1294    CannotSpreadAsRecord {
1295        #[label = "cannot spread value"]
1296        span: Span,
1297    },
1298
1299    /// Lists are not automatically spread when calling external commands
1300    ///
1301    /// ## Resolution
1302    ///
1303    /// Use the spread operator (put a '...' before the argument)
1304    #[error("Lists are not automatically spread when calling external commands")]
1305    #[diagnostic(
1306        code(nu::shell::cannot_pass_list_to_external),
1307        help("Either convert the list to a string or use the spread operator, like so: ...{arg}")
1308    )]
1309    CannotPassListToExternal {
1310        arg: String,
1311        #[label = "Spread operator (...) is necessary to spread lists"]
1312        span: Span,
1313    },
1314
1315    /// Out of bounds.
1316    ///
1317    /// ## Resolution
1318    ///
1319    /// Make sure the range is within the bounds of the input.
1320    #[error(
1321        "The selected range {left_flank}..{right_flank} is out of the bounds of the provided input"
1322    )]
1323    #[diagnostic(code(nu::shell::out_of_bounds))]
1324    OutOfBounds {
1325        left_flank: String,
1326        right_flank: String,
1327        #[label = "byte index is not a char boundary or is out of bounds of the input"]
1328        span: Span,
1329    },
1330
1331    /// The config directory could not be found
1332    #[error("The config directory could not be found")]
1333    #[diagnostic(
1334        code(nu::shell::config_dir_not_found),
1335        help(
1336            r#"On Linux, this would be $XDG_CONFIG_HOME or $HOME/.config.
1337On MacOS, this would be `$HOME/Library/Application Support`.
1338On Windows, this would be %USERPROFILE%\AppData\Roaming"#
1339        )
1340    )]
1341    ConfigDirNotFound {
1342        #[label = "Could not find config directory"]
1343        span: Option<Span>,
1344    },
1345
1346    /// XDG_CONFIG_HOME was set to an invalid path
1347    #[error(
1348        "$env.XDG_CONFIG_HOME ({xdg}) is invalid, using default config directory instead: {default}"
1349    )]
1350    #[diagnostic(
1351        code(nu::shell::xdg_config_home_invalid),
1352        help("Set XDG_CONFIG_HOME to an absolute path, or set it to an empty string to ignore it")
1353    )]
1354    InvalidXdgConfig { xdg: String, default: String },
1355
1356    /// An unexpected error occurred during IR evaluation.
1357    ///
1358    /// ## Resolution
1359    ///
1360    /// This is most likely a correctness issue with the IR compiler or evaluator. Please file a
1361    /// bug with the minimum code needed to reproduce the issue, if possible.
1362    #[error("IR evaluation error: {msg}")]
1363    #[diagnostic(
1364        code(nu::shell::ir_eval_error),
1365        help(
1366            "this is a bug, please report it at https://github.com/nushell/nushell/issues/new along with the code you were running if able"
1367        )
1368    )]
1369    IrEvalError {
1370        msg: String,
1371        #[label = "while running this code"]
1372        span: Option<Span>,
1373    },
1374
1375    #[error("OS feature is disabled: {msg}")]
1376    #[diagnostic(
1377        code(nu::shell::os_disabled),
1378        help("You're probably running outside an OS like a browser, we cannot support this")
1379    )]
1380    DisabledOsSupport {
1381        msg: String,
1382        #[label = "while running this code"]
1383        span: Option<Span>,
1384    },
1385
1386    #[error(transparent)]
1387    #[diagnostic(transparent)]
1388    Job(#[from] JobError),
1389
1390    #[error(transparent)]
1391    #[diagnostic(transparent)]
1392    ChainedError(ChainedError),
1393}
1394
1395impl ShellError {
1396    pub fn external_exit_code(&self) -> Option<Spanned<i32>> {
1397        let (item, span) = match *self {
1398            Self::NonZeroExitCode { exit_code, span } => (exit_code.into(), span),
1399            #[cfg(unix)]
1400            Self::TerminatedBySignal { signal, span, .. }
1401            | Self::CoreDumped { signal, span, .. } => (-signal, span),
1402            _ => return None,
1403        };
1404        Some(Spanned { item, span })
1405    }
1406
1407    pub fn exit_code(&self) -> Option<i32> {
1408        match self {
1409            Self::Return { .. } | Self::Break { .. } | Self::Continue { .. } => None,
1410            _ => self.external_exit_code().map(|e| e.item).or(Some(1)),
1411        }
1412    }
1413
1414    pub fn into_value(self, working_set: &StateWorkingSet, span: Span) -> Value {
1415        let exit_code = self.external_exit_code();
1416
1417        let mut record = record! {
1418            "msg" => Value::string(self.to_string(), span),
1419            "debug" => Value::string(format!("{self:?}"), span),
1420            "raw" => Value::error(self.clone(), span),
1421            "rendered" => Value::string(format_cli_error(working_set, &self, Some("nu::shell::error")), span),
1422            "json" => Value::string(serde_json::to_string(&self).expect("Could not serialize error"), span),
1423        };
1424
1425        if let Some(code) = exit_code {
1426            record.push("exit_code", Value::int(code.item.into(), code.span));
1427        }
1428
1429        Value::record(record, span)
1430    }
1431
1432    // TODO: Implement as From trait
1433    pub fn wrap(self, working_set: &StateWorkingSet, span: Span) -> ParseError {
1434        let msg = format_cli_error(working_set, &self, None);
1435        ParseError::LabeledError(
1436            msg,
1437            "Encountered error during parse-time evaluation".into(),
1438            span,
1439        )
1440    }
1441
1442    /// Convert self error to a [`ShellError::ChainedError`] variant.
1443    pub fn into_chainned(self, span: Span) -> Self {
1444        match self {
1445            ShellError::ChainedError(inner) => {
1446                ShellError::ChainedError(ChainedError::new_chained(inner, span))
1447            }
1448            other => ShellError::ChainedError(ChainedError::new(other, span)),
1449        }
1450    }
1451}
1452
1453impl From<Box<dyn std::error::Error>> for ShellError {
1454    fn from(error: Box<dyn std::error::Error>) -> ShellError {
1455        ShellError::GenericError {
1456            error: format!("{error:?}"),
1457            msg: error.to_string(),
1458            span: None,
1459            help: None,
1460            inner: vec![],
1461        }
1462    }
1463}
1464
1465impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
1466    fn from(error: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
1467        ShellError::GenericError {
1468            error: format!("{error:?}"),
1469            msg: error.to_string(),
1470            span: None,
1471            help: None,
1472            inner: vec![],
1473        }
1474    }
1475}
1476
1477impl From<super::LabeledError> for ShellError {
1478    fn from(error: super::LabeledError) -> Self {
1479        ShellError::LabeledError(Box::new(error))
1480    }
1481}
1482
1483/// `ShellError` always serializes as [`LabeledError`].
1484impl Serialize for ShellError {
1485    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1486    where
1487        S: serde::Serializer,
1488    {
1489        LabeledError::from_diagnostic(self).serialize(serializer)
1490    }
1491}
1492
1493/// `ShellError` always deserializes as if it were [`LabeledError`], resulting in a
1494/// [`ShellError::LabeledError`] variant.
1495impl<'de> Deserialize<'de> for ShellError {
1496    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1497    where
1498        D: serde::Deserializer<'de>,
1499    {
1500        LabeledError::deserialize(deserializer).map(ShellError::from)
1501    }
1502}
1503
1504#[test]
1505fn shell_error_serialize_roundtrip() {
1506    // Ensure that we can serialize and deserialize `ShellError`, and check that it basically would
1507    // look the same
1508    let original_error = ShellError::CantConvert {
1509        span: Span::new(100, 200),
1510        to_type: "Foo".into(),
1511        from_type: "Bar".into(),
1512        help: Some("this is a test".into()),
1513    };
1514    println!("orig_error = {original_error:#?}");
1515
1516    let serialized =
1517        serde_json::to_string_pretty(&original_error).expect("serde_json::to_string_pretty failed");
1518    println!("serialized = {serialized}");
1519
1520    let deserialized: ShellError =
1521        serde_json::from_str(&serialized).expect("serde_json::from_str failed");
1522    println!("deserialized = {deserialized:#?}");
1523
1524    // We don't expect the deserialized error to be the same as the original error, but its miette
1525    // properties should be comparable
1526    assert_eq!(original_error.to_string(), deserialized.to_string());
1527
1528    assert_eq!(
1529        original_error.code().map(|c| c.to_string()),
1530        deserialized.code().map(|c| c.to_string())
1531    );
1532
1533    let orig_labels = original_error
1534        .labels()
1535        .into_iter()
1536        .flatten()
1537        .collect::<Vec<_>>();
1538    let deser_labels = deserialized
1539        .labels()
1540        .into_iter()
1541        .flatten()
1542        .collect::<Vec<_>>();
1543
1544    assert_eq!(orig_labels, deser_labels);
1545
1546    assert_eq!(
1547        original_error.help().map(|c| c.to_string()),
1548        deserialized.help().map(|c| c.to_string())
1549    );
1550}
1551
1552#[cfg(test)]
1553mod test {
1554    use super::*;
1555
1556    impl From<std::io::Error> for ShellError {
1557        fn from(_: std::io::Error) -> ShellError {
1558            unimplemented!(
1559                "This implementation is defined in the test module to ensure no other implementation exists."
1560            )
1561        }
1562    }
1563
1564    impl From<Spanned<std::io::Error>> for ShellError {
1565        fn from(_: Spanned<std::io::Error>) -> Self {
1566            unimplemented!(
1567                "This implementation is defined in the test module to ensure no other implementation exists."
1568            )
1569        }
1570    }
1571
1572    impl From<ShellError> for std::io::Error {
1573        fn from(_: ShellError) -> Self {
1574            unimplemented!(
1575                "This implementation is defined in the test module to ensure no other implementation exists."
1576            )
1577        }
1578    }
1579}