nushell/crates/nu-protocol/src/shell_error.rs

420 lines
14 KiB
Rust
Raw Normal View History

use miette::Diagnostic;
2021-10-01 07:11:49 +02:00
use serde::{Deserialize, Serialize};
use thiserror::Error;
2021-09-06 01:16:27 +02:00
use crate::{ast::Operator, Span, Type};
2021-09-02 03:29:43 +02:00
2021-11-03 01:26:09 +01:00
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
/// and pass it into an error viewer to display to the user.
2021-10-01 07:11:49 +02:00
#[derive(Debug, Clone, Error, Diagnostic, Serialize, Deserialize)]
2021-09-02 03:29:43 +02:00
pub enum ShellError {
#[error("Type mismatch during operation.")]
#[diagnostic(code(nu::shell::type_mismatch), url(docsrs))]
2021-09-02 03:29:43 +02:00
OperatorMismatch {
#[label = "type mismatch for operator"]
2021-09-02 03:29:43 +02:00
op_span: Span,
lhs_ty: Type,
#[label("{lhs_ty}")]
2021-09-02 03:29:43 +02:00
lhs_span: Span,
rhs_ty: Type,
#[label("{rhs_ty}")]
2021-09-02 03:29:43 +02:00
rhs_span: Span,
},
2021-10-20 07:58:25 +02:00
#[error("Operator overflow.")]
#[diagnostic(code(nu::shell::operator_overflow), url(docsrs))]
OperatorOverflow(String, #[label = "{0}"] Span),
2021-10-09 03:02:01 +02:00
#[error("Pipeline mismatch.")]
#[diagnostic(code(nu::shell::pipeline_mismatch), url(docsrs))]
2021-12-03 00:11:25 +01:00
PipelineMismatch(
String,
#[label("expected: {0}")] Span,
#[label("value originates from here")] Span,
),
#[error("Type mismatch")]
#[diagnostic(code(nu::shell::type_mismatch), url(docsrs))]
2022-02-07 20:54:06 +01:00
TypeMismatch(String, #[label = "needs {0}"] Span),
2021-10-09 03:02:01 +02:00
#[error("Unsupported operator: {0}.")]
#[diagnostic(code(nu::shell::unsupported_operator), url(docsrs))]
UnsupportedOperator(Operator, #[label = "unsupported operator"] Span),
#[error("Unsupported operator: {0}.")]
#[diagnostic(code(nu::shell::unknown_operator), url(docsrs))]
UnknownOperator(String, #[label = "unsupported operator"] Span),
2021-10-08 00:20:23 +02:00
#[error("Missing parameter: {0}.")]
#[diagnostic(code(nu::shell::missing_parameter), url(docsrs))]
MissingParameter(String, #[label = "missing parameter: {0}"] Span),
// Be cautious, as flags can share the same span, resulting in a panic (ex: `rm -pt`)
#[error("Incompatible parameters.")]
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
IncompatibleParameters {
left_message: String,
#[label("{left_message}")]
left_span: Span,
right_message: String,
#[label("{right_message}")]
right_span: Span,
},
2021-11-09 21:17:37 +01:00
#[error("Delimiter error")]
#[diagnostic(code(nu::shell::delimiter_error), url(docsrs))]
DelimiterError(String, #[label("{0}")] Span),
#[error("Incompatible parameters.")]
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
IncompatibleParametersSingle(String, #[label = "{0}"] Span),
#[error("Feature not enabled.")]
#[diagnostic(code(nu::shell::feature_not_enabled), url(docsrs))]
FeatureNotEnabled(#[label = "feature not enabled"] Span),
#[error("Running external commands not supported")]
#[diagnostic(code(nu::shell::external_commands), url(docsrs))]
ExternalNotSupported(#[label = "external not supported"] Span),
#[error("Invalid Probability.")]
#[diagnostic(code(nu::shell::invalid_probability), url(docsrs))]
InvalidProbability(#[label = "invalid probability"] Span),
#[error("Invalid range {0}..{1}")]
#[diagnostic(code(nu::shell::invalid_range), url(docsrs))]
InvalidRange(String, String, #[label = "expected a valid range"] Span),
2021-12-03 00:11:25 +01:00
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable
#[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
NushellFailed(String),
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable
#[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
NushellFailedSpanned(String, String, #[label = "{1}"] Span),
#[error("Variable not found")]
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
VariableNotFoundAtRuntime(#[label = "variable not found"] Span),
#[error("Environment variable '{0}' not found")]
Use only $nu.env.PWD for getting the current directory (#587) * Use only $nu.env.PWD for getting current directory Because setting and reading to/from std::env changes the global state shich is problematic if we call `cd` from multiple threads (e.g., in a `par-each` block). With this change, when engine-q starts, it will either inherit existing PWD env var, or create a new one from `std::env::current_dir()`. Otherwise, everything that needs the current directory will get it from `$nu.env.PWD`. Each spawned external command will get its current directory per-process which should be thread-safe. One thing left to do is to patch nu-path for this as well since it uses `std::env::current_dir()` in its expansions. * Rename nu-path functions *_with is not *_relative which should be more descriptive and frees "with" for use in a followup commit. * Clone stack every each iter; Fix some commands Cloning the stack each iteration of `each` makes sure we're not reusing PWD between iterations. Some fixes in commands to make them use the new PWD. * Post-rebase cleanup, fmt, clippy * Change back _relative to _with in nu-path funcs Didn't use the idea I had for the new "_with". * Remove leftover current_dir from rebase * Add cwd sync at merge_delta() This makes sure the parser and completer always have up-to-date cwd. * Always pass absolute path to glob in ls * Do not allow PWD a relative path; Allow recovery Makes it possible to recover PWD by proceeding with the REPL cycle. * Clone stack in each also for byte/string stream * (WIP) Start moving env variables to engine state * (WIP) Move env vars to engine state (ugly) Quick and dirty code. * (WIP) Remove unused mut and args; Fmt * (WIP) Fix dataframe tests * (WIP) Fix missing args after rebase * (WIP) Clone only env vars, not the whole stack * (WIP) Add env var clone to `for` loop as well * Minor edits * Refactor merge_delta() to include stack merging. Less error-prone than doing it manually. * Clone env for each `update` command iteration * Mark env var hidden only when found in eng. state * Fix clippt warnings * Add TODO about env var reading * Do not clone empty environment in loops * Remove extra cwd collection * Split current_dir() into str and path; Fix autocd * Make completions respect PWD env var
2022-01-04 23:30:34 +01:00
#[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))]
EnvVarNotFoundAtRuntime(String, #[label = "environment variable not found"] Span),
#[error("Not found.")]
#[diagnostic(code(nu::parser::not_found), url(docsrs))]
NotFound(#[label = "did not find anything under this name"] Span),
#[error("Can't convert to {0}.")]
#[diagnostic(code(nu::shell::cant_convert), url(docsrs))]
CantConvert(String, String, #[label("can't convert {1} to {0}")] Span),
#[error("{0} is not representable as a string.")]
#[diagnostic(
code(nu::shell::env_var_not_a_string),
url(docsrs),
help(
r#"The '{0}' environment variable must be a string or be convertible to a string.
Either make sure {0} is a string, or add a 'to_string' entry for it in ENV_CONVERSIONS."#
)
)]
EnvVarNotAString(String, #[label("value not representable as a string")] Span),
#[error("Division by zero.")]
#[diagnostic(code(nu::shell::division_by_zero), url(docsrs))]
DivisionByZero(#[label("division by zero")] Span),
#[error("Can't convert range to countable values")]
#[diagnostic(code(nu::shell::range_to_countable), url(docsrs))]
CannotCreateRange(#[label = "can't convert to countable values"] Span),
#[error("Row number too large (max: {0}).")]
#[diagnostic(code(nu::shell::access_beyond_end), url(docsrs))]
AccessBeyondEnd(usize, #[label = "too large"] Span),
#[error("Row number too large.")]
#[diagnostic(code(nu::shell::access_beyond_end_of_stream), url(docsrs))]
AccessBeyondEndOfStream(#[label = "too large"] Span),
#[error("Data cannot be accessed with a cell path")]
#[diagnostic(code(nu::shell::incompatible_path_access), url(docsrs))]
IncompatiblePathAccess(String, #[label("{0} doesn't support cell paths")] Span),
#[error("Cannot find column")]
#[diagnostic(code(nu::shell::column_not_found), url(docsrs))]
CantFindColumn(
#[label = "cannot find column"] Span,
#[label = "value originates here"] Span,
),
2021-11-05 04:59:12 +01:00
#[error("Not a list value")]
#[diagnostic(code(nu::shell::not_a_list), url(docsrs))]
NotAList(
#[label = "value not a list"] Span,
#[label = "value originates here"] Span,
),
#[error("External command")]
2022-01-22 15:12:34 +01:00
#[diagnostic(code(nu::shell::external_command), url(docsrs), help("{1}"))]
ExternalCommand(String, String, #[label("{0}")] Span),
2021-09-24 14:03:39 +02:00
#[error("Unsupported input")]
#[diagnostic(code(nu::shell::unsupported_input), url(docsrs))]
UnsupportedInput(String, #[label("{0}")] Span),
2021-10-01 23:53:13 +02:00
#[error("Unable to parse datetime")]
#[diagnostic(
code(nu::shell::datetime_parse_error),
url(docsrs),
help(
r#"Examples of supported inputs:
* "5 pm"
* "2020/12/4"
* "2020.12.04 22:10 +2"
* "2020-04-12 22:10:57 +02:00"
* "2020-04-12T22:10:57.213231+02:00"
* "Tue, 1 Jul 2003 10:52:37 +0200""#
)
)]
DatetimeParseError(#[label("datetime parsing failed")] Span),
#[error("Network failure")]
#[diagnostic(code(nu::shell::network_failure), url(docsrs))]
NetworkFailure(String, #[label("{0}")] Span),
2021-10-09 03:02:01 +02:00
#[error("Command not found")]
#[diagnostic(code(nu::shell::command_not_found), url(docsrs))]
CommandNotFound(#[label("command not found")] Span),
2021-10-01 23:53:13 +02:00
#[error("Flag not found")]
#[diagnostic(code(nu::shell::flag_not_found), url(docsrs))]
FlagNotFound(String, #[label("{0} not found")] Span),
2021-10-05 05:43:07 +02:00
#[error("File not found")]
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
FileNotFound(#[label("file not found")] Span),
#[error("File not found")]
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
FileNotFoundCustom(String, #[label("{0}")] Span),
#[error("Plugin failed to load: {0}")]
#[diagnostic(code(nu::shell::plugin_failed_to_load), url(docsrs))]
2021-12-03 00:11:25 +01:00
PluginFailedToLoad(String),
#[error("Plugin failed to encode: {0}")]
#[diagnostic(code(nu::shell::plugin_failed_to_encode), url(docsrs))]
PluginFailedToEncode(String),
#[error("Plugin failed to decode: {0}")]
#[diagnostic(code(nu::shell::plugin_failed_to_decode), url(docsrs))]
PluginFailedToDecode(String),
2021-12-03 00:11:25 +01:00
#[error("I/O error")]
Use only $nu.env.PWD for getting the current directory (#587) * Use only $nu.env.PWD for getting current directory Because setting and reading to/from std::env changes the global state shich is problematic if we call `cd` from multiple threads (e.g., in a `par-each` block). With this change, when engine-q starts, it will either inherit existing PWD env var, or create a new one from `std::env::current_dir()`. Otherwise, everything that needs the current directory will get it from `$nu.env.PWD`. Each spawned external command will get its current directory per-process which should be thread-safe. One thing left to do is to patch nu-path for this as well since it uses `std::env::current_dir()` in its expansions. * Rename nu-path functions *_with is not *_relative which should be more descriptive and frees "with" for use in a followup commit. * Clone stack every each iter; Fix some commands Cloning the stack each iteration of `each` makes sure we're not reusing PWD between iterations. Some fixes in commands to make them use the new PWD. * Post-rebase cleanup, fmt, clippy * Change back _relative to _with in nu-path funcs Didn't use the idea I had for the new "_with". * Remove leftover current_dir from rebase * Add cwd sync at merge_delta() This makes sure the parser and completer always have up-to-date cwd. * Always pass absolute path to glob in ls * Do not allow PWD a relative path; Allow recovery Makes it possible to recover PWD by proceeding with the REPL cycle. * Clone stack in each also for byte/string stream * (WIP) Start moving env variables to engine state * (WIP) Move env vars to engine state (ugly) Quick and dirty code. * (WIP) Remove unused mut and args; Fmt * (WIP) Fix dataframe tests * (WIP) Fix missing args after rebase * (WIP) Clone only env vars, not the whole stack * (WIP) Add env var clone to `for` loop as well * Minor edits * Refactor merge_delta() to include stack merging. Less error-prone than doing it manually. * Clone env for each `update` command iteration * Mark env var hidden only when found in eng. state * Fix clippt warnings * Add TODO about env var reading * Do not clone empty environment in loops * Remove extra cwd collection * Split current_dir() into str and path; Fix autocd * Make completions respect PWD env var
2022-01-04 23:30:34 +01:00
#[diagnostic(code(nu::shell::io_error), url(docsrs), help("{0}"))]
2021-12-03 00:11:25 +01:00
IOError(String),
#[error("Cannot change to directory")]
#[diagnostic(code(nu::shell::cannot_cd_to_directory), url(docsrs))]
NotADirectory(#[label("is not a directory")] Span),
2021-10-05 05:43:07 +02:00
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found), url(docsrs))]
DirectoryNotFound(#[label("directory not found")] Span),
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found_custom), url(docsrs))]
DirectoryNotFoundCustom(String, #[label("{0}")] Span),
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found_help), url(docsrs), help("{1}"))]
DirectoryNotFoundHelp(#[label("directory not found")] Span, String),
2021-10-05 05:43:07 +02:00
#[error("Move not possible")]
#[diagnostic(code(nu::shell::move_not_possible), url(docsrs))]
MoveNotPossible {
source_message: String,
#[label("{source_message}")]
source_span: Span,
destination_message: String,
#[label("{destination_message}")]
destination_span: Span,
},
#[error("Move not possible")]
#[diagnostic(code(nu::shell::move_not_possible_single), url(docsrs))]
MoveNotPossibleSingle(String, #[label("{0}")] Span),
2021-10-07 23:18:03 +02:00
#[error("Create not possible")]
#[diagnostic(code(nu::shell::create_not_possible), url(docsrs))]
2021-10-07 23:18:03 +02:00
CreateNotPossible(String, #[label("{0}")] Span),
#[error("Remove not possible")]
#[diagnostic(code(nu::shell::remove_not_possible), url(docsrs))]
RemoveNotPossible(String, #[label("{0}")] Span),
2021-10-14 19:54:51 +02:00
#[error("No file to be removed")]
NoFileToBeRemoved(),
#[error("No file to be moved")]
NoFileToBeMoved(),
#[error("No file to be copied")]
NoFileToBeCopied(),
2021-10-26 21:50:39 +02:00
2021-11-07 22:48:50 +01:00
#[error("Name not found")]
#[diagnostic(code(nu::shell::name_not_found), url(docsrs))]
DidYouMean(String, #[label("did you mean '{0}'?")] Span),
#[error("Non-UTF8 string")]
#[diagnostic(code(nu::parser::non_utf8), url(docsrs))]
NonUtf8(#[label = "non-UTF8 string"] Span),
#[error("Casting error")]
#[diagnostic(code(nu::shell::downcast_not_possible), url(docsrs))]
DowncastNotPossible(String, #[label("{0}")] Span),
#[error("Unsupported config value")]
#[diagnostic(code(nu::shell::unsupported_config_value), url(docsrs))]
UnsupportedConfigValue(String, String, #[label = "expected {0}, got {1}"] Span),
#[error("Missing config value")]
#[diagnostic(code(nu::shell::missing_config_value), url(docsrs))]
MissingConfigValue(String, #[label = "missing {0}"] Span),
#[error("Negative value passed when positive one is required")]
#[diagnostic(code(nu::shell::needs_positive_value), url(docsrs))]
NeedsPositiveValue(#[label = "use a positive value"] Span),
#[error("{0}")]
#[diagnostic()]
SpannedLabeledError(String, String, #[label("{1}")] Span),
#[error("{0}")]
#[diagnostic(help("{3}"))]
SpannedLabeledErrorHelp(String, String, #[label("{1}")] Span, String),
#[error("{0}")]
#[diagnostic()]
SpannedLabeledErrorRelated(
String,
String,
#[label("{1}")] Span,
#[related] Vec<ShellError>,
),
#[error("{0}")]
#[diagnostic(help("{1}"))]
LabeledError(String, String),
#[error("{1}")]
#[diagnostic()]
OutsideSpannedLabeledError(#[source_code] String, String, String, #[label("{2}")] Span),
#[error("Deprecated command {0}")]
#[diagnostic(code(nu::shell::deprecated_command), url(docsrs))]
DeprecatedCommand(
String,
String,
2022-02-11 00:27:51 +01:00
#[label = "'{0}' is deprecated. Please use '{1}' instead."] Span,
),
}
impl From<std::io::Error> for ShellError {
fn from(input: std::io::Error) -> ShellError {
2021-12-03 00:11:25 +01:00
ShellError::IOError(format!("{:?}", input))
}
}
impl std::convert::From<Box<dyn std::error::Error>> for ShellError {
fn from(input: Box<dyn std::error::Error>) -> ShellError {
2021-12-03 00:11:25 +01:00
ShellError::IOError(input.to_string())
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
fn from(input: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
2021-12-03 00:11:25 +01:00
ShellError::IOError(format!("{:?}", input))
}
2021-09-02 03:29:43 +02:00
}
2021-11-07 22:48:50 +01:00
pub fn did_you_mean(possibilities: &[String], tried: &str) -> Option<String> {
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|word| {
let edit_distance = levenshtein_distance(word, tried);
(edit_distance, word.to_owned())
})
.collect();
possible_matches.sort();
if let Some((_, first)) = possible_matches.into_iter().next() {
Some(first)
} else {
None
}
}
// Borrowed from here https://github.com/wooorm/levenshtein-rs
pub fn levenshtein_distance(a: &str, b: &str) -> usize {
let mut result = 0;
/* Shortcut optimizations / degenerate cases. */
if a == b {
return result;
}
let length_a = a.chars().count();
let length_b = b.chars().count();
if length_a == 0 {
return length_b;
}
if length_b == 0 {
return length_a;
}
/* Initialize the vector.
*
* This is why its fast, normally a matrix is used,
* here we use a single vector. */
let mut cache: Vec<usize> = (1..).take(length_a).collect();
let mut distance_a;
let mut distance_b;
/* Loop. */
for (index_b, code_b) in b.chars().enumerate() {
result = index_b;
distance_a = index_b;
for (index_a, code_a) in a.chars().enumerate() {
distance_b = if code_a == code_b {
distance_a
} else {
distance_a + 1
};
distance_a = cache[index_a];
result = if distance_a > result {
if distance_b > result {
result + 1
} else {
distance_b
}
} else if distance_b > distance_a {
distance_a + 1
} else {
distance_b
};
cache[index_a] = result;
}
}
result
}