Skip to main content

nu_protocol/errors/shell_error/
mod.rs

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