Skip to main content

nu_protocol/errors/shell_error/
mod.rs

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