1use super::chained_error::ChainedError;
2use crate::{
3    ConfigError, LabeledError, ParseError, Span, Spanned, Type, Value, ast::Operator,
4    engine::StateWorkingSet, format_cli_error, record,
5};
6use job::JobError;
7use miette::Diagnostic;
8use serde::{Deserialize, Serialize};
9use std::num::NonZeroI32;
10use thiserror::Error;
11
12pub mod bridge;
13pub mod io;
14pub mod job;
15pub mod location;
16
17#[derive(Debug, Clone, Error, Diagnostic, PartialEq)]
21pub enum ShellError {
22    #[error("The '{op}' operator does not work on values of type '{unsupported}'.")]
24    #[diagnostic(code(nu::shell::operator_unsupported_type))]
25    OperatorUnsupportedType {
26        op: Operator,
27        unsupported: Type,
28        #[label = "does not support '{unsupported}'"]
29        op_span: Span,
30        #[label("{unsupported}")]
31        unsupported_span: Span,
32        #[help]
33        help: Option<&'static str>,
34    },
35
36    #[error("Types '{lhs}' and '{rhs}' are not compatible for the '{op}' operator.")]
38    #[diagnostic(code(nu::shell::operator_incompatible_types))]
39    OperatorIncompatibleTypes {
40        op: Operator,
41        lhs: Type,
42        rhs: Type,
43        #[label = "does not operate between '{lhs}' and '{rhs}'"]
44        op_span: Span,
45        #[label("{lhs}")]
46        lhs_span: Span,
47        #[label("{rhs}")]
48        rhs_span: Span,
49        #[help]
50        help: Option<&'static str>,
51    },
52
53    #[error("Operator overflow.")]
60    #[diagnostic(code(nu::shell::operator_overflow))]
61    OperatorOverflow {
62        msg: String,
63        #[label = "{msg}"]
64        span: Span,
65        #[help]
66        help: Option<String>,
67    },
68
69    #[error("Pipeline mismatch.")]
76    #[diagnostic(code(nu::shell::pipeline_mismatch))]
77    PipelineMismatch {
78        exp_input_type: String,
79        #[label("expected: {exp_input_type}")]
80        dst_span: Span,
81        #[label("value originates here")]
82        src_span: Span,
83    },
84
85    #[error("Input type not supported.")]
95    #[diagnostic(code(nu::shell::only_supports_this_input_type))]
96    OnlySupportsThisInputType {
97        exp_input_type: String,
98        wrong_type: String,
99        #[label("only {exp_input_type} input data is supported")]
100        dst_span: Span,
101        #[label("input type: {wrong_type}")]
102        src_span: Span,
103    },
104
105    #[error("Pipeline empty.")]
111    #[diagnostic(code(nu::shell::pipeline_mismatch))]
112    PipelineEmpty {
113        #[label("no input value was piped in")]
114        dst_span: Span,
115    },
116
117    #[error("Type mismatch.")]
124    #[diagnostic(code(nu::shell::type_mismatch))]
125    TypeMismatch {
126        err_message: String,
127        #[label = "{err_message}"]
128        span: Span,
129    },
130
131    #[error("Type mismatch")]
137    #[diagnostic(code(nu::shell::type_mismatch))]
138    RuntimeTypeMismatch {
139        expected: Type,
140        actual: Type,
141        #[label = "expected {expected}, but got {actual}"]
142        span: Span,
143    },
144
145    #[error("Invalid value")]
151    #[diagnostic(code(nu::shell::invalid_value))]
152    InvalidValue {
153        valid: String,
154        actual: String,
155        #[label = "expected {valid}, but got {actual}"]
156        span: Span,
157    },
158
159    #[error("Incorrect value.")]
165    #[diagnostic(code(nu::shell::incorrect_value))]
166    IncorrectValue {
167        msg: String,
168        #[label = "{msg}"]
169        val_span: Span,
170        #[label = "encountered here"]
171        call_span: Span,
172    },
173
174    #[error("Assignment operations require a variable.")]
180    #[diagnostic(code(nu::shell::assignment_requires_variable))]
181    AssignmentRequiresVar {
182        #[label = "needs to be a variable"]
183        lhs_span: Span,
184    },
185
186    #[error("Assignment to an immutable variable.")]
192    #[diagnostic(code(nu::shell::assignment_requires_mutable_variable))]
193    AssignmentRequiresMutableVar {
194        #[label = "needs to be a mutable variable"]
195        lhs_span: Span,
196    },
197
198    #[error("Unknown operator: {op_token}.")]
204    #[diagnostic(code(nu::shell::unknown_operator))]
205    UnknownOperator {
206        op_token: String,
207        #[label = "unknown operator"]
208        span: Span,
209    },
210
211    #[error("Missing parameter: {param_name}.")]
217    #[diagnostic(code(nu::shell::missing_parameter))]
218    MissingParameter {
219        param_name: String,
220        #[label = "missing parameter: {param_name}"]
221        span: Span,
222    },
223
224    #[error("Incompatible parameters.")]
230    #[diagnostic(code(nu::shell::incompatible_parameters))]
231    IncompatibleParameters {
232        left_message: String,
233        #[label("{left_message}")]
235        left_span: Span,
236        right_message: String,
237        #[label("{right_message}")]
238        right_span: Span,
239    },
240
241    #[error("Delimiter error")]
247    #[diagnostic(code(nu::shell::delimiter_error))]
248    DelimiterError {
249        msg: String,
250        #[label("{msg}")]
251        span: Span,
252    },
253
254    #[error("Incompatible parameters.")]
262    #[diagnostic(code(nu::shell::incompatible_parameters))]
263    IncompatibleParametersSingle {
264        msg: String,
265        #[label = "{msg}"]
266        span: Span,
267    },
268
269    #[error("Running external commands not supported")]
275    #[diagnostic(code(nu::shell::external_commands))]
276    ExternalNotSupported {
277        #[label = "external not supported"]
278        span: Span,
279    },
280
281    #[error("Invalid Probability.")]
288    #[diagnostic(code(nu::shell::invalid_probability))]
289    InvalidProbability {
290        #[label = "invalid probability: must be between 0 and 1"]
291        span: Span,
292    },
293
294    #[error("Invalid range {left_flank}..{right_flank}")]
300    #[diagnostic(code(nu::shell::invalid_range))]
301    InvalidRange {
302        left_flank: String,
303        right_flank: String,
304        #[label = "expected a valid range"]
305        span: Span,
306    },
307
308    #[error("Nushell failed: {msg}.")]
314    #[diagnostic(
315        code(nu::shell::nushell_failed),
316        help(
317            "This shouldn't happen. Please file an issue: https://github.com/nushell/nushell/issues"
318        )
319    )]
320    NushellFailed { msg: String },
322
323    #[error("Nushell failed: {msg}.")]
329    #[diagnostic(
330        code(nu::shell::nushell_failed_spanned),
331        help(
332            "This shouldn't happen. Please file an issue: https://github.com/nushell/nushell/issues"
333        )
334    )]
335    NushellFailedSpanned {
337        msg: String,
338        label: String,
339        #[label = "{label}"]
340        span: Span,
341    },
342
343    #[error("Nushell failed: {msg}.")]
349    #[diagnostic(code(nu::shell::nushell_failed_help))]
350    NushellFailedHelp {
352        msg: String,
353        #[help]
354        help: String,
355    },
356
357    #[error("Variable not found")]
363    #[diagnostic(code(nu::shell::variable_not_found))]
364    VariableNotFoundAtRuntime {
365        #[label = "variable not found"]
366        span: Span,
367    },
368
369    #[error("Environment variable '{envvar_name}' not found")]
375    #[diagnostic(code(nu::shell::env_variable_not_found))]
376    EnvVarNotFoundAtRuntime {
377        envvar_name: String,
378        #[label = "environment variable not found"]
379        span: Span,
380    },
381
382    #[error("Module '{mod_name}' not found")]
388    #[diagnostic(code(nu::shell::module_not_found))]
389    ModuleNotFoundAtRuntime {
390        mod_name: String,
391        #[label = "module not found"]
392        span: Span,
393    },
394
395    #[error("Overlay '{overlay_name}' not found")]
401    #[diagnostic(code(nu::shell::overlay_not_found))]
402    OverlayNotFoundAtRuntime {
403        overlay_name: String,
404        #[label = "overlay not found"]
405        span: Span,
406    },
407
408    #[error("Not found.")]
414    #[diagnostic(code(nu::parser::not_found))]
415    NotFound {
416        #[label = "did not find anything under this name"]
417        span: Span,
418    },
419
420    #[error("Can't convert to {to_type}.")]
426    #[diagnostic(code(nu::shell::cant_convert))]
427    CantConvert {
428        to_type: String,
429        from_type: String,
430        #[label("can't convert {from_type} to {to_type}")]
431        span: Span,
432        #[help]
433        help: Option<String>,
434    },
435
436    #[error("Can't convert {from_type} to the specified unit.")]
442    #[diagnostic(code(nu::shell::cant_convert_value_to_unit))]
443    CantConvertToUnit {
444        to_type: String,
445        from_type: String,
446        #[label("can't convert {from_type} to {to_type}")]
447        span: Span,
448        #[label("conversion originates here")]
449        unit_span: Span,
450        #[help]
451        help: Option<String>,
452    },
453
454    #[error("'{envvar_name}' is not representable as a string.")]
460    #[diagnostic(
461            code(nu::shell::env_var_not_a_string),
462            help(
463                r#"The '{envvar_name}' environment variable must be a string or be convertible to a string.
464    Either make sure '{envvar_name}' is a string, or add a 'to_string' entry for it in ENV_CONVERSIONS."#
465            )
466        )]
467    EnvVarNotAString {
468        envvar_name: String,
469        #[label("value not representable as a string")]
470        span: Span,
471    },
472
473    #[error("{envvar_name} cannot be set manually.")]
479    #[diagnostic(
480        code(nu::shell::automatic_env_var_set_manually),
481        help(
482            r#"The environment variable '{envvar_name}' is set automatically by Nushell and cannot be set manually."#
483        )
484    )]
485    AutomaticEnvVarSetManually {
486        envvar_name: String,
487        #[label("cannot set '{envvar_name}' manually")]
488        span: Span,
489    },
490
491    #[error("Cannot replace environment.")]
498    #[diagnostic(
499        code(nu::shell::cannot_replace_env),
500        help(r#"Assigning a value to '$env' is not allowed."#)
501    )]
502    CannotReplaceEnv {
503        #[label("setting '$env' not allowed")]
504        span: Span,
505    },
506
507    #[error("Division by zero.")]
513    #[diagnostic(code(nu::shell::division_by_zero))]
514    DivisionByZero {
515        #[label("division by zero")]
516        span: Span,
517    },
518
519    #[error("Can't convert range to countable values")]
527    #[diagnostic(code(nu::shell::range_to_countable))]
528    CannotCreateRange {
529        #[label = "can't convert to countable values"]
530        span: Span,
531    },
532
533    #[error("Row number too large (max: {max_idx}).")]
539    #[diagnostic(code(nu::shell::access_beyond_end))]
540    AccessBeyondEnd {
541        max_idx: usize,
542        #[label = "index too large (max: {max_idx})"]
543        span: Span,
544    },
545
546    #[error("Inserted at wrong row number (should be {available_idx}).")]
552    #[diagnostic(code(nu::shell::access_beyond_end))]
553    InsertAfterNextFreeIndex {
554        available_idx: usize,
555        #[label = "can't insert at index (the next available index is {available_idx})"]
556        span: Span,
557    },
558
559    #[error("Row number too large (empty content).")]
565    #[diagnostic(code(nu::shell::access_beyond_end))]
566    AccessEmptyContent {
567        #[label = "index too large (empty content)"]
568        span: Span,
569    },
570
571    #[error("Row number too large.")]
578    #[diagnostic(code(nu::shell::access_beyond_end_of_stream))]
579    AccessBeyondEndOfStream {
580        #[label = "index too large"]
581        span: Span,
582    },
583
584    #[error("Data cannot be accessed with a cell path")]
590    #[diagnostic(code(nu::shell::incompatible_path_access))]
591    IncompatiblePathAccess {
592        type_name: String,
593        #[label("{type_name} doesn't support cell paths")]
594        span: Span,
595    },
596
597    #[error("Cannot find column '{col_name}'")]
603    #[diagnostic(code(nu::shell::column_not_found))]
604    CantFindColumn {
605        col_name: String,
606        #[label = "cannot find column '{col_name}'"]
607        span: Option<Span>,
608        #[label = "value originates here"]
609        src_span: Span,
610    },
611
612    #[error("Column already exists")]
618    #[diagnostic(code(nu::shell::column_already_exists))]
619    ColumnAlreadyExists {
620        col_name: String,
621        #[label = "column '{col_name}' already exists"]
622        span: Span,
623        #[label = "value originates here"]
624        src_span: Span,
625    },
626
627    #[error("Not a list value")]
633    #[diagnostic(code(nu::shell::not_a_list))]
634    NotAList {
635        #[label = "value not a list"]
636        dst_span: Span,
637        #[label = "value originates here"]
638        src_span: Span,
639    },
640
641    #[error("Record field or table column used twice: {col_name}")]
647    #[diagnostic(code(nu::shell::column_defined_twice))]
648    ColumnDefinedTwice {
649        col_name: String,
650        #[label = "field redefined here"]
651        second_use: Span,
652        #[label = "field first defined here"]
653        first_use: Span,
654    },
655
656    #[error("Attempted to create a record from different number of columns and values")]
662    #[diagnostic(code(nu::shell::record_cols_vals_mismatch))]
663    RecordColsValsMismatch {
664        #[label = "problematic value"]
665        bad_value: Span,
666        #[label = "attempted to create the record here"]
667        creation_site: Span,
668    },
669
670    #[error("Relative range values cannot be used with streams that don't have a known length")]
676    #[diagnostic(code(nu::shell::relative_range_on_infinite_stream))]
677    RelativeRangeOnInfiniteStream {
678        #[label = "Relative range values cannot be used with streams that don't have a known length"]
679        span: Span,
680    },
681
682    #[error("External command failed")]
688    #[diagnostic(code(nu::shell::external_command), help("{help}"))]
689    ExternalCommand {
690        label: String,
691        help: String,
692        #[label("{label}")]
693        span: Span,
694    },
695
696    #[error("External command had a non-zero exit code")]
702    #[diagnostic(code(nu::shell::non_zero_exit_code))]
703    NonZeroExitCode {
704        exit_code: NonZeroI32,
705        #[label("exited with code {exit_code}")]
706        span: Span,
707    },
708
709    #[cfg(unix)]
710    #[error("External command was terminated by a signal")]
716    #[diagnostic(code(nu::shell::terminated_by_signal))]
717    TerminatedBySignal {
718        signal_name: String,
719        signal: i32,
720        #[label("terminated by {signal_name} ({signal})")]
721        span: Span,
722    },
723
724    #[cfg(unix)]
725    #[error("External command core dumped")]
731    #[diagnostic(code(nu::shell::core_dumped))]
732    CoreDumped {
733        signal_name: String,
734        signal: i32,
735        #[label("core dumped with {signal_name} ({signal})")]
736        span: Span,
737    },
738
739    #[error("Unsupported input")]
745    #[diagnostic(code(nu::shell::unsupported_input))]
746    UnsupportedInput {
747        msg: String,
748        input: String,
749        #[label("{msg}")]
750        msg_span: Span,
751        #[label("{input}")]
752        input_span: Span,
753    },
754
755    #[error("Unable to parse datetime: [{msg}].")]
770    #[diagnostic(
771        code(nu::shell::datetime_parse_error),
772        help(
773            r#"Examples of supported inputs:
774 * "5 pm"
775 * "2020/12/4"
776 * "2020.12.04 22:10 +2"
777 * "2020-04-12 22:10:57 +02:00"
778 * "2020-04-12T22:10:57.213231+02:00"
779 * "Tue, 1 Jul 2003 10:52:37 +0200""#
780        )
781    )]
782    DatetimeParseError {
783        msg: String,
784        #[label("datetime parsing failed")]
785        span: Span,
786    },
787
788    #[error("Network failure")]
794    #[diagnostic(code(nu::shell::network_failure))]
795    NetworkFailure {
796        msg: String,
797        #[label("{msg}")]
798        span: Span,
799    },
800
801    #[error("Command not found")]
807    #[diagnostic(code(nu::shell::command_not_found))]
808    CommandNotFound {
809        #[label("command not found")]
810        span: Span,
811    },
812
813    #[error("Alias not found")]
819    #[diagnostic(code(nu::shell::alias_not_found))]
820    AliasNotFound {
821        #[label("alias not found")]
822        span: Span,
823    },
824
825    #[error("The registered plugin data for `{plugin_name}` is invalid")]
831    #[diagnostic(code(nu::shell::plugin_registry_data_invalid))]
832    PluginRegistryDataInvalid {
833        plugin_name: String,
834        #[label("plugin `{plugin_name}` loaded here")]
835        span: Option<Span>,
836        #[help(
837            "the format in the plugin registry file is not compatible with this version of Nushell.\n\nTry adding the plugin again with `{}`"
838        )]
839        add_command: String,
840    },
841
842    #[error("Plugin failed to load: {msg}")]
848    #[diagnostic(code(nu::shell::plugin_failed_to_load))]
849    PluginFailedToLoad { msg: String },
850
851    #[error("Plugin failed to encode: {msg}")]
857    #[diagnostic(code(nu::shell::plugin_failed_to_encode))]
858    PluginFailedToEncode { msg: String },
859
860    #[error("Plugin failed to decode: {msg}")]
866    #[diagnostic(code(nu::shell::plugin_failed_to_decode))]
867    PluginFailedToDecode { msg: String },
868
869    #[error("Custom value `{name}` cannot be sent to plugin")]
876    #[diagnostic(code(nu::shell::custom_value_incorrect_for_plugin))]
877    CustomValueIncorrectForPlugin {
878        name: String,
879        #[label("the `{dest_plugin}` plugin does not support this kind of value")]
880        span: Span,
881        dest_plugin: String,
882        #[help("this value came from the `{}` plugin")]
883        src_plugin: Option<String>,
884    },
885
886    #[error("Custom value failed to encode")]
893    #[diagnostic(code(nu::shell::custom_value_failed_to_encode))]
894    CustomValueFailedToEncode {
895        msg: String,
896        #[label("{msg}")]
897        span: Span,
898    },
899
900    #[error("Custom value failed to decode")]
907    #[diagnostic(code(nu::shell::custom_value_failed_to_decode))]
908    #[diagnostic(help("the plugin may have been updated and no longer support this custom value"))]
909    CustomValueFailedToDecode {
910        msg: String,
911        #[label("{msg}")]
912        span: Span,
913    },
914
915    #[error(transparent)]
921    #[diagnostic(transparent)]
922    Io(#[from] io::IoError),
923
924    #[error("Name not found")]
930    #[diagnostic(code(nu::shell::name_not_found))]
931    DidYouMean {
932        suggestion: String,
933        #[label("did you mean '{suggestion}'?")]
934        span: Span,
935    },
936
937    #[error("{msg}")]
943    #[diagnostic(code(nu::shell::did_you_mean_custom))]
944    DidYouMeanCustom {
945        msg: String,
946        suggestion: String,
947        #[label("did you mean '{suggestion}'?")]
948        span: Span,
949    },
950
951    #[error("Non-UTF8 string")]
957    #[diagnostic(
958        code(nu::parser::non_utf8),
959        help("see `decode` for handling character sets other than UTF-8")
960    )]
961    NonUtf8 {
962        #[label("non-UTF8 string")]
963        span: Span,
964    },
965
966    #[error("Non-UTF8 string")]
972    #[diagnostic(
973        code(nu::parser::non_utf8_custom),
974        help("see `decode` for handling character sets other than UTF-8")
975    )]
976    NonUtf8Custom {
977        msg: String,
978        #[label("{msg}")]
979        span: Span,
980    },
981
982    #[error("Encountered {} error(s) when updating config", errors.len())]
988    #[diagnostic(code(nu::shell::invalid_config))]
989    InvalidConfig {
990        #[related]
991        errors: Vec<ConfigError>,
992    },
993
994    #[error("Value is missing a required '{column}' column")]
1000    #[diagnostic(code(nu::shell::missing_required_column))]
1001    MissingRequiredColumn {
1002        column: &'static str,
1003        #[label("has no '{column}' column")]
1004        span: Span,
1005    },
1006
1007    #[error("Negative value passed when positive one is required")]
1013    #[diagnostic(code(nu::shell::needs_positive_value))]
1014    NeedsPositiveValue {
1015        #[label("use a positive value")]
1016        span: Span,
1017    },
1018
1019    #[error("{error}")]
1021    #[diagnostic()]
1022    GenericError {
1023        error: String,
1024        msg: String,
1025        #[label("{msg}")]
1026        span: Option<Span>,
1027        #[help]
1028        help: Option<String>,
1029        #[related]
1030        inner: Vec<ShellError>,
1031    },
1032
1033    #[error("{error}")]
1035    #[diagnostic()]
1036    OutsideSpannedLabeledError {
1037        #[source_code]
1038        src: String,
1039        error: String,
1040        msg: String,
1041        #[label("{msg}")]
1042        span: Span,
1043    },
1044
1045    #[error(transparent)]
1047    #[diagnostic(transparent)]
1048    LabeledError(#[from] Box<super::LabeledError>),
1049
1050    #[error("Removed command: {removed}")]
1056    #[diagnostic(code(nu::shell::removed_command))]
1057    RemovedCommand {
1058        removed: String,
1059        replacement: String,
1060        #[label("'{removed}' has been removed from Nushell. Please use '{replacement}' instead.")]
1061        span: Span,
1062    },
1063
1064    #[error("Eval block failed with pipeline input")]
1067    #[diagnostic(code(nu::shell::eval_block_with_input))]
1068    EvalBlockWithInput {
1069        #[label("source value")]
1070        span: Span,
1071        #[related]
1072        sources: Vec<ShellError>,
1073    },
1074
1075    #[error("Break used outside of loop")]
1077    Break {
1078        #[label("used outside of loop")]
1079        span: Span,
1080    },
1081
1082    #[error("Continue used outside of loop")]
1084    Continue {
1085        #[label("used outside of loop")]
1086        span: Span,
1087    },
1088
1089    #[error("Return used outside of custom command or closure")]
1091    Return {
1092        #[label("used outside of custom command or closure")]
1093        span: Span,
1094        value: Box<Value>,
1095    },
1096
1097    #[error("Recursion limit ({recursion_limit}) reached")]
1103    #[diagnostic(code(nu::shell::recursion_limit_reached))]
1104    RecursionLimitReached {
1105        recursion_limit: u64,
1106        #[label("This called itself too many times")]
1107        span: Option<Span>,
1108    },
1109
1110    #[error("Operation interrupted")]
1112    Interrupted {
1113        #[label("This operation was interrupted")]
1114        span: Span,
1115    },
1116
1117    #[error("Operation interrupted by user")]
1119    InterruptedByUser {
1120        #[label("This operation was interrupted")]
1121        span: Option<Span>,
1122    },
1123
1124    #[error("Match guard not bool")]
1127    #[diagnostic(
1128        code(nu::shell::match_guard_not_bool),
1129        help("Match guards should evaluate to a boolean")
1130    )]
1131    MatchGuardNotBool {
1132        #[label("not a boolean expression")]
1133        span: Span,
1134    },
1135
1136    #[error("Missing const eval implementation")]
1141    #[diagnostic(
1142        code(nu::shell::missing_const_eval_implementation),
1143        help(
1144            "The command lacks an implementation for constant evaluation. \
1145This is an internal Nushell error, please file an issue https://github.com/nushell/nushell/issues."
1146        )
1147    )]
1148    MissingConstEvalImpl {
1149        #[label("command lacks constant implementation")]
1150        span: Span,
1151    },
1152
1153    #[error("Found parsing error in expression.")]
1161    #[diagnostic(
1162        code(nu::shell::parse_error_in_constant),
1163        help(
1164            "This expression is supposed to be evaluated into a constant, which means error-free."
1165        )
1166    )]
1167    ParseErrorInConstant {
1168        #[label("Parsing error detected in expression")]
1169        span: Span,
1170    },
1171
1172    #[error("Not a constant.")]
1178    #[diagnostic(
1179        code(nu::shell::not_a_constant),
1180        help(
1181            "Only a subset of expressions are allowed constants during parsing. Try using the 'const' command or typing the value literally."
1182        )
1183    )]
1184    NotAConstant {
1185        #[label("Value is not a parse-time constant")]
1186        span: Span,
1187    },
1188
1189    #[error("Not a const command.")]
1196    #[diagnostic(
1197        code(nu::shell::not_a_const_command),
1198        help(
1199            "Only a subset of builtin commands, and custom commands built only from those commands, can run at parse time."
1200        )
1201    )]
1202    NotAConstCommand {
1203        #[label("This command cannot run at parse time.")]
1204        span: Span,
1205    },
1206
1207    #[error("Help message not a constant.")]
1213    #[diagnostic(
1214        code(nu::shell::not_a_const_help),
1215        help("Help messages are currently not supported to be constants.")
1216    )]
1217    NotAConstHelp {
1218        #[label("This command cannot run at parse time.")]
1219        span: Span,
1220    },
1221
1222    #[error("{deprecation_type} deprecated.")]
1223    #[diagnostic(code(nu::shell::deprecated), severity(Warning))]
1224    DeprecationWarning {
1225        deprecation_type: &'static str,
1226        suggestion: String,
1227        #[label("{suggestion}")]
1228        span: Span,
1229        #[help]
1230        help: Option<&'static str>,
1231    },
1232
1233    #[error("Invalid glob pattern")]
1239    #[diagnostic(
1240        code(nu::shell::invalid_glob_pattern),
1241        help("Refer to xxx for help on nushell glob patterns.")
1242    )]
1243    InvalidGlobPattern {
1244        msg: String,
1245        #[label("{msg}")]
1246        span: Span,
1247    },
1248
1249    #[error("Invalid unit")]
1255    #[diagnostic(
1256        code(nu::shell::invalid_unit),
1257        help("Supported units are: {supported_units}")
1258    )]
1259    InvalidUnit {
1260        supported_units: String,
1261        #[label("encountered here")]
1262        span: Span,
1263    },
1264
1265    #[error("Not a list")]
1271    #[diagnostic(
1272        code(nu::shell::cannot_spread_as_list),
1273        help(
1274            "Only lists can be spread inside lists and command calls. Try converting the value to a list before spreading."
1275        )
1276    )]
1277    CannotSpreadAsList {
1278        #[label = "cannot spread value"]
1279        span: Span,
1280    },
1281
1282    #[error("Not a record")]
1288    #[diagnostic(
1289        code(nu::shell::cannot_spread_as_record),
1290        help(
1291            "Only records can be spread inside records. Try converting the value to a record before spreading."
1292        )
1293    )]
1294    CannotSpreadAsRecord {
1295        #[label = "cannot spread value"]
1296        span: Span,
1297    },
1298
1299    #[error("Lists are not automatically spread when calling external commands")]
1305    #[diagnostic(
1306        code(nu::shell::cannot_pass_list_to_external),
1307        help("Either convert the list to a string or use the spread operator, like so: ...{arg}")
1308    )]
1309    CannotPassListToExternal {
1310        arg: String,
1311        #[label = "Spread operator (...) is necessary to spread lists"]
1312        span: Span,
1313    },
1314
1315    #[error(
1321        "The selected range {left_flank}..{right_flank} is out of the bounds of the provided input"
1322    )]
1323    #[diagnostic(code(nu::shell::out_of_bounds))]
1324    OutOfBounds {
1325        left_flank: String,
1326        right_flank: String,
1327        #[label = "byte index is not a char boundary or is out of bounds of the input"]
1328        span: Span,
1329    },
1330
1331    #[error("The config directory could not be found")]
1333    #[diagnostic(
1334        code(nu::shell::config_dir_not_found),
1335        help(
1336            r#"On Linux, this would be $XDG_CONFIG_HOME or $HOME/.config.
1337On MacOS, this would be `$HOME/Library/Application Support`.
1338On Windows, this would be %USERPROFILE%\AppData\Roaming"#
1339        )
1340    )]
1341    ConfigDirNotFound {
1342        #[label = "Could not find config directory"]
1343        span: Option<Span>,
1344    },
1345
1346    #[error(
1348        "$env.XDG_CONFIG_HOME ({xdg}) is invalid, using default config directory instead: {default}"
1349    )]
1350    #[diagnostic(
1351        code(nu::shell::xdg_config_home_invalid),
1352        help("Set XDG_CONFIG_HOME to an absolute path, or set it to an empty string to ignore it")
1353    )]
1354    InvalidXdgConfig { xdg: String, default: String },
1355
1356    #[error("IR evaluation error: {msg}")]
1363    #[diagnostic(
1364        code(nu::shell::ir_eval_error),
1365        help(
1366            "this is a bug, please report it at https://github.com/nushell/nushell/issues/new along with the code you were running if able"
1367        )
1368    )]
1369    IrEvalError {
1370        msg: String,
1371        #[label = "while running this code"]
1372        span: Option<Span>,
1373    },
1374
1375    #[error("OS feature is disabled: {msg}")]
1376    #[diagnostic(
1377        code(nu::shell::os_disabled),
1378        help("You're probably running outside an OS like a browser, we cannot support this")
1379    )]
1380    DisabledOsSupport {
1381        msg: String,
1382        #[label = "while running this code"]
1383        span: Option<Span>,
1384    },
1385
1386    #[error(transparent)]
1387    #[diagnostic(transparent)]
1388    Job(#[from] JobError),
1389
1390    #[error(transparent)]
1391    #[diagnostic(transparent)]
1392    ChainedError(ChainedError),
1393}
1394
1395impl ShellError {
1396    pub fn external_exit_code(&self) -> Option<Spanned<i32>> {
1397        let (item, span) = match *self {
1398            Self::NonZeroExitCode { exit_code, span } => (exit_code.into(), span),
1399            #[cfg(unix)]
1400            Self::TerminatedBySignal { signal, span, .. }
1401            | Self::CoreDumped { signal, span, .. } => (-signal, span),
1402            _ => return None,
1403        };
1404        Some(Spanned { item, span })
1405    }
1406
1407    pub fn exit_code(&self) -> Option<i32> {
1408        match self {
1409            Self::Return { .. } | Self::Break { .. } | Self::Continue { .. } => None,
1410            _ => self.external_exit_code().map(|e| e.item).or(Some(1)),
1411        }
1412    }
1413
1414    pub fn into_value(self, working_set: &StateWorkingSet, span: Span) -> Value {
1415        let exit_code = self.external_exit_code();
1416
1417        let mut record = record! {
1418            "msg" => Value::string(self.to_string(), span),
1419            "debug" => Value::string(format!("{self:?}"), span),
1420            "raw" => Value::error(self.clone(), span),
1421            "rendered" => Value::string(format_cli_error(working_set, &self, Some("nu::shell::error")), span),
1422            "json" => Value::string(serde_json::to_string(&self).expect("Could not serialize error"), span),
1423        };
1424
1425        if let Some(code) = exit_code {
1426            record.push("exit_code", Value::int(code.item.into(), code.span));
1427        }
1428
1429        Value::record(record, span)
1430    }
1431
1432    pub fn wrap(self, working_set: &StateWorkingSet, span: Span) -> ParseError {
1434        let msg = format_cli_error(working_set, &self, None);
1435        ParseError::LabeledError(
1436            msg,
1437            "Encountered error during parse-time evaluation".into(),
1438            span,
1439        )
1440    }
1441
1442    pub fn into_chainned(self, span: Span) -> Self {
1444        match self {
1445            ShellError::ChainedError(inner) => {
1446                ShellError::ChainedError(ChainedError::new_chained(inner, span))
1447            }
1448            other => ShellError::ChainedError(ChainedError::new(other, span)),
1449        }
1450    }
1451}
1452
1453impl From<Box<dyn std::error::Error>> for ShellError {
1454    fn from(error: Box<dyn std::error::Error>) -> ShellError {
1455        ShellError::GenericError {
1456            error: format!("{error:?}"),
1457            msg: error.to_string(),
1458            span: None,
1459            help: None,
1460            inner: vec![],
1461        }
1462    }
1463}
1464
1465impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
1466    fn from(error: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
1467        ShellError::GenericError {
1468            error: format!("{error:?}"),
1469            msg: error.to_string(),
1470            span: None,
1471            help: None,
1472            inner: vec![],
1473        }
1474    }
1475}
1476
1477impl From<super::LabeledError> for ShellError {
1478    fn from(error: super::LabeledError) -> Self {
1479        ShellError::LabeledError(Box::new(error))
1480    }
1481}
1482
1483impl Serialize for ShellError {
1485    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1486    where
1487        S: serde::Serializer,
1488    {
1489        LabeledError::from_diagnostic(self).serialize(serializer)
1490    }
1491}
1492
1493impl<'de> Deserialize<'de> for ShellError {
1496    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1497    where
1498        D: serde::Deserializer<'de>,
1499    {
1500        LabeledError::deserialize(deserializer).map(ShellError::from)
1501    }
1502}
1503
1504#[test]
1505fn shell_error_serialize_roundtrip() {
1506    let original_error = ShellError::CantConvert {
1509        span: Span::new(100, 200),
1510        to_type: "Foo".into(),
1511        from_type: "Bar".into(),
1512        help: Some("this is a test".into()),
1513    };
1514    println!("orig_error = {original_error:#?}");
1515
1516    let serialized =
1517        serde_json::to_string_pretty(&original_error).expect("serde_json::to_string_pretty failed");
1518    println!("serialized = {serialized}");
1519
1520    let deserialized: ShellError =
1521        serde_json::from_str(&serialized).expect("serde_json::from_str failed");
1522    println!("deserialized = {deserialized:#?}");
1523
1524    assert_eq!(original_error.to_string(), deserialized.to_string());
1527
1528    assert_eq!(
1529        original_error.code().map(|c| c.to_string()),
1530        deserialized.code().map(|c| c.to_string())
1531    );
1532
1533    let orig_labels = original_error
1534        .labels()
1535        .into_iter()
1536        .flatten()
1537        .collect::<Vec<_>>();
1538    let deser_labels = deserialized
1539        .labels()
1540        .into_iter()
1541        .flatten()
1542        .collect::<Vec<_>>();
1543
1544    assert_eq!(orig_labels, deser_labels);
1545
1546    assert_eq!(
1547        original_error.help().map(|c| c.to_string()),
1548        deserialized.help().map(|c| c.to_string())
1549    );
1550}
1551
1552#[cfg(test)]
1553mod test {
1554    use super::*;
1555
1556    impl From<std::io::Error> for ShellError {
1557        fn from(_: std::io::Error) -> ShellError {
1558            unimplemented!(
1559                "This implementation is defined in the test module to ensure no other implementation exists."
1560            )
1561        }
1562    }
1563
1564    impl From<Spanned<std::io::Error>> for ShellError {
1565        fn from(_: Spanned<std::io::Error>) -> Self {
1566            unimplemented!(
1567                "This implementation is defined in the test module to ensure no other implementation exists."
1568            )
1569        }
1570    }
1571
1572    impl From<ShellError> for std::io::Error {
1573        fn from(_: ShellError) -> Self {
1574            unimplemented!(
1575                "This implementation is defined in the test module to ensure no other implementation exists."
1576            )
1577        }
1578    }
1579}