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    /// The code being executed called itself too many times.
1154    ///
1155    /// ## Resolution
1156    ///
1157    /// Adjust your Nu code to
1158    #[error("Recursion limit ({recursion_limit}) reached")]
1159    #[diagnostic(code(nu::shell::recursion_limit_reached))]
1160    RecursionLimitReached {
1161        recursion_limit: u64,
1162        #[label("This called itself too many times")]
1163        span: Option<Span>,
1164    },
1165
1166    /// Operation interrupted
1167    #[error("Operation interrupted")]
1168    Interrupted {
1169        #[label("This operation was interrupted")]
1170        span: Span,
1171    },
1172
1173    /// An attempt to use, as a match guard, an expression that
1174    /// does not resolve into a boolean
1175    #[error("Match guard not bool")]
1176    #[diagnostic(
1177        code(nu::shell::match_guard_not_bool),
1178        help("Match guards should evaluate to a boolean")
1179    )]
1180    MatchGuardNotBool {
1181        #[label("not a boolean expression")]
1182        span: Span,
1183    },
1184
1185    /// An attempt to run a command marked for constant evaluation lacking the const. eval.
1186    /// implementation.
1187    ///
1188    /// This is an internal Nushell error, please file an issue.
1189    #[error("Missing const eval implementation")]
1190    #[diagnostic(
1191        code(nu::shell::missing_const_eval_implementation),
1192        help(
1193            "The command lacks an implementation for constant evaluation. \
1194This is an internal Nushell error, please file an issue https://github.com/nushell/nushell/issues."
1195        )
1196    )]
1197    MissingConstEvalImpl {
1198        #[label("command lacks constant implementation")]
1199        span: Span,
1200    },
1201
1202    /// TODO: Get rid of this error by moving the check before evaluation
1203    ///
1204    /// Tried evaluating of a subexpression with parsing error
1205    ///
1206    /// ## Resolution
1207    ///
1208    /// Fix the parsing error first.
1209    #[error("Found parsing error in expression.")]
1210    #[diagnostic(
1211        code(nu::shell::parse_error_in_constant),
1212        help(
1213            "This expression is supposed to be evaluated into a constant, which means error-free."
1214        )
1215    )]
1216    ParseErrorInConstant {
1217        #[label("Parsing error detected in expression")]
1218        span: Span,
1219    },
1220
1221    /// Tried assigning non-constant value to a constant
1222    ///
1223    /// ## Resolution
1224    ///
1225    /// Only a subset of expressions are allowed to be assigned as a constant during parsing.
1226    #[error("Not a constant.")]
1227    #[diagnostic(
1228        code(nu::shell::not_a_constant),
1229        help(
1230            "Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally."
1231        )
1232    )]
1233    NotAConstant {
1234        #[label("Value is not a parse-time constant")]
1235        span: Span,
1236    },
1237
1238    // TODO: Update help text once custom const commands are supported
1239    /// Tried running a command that is not const-compatible
1240    ///
1241    /// ## Resolution
1242    ///
1243    /// Only a subset of builtin commands can run at parse time.
1244    #[error("Not a const command.")]
1245    #[diagnostic(
1246        code(nu::shell::not_a_const_command),
1247        help("Only a subset of builtin commands can run at parse time.")
1248    )]
1249    NotAConstCommand {
1250        #[label("This command cannot run at parse time.")]
1251        span: Span,
1252    },
1253
1254    /// Tried getting a help message at parse time.
1255    ///
1256    /// ## Resolution
1257    ///
1258    /// Help messages are not supported at parse time.
1259    #[error("Help message not a constant.")]
1260    #[diagnostic(
1261        code(nu::shell::not_a_const_help),
1262        help("Help messages are currently not supported to be constants.")
1263    )]
1264    NotAConstHelp {
1265        #[label("This command cannot run at parse time.")]
1266        span: Span,
1267    },
1268
1269    #[error("{deprecation_type} deprecated.")]
1270    #[diagnostic(code(nu::shell::deprecated), severity(Warning))]
1271    DeprecationWarning {
1272        deprecation_type: &'static str,
1273        suggestion: String,
1274        #[label("{suggestion}")]
1275        span: Span,
1276        #[help]
1277        help: Option<&'static str>,
1278    },
1279
1280    /// Invalid glob pattern
1281    ///
1282    /// ## Resolution
1283    ///
1284    /// Correct glob pattern
1285    #[error("Invalid glob pattern")]
1286    #[diagnostic(
1287        code(nu::shell::invalid_glob_pattern),
1288        help("Refer to xxx for help on nushell glob patterns.")
1289    )]
1290    InvalidGlobPattern {
1291        msg: String,
1292        #[label("{msg}")]
1293        span: Span,
1294    },
1295
1296    /// Invalid unit
1297    ///
1298    /// ## Resolution
1299    ///
1300    /// Correct unit
1301    #[error("Invalid unit")]
1302    #[diagnostic(
1303        code(nu::shell::invalid_unit),
1304        help("Supported units are: {supported_units}")
1305    )]
1306    InvalidUnit {
1307        supported_units: String,
1308        #[label("encountered here")]
1309        span: Span,
1310    },
1311
1312    /// Tried spreading a non-list inside a list or command call.
1313    ///
1314    /// ## Resolution
1315    ///
1316    /// Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading.
1317    #[error("Not a list")]
1318    #[diagnostic(
1319        code(nu::shell::cannot_spread_as_list),
1320        help(
1321            "Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading."
1322        )
1323    )]
1324    CannotSpreadAsList {
1325        #[label = "cannot spread value"]
1326        span: Span,
1327    },
1328
1329    /// Tried spreading a non-record inside a record.
1330    ///
1331    /// ## Resolution
1332    ///
1333    /// Only records can be spread inside records. Try converting the value to a record before spreading.
1334    #[error("Not a record")]
1335    #[diagnostic(
1336        code(nu::shell::cannot_spread_as_record),
1337        help(
1338            "Only records can be spread inside records. Try converting the value to a record before spreading."
1339        )
1340    )]
1341    CannotSpreadAsRecord {
1342        #[label = "cannot spread value"]
1343        span: Span,
1344    },
1345
1346    /// Lists are not automatically spread when calling external commands
1347    ///
1348    /// ## Resolution
1349    ///
1350    /// Use the spread operator (put a '...' before the argument)
1351    #[error("Lists are not automatically spread when calling external commands")]
1352    #[diagnostic(
1353        code(nu::shell::cannot_pass_list_to_external),
1354        help("Either convert the list to a string or use the spread operator, like so: ...{arg}")
1355    )]
1356    CannotPassListToExternal {
1357        arg: String,
1358        #[label = "Spread operator (...) is necessary to spread lists"]
1359        span: Span,
1360    },
1361
1362    /// Out of bounds.
1363    ///
1364    /// ## Resolution
1365    ///
1366    /// Make sure the range is within the bounds of the input.
1367    #[error(
1368        "The selected range {left_flank}..{right_flank} is out of the bounds of the provided input"
1369    )]
1370    #[diagnostic(code(nu::shell::out_of_bounds))]
1371    OutOfBounds {
1372        left_flank: String,
1373        right_flank: String,
1374        #[label = "byte index is not a char boundary or is out of bounds of the input"]
1375        span: Span,
1376    },
1377
1378    /// The config directory could not be found
1379    #[error("The config directory could not be found")]
1380    #[diagnostic(
1381        code(nu::shell::config_dir_not_found),
1382        help(
1383            r#"On Linux, this would be $XDG_CONFIG_HOME or $HOME/.config.
1384On MacOS, this would be `$HOME/Library/Application Support`.
1385On Windows, this would be %USERPROFILE%\AppData\Roaming"#
1386        )
1387    )]
1388    ConfigDirNotFound {
1389        #[label = "Could not find config directory"]
1390        span: Span,
1391    },
1392
1393    /// XDG_CONFIG_HOME was set to an invalid path
1394    #[error(
1395        "$env.XDG_CONFIG_HOME ({xdg}) is invalid, using default config directory instead: {default}"
1396    )]
1397    #[diagnostic(
1398        code(nu::shell::xdg_config_home_invalid),
1399        help("Set XDG_CONFIG_HOME to an absolute path, or set it to an empty string to ignore it")
1400    )]
1401    InvalidXdgConfig { xdg: String, default: String },
1402
1403    /// An unexpected error occurred during IR evaluation.
1404    ///
1405    /// ## Resolution
1406    ///
1407    /// This is most likely a correctness issue with the IR compiler or evaluator. Please file a
1408    /// bug with the minimum code needed to reproduce the issue, if possible.
1409    #[error("IR evaluation error: {msg}")]
1410    #[diagnostic(
1411        code(nu::shell::ir_eval_error),
1412        help(
1413            "this is a bug, please report it at https://github.com/nushell/nushell/issues/new along with the code you were running if able"
1414        )
1415    )]
1416    IrEvalError {
1417        msg: String,
1418        #[label = "while running this code"]
1419        span: Option<Span>,
1420    },
1421
1422    #[error("OS feature is disabled: {msg}")]
1423    #[diagnostic(
1424        code(nu::shell::os_disabled),
1425        help("You're probably running outside an OS like a browser, we cannot support this")
1426    )]
1427    DisabledOsSupport {
1428        msg: String,
1429        #[label = "while running this code"]
1430        span: Span,
1431    },
1432
1433    #[error(transparent)]
1434    #[diagnostic(transparent)]
1435    Job(#[from] JobError),
1436
1437    #[error(transparent)]
1438    #[diagnostic(transparent)]
1439    ChainedError(ChainedError),
1440}
1441
1442impl ShellError {
1443    pub fn external_exit_code(&self) -> Option<Spanned<i32>> {
1444        let (item, span) = match *self {
1445            Self::NonZeroExitCode { exit_code, span } => (exit_code.into(), span),
1446            #[cfg(unix)]
1447            Self::TerminatedBySignal { signal, span, .. }
1448            | Self::CoreDumped { signal, span, .. } => (-signal, span),
1449            _ => return None,
1450        };
1451        Some(Spanned { item, span })
1452    }
1453
1454    pub fn exit_code(&self) -> Option<i32> {
1455        match self {
1456            Self::Return { .. } | Self::Break { .. } | Self::Continue { .. } => None,
1457            _ => self.external_exit_code().map(|e| e.item).or(Some(1)),
1458        }
1459    }
1460
1461    pub fn into_full_value(
1462        self,
1463        working_set: &StateWorkingSet,
1464        stack: &Stack,
1465        span: Span,
1466    ) -> Value {
1467        let exit_code = self.external_exit_code();
1468
1469        let mut record = record! {
1470            "msg" => Value::string(self.to_string(), span),
1471            "debug" => Value::string(format!("{self:?}"), span),
1472            "raw" => Value::error(self.clone(), span),
1473            "rendered" => Value::string(format_cli_error(Some(stack), working_set, &self, Some("nu::shell::error")), span),
1474            "json" => Value::string(serde_json::to_string(&self).expect("Could not serialize error"), span),
1475        };
1476
1477        if let Some(code) = exit_code {
1478            record.push("exit_code", Value::int(code.item.into(), code.span));
1479        }
1480
1481        Value::record(record, span)
1482    }
1483
1484    // TODO: Implement as From trait
1485    pub fn wrap(self, working_set: &StateWorkingSet, span: Span) -> ParseError {
1486        let msg = format_cli_error(None, working_set, &self, None);
1487        ParseError::LabeledError(
1488            msg,
1489            "Encountered error during parse-time evaluation".into(),
1490            span,
1491        )
1492    }
1493
1494    /// Convert self error to a [`ShellError::ChainedError`] variant.
1495    pub fn into_chained(self, span: Span) -> Self {
1496        Self::ChainedError(match self {
1497            Self::ChainedError(inner) => ChainedError::new_chained(inner, span),
1498            other => {
1499                // If it's not already a chained error, it could have more errors below
1500                // it that we want to chain together
1501                let error = other.clone();
1502                let mut now = ChainedError::new(other, span);
1503                if let Some(related) = error.related() {
1504                    let mapped = related
1505                        .map(|s| {
1506                            let shellerror: Self = Self::from_diagnostic(s);
1507                            shellerror
1508                        })
1509                        .collect::<Vec<_>>();
1510                    if !mapped.is_empty() {
1511                        now.sources = [now.sources, mapped].concat();
1512                    };
1513                }
1514                now
1515            }
1516        })
1517    }
1518
1519    pub fn from_diagnostic(diag: &(impl miette::Diagnostic + ?Sized)) -> Self {
1520        Self::LabeledError(LabeledError::from_diagnostic(diag).into())
1521    }
1522}
1523
1524impl FromValue for ShellError {
1525    fn from_value(v: Value) -> Result<Self, ShellError> {
1526        let from_type = v.get_type();
1527        match v {
1528            Value::Error { error, .. } => Ok(*error),
1529            // Also let it come from the into_full_value record.
1530            Value::Record {
1531                val, internal_span, ..
1532            } => Self::from_value(
1533                (*val)
1534                    .get("raw")
1535                    .ok_or(ShellError::CantConvert {
1536                        to_type: Self::expected_type().to_string(),
1537                        from_type: from_type.to_string(),
1538                        span: internal_span,
1539                        help: None,
1540                    })?
1541                    .clone(),
1542            ),
1543            Value::Nothing { internal_span } => Ok(Self::GenericError {
1544                error: "error".into(),
1545                msg: "is nothing".into(),
1546                span: Some(internal_span),
1547                help: None,
1548                inner: vec![],
1549            }),
1550            _ => Err(ShellError::CantConvert {
1551                to_type: Self::expected_type().to_string(),
1552                from_type: v.get_type().to_string(),
1553                span: v.span(),
1554                help: None,
1555            }),
1556        }
1557    }
1558}
1559
1560impl From<Box<dyn std::error::Error>> for ShellError {
1561    fn from(error: Box<dyn std::error::Error>) -> ShellError {
1562        ShellError::GenericError {
1563            error: format!("{error:?}"),
1564            msg: error.to_string(),
1565            span: None,
1566            help: None,
1567            inner: vec![],
1568        }
1569    }
1570}
1571
1572impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
1573    fn from(error: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
1574        ShellError::GenericError {
1575            error: format!("{error:?}"),
1576            msg: error.to_string(),
1577            span: None,
1578            help: None,
1579            inner: vec![],
1580        }
1581    }
1582}
1583
1584impl From<super::LabeledError> for ShellError {
1585    fn from(error: super::LabeledError) -> Self {
1586        ShellError::LabeledError(Box::new(error))
1587    }
1588}
1589
1590/// `ShellError` always serializes as [`LabeledError`].
1591impl Serialize for ShellError {
1592    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1593    where
1594        S: serde::Serializer,
1595    {
1596        LabeledError::from_diagnostic(self).serialize(serializer)
1597    }
1598}
1599
1600/// `ShellError` always deserializes as if it were [`LabeledError`], resulting in a
1601/// [`ShellError::LabeledError`] variant.
1602impl<'de> Deserialize<'de> for ShellError {
1603    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1604    where
1605        D: serde::Deserializer<'de>,
1606    {
1607        LabeledError::deserialize(deserializer).map(ShellError::from)
1608    }
1609}
1610
1611#[test]
1612fn shell_error_serialize_roundtrip() {
1613    // Ensure that we can serialize and deserialize `ShellError`, and check that it basically would
1614    // look the same
1615    let original_error = ShellError::CantConvert {
1616        span: Span::new(100, 200),
1617        to_type: "Foo".into(),
1618        from_type: "Bar".into(),
1619        help: Some("this is a test".into()),
1620    };
1621    println!("orig_error = {original_error:#?}");
1622
1623    let serialized =
1624        serde_json::to_string_pretty(&original_error).expect("serde_json::to_string_pretty failed");
1625    println!("serialized = {serialized}");
1626
1627    let deserialized: ShellError =
1628        serde_json::from_str(&serialized).expect("serde_json::from_str failed");
1629    println!("deserialized = {deserialized:#?}");
1630
1631    // We don't expect the deserialized error to be the same as the original error, but its miette
1632    // properties should be comparable
1633    assert_eq!(original_error.to_string(), deserialized.to_string());
1634
1635    assert_eq!(
1636        original_error.code().map(|c| c.to_string()),
1637        deserialized.code().map(|c| c.to_string())
1638    );
1639
1640    let orig_labels = original_error
1641        .labels()
1642        .into_iter()
1643        .flatten()
1644        .collect::<Vec<_>>();
1645    let deser_labels = deserialized
1646        .labels()
1647        .into_iter()
1648        .flatten()
1649        .collect::<Vec<_>>();
1650
1651    assert_eq!(orig_labels, deser_labels);
1652
1653    assert_eq!(
1654        original_error.help().map(|c| c.to_string()),
1655        deserialized.help().map(|c| c.to_string())
1656    );
1657}
1658
1659#[cfg(test)]
1660mod test {
1661    use super::*;
1662
1663    impl From<std::io::Error> for ShellError {
1664        fn from(_: std::io::Error) -> ShellError {
1665            unimplemented!(
1666                "This implementation is defined in the test module to ensure no other implementation exists."
1667            )
1668        }
1669    }
1670
1671    impl From<Spanned<std::io::Error>> for ShellError {
1672        fn from(_: Spanned<std::io::Error>) -> Self {
1673            unimplemented!(
1674                "This implementation is defined in the test module to ensure no other implementation exists."
1675            )
1676        }
1677    }
1678
1679    impl From<ShellError> for std::io::Error {
1680        fn from(_: ShellError) -> Self {
1681            unimplemented!(
1682                "This implementation is defined in the test module to ensure no other implementation exists."
1683            )
1684        }
1685    }
1686}