Standardise the use of ShellError::UnsupportedInput and ShellError::TypeMismatch and add spans to every instance of the former (#7217)

# Description

* I was dismayed to discover recently that UnsupportedInput and
TypeMismatch are used *extremely* inconsistently across the codebase.
UnsupportedInput is sometimes used for input type-checks (as per the
name!!), but *also* used for argument type-checks. TypeMismatch is also
used for both.
I thus devised the following standard: input type-checking *only* uses
UnsupportedInput, and argument type-checking *only* uses TypeMismatch.
Moreover, to differentiate them, UnsupportedInput now has *two* error
arrows (spans), one pointing at the command and the other at the input
origin, while TypeMismatch only has the one (because the command should
always be nearby)
* In order to apply that standard, a very large number of
UnsupportedInput uses were changed so that the input's span could be
retrieved and delivered to it.
* Additionally, I noticed many places where **errors are not propagated
correctly**: there are lots of `match` sites which take a Value::Error,
then throw it away and replace it with a new Value::Error with
less/misleading information (such as reporting the error as an
"incorrect type"). I believe that the earliest errors are the most
important, and should always be propagated where possible.
* Also, to standardise one broad subset of UnsupportedInput error
messages, who all used slightly different wordings of "expected
`<type>`, got `<type>`", I created OnlySupportsThisInputType as a
variant of it.
* Finally, a bunch of error sites that had "repeated spans" - i.e. where
an error expected two spans, but `call.head` was given for both - were
fixed to use different spans.

# Example
BEFORE
```
〉20b | str starts-with 'a'
Error: nu:🐚:unsupported_input (link)

  × Unsupported input
   ╭─[entry #31:1:1]
 1 │ 20b | str starts-with 'a'
   ·   ┬
   ·   ╰── Input's type is filesize. This command only works with strings.
   ╰────

〉'a' | math cos
Error: nu:🐚:unsupported_input (link)

  × Unsupported input
   ╭─[entry #33:1:1]
 1 │ 'a' | math cos
   · ─┬─
   ·  ╰── Only numerical values are supported, input type: String
   ╰────

〉0x[12] | encode utf8
Error: nu:🐚:unsupported_input (link)

  × Unsupported input
   ╭─[entry #38:1:1]
 1 │ 0x[12] | encode utf8
   ·          ───┬──
   ·             ╰── non-string input
   ╰────
```
AFTER
```
〉20b | str starts-with 'a'
Error: nu:🐚:pipeline_mismatch (link)

  × Pipeline mismatch.
   ╭─[entry #1:1:1]
 1 │ 20b | str starts-with 'a'
   ·   ┬   ───────┬───────
   ·   │          ╰── only string input data is supported
   ·   ╰── input type: filesize
   ╰────

〉'a' | math cos
Error: nu:🐚:pipeline_mismatch (link)

  × Pipeline mismatch.
   ╭─[entry #2:1:1]
 1 │ 'a' | math cos
   · ─┬─   ────┬───
   ·  │        ╰── only numeric input data is supported
   ·  ╰── input type: string
   ╰────

〉0x[12] | encode utf8
Error: nu:🐚:pipeline_mismatch (link)

  × Pipeline mismatch.
   ╭─[entry #3:1:1]
 1 │ 0x[12] | encode utf8
   · ───┬──   ───┬──
   ·    │        ╰── only string input data is supported
   ·    ╰── input type: binary
   ╰────
```

# User-Facing Changes

Various error messages suddenly make more sense (i.e. have two arrows
instead of one).

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
Leon 2022-12-23 16:48:53 +10:00 committed by GitHub
parent 9364bad625
commit dd7b7311b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
154 changed files with 1617 additions and 1025 deletions

View File

@ -43,6 +43,10 @@ impl Command for SubCommand {
let head = call.head; let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?; let target: i64 = call.req(engine_state, stack, 0)?;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, target, head), move |value| operate(value, target, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -74,13 +78,15 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
val: val & target, val: val & target,
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Only integer values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(head), other.expect_span(),
), ),
}, },
} }

View File

@ -56,11 +56,17 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head, signed, bytes_len), move |value| operate(value, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -140,14 +146,17 @@ fn operate(value: Value, head: Span, signed: bool, number_size: NumberBytes) ->
Value::Int { val: out_val, span } Value::Int { val: out_val, span }
} }
} }
other => Value::Error { other => match other {
error: ShellError::UnsupportedInput( // Propagate errors inside the value
format!( Value::Error { .. } => other,
"Only numerical values are supported, input type: {:?}", _ => Value::Error {
other.get_type() error: ShellError::OnlySupportsThisInputType(
"numeric".into(),
other.get_type().to_string(),
head,
other.expect_span(),
), ),
other.span().unwrap_or(head), },
),
}, },
} }
} }

View File

@ -43,6 +43,10 @@ impl Command for SubCommand {
let head = call.head; let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?; let target: i64 = call.req(engine_state, stack, 0)?;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, target, head), move |value| operate(value, target, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -74,13 +78,15 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
val: val | target, val: val | target,
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Only integer values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(head), other.expect_span(),
), ),
}, },
} }

View File

@ -60,11 +60,16 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -130,13 +135,15 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedEight => get_rotate_left(val as i64, bits, span), SignedEight => get_rotate_left(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Only integer values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(head), other.expect_span(),
), ),
}, },
} }

View File

@ -60,11 +60,16 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -134,13 +139,15 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedEight => get_rotate_right(val as i64, bits, span), SignedEight => get_rotate_right(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Only integer values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(head), other.expect_span(),
), ),
}, },
} }

View File

@ -60,11 +60,16 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -156,13 +161,15 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedEight => get_shift_left(val as i64, bits, span), SignedEight => get_shift_left(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Only integer values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(head), other.expect_span(),
), ),
}, },
} }

View File

@ -60,11 +60,16 @@ impl Command for SubCommand {
if let Some(val) = number_bytes { if let Some(val) = number_bytes {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(), "Only 1, 2, 4, 8, or 'auto' bytes are supported as word sizes".to_string(),
"value originates from here".to_string(),
head,
val.span, val.span,
)); ));
} }
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, bits, head, signed, bytes_len), move |value| operate(value, bits, head, signed, bytes_len),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -146,13 +151,15 @@ fn operate(value: Value, bits: usize, head: Span, signed: bool, number_size: Num
SignedEight => get_shift_right(val as i64, bits, span), SignedEight => get_shift_right(val as i64, bits, span),
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Only integer values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(head), other.expect_span(),
), ),
}, },
} }

View File

@ -42,7 +42,10 @@ impl Command for SubCommand {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let target: i64 = call.req(engine_state, stack, 0)?; let target: i64 = call.req(engine_state, stack, 0)?;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, target, head), move |value| operate(value, target, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -74,13 +77,15 @@ fn operate(value: Value, target: i64, head: Span) -> Value {
val: val ^ target, val: val ^ target,
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Only integer values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(head), other.expect_span(),
), ),
}, },
} }

View File

@ -122,13 +122,15 @@ fn add(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => add_impl(val, args, *val_span), } => add_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -30,7 +30,9 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
Value::List { mut vals, span } => { Value::List { mut vals, span } => {
if vals.len() != 2 { if vals.len() != 2 {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"More than two indices given".to_string(), "More than two indices in range".to_string(),
"value originates from here".to_string(),
head,
span, span,
)); ));
} else { } else {
@ -38,10 +40,14 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
let end = match end { let end = match end {
Value::Int { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val, Value::String { val, .. } => val,
// Explictly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes. Expecting a string or int".to_string(), "Only string or list<int> ranges are supported".into(),
other.span().unwrap_or(head), format!("input type: {:?}", other.get_type()),
head,
other.expect_span(),
)) ))
} }
}; };
@ -49,10 +55,14 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
let start = match start { let start = match start {
Value::Int { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val, Value::String { val, .. } => val,
// Explictly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes. Expecting a string or int".to_string(), "Only string or list<int> ranges are supported".into(),
other.span().unwrap_or(head), format!("input type: {:?}", other.get_type()),
head,
other.expect_span(),
)) ))
} }
}; };
@ -66,15 +76,21 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
None => { None => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span, span,
)) ))
} }
} }
} }
// Explictly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
other.span().unwrap_or(head), "with this range".to_string(),
head,
other.expect_span(),
)) ))
} }
}; };
@ -87,6 +103,8 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
Err(_) => { Err(_) => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span, span,
)) ))
} }
@ -100,6 +118,8 @@ fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellEr
Err(_) => { Err(_) => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(), "could not perform subbytes".to_string(),
"with this range".to_string(),
head,
span, span,
)) ))
} }
@ -232,13 +252,15 @@ fn at(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => at_impl(val, args, *val_span), } => at_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }
@ -262,7 +284,7 @@ fn at_impl(input: &[u8], arg: &Arguments, span: Span) -> Value {
match start.cmp(&end) { match start.cmp(&end) {
Ordering::Equal => Value::Binary { val: vec![], span }, Ordering::Equal => Value::Binary { val: vec![], span },
Ordering::Greater => Value::Error { Ordering::Greater => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::TypeMismatch(
"End must be greater than or equal to Start".to_string(), "End must be greater than or equal to Start".to_string(),
arg.arg_span, arg.arg_span,
), ),

View File

@ -52,10 +52,12 @@ impl Command for BytesBuild {
let val = eval_expression(engine_state, stack, expr)?; let val = eval_expression(engine_state, stack, expr)?;
match val { match val {
Value::Binary { mut val, .. } => output.append(&mut val), Value::Binary { mut val, .. } => output.append(&mut val),
// Explictly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"only support expression which yields to binary data".to_string(), "only binary data arguments are supported".to_string(),
other.span().unwrap_or(call.head), other.expect_span(),
)) ))
} }
} }

View File

@ -54,14 +54,16 @@ impl Command for BytesCollect {
output_binary.append(&mut work_sep) output_binary.append(&mut work_sep)
} }
} }
// Explictly propagate errors instead of dropping them.
Value::Error { error } => return Err(error),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::OnlySupportsThisInputType(
format!( "integer".into(),
"The element type is {}, this command only works with bytes.", other.get_type().to_string(),
other.get_type() call.head,
), // This line requires the Value::Error match above.
other.span().unwrap_or(call.head), other.expect_span(),
)) ));
} }
} }
} }

View File

@ -90,13 +90,15 @@ fn ends_with(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => Value::boolean(val.ends_with(&args.pattern), *val_span), } => Value::boolean(val.ends_with(&args.pattern), *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "binary".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -132,13 +132,15 @@ fn index_of(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => index_of_impl(val, args, *val_span), } => index_of_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "binary".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -71,13 +71,15 @@ fn length(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => Value::int(val.len() as i64, *val_span), } => Value::int(val.len() as i64, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "binary".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -61,7 +61,7 @@ impl Command for BytesRemove {
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?; let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if pattern_to_remove.item.is_empty() { if pattern_to_remove.item.is_empty() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"the pattern to remove cannot be empty".to_string(), "the pattern to remove cannot be empty".to_string(),
pattern_to_remove.span, pattern_to_remove.span,
)); ));
@ -139,13 +139,15 @@ fn remove(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => remove_impl(val, args, *val_span), } => remove_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "binary".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -61,7 +61,7 @@ impl Command for BytesReplace {
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?; let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if find.item.is_empty() { if find.item.is_empty() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"the pattern to find cannot be empty".to_string(), "the pattern to find cannot be empty".to_string(),
find.span, find.span,
)); ));
@ -130,13 +130,15 @@ fn replace(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => replace_impl(val, args, *val_span), } => replace_impl(val, args, *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "binary".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -81,13 +81,15 @@ fn reverse(val: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
span: *val_span, span: *val_span,
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "binary".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -96,13 +96,15 @@ fn starts_with(val: &Value, args: &Arguments, span: Span) -> Value {
val, val,
span: val_span, span: val_span,
} => Value::boolean(val.starts_with(&args.pattern), *val_span), } => Value::boolean(val.starts_with(&args.pattern), *val_span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => val.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "binary".into(),
"Input's type is {}. This command only works with bytes.", other.get_type().to_string(),
other.get_type()
),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -78,13 +78,14 @@ impl HashableValue {
Value::String { val, span } => Ok(HashableValue::String { val, span }), Value::String { val, span } => Ok(HashableValue::String { val, span }),
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }), Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
_ => { // Explictly propagate errors instead of dropping them.
let input_span = value.span().unwrap_or(span); Value::Error { error } => Err(error),
Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
format!("input value {value:?} is not hashable"), "input value is not hashable".into(),
input_span, format!("input type: {:?}", value.get_type()),
)) span,
} value.expect_span(),
)),
} }
} }

View File

@ -98,12 +98,11 @@ impl Command for Histogram {
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?; let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
let frequency_column_name = match frequency_name_arg { let frequency_column_name = match frequency_name_arg {
Some(inner) => { Some(inner) => {
let span = inner.span;
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) { if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"frequency-column-name can't be 'value', 'count' or 'percentage'" "frequency-column-name can't be 'value', 'count' or 'percentage'"
.to_string(), .to_string(),
span, inner.span,
)); ));
} }
inner.item inner.item
@ -119,7 +118,7 @@ impl Command for Histogram {
"normalize" => PercentageCalcMethod::Normalize, "normalize" => PercentageCalcMethod::Normalize,
"relative" => PercentageCalcMethod::Relative, "relative" => PercentageCalcMethod::Relative,
_ => { _ => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"calc method can only be 'normalize' or 'relative'".to_string(), "calc method can only be 'normalize' or 'relative'".to_string(),
inner.span, inner.span,
)) ))
@ -137,6 +136,8 @@ impl Command for Histogram {
frequency_column_name, frequency_column_name,
calc_method, calc_method,
span, span,
// Note that as_list() filters out Value::Error here.
data_as_value.expect_span(),
), ),
Err(e) => Err(e), Err(e) => Err(e),
} }
@ -149,6 +150,7 @@ fn run_histogram(
freq_column: String, freq_column: String,
calc_method: PercentageCalcMethod, calc_method: PercentageCalcMethod,
head_span: Span, head_span: Span,
list_span: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let mut inputs = vec![]; let mut inputs = vec![];
// convert from inputs to hashable values. // convert from inputs to hashable values.
@ -157,14 +159,24 @@ fn run_histogram(
// some invalid input scenario needs to handle: // some invalid input scenario needs to handle:
// Expect input is a list of hashable value, if one value is not hashable, throw out error. // Expect input is a list of hashable value, if one value is not hashable, throw out error.
for v in values { for v in values {
let current_span = v.span().unwrap_or(head_span); match v {
inputs.push(HashableValue::from_value(v, head_span).map_err(|_| { // Propagate existing errors.
ShellError::UnsupportedInput( Value::Error { error } => return Err(error),
"--column-name is not provided, can only support a list of simple value." _ => {
.to_string(), let t = v.get_type();
current_span, let span = v.expect_span();
) inputs.push(HashableValue::from_value(v, head_span).map_err(|_| {
})?); ShellError::UnsupportedInput(
"Since --column-name was not provided, only lists of hashable values are supported.".to_string(),
format!(
"input type: {:?}", t
),
head_span,
span,
)
})?)
}
}
} }
} }
Some(ref col) => { Some(ref col) => {
@ -186,14 +198,17 @@ fn run_histogram(
} }
} }
} }
// Propagate existing errors.
Value::Error { error } => return Err(error),
_ => continue, _ => continue,
} }
} }
if inputs.is_empty() { if inputs.is_empty() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::CantFindColumn(
format!("expect input is table, and inputs doesn't contain any value which has {col_name} column"), col_name.clone(),
head_span, head_span,
list_span,
)); ));
} }
} }

View File

@ -84,10 +84,15 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input { match input {
Value::Int { val, .. } => fmt_it(*val, span), Value::Int { val, .. } => fmt_it(*val, span),
Value::Filesize { val, .. } => fmt_it(*val, span), Value::Filesize { val, .. } => fmt_it(*val, span),
_ => Value::Error { // Propagate errors by explicitly matching them before the final case.
error: ShellError::UnsupportedInput( Value::Error { .. } => input.clone(),
format!("unsupported input type: {:?}", input.get_type()), other => Value::Error {
error: ShellError::OnlySupportsThisInputType(
"integer or filesize".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -177,13 +177,24 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
val: int_to_endian(i64::from(*val)), val: int_to_endian(i64::from(*val)),
span, span,
}, },
Value::Duration { val, .. } => Value::Binary {
val: int_to_endian(*val),
span,
},
Value::Date { val, .. } => Value::Binary { Value::Date { val, .. } => Value::Binary {
val: val.format("%c").to_string().as_bytes().to_vec(), val: val.format("%c").to_string().as_bytes().to_vec(),
span, span,
}, },
// Propagate errors by explicitly matching them before the final case.
_ => Value::Error { Value::Error { .. } => input.clone(),
error: ShellError::UnsupportedInput("'into binary' for unsupported type".into(), span), other => Value::Error {
error: ShellError::OnlySupportsThisInputType(
"integer, float, filesize, string, date, duration, binary or bool".into(),
other.get_type().to_string(),
span,
// This line requires the Value::Error match above.
other.expect_span(),
),
}, },
} }
} }

View File

@ -163,10 +163,15 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
Ok(val) => Value::Bool { val, span }, Ok(val) => Value::Bool { val, span },
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
}, },
_ => Value::Error { // Propagate errors by explicitly matching them before the final case.
error: ShellError::UnsupportedInput( Value::Error { .. } => input.clone(),
"'into bool' does not support this input".into(), other => Value::Error {
error: ShellError::OnlySupportsThisInputType(
"bool, integer, float or string".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -147,41 +147,19 @@ impl Command for SubCommand {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
let example_result_1 = |secs: i64, nsecs: u32| { let example_result_1 = |secs: i64, nsecs: u32| match Utc.timestamp_opt(secs, nsecs) {
let dt = match Utc.timestamp_opt(secs, nsecs) { LocalResult::Single(dt) => Some(Value::Date {
LocalResult::Single(dt) => Some(dt), val: dt.into(),
_ => None, span: Span::test_data(),
}; }),
match dt { _ => panic!("datetime: help example is invalid"),
Some(dt) => Some(Value::Date {
val: dt.into(),
span: Span::test_data(),
}),
None => Some(Value::Error {
error: ShellError::UnsupportedInput(
"The given datetime representation is unsupported.".to_string(),
Span::test_data(),
),
}),
}
}; };
let example_result_2 = |millis: i64| { let example_result_2 = |millis: i64| match Utc.timestamp_millis_opt(millis) {
let dt = match Utc.timestamp_millis_opt(millis) { LocalResult::Single(dt) => Some(Value::Date {
LocalResult::Single(dt) => Some(dt), val: dt.into(),
_ => None, span: Span::test_data(),
}; }),
match dt { _ => panic!("datetime: help example is invalid"),
Some(dt) => Some(Value::Date {
val: dt.into(),
span: Span::test_data(),
}),
None => Some(Value::Error {
error: ShellError::UnsupportedInput(
"The given datetime representation is unsupported.".to_string(),
Span::test_data(),
),
}),
}
}; };
vec![ vec![
Example { Example {
@ -213,7 +191,7 @@ impl Command for SubCommand {
}, },
Example { Example {
description: description:
"Convert timestamps like the sqlite history t", "Convert a millisecond-precise timestamp",
example: "1656165681720 | into datetime", example: "1656165681720 | into datetime",
result: example_result_2(1656165681720) result: example_result_2(1656165681720)
}, },
@ -231,11 +209,16 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
let timestamp = match input { let timestamp = match input {
Value::Int { val, .. } => Ok(*val), Value::Int { val, .. } => Ok(*val),
Value::String { val, .. } => val.parse::<i64>(), Value::String { val, .. } => val.parse::<i64>(),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return input.clone(),
other => { other => {
return Value::Error { return Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!("Expected string or int, got {} instead", other.get_type()), "string and integer".into(),
other.get_type().to_string(),
head, head,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}; };
} }
@ -248,113 +231,68 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
if ts.abs() > TIMESTAMP_BOUND { if ts.abs() > TIMESTAMP_BOUND {
return Value::Error { return Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"Given timestamp is out of range, it should between -8e+12 and 8e+12" "timestamp is out of range; it should between -8e+12 and 8e+12".to_string(),
.to_string(), format!("timestamp is {:?}", ts),
head, head,
// Again, can safely unwrap this from here on
input.expect_span(),
), ),
}; };
} }
macro_rules! match_datetime {
($expr:expr) => {
match $expr {
LocalResult::Single(dt) => Value::Date {
val: dt.into(),
span: head,
},
_ => {
return Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".into(),
format!("timestamp is {:?}", ts),
head,
head,
),
};
}
}
};
}
return match timezone { return match timezone {
// default to UTC // default to UTC
None => { None => {
// be able to convert chrono::Utc::now() // be able to convert chrono::Utc::now()
let dt = match ts.to_string().len() { match ts.to_string().len() {
x if x > 13 => Utc.timestamp_nanos(ts).into(), x if x > 13 => Value::Date {
x if x > 10 => match Utc.timestamp_millis_opt(ts) { val: Utc.timestamp_nanos(ts).into(),
LocalResult::Single(dt) => dt.into(), span: head,
_ => {
return Value::Error {
// This error message is from chrono
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid."
.to_string(),
head,
),
};
}
}, },
_ => match Utc.timestamp_opt(ts, 0) { x if x > 10 => match_datetime!(Utc.timestamp_millis_opt(ts)),
LocalResult::Single(dt) => dt.into(), _ => match_datetime!(Utc.timestamp_opt(ts, 0)),
_ => {
return Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid."
.to_string(),
head,
),
}
}
},
};
Value::Date {
val: dt,
span: head,
} }
} }
Some(Spanned { item, span }) => match item { Some(Spanned { item, span }) => match item {
Zone::Utc => match Utc.timestamp_opt(ts, 0) { Zone::Utc => match_datetime!(Utc.timestamp_opt(ts, 0)),
LocalResult::Single(val) => Value::Date { Zone::Local => match_datetime!(Local.timestamp_opt(ts, 0)),
val: val.into(),
span: head,
},
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
Zone::Local => match Local.timestamp_opt(ts, 0) {
LocalResult::Single(val) => Value::Date {
val: val.into(),
span: head,
},
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) { Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
Some(eastoffset) => match eastoffset.timestamp_opt(ts, 0) { Some(eastoffset) => match_datetime!(eastoffset.timestamp_opt(ts, 0)),
LocalResult::Single(val) => Value::Date { val, span: head },
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
None => Value::Error { None => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::DatetimeParseError(*span),
"The given local datetime representation is invalid.".to_string(),
*span,
),
}, },
}, },
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) { Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
Some(westoffset) => match westoffset.timestamp_opt(ts, 0) { Some(westoffset) => match_datetime!(westoffset.timestamp_opt(ts, 0)),
LocalResult::Single(val) => Value::Date { val, span: head },
_ => Value::Error {
error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid.".to_string(),
*span,
),
},
},
None => Value::Error { None => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::DatetimeParseError(*span),
"The given local datetime representation is invalid.".to_string(),
*span,
),
}, },
}, },
Zone::Error => Value::Error { Zone::Error => Value::Error {
error: ShellError::UnsupportedInput( // This is an argument error, not an input error
"Cannot convert given timezone or offset to timestamp".to_string(), error: ShellError::TypeMismatch(
"Invalid timezone or offset".to_string(),
*span, *span,
), ),
}, },
@ -391,10 +329,15 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
}, },
} }
} }
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!("Expected string, got {} instead", other.get_type()), "string".into(),
other.get_type().to_string(),
head, head,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -105,18 +105,17 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
}, },
span: *span, span: *span,
}, },
other => { // Propagate errors by explicitly matching them before the final case.
let span = other.span(); Value::Error { .. } => input.clone(),
match span { other => Value::Error {
Ok(s) => { error: ShellError::OnlySupportsThisInputType(
let got = format!("Expected a string, got {} instead", other.get_type()); "string, integer or bool".into(),
Value::Error { other.get_type().to_string(),
error: ShellError::UnsupportedInput(got, s), head,
} // This line requires the Value::Error match above.
} other.expect_span(),
Err(e) => Value::Error { error: e }, ),
} },
}
} }
} }

View File

@ -468,9 +468,11 @@ fn action(
} }
} else { } else {
Value::Error { Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::CantConvert(
"'into duration' does not support this string input".into(), "string".into(),
"duration".into(),
span, span,
None,
), ),
} }
} }
@ -481,10 +483,15 @@ fn action(
} }
} }
} }
_ => Value::Error { // Propagate errors by explicitly matching them before the final case.
error: ShellError::UnsupportedInput( Value::Error { .. } => input.clone(),
"'into duration' does not support this input".into(), other => Value::Error {
error: ShellError::OnlySupportsThisInputType(
"string or duration".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }

View File

@ -116,20 +116,18 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
val: 0, val: 0,
span: value_span, span: value_span,
}, },
_ => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
"'into filesize' for unsupported type".into(), "string and integer".into(),
other.get_type().to_string(),
span,
value_span, value_span,
), ),
}, },
} }
} else { } else {
Value::Error { // Propagate existing errors
error: ShellError::UnsupportedInput( input.clone()
"'into filesize' for unsupported type".into(),
span,
),
}
} }
} }
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> { fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {

View File

@ -70,7 +70,7 @@ impl Command for SubCommand {
let radix: u32 = match radix { let radix: u32 = match radix {
Some(Value::Int { val, span }) => { Some(Value::Int { val, span }) => {
if !(2..=36).contains(&val) { if !(2..=36).contains(&val) {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"Radix must lie in the range [2, 36]".to_string(), "Radix must lie in the range [2, 36]".to_string(),
span, span,
)); ));
@ -187,9 +187,11 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
Ok(v) => v, Ok(v) => v,
_ => { _ => {
return Value::Error { return Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::CantConvert(
"Could not convert float to integer".to_string(), "float".to_string(),
"integer".to_string(),
span, span,
None,
), ),
} }
} }
@ -219,6 +221,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
val: val.timestamp(), val: val.timestamp(),
span, span,
}, },
Value::Duration { val, .. } => Value::Int { val: *val, span },
Value::Binary { val, span } => { Value::Binary { val, span } => {
use byteorder::{BigEndian, ByteOrder, LittleEndian}; use byteorder::{BigEndian, ByteOrder, LittleEndian};
@ -240,10 +243,15 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
Value::int(BigEndian::read_i64(&val), *span) Value::int(BigEndian::read_i64(&val), *span)
} }
} }
_ => Value::Error { // Propagate errors by explicitly matching them before the final case.
error: ShellError::UnsupportedInput( Value::Error { .. } => input.clone(),
format!("'into int' for unsupported type '{}'", input.get_type()), other => Value::Error {
error: ShellError::OnlySupportsThisInputType(
"integer, float, filesize, date, string, binary, duration or bool".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
} }
@ -281,13 +289,18 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
} }
val.to_string() val.to_string()
} }
_ => { // Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return input.clone(),
other => {
return Value::Error { return Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
"only strings or integers are supported".to_string(), "string and integer".into(),
other.get_type().to_string(),
head, head,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
} };
} }
}; };
match i64::from_str_radix(i.trim(), radix) { match i64::from_str_radix(i.trim(), radix) {

View File

@ -183,12 +183,16 @@ fn into_record(
Value::Record { cols, vals, span } Value::Record { cols, vals, span }
} }
Value::Record { cols, vals, span } => Value::Record { cols, vals, span }, Value::Record { cols, vals, span } => Value::Record { cols, vals, span },
other => { Value::Error { .. } => input,
return Err(ShellError::UnsupportedInput( other => Value::Error {
"'into record' does not support this input".into(), error: ShellError::OnlySupportsThisInputType(
other.span().unwrap_or(call.head), "string".into(),
)) other.get_type().to_string(),
} call.head,
// This line requires the Value::Error match above.
other.expect_span(),
),
},
}; };
Ok(res.into_pipeline_data()) Ok(res.into_pipeline_data())
} }

View File

@ -154,7 +154,7 @@ fn string_helper(
let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?; let decimals_value: Option<i64> = call.get_flag(engine_state, stack, "decimals")?;
if let Some(decimal_val) = decimals_value { if let Some(decimal_val) = decimals_value {
if decimals && decimal_val.is_negative() { if decimals && decimal_val.is_negative() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"Cannot accept negative integers for decimals arguments".to_string(), "Cannot accept negative integers for decimals arguments".to_string(),
head, head,
)); ));
@ -251,9 +251,11 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
vals: _, vals: _,
span: _, span: _,
} => Value::Error { } => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::CantConvert(
"Cannot convert Record into string".to_string(), "record".into(),
"string".into(),
span, span,
Some("try using the `to nuon` command".into()),
), ),
}, },
Value::Binary { .. } => Value::Error { Value::Binary { .. } => Value::Error {

View File

@ -39,6 +39,8 @@ impl Command for Debug {
let config = engine_state.get_config().clone(); let config = engine_state.get_config().clone();
let raw = call.has_flag("raw"); let raw = call.has_flag("raw");
// Should PipelineData::Empty result in an error here?
input.map( input.map(
move |x| { move |x| {
if raw { if raw {

View File

@ -218,12 +218,14 @@ fn action(
// and we're done // and we're done
Ok(Value::Nothing { span: *span }) Ok(Value::Nothing { span: *span })
} }
_ => Err(ShellError::UnsupportedInput( // Propagate errors by explicitly matching them before the final case.
format!( Value::Error { error } => Err(error.clone()),
"Expected a list but instead received a {}", other => Err(ShellError::OnlySupportsThisInputType(
input.get_type() "list".into(),
), other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
)), )),
} }
} }

View File

@ -70,7 +70,7 @@ fn command(
.ok_or_else(|| { .ok_or_else(|| {
ShellError::GenericError( ShellError::GenericError(
"Empty names list".into(), "Empty names list".into(),
"No column names where found".into(), "No column names were found".into(),
Some(col_span), Some(col_span),
None, None,
Vec::new(), Vec::new(),

View File

@ -467,7 +467,9 @@ pub fn create_column(
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid." "The given local datetime representation is invalid."
.to_string(), .to_string(),
format!("timestamp is {:?}", a),
span, span,
Span::unknown(),
), ),
} }
} }
@ -480,7 +482,9 @@ pub fn create_column(
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid." "The given local datetime representation is invalid."
.to_string(), .to_string(),
format!("timestamp is {:?}", a),
span, span,
Span::unknown(),
), ),
} }
} }
@ -528,7 +532,9 @@ pub fn create_column(
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid." "The given local datetime representation is invalid."
.to_string(), .to_string(),
format!("timestamp is {:?}", a),
span, span,
Span::unknown(),
), ),
} }
} }
@ -541,7 +547,9 @@ pub fn create_column(
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"The given local datetime representation is invalid." "The given local datetime representation is invalid."
.to_string(), .to_string(),
format!("timestamp is {:?}", a),
span, span,
Span::unknown(),
), ),
} }
} }

View File

@ -61,13 +61,10 @@ impl Command for SubCommand {
let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?; let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
if input.is_nothing() { // This doesn't match explicit nulls
return Err(ShellError::UnsupportedInput( if matches!(input, PipelineData::Empty) {
"Input was nothing. You must pipe an input to this command.".into(), return Err(ShellError::PipelineEmpty(head));
head,
));
} }
input.map( input.map(
move |value| match &format { move |value| match &format {
Some(format) => format_helper(value, format.item.as_str(), format.span, head), Some(format) => format_helper(value, format.item.as_str(), format.span, head),
@ -135,7 +132,7 @@ where
span, span,
}, },
Err(_) => Value::Error { Err(_) => Value::Error {
error: ShellError::UnsupportedInput("invalid format".to_string(), span), error: ShellError::TypeMismatch("invalid format".to_string(), span),
}, },
} }
} }

View File

@ -47,6 +47,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) input.map(move |value| helper(value, head), engine_state.ctrlc.clone())
} }

View File

@ -4,7 +4,8 @@ use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Type; use nu_protocol::Type;
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError::DatetimeParseError, Signature, Span, Value, Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty,
Signature, Span, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -41,6 +42,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(PipelineEmpty(head));
}
input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) input.map(move |value| helper(value, head), engine_state.ctrlc.clone())
} }

View File

@ -4,7 +4,8 @@ use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Type; use nu_protocol::Type;
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, ShellError::DatetimeParseError, Signature, Span, Value, Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty,
Signature, Span, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -41,6 +42,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(PipelineEmpty(head));
}
input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) input.map(move |value| helper(value, head), engine_state.ctrlc.clone())
} }

View File

@ -56,7 +56,10 @@ impl Command for SubCommand {
let head = call.head; let head = call.head;
let timezone: Spanned<String> = call.req(engine_state, stack, 0)?; let timezone: Spanned<String> = call.req(engine_state, stack, 0)?;
//Ok(PipelineData::new()) // This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| helper(value, head, &timezone), move |value| helper(value, head, &timezone),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -64,26 +67,15 @@ impl Command for SubCommand {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
let example_result_1 = || { let example_result_1 = || match FixedOffset::east_opt(5 * 3600)
let dt = match FixedOffset::east_opt(5 * 3600) { .expect("to timezone: help example is invalid")
Some(dt) => match dt.with_ymd_and_hms(2020, 10, 10, 13, 00, 00) { .with_ymd_and_hms(2020, 10, 10, 13, 00, 00)
LocalResult::Single(dt) => Some(dt), {
_ => None, LocalResult::Single(dt) => Some(Value::Date {
}, val: dt,
_ => None, span: Span::test_data(),
}; }),
match dt { _ => panic!("to timezone: help example is invalid"),
Some(dt) => Some(Value::Date {
val: dt,
span: Span::test_data(),
}),
None => Some(Value::Error {
error: ShellError::UnsupportedInput(
"The given datetime representation is unsupported.".to_string(),
Span::test_data(),
),
}),
}
}; };
vec![ vec![
@ -145,7 +137,7 @@ fn _to_timezone(dt: DateTime<FixedOffset>, timezone: &Spanned<String>, span: Spa
match datetime_in_timezone(&dt, timezone.item.as_str()) { match datetime_in_timezone(&dt, timezone.item.as_str()) {
Ok(dt) => Value::Date { val: dt, span }, Ok(dt) => Value::Date { val: dt, span },
Err(_) => Value::Error { Err(_) => Value::Error {
error: ShellError::UnsupportedInput(String::from("invalid time zone"), timezone.span), error: ShellError::TypeMismatch(String::from("invalid time zone"), timezone.span),
}, },
} }
} }

View File

@ -80,7 +80,9 @@ impl Command for LoadEnv {
} }
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
"'load-env' expects a single record".into(), "'load-env' expects a single record".into(),
"value originated from here".into(),
span, span,
input.span().unwrap_or(span),
)), )),
}, },
} }

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
Category, Example, PipelineData, RawStream, ShellError, Signature, Spanned, SyntaxShape, Type, Category, Example, PipelineData, RawStream, ShellError, Signature, Span, Spanned, SyntaxShape,
Value, Type, Value,
}; };
use std::fs::File; use std::fs::File;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
@ -188,9 +188,14 @@ impl Command for Save {
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }
v => Err(ShellError::UnsupportedInput( // Propagate errors by explicitly matching them before the final case.
format!("{:?} not supported", v.get_type()), Value::Error { error } => Err(error),
other => Err(ShellError::OnlySupportsThisInputType(
"string, binary or list".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
)), )),
} }
} else { } else {
@ -203,16 +208,16 @@ impl Command for Save {
} => { } => {
// delegate a thread to redirect stderr to result. // delegate a thread to redirect stderr to result.
let handler = stderr.map(|stderr_stream| match stderr_file { let handler = stderr.map(|stderr_stream| match stderr_file {
Some(stderr_file) => { Some(stderr_file) => std::thread::spawn(move || {
std::thread::spawn(move || stream_to_file(stderr_stream, stderr_file)) stream_to_file(stderr_stream, stderr_file, span)
} }),
None => std::thread::spawn(move || { None => std::thread::spawn(move || {
let _ = stderr_stream.into_bytes(); let _ = stderr_stream.into_bytes();
Ok(PipelineData::empty()) Ok(PipelineData::empty())
}), }),
}); });
let res = stream_to_file(stream, file); let res = stream_to_file(stream, file, span);
if let Some(h) = handler { if let Some(h) = handler {
match h.join() { match h.join() {
Err(err) => { Err(err) => {
@ -264,9 +269,14 @@ impl Command for Save {
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }
v => Err(ShellError::UnsupportedInput( // Propagate errors by explicitly matching them before the final case.
format!("{:?} not supported", v.get_type()), Value::Error { error } => Err(error),
other => Err(ShellError::OnlySupportsThisInputType(
"string, binary or list".into(),
other.get_type().to_string(),
span, span,
// This line requires the Value::Error match above.
other.expect_span(),
)), )),
}, },
} }
@ -304,7 +314,11 @@ impl Command for Save {
} }
} }
fn stream_to_file(mut stream: RawStream, file: File) -> Result<PipelineData, ShellError> { fn stream_to_file(
mut stream: RawStream,
file: File,
span: Span,
) -> Result<PipelineData, ShellError> {
let mut writer = BufWriter::new(file); let mut writer = BufWriter::new(file);
stream stream
@ -313,10 +327,15 @@ fn stream_to_file(mut stream: RawStream, file: File) -> Result<PipelineData, She
Ok(v) => match v { Ok(v) => match v {
Value::String { val, .. } => val.into_bytes(), Value::String { val, .. } => val.into_bytes(),
Value::Binary { val, .. } => val, Value::Binary { val, .. } => val,
_ => { // Propagate errors by explicitly matching them before the final case.
return Err(ShellError::UnsupportedInput( Value::Error { error } => return Err(error),
format!("{:?} not supported", v.get_type()), other => {
v.span()?, return Err(ShellError::OnlySupportsThisInputType(
"string or binary".into(),
other.get_type().to_string(),
span,
// This line requires the Value::Error match above.
other.expect_span(),
)); ));
} }
}, },

View File

@ -94,7 +94,7 @@ impl Command for Touch {
Some(reference) => { Some(reference) => {
let reference_path = Path::new(&reference.item); let reference_path = Path::new(&reference.item);
if !reference_path.exists() { if !reference_path.exists() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"path provided is invalid".to_string(), "path provided is invalid".to_string(),
reference.span, reference.span,
)); ));

View File

@ -98,8 +98,8 @@ impl Command for Watch {
Some(val) => match u64::try_from(val.item) { Some(val) => match u64::try_from(val.item) {
Ok(val) => Duration::from_millis(val), Ok(val) => Duration::from_millis(val),
Err(_) => { Err(_) => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"Input out of range".to_string(), "Debounce duration is invalid".to_string(),
val.span, val.span,
)) ))
} }
@ -117,7 +117,12 @@ impl Command for Watch {
match nu_glob::Pattern::new(&absolute_path.to_string_lossy()) { match nu_glob::Pattern::new(&absolute_path.to_string_lossy()) {
Ok(pattern) => Some(pattern), Ok(pattern) => Some(pattern),
Err(_) => return Err(ShellError::UnsupportedInput("".to_string(), glob.span)), Err(_) => {
return Err(ShellError::TypeMismatch(
"Glob pattern is invalid".to_string(),
glob.span,
))
}
} }
} }
None => None, None => None,

View File

@ -124,15 +124,15 @@ impl Command for DropNth {
// check for negative range inputs, e.g., (2..-5) // check for negative range inputs, e.g., (2..-5)
if from.is_negative() || to.is_negative() { if from.is_negative() || to.is_negative() {
let span: Spanned<Range> = call.req(engine_state, stack, 0)?; let span: Spanned<Range> = call.req(engine_state, stack, 0)?;
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"Drop nth accepts only positive integers".to_string(), "drop nth accepts only positive integers".to_string(),
span.span, span.span,
)); ));
} }
// check if the upper bound is smaller than the lower bound, e.g., do not accept 4..2 // check if the upper bound is smaller than the lower bound, e.g., do not accept 4..2
if to < from { if to < from {
let span: Spanned<Range> = call.req(engine_state, stack, 0)?; let span: Spanned<Range> = call.req(engine_state, stack, 0)?;
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"The upper bound needs to be equal or larger to the lower bound" "The upper bound needs to be equal or larger to the lower bound"
.to_string(), .to_string(),
span.span, span.span,

View File

@ -185,7 +185,7 @@ fn find_with_regex(
let regex = flags.to_string() + regex.as_str(); let regex = flags.to_string() + regex.as_str();
let re = Regex::new(regex.as_str()) let re = Regex::new(regex.as_str())
.map_err(|e| ShellError::UnsupportedInput(format!("incorrect regex: {}", e), span))?; .map_err(|e| ShellError::TypeMismatch(format!("invalid regex: {}", e), span))?;
input.filter( input.filter(
move |value| match value { move |value| match value {
@ -478,22 +478,20 @@ fn find_with_rest_and_highlight(
} }
} }
} }
_ => { // Propagate errors by explicitly matching them before the final case.
Value::Error { error } => return Err(error),
other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
format!( "unsupported type from raw stream".into(),
"Unsupport value type '{}' from raw stream", format!("input: {:?}", other.get_type()),
value.get_type()
),
span, span,
)) // This line requires the Value::Error match above.
other.expect_span(),
));
} }
}, },
_ => { // Propagate any errors that were in the stream
return Err(ShellError::UnsupportedInput( Err(e) => return Err(e),
"Unsupport type from raw stream".to_string(),
span,
))
}
}; };
} }
Ok(output.into_pipeline_data(ctrlc)) Ok(output.into_pipeline_data(ctrlc))

View File

@ -105,19 +105,6 @@ fn first_helper(
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata(); let metadata = input.metadata();
let input_span = input.span();
let input_not_supported_error = || -> ShellError {
// can't always get a span for input, so try our best and fall back on the span for the `first` call if needed
if let Some(span) = input_span {
ShellError::UnsupportedInput("first does not support this input type".into(), span)
} else {
ShellError::UnsupportedInput(
"first was given an unsupported input type".into(),
call.span(),
)
}
};
match input { match input {
PipelineData::Value(val, _) => match val { PipelineData::Value(val, _) => match val {
Value::List { vals, .. } => { Value::List { vals, .. } => {
@ -153,7 +140,15 @@ fn first_helper(
.set_metadata(metadata)) .set_metadata(metadata))
} }
} }
_ => Err(input_not_supported_error()), // Propagate errors by explicitly matching them before the final case.
Value::Error { error } => Err(error),
other => Err(ShellError::OnlySupportsThisInputType(
"list, binary or range".into(),
other.get_type().to_string(),
head,
// This line requires the Value::Error match above.
other.expect_span(),
)),
}, },
PipelineData::ListStream(mut ls, metadata) => { PipelineData::ListStream(mut ls, metadata) => {
if return_single_element { if return_single_element {
@ -169,7 +164,18 @@ fn first_helper(
.set_metadata(metadata)) .set_metadata(metadata))
} }
} }
_ => Err(input_not_supported_error()), PipelineData::ExternalStream { span, .. } => Err(ShellError::OnlySupportsThisInputType(
"list, binary or range".into(),
"raw data".into(),
head,
span,
)),
PipelineData::Empty => Err(ShellError::OnlySupportsThisInputType(
"list, binary or range".into(),
"null".into(),
call.head,
call.head, // TODO: make PipelineData::Empty spanned, so that the span can be used here.
)),
} }
} }
#[cfg(test)] #[cfg(test)]

View File

@ -168,13 +168,18 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span, all: bool) ->
vals, vals,
span: _, span: _,
} => (cols, vals), } => (cols, vals),
x => { // Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return vec![item.clone()],
other => {
return vec![Value::Error { return vec![Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!("This should be a record, but instead got {}", x.get_type()), "record".into(),
tag, other.get_type().to_string(),
_name_tag,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}] }];
} }
}; };
@ -221,13 +226,15 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span, all: bool) ->
out.insert(column.to_string(), value.clone()); out.insert(column.to_string(), value.clone());
} }
} }
Value::List { vals, span: _ } Value::List { vals, span }
if all && vals.iter().all(|f| f.as_record().is_ok()) => if all && vals.iter().all(|f| f.as_record().is_ok()) =>
{ {
if need_flatten && inner_table.is_some() { if need_flatten && inner_table.is_some() {
return vec![Value::Error{ error: ShellError::UnsupportedInput( return vec![Value::Error{ error: ShellError::UnsupportedInput(
"can only flatten one inner list at the same time. tried flattening more than one column with inner lists... but is flattened already".to_string(), "can only flatten one inner list at a time. tried flattening more than one column with inner lists... but is flattened already".to_string(),
s "value originates from here".into(),
s,
*span
)} )}
]; ];
} }
@ -259,14 +266,13 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span, all: bool) ->
out.insert(column.to_string(), value.clone()); out.insert(column.to_string(), value.clone());
} }
} }
Value::List { Value::List { vals: values, span } => {
vals: values,
span: _,
} => {
if need_flatten && inner_table.is_some() { if need_flatten && inner_table.is_some() {
return vec![Value::Error{ error: ShellError::UnsupportedInput( return vec![Value::Error{ error: ShellError::UnsupportedInput(
"can only flatten one inner list at the same time. tried flattening more than one column with inner lists... but is flattened already".to_string(), "can only flatten one inner list at a time. tried flattening more than one column with inner lists... but is flattened already".to_string(),
s "value originates from here".into(),
s,
*span
)} )}
]; ];
} }

View File

@ -242,17 +242,19 @@ pub fn group(
match grouper { match grouper {
Grouper::ByColumn(Some(column_name)) => { Grouper::ByColumn(Some(column_name)) => {
let block = let block = Box::new(move |_, row: &Value| {
Box::new( if let Value::Error { error } = row {
move |_, row: &Value| match row.get_data_by_key(&column_name.item) { return Err(error.clone());
Some(group_key) => Ok(group_key.as_string()?), };
None => Err(ShellError::CantFindColumn( match row.get_data_by_key(&column_name.item) {
column_name.item.to_string(), Some(group_key) => Ok(group_key.as_string()?),
column_name.span, None => Err(ShellError::CantFindColumn(
row.span().unwrap_or(column_name.span), column_name.item.to_string(),
)), column_name.span,
}, row.expect_span(),
); )),
}
});
data_group(values, &Some(block), name) data_group(values, &Some(block), name)
} }

View File

@ -139,7 +139,7 @@ fn extract_headers(value: &Value, config: &Config) -> Result<Vec<String>, ShellE
for v in vals { for v in vals {
if !is_valid_header(v) { if !is_valid_header(v) {
return Err(ShellError::TypeMismatch( return Err(ShellError::TypeMismatch(
"compatible type: Null, String, Bool, Float, Int".to_string(), "needs compatible type: Null, String, Bool, Float, Int".to_string(),
v.span()?, v.span()?,
)); ));
} }

View File

@ -161,9 +161,12 @@ fn insert(
match output { match output {
Ok(pd) => { Ok(pd) => {
if let Err(e) = let span = pd.span().unwrap_or(span);
input.insert_data_at_cell_path(&cell_path.members, pd.into_value(span)) if let Err(e) = input.insert_data_at_cell_path(
{ &cell_path.members,
pd.into_value(span),
span,
) {
return Value::Error { error: e }; return Value::Error { error: e };
} }
@ -197,7 +200,9 @@ fn insert(
move |mut input| { move |mut input| {
let replacement = replacement.clone(); let replacement = replacement.clone();
if let Err(e) = input.insert_data_at_cell_path(&cell_path.members, replacement) { if let Err(e) =
input.insert_data_at_cell_path(&cell_path.members, replacement, span)
{
return Value::Error { error: e }; return Value::Error { error: e };
} }

View File

@ -109,10 +109,18 @@ impl Command for Lines {
Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) Ok(iter.into_pipeline_data(engine_state.ctrlc.clone()))
} }
PipelineData::Value(val, ..) => Err(ShellError::UnsupportedInput( PipelineData::Value(val, ..) => {
format!("Not supported input: {}", val.as_string()?), match val {
head, // Propagate existing errors
)), Value::Error { error } => Err(error),
_ => Err(ShellError::OnlySupportsThisInputType(
"string or raw data".into(),
val.get_type().to_string(),
head,
val.expect_span(),
)),
}
}
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()), PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()),
PipelineData::ExternalStream { PipelineData::ExternalStream {
stdout: Some(stream), stdout: Some(stream),
@ -189,6 +197,7 @@ impl Iterator for RawStreamLinesAdapter {
match result { match result {
Ok(v) => { Ok(v) => {
match v { match v {
// TODO: Value::Binary support required?
Value::String { val, span } => { Value::String { val, span } => {
self.span = span; self.span = span;
@ -223,12 +232,16 @@ impl Iterator for RawStreamLinesAdapter {
// save completed lines // save completed lines
self.queue.append(&mut lines); self.queue.append(&mut lines);
} }
// TODO: Value::Binary support required? // Propagate errors by explicitly matching them before the final case.
_ => { Value::Error { error } => return Some(Err(error)),
return Some(Err(ShellError::UnsupportedInput( other => {
"Unsupport type from raw stream".to_string(), return Some(Err(ShellError::OnlySupportsThisInputType(
"string".into(),
other.get_type().to_string(),
self.span, self.span,
))) // This line requires the Value::Error match above.
other.expect_span(),
)));
} }
} }
} }

View File

@ -114,7 +114,7 @@ fn rename(
if let Some(ref cols) = specified_column { if let Some(ref cols) = specified_column {
if cols.len() != 2 { if cols.len() != 2 {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"The list must contain only two values: the column's name and its replacement value" "The list must contain only two values: the column's name and its replacement value"
.to_string(), .to_string(),
list_span, list_span,
@ -140,8 +140,15 @@ fn rename(
if !cols.contains(&c[0]) { if !cols.contains(&c[0]) {
return Value::Error { return Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"The specified column does not exist".to_string(), format!(
specified_col_span.unwrap_or(span), "The column '{}' does not exist in the input",
&c[0]
),
"value originated from here".into(),
// Arrow 1 points at the specified column name,
specified_col_span.unwrap_or(head_span),
// Arrow 2 points at the input value.
span,
), ),
}; };
} }
@ -165,11 +172,15 @@ fn rename(
Value::Record { cols, vals, span } Value::Record { cols, vals, span }
} }
x => Value::Error { // Propagate errors by explicitly matching them before the final case.
error: ShellError::UnsupportedInput( Value::Error { .. } => item.clone(),
"can't rename: input is not table, so no column names available for rename" other => Value::Error {
.to_string(), error: ShellError::OnlySupportsThisInputType(
x.span().unwrap_or(head_span), "record".into(),
other.get_type().to_string(),
head_span,
// This line requires the Value::Error match above.
other.expect_span(),
), ),
}, },
}, },

View File

@ -208,6 +208,7 @@ pub fn rotate(
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let metadata = input.metadata(); let metadata = input.metadata();
let col_given_names: Vec<String> = call.rest(engine_state, stack, 0)?; let col_given_names: Vec<String> = call.rest(engine_state, stack, 0)?;
let span = input.span();
let mut values = input.into_iter().collect::<Vec<_>>(); let mut values = input.into_iter().collect::<Vec<_>>();
let mut old_column_names = vec![]; let mut old_column_names = vec![];
let mut new_values = vec![]; let mut new_values = vec![];
@ -222,17 +223,13 @@ pub fn rotate(
if !values.is_empty() { if !values.is_empty() {
for val in values.into_iter() { for val in values.into_iter() {
match val { match val {
Value::Record { Value::Record { cols, vals, .. } => {
cols,
vals,
span: _,
} => {
old_column_names = cols; old_column_names = cols;
for v in vals { for v in vals {
new_values.push(v) new_values.push(v)
} }
} }
Value::List { vals, span: _ } => { Value::List { vals, .. } => {
not_a_record = true; not_a_record = true;
for v in vals { for v in vals {
new_values.push(v); new_values.push(v);
@ -250,8 +247,11 @@ pub fn rotate(
} }
} else { } else {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Rotate command requires a Nu value as input".to_string(), "list input is empty".to_string(),
"value originates from here".into(),
call.head, call.head,
// TODO: Maybe make all Pipelines have spans, so that this doesn't need to be unwrapped.
span.unwrap_or(call.head),
)); ));
} }

View File

@ -79,7 +79,7 @@ impl Command for Skip {
let n: usize = match n { let n: usize = match n {
Some(Value::Int { val, span }) => val.try_into().map_err(|err| { Some(Value::Int { val, span }) => val.try_into().map_err(|err| {
ShellError::UnsupportedInput( ShellError::TypeMismatch(
format!("Could not convert {} to unsigned integer: {}", val, err), format!("Could not convert {} to unsigned integer: {}", val, err),
span, span,
) )

View File

@ -53,19 +53,6 @@ impl Command for Take {
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata(); let metadata = input.metadata();
let input_span = input.span();
let input_not_supported_error = || -> ShellError {
// can't always get a span for input, so try our best and fall back on the span for the `take` call if needed
if let Some(span) = input_span {
ShellError::UnsupportedInput("take does not support this input type".into(), span)
} else {
ShellError::UnsupportedInput(
"take was given an unsupported input type".into(),
call.span(),
)
}
};
match input { match input {
PipelineData::Value(val, _) => match val { PipelineData::Value(val, _) => match val {
Value::List { vals, .. } => Ok(vals Value::List { vals, .. } => Ok(vals
@ -85,13 +72,34 @@ impl Command for Take {
.take(rows_desired) .take(rows_desired)
.into_pipeline_data(ctrlc) .into_pipeline_data(ctrlc)
.set_metadata(metadata)), .set_metadata(metadata)),
_ => Err(input_not_supported_error()), // Propagate errors by explicitly matching them before the final case.
Value::Error { error } => Err(error),
other => Err(ShellError::OnlySupportsThisInputType(
"list, binary or range".into(),
other.get_type().to_string(),
call.head,
// This line requires the Value::Error match above.
other.expect_span(),
)),
}, },
PipelineData::ListStream(ls, metadata) => Ok(ls PipelineData::ListStream(ls, metadata) => Ok(ls
.take(rows_desired) .take(rows_desired)
.into_pipeline_data(ctrlc) .into_pipeline_data(ctrlc)
.set_metadata(metadata)), .set_metadata(metadata)),
_ => Err(input_not_supported_error()), PipelineData::ExternalStream { span, .. } => {
Err(ShellError::OnlySupportsThisInputType(
"list, binary or range".into(),
"raw data".into(),
call.head,
span,
))
}
PipelineData::Empty => Err(ShellError::OnlySupportsThisInputType(
"list, binary or range".into(),
"null".into(),
call.head,
call.head, // TODO: make PipelineData::Empty spanned, so that the span can be used here.
)),
} }
} }

View File

@ -64,7 +64,7 @@ pub fn from_delimited_data(
input: PipelineData, input: PipelineData,
name: Span, name: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let (concat_string, metadata) = input.collect_string_strict(name)?; let (concat_string, _span, metadata) = input.collect_string_strict(name)?;
Ok( Ok(
from_delimited_string_to_value(concat_string, noheaders, no_infer, sep, trim, name) from_delimited_string_to_value(concat_string, noheaders, no_infer, sep, trim, name)
@ -80,7 +80,7 @@ pub fn trim_from_str(trim: Option<Value>) -> Result<Trim, ShellError> {
"headers" => Ok(Trim::Headers), "headers" => Ok(Trim::Headers),
"fields" => Ok(Trim::Fields), "fields" => Ok(Trim::Fields),
"none" => Ok(Trim::None), "none" => Ok(Trim::None),
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::TypeMismatch(
"the only possible values for trim are 'all', 'headers', 'fields' and 'none'" "the only possible values for trim are 'all', 'headers', 'fields' and 'none'"
.into(), .into(),
span, span,

View File

@ -178,7 +178,7 @@ fn from_eml(
preview_body: Option<Spanned<i64>>, preview_body: Option<Spanned<i64>>,
head: Span, head: Span,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let (value, metadata) = input.collect_string_strict(head)?; let (value, _span, metadata, ..) = input.collect_string_strict(head)?;
let body_preview = preview_body let body_preview = preview_body
.map(|b| b.item as usize) .map(|b| b.item as usize)

View File

@ -94,7 +94,7 @@ END:VCALENDAR' | from ics",
} }
fn from_ics(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> { fn from_ics(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
let (input_string, metadata) = input.collect_string_strict(head)?; let (input_string, span, metadata) = input.collect_string_strict(head)?;
let input_string = input_string let input_string = input_string
.lines() .lines()
@ -114,7 +114,9 @@ fn from_ics(input: PipelineData, head: Span) -> Result<PipelineData, ShellError>
Err(e) => output.push(Value::Error { Err(e) => output.push(Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
format!("input cannot be parsed as .ics ({})", e), format!("input cannot be parsed as .ics ({})", e),
"value originates from here".into(),
head, head,
span,
), ),
}), }),
} }

View File

@ -56,7 +56,11 @@ b=2' | from ini",
} }
} }
pub fn from_ini_string_to_value(s: String, span: Span) -> Result<Value, ShellError> { pub fn from_ini_string_to_value(
s: String,
span: Span,
val_span: Span,
) -> Result<Value, ShellError> {
let v: Result<IndexMap<String, IndexMap<String, String>>, serde_ini::de::Error> = let v: Result<IndexMap<String, IndexMap<String, String>>, serde_ini::de::Error> =
serde_ini::from_str(&s); serde_ini::from_str(&s);
match v { match v {
@ -77,15 +81,17 @@ pub fn from_ini_string_to_value(s: String, span: Span) -> Result<Value, ShellErr
} }
Err(err) => Err(ShellError::UnsupportedInput( Err(err) => Err(ShellError::UnsupportedInput(
format!("Could not load ini: {}", err), format!("Could not load ini: {}", err),
"value originates from here".into(),
span, span,
val_span,
)), )),
} }
} }
fn from_ini(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> { fn from_ini(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
let (concat_string, metadata) = input.collect_string_strict(head)?; let (concat_string, span, metadata) = input.collect_string_strict(head)?;
match from_ini_string_to_value(concat_string, head) { match from_ini_string_to_value(concat_string, head, span) {
Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)), Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)),
Err(other) => Err(other), Err(other) => Err(other),
} }

View File

@ -64,7 +64,7 @@ impl Command for FromJson {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let span = call.head; let span = call.head;
let (string_input, metadata) = input.collect_string_strict(span)?; let (string_input, span, metadata) = input.collect_string_strict(span)?;
if string_input.is_empty() { if string_input.is_empty() {
return Ok(PipelineData::new_with_metadata(metadata, span)); return Ok(PipelineData::new_with_metadata(metadata, span));

View File

@ -62,7 +62,7 @@ impl Command for FromNuon {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let head = call.head; let head = call.head;
let (string_input, metadata) = input.collect_string_strict(head)?; let (string_input, _span, metadata) = input.collect_string_strict(head)?;
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();

View File

@ -93,10 +93,13 @@ fn collect_binary(input: PipelineData, span: Span) -> Result<Vec<u8>, ShellError
Some(Value::Binary { val: b, .. }) => { Some(Value::Binary { val: b, .. }) => {
bytes.extend_from_slice(&b); bytes.extend_from_slice(&b);
} }
Some(Value::Error { error }) => return Err(error),
Some(x) => { Some(x) => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Expected binary from pipeline".to_string(), "Expected binary from pipeline".to_string(),
x.span().unwrap_or(span), "value originates from here".into(),
span,
x.expect_span(),
)) ))
} }
None => break, None => break,
@ -111,10 +114,17 @@ fn from_ods(
head: Span, head: Span,
sel_sheets: Vec<String>, sel_sheets: Vec<String>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = input.span();
let bytes = collect_binary(input, head)?; let bytes = collect_binary(input, head)?;
let buf: Cursor<Vec<u8>> = Cursor::new(bytes); let buf: Cursor<Vec<u8>> = Cursor::new(bytes);
let mut ods = Ods::<_>::new(buf) let mut ods = Ods::<_>::new(buf).map_err(|_| {
.map_err(|_| ShellError::UnsupportedInput("Could not load ods file".to_string(), head))?; ShellError::UnsupportedInput(
"Could not load ODS file".to_string(),
"value originates from here".into(),
head,
span.unwrap_or(head),
)
})?;
let mut dict = IndexMap::new(); let mut dict = IndexMap::new();
@ -170,7 +180,9 @@ fn from_ods(
} else { } else {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Could not load sheet".to_string(), "Could not load sheet".to_string(),
"value originates from here".into(),
head, head,
span.unwrap_or(head),
)); ));
} }
} }

View File

@ -275,7 +275,7 @@ fn from_ssv(
let minimum_spaces: Option<Spanned<usize>> = let minimum_spaces: Option<Spanned<usize>> =
call.get_flag(engine_state, stack, "minimum-spaces")?; call.get_flag(engine_state, stack, "minimum-spaces")?;
let (concat_string, metadata) = input.collect_string_strict(name)?; let (concat_string, _span, metadata) = input.collect_string_strict(name)?;
let split_at = match minimum_spaces { let split_at = match minimum_spaces {
Some(number) => number.item, Some(number) => number.item,
None => DEFAULT_MINIMUM_SPACES, None => DEFAULT_MINIMUM_SPACES,

View File

@ -63,7 +63,7 @@ b = [1, 2]' | from toml",
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, ShellError> { ) -> Result<nu_protocol::PipelineData, ShellError> {
let span = call.head; let span = call.head;
let (mut string_input, metadata) = input.collect_string_strict(span)?; let (mut string_input, span, metadata) = input.collect_string_strict(span)?;
string_input.push('\n'); string_input.push('\n');
Ok(convert_string_to_value(string_input, span)?.into_pipeline_data_with_metadata(metadata)) Ok(convert_string_to_value(string_input, span)?.into_pipeline_data_with_metadata(metadata))
} }

View File

@ -55,7 +55,7 @@ impl Command for FromUrl {
} }
fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> { fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
let (concat_string, metadata) = input.collect_string_strict(head)?; let (concat_string, span, metadata) = input.collect_string_strict(head)?;
let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string); let result = serde_urlencoded::from_str::<Vec<(String, String)>>(&concat_string);
@ -78,8 +78,10 @@ fn from_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError>
)) ))
} }
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
"String not compatible with url-encoding".to_string(), "String not compatible with URL encoding".to_string(),
"value originates from here".into(),
head, head,
span,
)), )),
} }
} }

View File

@ -107,7 +107,7 @@ END:VCARD' | from vcf",
} }
fn from_vcf(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> { fn from_vcf(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
let (input_string, metadata) = input.collect_string_strict(head)?; let (input_string, span, metadata) = input.collect_string_strict(head)?;
let input_string = input_string let input_string = input_string
.lines() .lines()
@ -124,7 +124,9 @@ fn from_vcf(input: PipelineData, head: Span) -> Result<PipelineData, ShellError>
Err(e) => Value::Error { Err(e) => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
format!("input cannot be parsed as .vcf ({})", e), format!("input cannot be parsed as .vcf ({})", e),
"value originates from here".into(),
head, head,
span,
), ),
}, },
}); });

View File

@ -96,7 +96,9 @@ fn collect_binary(input: PipelineData, span: Span) -> Result<Vec<u8>, ShellError
Some(x) => { Some(x) => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Expected binary from pipeline".to_string(), "Expected binary from pipeline".to_string(),
x.span().unwrap_or(span), "value originates from here".into(),
span,
x.expect_span(),
)) ))
} }
None => break, None => break,
@ -111,10 +113,17 @@ fn from_xlsx(
head: Span, head: Span,
sel_sheets: Vec<String>, sel_sheets: Vec<String>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = input.span();
let bytes = collect_binary(input, head)?; let bytes = collect_binary(input, head)?;
let buf: Cursor<Vec<u8>> = Cursor::new(bytes); let buf: Cursor<Vec<u8>> = Cursor::new(bytes);
let mut xlsx = Xlsx::<_>::new(buf) let mut xlsx = Xlsx::<_>::new(buf).map_err(|_| {
.map_err(|_| ShellError::UnsupportedInput("Could not load xlsx file".to_string(), head))?; ShellError::UnsupportedInput(
"Could not load XLSX file".to_string(),
"value originates from here".into(),
head,
span.unwrap_or(head),
)
})?;
let mut dict = IndexMap::new(); let mut dict = IndexMap::new();
@ -170,7 +179,9 @@ fn from_xlsx(
} else { } else {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Could not load sheet".to_string(), "Could not load sheet".to_string(),
"value originates from here".into(),
head, head,
span.unwrap_or(head),
)); ));
} }
} }

View File

@ -178,13 +178,15 @@ pub fn from_xml_string_to_value(s: String, span: Span) -> Result<Value, roxmltre
} }
fn from_xml(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> { fn from_xml(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
let (concat_string, metadata) = input.collect_string_strict(head)?; let (concat_string, span, metadata) = input.collect_string_strict(head)?;
match from_xml_string_to_value(concat_string, head) { match from_xml_string_to_value(concat_string, head) {
Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)), Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)),
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
"Could not parse string as xml".to_string(), "Could not parse string as XML".to_string(),
"value originates from here".into(),
head, head,
span,
)), )),
} }
} }

View File

@ -76,9 +76,17 @@ impl Command for FromYml {
} }
} }
fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value, span: Span) -> Result<Value, ShellError> { fn convert_yaml_value_to_nu_value(
let err_not_compatible_number = v: &serde_yaml::Value,
ShellError::UnsupportedInput("Expected a compatible number".to_string(), span); span: Span,
val_span: Span,
) -> Result<Value, ShellError> {
let err_not_compatible_number = ShellError::UnsupportedInput(
"Expected a nu-compatible number in YAML input".to_string(),
"value originates from here".into(),
span,
val_span,
);
Ok(match v { Ok(match v {
serde_yaml::Value::Bool(b) => Value::Bool { val: *b, span }, serde_yaml::Value::Bool(b) => Value::Bool { val: *b, span },
serde_yaml::Value::Number(n) if n.is_i64() => Value::Int { serde_yaml::Value::Number(n) if n.is_i64() => Value::Int {
@ -96,7 +104,7 @@ fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value, span: Span) -> Result<V
serde_yaml::Value::Sequence(a) => { serde_yaml::Value::Sequence(a) => {
let result: Result<Vec<Value>, ShellError> = a let result: Result<Vec<Value>, ShellError> = a
.iter() .iter()
.map(|x| convert_yaml_value_to_nu_value(x, span)) .map(|x| convert_yaml_value_to_nu_value(x, span, val_span))
.collect(); .collect();
Value::List { Value::List {
vals: result?, vals: result?,
@ -113,13 +121,16 @@ fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value, span: Span) -> Result<V
// A ShellError that we re-use multiple times in the Mapping scenario // A ShellError that we re-use multiple times in the Mapping scenario
let err_unexpected_map = ShellError::UnsupportedInput( let err_unexpected_map = ShellError::UnsupportedInput(
format!("Unexpected YAML:\nKey: {:?}\nValue: {:?}", k, v), format!("Unexpected YAML:\nKey: {:?}\nValue: {:?}", k, v),
"value originates from here".into(),
span, span,
val_span,
); );
match (k, v) { match (k, v) {
(serde_yaml::Value::String(k), _) => { (serde_yaml::Value::String(k), _) => {
collected collected.item.insert(
.item k.clone(),
.insert(k.clone(), convert_yaml_value_to_nu_value(v, span)?); convert_yaml_value_to_nu_value(v, span, val_span)?,
);
} }
// Hard-code fix for cases where "v" is a string without quotations with double curly braces // Hard-code fix for cases where "v" is a string without quotations with double curly braces
// e.g. k = value // e.g. k = value
@ -152,18 +163,27 @@ fn convert_yaml_value_to_nu_value(v: &serde_yaml::Value, span: Span) -> Result<V
Value::from(collected) Value::from(collected)
} }
serde_yaml::Value::Null => Value::nothing(span), serde_yaml::Value::Null => Value::nothing(span),
x => unimplemented!("Unsupported yaml case: {:?}", x), x => unimplemented!("Unsupported YAML case: {:?}", x),
}) })
} }
pub fn from_yaml_string_to_value(s: String, span: Span) -> Result<Value, ShellError> { pub fn from_yaml_string_to_value(
s: String,
span: Span,
val_span: Span,
) -> Result<Value, ShellError> {
let mut documents = vec![]; let mut documents = vec![];
for document in serde_yaml::Deserializer::from_str(&s) { for document in serde_yaml::Deserializer::from_str(&s) {
let v: serde_yaml::Value = serde_yaml::Value::deserialize(document).map_err(|x| { let v: serde_yaml::Value = serde_yaml::Value::deserialize(document).map_err(|x| {
ShellError::UnsupportedInput(format!("Could not load yaml: {}", x), span) ShellError::UnsupportedInput(
format!("Could not load YAML: {}", x),
"value originates from here".into(),
span,
val_span,
)
})?; })?;
documents.push(convert_yaml_value_to_nu_value(&v, span)?); documents.push(convert_yaml_value_to_nu_value(&v, span, val_span)?);
} }
match documents.len() { match documents.len() {
@ -213,9 +233,9 @@ pub fn get_examples() -> Vec<Example> {
} }
fn from_yaml(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> { fn from_yaml(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
let (concat_string, metadata) = input.collect_string_strict(head)?; let (concat_string, span, metadata) = input.collect_string_strict(head)?;
match from_yaml_string_to_value(concat_string, head) { match from_yaml_string_to_value(concat_string, head, span) {
Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)), Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)),
Err(other) => Err(other), Err(other) => Err(other),
} }
@ -255,7 +275,11 @@ mod test {
]; ];
let config = Config::default(); let config = Config::default();
for tc in tt { for tc in tt {
let actual = from_yaml_string_to_value(tc.input.to_owned(), Span::test_data()); let actual = from_yaml_string_to_value(
tc.input.to_owned(),
Span::test_data(),
Span::test_data(),
);
if actual.is_err() { if actual.is_err() {
assert!( assert!(
tc.expected.is_err(), tc.expected.is_err(),

View File

@ -80,7 +80,7 @@ fn to_csv(
} else { } else {
let vec_s: Vec<char> = s.chars().collect(); let vec_s: Vec<char> = s.chars().collect();
if vec_s.len() != 1 { if vec_s.len() != 1 {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"Expected a single separator char from --separator".to_string(), "Expected a single separator char from --separator".to_string(),
span, span,
)); ));

View File

@ -20,17 +20,17 @@ fn from_value_to_delimited_string(
for (k, v) in cols.iter().zip(vals.iter()) { for (k, v) in cols.iter().zip(vals.iter()) {
fields.push_back(k.clone()); fields.push_back(k.clone());
values.push_back(to_string_tagged_value(v, config, *span)?); values.push_back(to_string_tagged_value(v, config, head, *span)?);
} }
wtr.write_record(fields).expect("can not write."); wtr.write_record(fields).expect("can not write.");
wtr.write_record(values).expect("can not write."); wtr.write_record(values).expect("can not write.");
let v = String::from_utf8(wtr.into_inner().map_err(|_| { let v = String::from_utf8(wtr.into_inner().map_err(|_| {
ShellError::UnsupportedInput("Could not convert record".to_string(), *span) ShellError::CantConvert("record".to_string(), "string".to_string(), *span, None)
})?) })?)
.map_err(|_| { .map_err(|_| {
ShellError::UnsupportedInput("Could not convert record".to_string(), *span) ShellError::CantConvert("record".to_string(), "string".to_string(), *span, None)
})?; })?;
Ok(v) Ok(v)
} }
@ -45,7 +45,7 @@ fn from_value_to_delimited_string(
wtr.write_record( wtr.write_record(
vals.iter() vals.iter()
.map(|ele| { .map(|ele| {
to_string_tagged_value(ele, config, *span) to_string_tagged_value(ele, config, head, *span)
.unwrap_or_else(|_| String::new()) .unwrap_or_else(|_| String::new())
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@ -59,7 +59,7 @@ fn from_value_to_delimited_string(
let mut row = vec![]; let mut row = vec![];
for desc in &merged_descriptors { for desc in &merged_descriptors {
row.push(match l.to_owned().get_data_by_key(desc) { row.push(match l.to_owned().get_data_by_key(desc) {
Some(s) => to_string_tagged_value(&s, config, *span)?, Some(s) => to_string_tagged_value(&s, config, head, *span)?,
None => String::new(), None => String::new(),
}); });
} }
@ -67,18 +67,25 @@ fn from_value_to_delimited_string(
} }
} }
let v = String::from_utf8(wtr.into_inner().map_err(|_| { let v = String::from_utf8(wtr.into_inner().map_err(|_| {
ShellError::UnsupportedInput("Could not convert record".to_string(), *span) ShellError::CantConvert("record".to_string(), "string".to_string(), *span, None)
})?) })?)
.map_err(|_| { .map_err(|_| {
ShellError::UnsupportedInput("Could not convert record".to_string(), *span) ShellError::CantConvert("record".to_string(), "string".to_string(), *span, None)
})?; })?;
Ok(v) Ok(v)
} }
_ => to_string_tagged_value(value, config, head), // Propagate errors by explicitly matching them before the final case.
Value::Error { error } => Err(error.clone()),
other => to_string_tagged_value(value, config, other.expect_span(), head),
} }
} }
fn to_string_tagged_value(v: &Value, config: &Config, span: Span) -> Result<String, ShellError> { fn to_string_tagged_value(
v: &Value,
config: &Config,
span: Span,
head: Span,
) -> Result<String, ShellError> {
match &v { match &v {
Value::String { .. } Value::String { .. }
| Value::Bool { .. } | Value::Bool { .. }
@ -86,7 +93,6 @@ fn to_string_tagged_value(v: &Value, config: &Config, span: Span) -> Result<Stri
| Value::Duration { .. } | Value::Duration { .. }
| Value::Binary { .. } | Value::Binary { .. }
| Value::CustomValue { .. } | Value::CustomValue { .. }
| Value::Error { .. }
| Value::Filesize { .. } | Value::Filesize { .. }
| Value::CellPath { .. } | Value::CellPath { .. }
| Value::List { .. } | Value::List { .. }
@ -94,9 +100,13 @@ fn to_string_tagged_value(v: &Value, config: &Config, span: Span) -> Result<Stri
| Value::Float { .. } => Ok(v.clone().into_abbreviated_string(config)), | Value::Float { .. } => Ok(v.clone().into_abbreviated_string(config)),
Value::Date { val, .. } => Ok(val.to_string()), Value::Date { val, .. } => Ok(val.to_string()),
Value::Nothing { .. } => Ok(String::new()), Value::Nothing { .. } => Ok(String::new()),
// Propagate existing errors
Value::Error { error } => Err(error.clone()),
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
"Unexpected value".to_string(), "Unexpected type".to_string(),
v.span().unwrap_or(span), format!("input type: {:?}", v.get_type()),
head,
span,
)), )),
} }
} }

View File

@ -58,7 +58,9 @@ fn value_to_string(v: &Value, span: Span) -> Result<String, ShellError> {
if write!(s, "{:02X}", byte).is_err() { if write!(s, "{:02X}", byte).is_err() {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"could not convert binary to string".into(), "could not convert binary to string".into(),
"value originates from here".into(),
span, span,
v.expect_span(),
)); ));
} }
} }
@ -66,11 +68,15 @@ fn value_to_string(v: &Value, span: Span) -> Result<String, ShellError> {
} }
Value::Block { .. } => Err(ShellError::UnsupportedInput( Value::Block { .. } => Err(ShellError::UnsupportedInput(
"blocks are currently not nuon-compatible".into(), "blocks are currently not nuon-compatible".into(),
"value originates from here".into(),
span, span,
v.expect_span(),
)), )),
Value::Closure { .. } => Err(ShellError::UnsupportedInput( Value::Closure { .. } => Err(ShellError::UnsupportedInput(
"closure not supported".into(), "closures are currently not nuon-compatible".into(),
"value originates from here".into(),
span, span,
v.expect_span(),
)), )),
Value::Bool { val, .. } => { Value::Bool { val, .. } => {
if *val { if *val {
@ -81,19 +87,21 @@ fn value_to_string(v: &Value, span: Span) -> Result<String, ShellError> {
} }
Value::CellPath { .. } => Err(ShellError::UnsupportedInput( Value::CellPath { .. } => Err(ShellError::UnsupportedInput(
"cellpaths are currently not nuon-compatible".to_string(), "cellpaths are currently not nuon-compatible".to_string(),
"value originates from here".into(),
span, span,
v.expect_span(),
)), )),
Value::CustomValue { .. } => Err(ShellError::UnsupportedInput( Value::CustomValue { .. } => Err(ShellError::UnsupportedInput(
"customs are currently not nuon-compatible".to_string(), "custom values are currently not nuon-compatible".to_string(),
"value originates from here".into(),
span, span,
v.expect_span(),
)), )),
Value::Date { val, .. } => Ok(val.to_rfc3339()), Value::Date { val, .. } => Ok(val.to_rfc3339()),
// FIXME: make duratiobs use the shortest lossless representation. // FIXME: make durations use the shortest lossless representation.
Value::Duration { val, .. } => Ok(format!("{}ns", *val)), Value::Duration { val, .. } => Ok(format!("{}ns", *val)),
Value::Error { .. } => Err(ShellError::UnsupportedInput( // Propagate existing errors
"errors are currently not nuon-compatible".to_string(), Value::Error { error } => Err(error.clone()),
span,
)),
// FIXME: make filesizes use the shortest lossless representation. // FIXME: make filesizes use the shortest lossless representation.
Value::Filesize { val, .. } => Ok(format!("{}b", *val)), Value::Filesize { val, .. } => Ok(format!("{}b", *val)),
Value::Float { val, .. } => { Value::Float { val, .. } => {

View File

@ -130,7 +130,9 @@ fn value_to_toml_value(
Value::List { ref vals, span } => match &vals[..] { Value::List { ref vals, span } => match &vals[..] {
[Value::Record { .. }, _end @ ..] => helper(engine_state, v), [Value::Record { .. }, _end @ ..] => helper(engine_state, v),
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
"Expected a table with TOML-compatible structure from pipeline".to_string(), "Expected a table with TOML-compatible structure".to_string(),
"value originates from here".into(),
head,
*span, *span,
)), )),
}, },
@ -138,14 +140,20 @@ fn value_to_toml_value(
// Attempt to de-serialize the String // Attempt to de-serialize the String
toml::de::from_str(val).map_err(|_| { toml::de::from_str(val).map_err(|_| {
ShellError::UnsupportedInput( ShellError::UnsupportedInput(
format!("{:?} unable to de-serialize string to TOML", val), "unable to de-serialize string to TOML".into(),
format!("input: '{:?}'", val),
head,
*span, *span,
) )
}) })
} }
// Propagate existing errors
Value::Error { error } => Err(error.clone()),
_ => Err(ShellError::UnsupportedInput( _ => Err(ShellError::UnsupportedInput(
format!("{:?} is not a valid top-level TOML", v.get_type()), format!("{:?} is not valid top-level TOML", v.get_type()),
v.span().unwrap_or(head), "value originates from here".into(),
head,
v.expect_span(),
)), )),
} }
} }

View File

@ -47,7 +47,9 @@ fn to_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
.into_iter() .into_iter()
.map(move |value| match value { .map(move |value| match value {
Value::Record { Value::Record {
ref cols, ref vals, .. ref cols,
ref vals,
span,
} => { } => {
let mut row_vec = vec![]; let mut row_vec = vec![];
for (k, v) in cols.iter().zip(vals.iter()) { for (k, v) in cols.iter().zip(vals.iter()) {
@ -57,8 +59,10 @@ fn to_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
} }
_ => { _ => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Expected table with string values".to_string(), "Expected a record with string values".to_string(),
"value originates from here".into(),
head, head,
span,
)); ));
} }
} }
@ -74,9 +78,13 @@ fn to_url(input: PipelineData, head: Span) -> Result<PipelineData, ShellError> {
)), )),
} }
} }
// Propagate existing errors
Value::Error { error } => Err(error),
other => Err(ShellError::UnsupportedInput( other => Err(ShellError::UnsupportedInput(
"Expected a table from pipeline".to_string(), "Expected a table from pipeline".to_string(),
other.span().unwrap_or(head), "value originates from here".into(),
head,
other.expect_span(),
)), )),
}) })
.collect(); .collect();

View File

@ -92,6 +92,7 @@ pub fn cal(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
// TODO: Error if a value is piped in
_input: PipelineData, _input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let mut calendar_vec_deque = VecDeque::new(); let mut calendar_vec_deque = VecDeque::new();
@ -141,7 +142,7 @@ pub fn cal(
} }
fn get_invalid_year_shell_error(head: Span) -> ShellError { fn get_invalid_year_shell_error(head: Span) -> ShellError {
ShellError::UnsupportedInput("The year is invalid".to_string(), head) ShellError::TypeMismatch("The year is invalid".to_string(), head)
} }
struct MonthHelper { struct MonthHelper {
@ -274,7 +275,7 @@ fn add_month_to_table(
if days_of_the_week.contains(&s.as_str()) { if days_of_the_week.contains(&s.as_str()) {
week_start_day = s.to_string(); week_start_day = s.to_string();
} else { } else {
return Err(ShellError::UnsupportedInput( return Err(ShellError::TypeMismatch(
"The specified week start day is invalid".to_string(), "The specified week start day is invalid".to_string(),
day.span, day.span,
)); ));

View File

@ -103,6 +103,8 @@ where
let (bytes, span) = match input { let (bytes, span) = match input {
Value::String { val, span } => (val.as_bytes(), *span), Value::String { val, span } => (val.as_bytes(), *span),
Value::Binary { val, span } => (val.as_slice(), *span), Value::Binary { val, span } => (val.as_slice(), *span),
// Propagate existing errors
Value::Error { .. } => return input.clone(),
other => { other => {
let span = match input.span() { let span = match input.span() {
Ok(span) => span, Ok(span) => span,
@ -110,13 +112,11 @@ where
}; };
return Value::Error { return Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "string or binary".into(),
"Type `{}` is not supported for {} hashing input", other.get_type().to_string(),
other.get_type(),
D::name()
),
span, span,
other.expect_span(),
), ),
}; };
} }

View File

@ -49,7 +49,16 @@ where
C: Fn(&Value, &A, Span) -> Value + Send + Sync + 'static + Clone + Copy, C: Fn(&Value, &A, Span) -> Value + Send + Sync + 'static + Clone + Copy,
{ {
match arg.take_cell_paths() { match arg.take_cell_paths() {
None => input.map(move |v| cmd(&v, &arg, v.span().unwrap_or(span)), ctrlc), None => input.map(
move |v| {
match v {
// Propagate errors inside the input
Value::Error { .. } => v,
_ => cmd(&v, &arg, span),
}
},
ctrlc,
),
Some(column_paths) => { Some(column_paths) => {
let arg = Arc::new(arg); let arg = Arc::new(arg);
input.map( input.map(
@ -58,7 +67,13 @@ where
let opt = arg.clone(); let opt = arg.clone();
let r = v.update_cell_path( let r = v.update_cell_path(
&path.members, &path.members,
Box::new(move |old| cmd(old, &opt, old.span().unwrap_or(span))), Box::new(move |old| {
match old {
// Propagate errors inside the input
Value::Error { .. } => old.clone(),
_ => cmd(old, &opt, span),
}
}),
); );
if let Err(error) = r { if let Err(error) = r {
return Value::Error { error }; return Value::Error { error };

View File

@ -66,13 +66,13 @@ fn abs_helper(val: Value, head: Span) -> Value {
val: val.abs(), val: val.abs(),
span, span,
}, },
Value::Error { .. } => val,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type()
),
head, head,
other.expect_span(),
), ),
}, },
} }

View File

@ -35,6 +35,10 @@ impl Command for SubCommand {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let use_degrees = call.has_flag("degrees"); let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head, use_degrees), move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -75,18 +79,20 @@ fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
Value::Error { Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"'arccos' undefined for values outside the closed interval [-1, 1].".into(), "'arccos' undefined for values outside the closed interval [-1, 1].".into(),
"value originates from here".into(),
head,
span, span,
), ),
} }
} }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -33,6 +33,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head), move |value| operate(value, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -65,18 +69,20 @@ fn operate(value: Value, head: Span) -> Value {
Value::Error { Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"'arccosh' undefined for values below 1.".into(), "'arccosh' undefined for values below 1.".into(),
"value originates from here".into(),
head,
span, span,
), ),
} }
} }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -35,6 +35,10 @@ impl Command for SubCommand {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let use_degrees = call.has_flag("degrees"); let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head, use_degrees), move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -76,18 +80,20 @@ fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
Value::Error { Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"'arcsin' undefined for values outside the closed interval [-1, 1].".into(), "'arcsin' undefined for values outside the closed interval [-1, 1].".into(),
"value originates from here".into(),
head,
span, span,
), ),
} }
} }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -33,6 +33,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head), move |value| operate(value, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -61,13 +65,13 @@ fn operate(value: Value, head: Span) -> Value {
Value::Float { val, span } Value::Float { val, span }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -35,6 +35,10 @@ impl Command for SubCommand {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let use_degrees = call.has_flag("degrees"); let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head, use_degrees), move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -72,13 +76,13 @@ fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
Value::Float { val, span } Value::Float { val, span }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -33,6 +33,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head), move |value| operate(value, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -65,18 +69,20 @@ fn operate(value: Value, head: Span) -> Value {
Value::Error { Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"'arctanh' undefined for values outside the open interval (-1, 1).".into(), "'arctanh' undefined for values outside the open interval (-1, 1).".into(),
"value originates from here".into(),
head,
span, span,
), ),
} }
} }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -45,9 +45,9 @@ impl Command for SubCommand {
} }
} }
pub fn average(values: &[Value], head: &Span) -> Result<Value, ShellError> { pub fn average(values: &[Value], span: Span, head: &Span) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation); let sum = reducer_for(Reduce::Summation);
let total = &sum(Value::int(0, *head), values.to_vec(), *head)?; let total = &sum(Value::int(0, *head), values.to_vec(), span, *head)?;
match total { match total {
Value::Filesize { val, span } => Ok(Value::Filesize { Value::Filesize { val, span } => Ok(Value::Filesize {
val: val / values.len() as i64, val: val / values.len() as i64,

View File

@ -33,6 +33,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head), move |value| operate(value, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -58,13 +62,13 @@ fn operate(value: Value, head: Span) -> Value {
val: val.ceil(), val: val.ceil(),
span, span,
}, },
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -35,6 +35,10 @@ impl Command for SubCommand {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
let use_degrees = call.has_flag("degrees"); let use_degrees = call.has_flag("degrees");
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head, use_degrees), move |value| operate(value, head, use_degrees),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -82,13 +86,13 @@ fn operate(value: Value, head: Span, use_degrees: bool) -> Value {
span, span,
} }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -33,6 +33,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head), move |value| operate(value, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -63,13 +67,13 @@ fn operate(value: Value, head: Span) -> Value {
span, span,
} }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -63,6 +63,8 @@ pub fn eval(
Ok(value) => Ok(PipelineData::Value(value, None)), Ok(value) => Ok(PipelineData::Value(value, None)),
Err(err) => Err(ShellError::UnsupportedInput( Err(err) => Err(ShellError::UnsupportedInput(
format!("Math evaluation error: {}", err), format!("Math evaluation error: {}", err),
"value originates from here".into(),
head,
expr.span, expr.span,
)), )),
} }
@ -71,25 +73,19 @@ pub fn eval(
return Ok(input); return Ok(input);
} }
input.map( input.map(
move |val| { move |val| match val.as_string() {
if let Ok(string) = val.as_string() { Ok(string) => match parse(&string, &val.span().unwrap_or(head)) {
match parse(&string, &val.span().unwrap_or(head)) { Ok(value) => value,
Ok(value) => value, Err(err) => Value::Error {
Err(err) => Value::Error {
error: ShellError::UnsupportedInput(
format!("Math evaluation error: {}", err),
val.span().unwrap_or(head),
),
},
}
} else {
Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"Expected a string from pipeline".to_string(), format!("Math evaluation error: {}", err),
val.span().unwrap_or(head), "value originates from here".into(),
head,
val.expect_span(),
), ),
} },
} },
Err(error) => Value::Error { error },
}, },
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )

View File

@ -33,6 +33,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head), move |value| operate(value, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -58,13 +62,13 @@ fn operate(value: Value, head: Span) -> Value {
val: val.floor(), val: val.floor(),
span, span,
}, },
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -33,6 +33,10 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head), move |value| operate(value, head),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -65,18 +69,20 @@ fn operate(value: Value, head: Span) -> Value {
Value::Error { Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"'ln' undefined for values outside the open interval (0, Inf).".into(), "'ln' undefined for values outside the open interval (0, Inf).".into(),
"value originates from here".into(),
head,
span, span,
), ),
} }
} }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -46,10 +46,15 @@ impl Command for SubCommand {
if base.item <= 0.0f64 { if base.item <= 0.0f64 {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Base has to be greater 0".into(), "Base has to be greater 0".into(),
"value originates from here".into(),
head,
base.span, base.span,
)); ));
} }
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
let base = base.item; let base = base.item;
input.map( input.map(
move |value| operate(value, head, base), move |value| operate(value, head, base),
@ -94,6 +99,8 @@ fn operate(value: Value, head: Span, base: f64) -> Value {
error: ShellError::UnsupportedInput( error: ShellError::UnsupportedInput(
"'math log' undefined for values outside the open interval (0, Inf)." "'math log' undefined for values outside the open interval (0, Inf)."
.into(), .into(),
"value originates from here".into(),
head,
span, span,
), ),
}; };
@ -109,13 +116,13 @@ fn operate(value: Value, head: Span, base: f64) -> Value {
Value::Float { val, span } Value::Float { val, span }
} }
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

View File

@ -59,9 +59,9 @@ impl Command for SubCommand {
} }
} }
pub fn maximum(values: &[Value], head: &Span) -> Result<Value, ShellError> { pub fn maximum(values: &[Value], span: Span, head: &Span) -> Result<Value, ShellError> {
let max_func = reducer_for(Reduce::Maximum); let max_func = reducer_for(Reduce::Maximum);
max_func(Value::nothing(*head), values.to_vec(), *head) max_func(Value::nothing(*head), values.to_vec(), span, *head)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -66,7 +66,7 @@ enum Pick {
Median, Median,
} }
pub fn median(values: &[Value], head: &Span) -> Result<Value, ShellError> { pub fn median(values: &[Value], span: Span, head: &Span) -> Result<Value, ShellError> {
let take = if values.len() % 2 == 0 { let take = if values.len() % 2 == 0 {
Pick::MedianAverage Pick::MedianAverage
} else { } else {
@ -103,9 +103,14 @@ pub fn median(values: &[Value], head: &Span) -> Result<Value, ShellError> {
match take { match take {
Pick::Median => { Pick::Median => {
let idx = (values.len() as f64 / 2.0).floor() as usize; let idx = (values.len() as f64 / 2.0).floor() as usize;
let out = sorted let out = sorted.get(idx).ok_or_else(|| {
.get(idx) ShellError::UnsupportedInput(
.ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), *head))?; "Empty input".to_string(),
"value originates from here".into(),
*head,
span,
)
})?;
Ok(out.clone()) Ok(out.clone())
} }
Pick::MedianAverage => { Pick::MedianAverage => {
@ -114,15 +119,29 @@ pub fn median(values: &[Value], head: &Span) -> Result<Value, ShellError> {
let left = sorted let left = sorted
.get(idx_start) .get(idx_start)
.ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), *head))? .ok_or_else(|| {
ShellError::UnsupportedInput(
"Empty input".to_string(),
"value originates from here".into(),
*head,
span,
)
})?
.clone(); .clone();
let right = sorted let right = sorted
.get(idx_end) .get(idx_end)
.ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), *head))? .ok_or_else(|| {
ShellError::UnsupportedInput(
"Empty input".to_string(),
"value originates from here".into(),
*head,
span,
)
})?
.clone(); .clone();
average(&[left, right], head) average(&[left, right], span, head)
} }
} }
} }

View File

@ -59,9 +59,9 @@ impl Command for SubCommand {
} }
} }
pub fn minimum(values: &[Value], head: &Span) -> Result<Value, ShellError> { pub fn minimum(values: &[Value], span: Span, head: &Span) -> Result<Value, ShellError> {
let min_func = reducer_for(Reduce::Minimum); let min_func = reducer_for(Reduce::Minimum);
min_func(Value::nothing(*head), values.to_vec(), *head) min_func(Value::nothing(*head), values.to_vec(), span, *head)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -97,7 +97,7 @@ impl Command for SubCommand {
} }
} }
pub fn mode(values: &[Value], head: &Span) -> Result<Value, ShellError> { pub fn mode(values: &[Value], _span: Span, head: &Span) -> Result<Value, ShellError> {
if let Some(Err(values)) = values if let Some(Err(values)) = values
.windows(2) .windows(2)
.map(|elem| { .map(|elem| {
@ -132,9 +132,12 @@ pub fn mode(values: &[Value], head: &Span) -> Result<Value, ShellError> {
Value::Filesize { val, .. } => { Value::Filesize { val, .. } => {
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Filesize)) Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Filesize))
} }
Value::Error { error } => Err(error.clone()),
other => Err(ShellError::UnsupportedInput( other => Err(ShellError::UnsupportedInput(
"Unable to give a result with this input".to_string(), "Unable to give a result with this input".to_string(),
other.span()?, "value originates from here".into(),
*head,
other.expect_span(),
)), )),
}) })
.collect::<Result<Vec<HashableType>, ShellError>>()?; .collect::<Result<Vec<HashableType>, ShellError>>()?;

View File

@ -46,9 +46,9 @@ impl Command for SubCommand {
} }
/// Calculate product of given values /// Calculate product of given values
pub fn product(values: &[Value], head: &Span) -> Result<Value, ShellError> { pub fn product(values: &[Value], span: Span, head: &Span) -> Result<Value, ShellError> {
let product_func = reducer_for(Reduce::Product); let product_func = reducer_for(Reduce::Product);
product_func(Value::nothing(*head), values.to_vec(), *head) product_func(Value::nothing(*head), values.to_vec(), span, *head)
} }
#[cfg(test)] #[cfg(test)]

View File

@ -9,21 +9,28 @@ pub enum Reduce {
} }
pub type ReducerFunction = pub type ReducerFunction =
Box<dyn Fn(Value, Vec<Value>, Span) -> Result<Value, ShellError> + Send + Sync + 'static>; Box<dyn Fn(Value, Vec<Value>, Span, Span) -> Result<Value, ShellError> + Send + Sync + 'static>;
pub fn reducer_for(command: Reduce) -> ReducerFunction { pub fn reducer_for(command: Reduce) -> ReducerFunction {
match command { match command {
Reduce::Summation => Box::new(|_, values, head| sum(values, head)), Reduce::Summation => Box::new(|_, values, span, head| sum(values, span, head)),
Reduce::Product => Box::new(|_, values, head| product(values, head)), Reduce::Product => Box::new(|_, values, span, head| product(values, span, head)),
Reduce::Minimum => Box::new(|_, values, head| min(values, head)), Reduce::Minimum => Box::new(|_, values, span, head| min(values, span, head)),
Reduce::Maximum => Box::new(|_, values, head| max(values, head)), Reduce::Maximum => Box::new(|_, values, span, head| max(values, span, head)),
} }
} }
pub fn max(data: Vec<Value>, head: Span) -> Result<Value, ShellError> { pub fn max(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
let mut biggest = data let mut biggest = data
.first() .first()
.ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), head))? .ok_or_else(|| {
ShellError::UnsupportedInput(
"Empty input".to_string(),
"value originates from here".into(),
head,
span,
)
})?
.clone(); .clone();
for value in &data { for value in &data {
@ -44,10 +51,17 @@ pub fn max(data: Vec<Value>, head: Span) -> Result<Value, ShellError> {
Ok(biggest) Ok(biggest)
} }
pub fn min(data: Vec<Value>, head: Span) -> Result<Value, ShellError> { pub fn min(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
let mut smallest = data let mut smallest = data
.first() .first()
.ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), head))? .ok_or_else(|| {
ShellError::UnsupportedInput(
"Empty input".to_string(),
"value originates from here".into(),
head,
span,
)
})?
.clone(); .clone();
for value in &data { for value in &data {
@ -68,7 +82,7 @@ pub fn min(data: Vec<Value>, head: Span) -> Result<Value, ShellError> {
Ok(smallest) Ok(smallest)
} }
pub fn sum(data: Vec<Value>, head: Span) -> Result<Value, ShellError> { pub fn sum(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
let initial_value = data.get(0); let initial_value = data.get(0);
let mut acc = match initial_value { let mut acc = match initial_value {
@ -83,7 +97,9 @@ pub fn sum(data: Vec<Value>, head: Span) -> Result<Value, ShellError> {
Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::int(0, *span)), Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::int(0, *span)),
None => Err(ShellError::UnsupportedInput( None => Err(ShellError::UnsupportedInput(
"Empty input".to_string(), "Empty input".to_string(),
"value originates from here".into(),
head, head,
span,
)), )),
_ => Ok(Value::nothing(head)), _ => Ok(Value::nothing(head)),
}?; }?;
@ -96,10 +112,13 @@ pub fn sum(data: Vec<Value>, head: Span) -> Result<Value, ShellError> {
| Value::Duration { .. } => { | Value::Duration { .. } => {
acc = acc.add(head, value, head)?; acc = acc.add(head, value, head)?;
} }
Value::Error { error } => return Err(error.clone()),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Attempted to compute the sum of a value that cannot be summed".to_string(), "Attempted to compute the sum of a value that cannot be summed".to_string(),
other.span().unwrap_or(head), "value originates from here".into(),
head,
other.expect_span(),
)); ));
} }
} }
@ -107,14 +126,16 @@ pub fn sum(data: Vec<Value>, head: Span) -> Result<Value, ShellError> {
Ok(acc) Ok(acc)
} }
pub fn product(data: Vec<Value>, head: Span) -> Result<Value, ShellError> { pub fn product(data: Vec<Value>, span: Span, head: Span) -> Result<Value, ShellError> {
let initial_value = data.get(0); let initial_value = data.get(0);
let mut acc = match initial_value { let mut acc = match initial_value {
Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::int(1, *span)), Some(Value::Int { span, .. }) | Some(Value::Float { span, .. }) => Ok(Value::int(1, *span)),
None => Err(ShellError::UnsupportedInput( None => Err(ShellError::UnsupportedInput(
"Empty input".to_string(), "Empty input".to_string(),
"value originates from here".into(),
head, head,
span,
)), )),
_ => Ok(Value::nothing(head)), _ => Ok(Value::nothing(head)),
}?; }?;
@ -124,11 +145,14 @@ pub fn product(data: Vec<Value>, head: Span) -> Result<Value, ShellError> {
Value::Int { .. } | Value::Float { .. } => { Value::Int { .. } | Value::Float { .. } => {
acc = acc.mul(head, value, head)?; acc = acc.mul(head, value, head)?;
} }
Value::Error { error } => return Err(error.clone()),
other => { other => {
return Err(ShellError::UnsupportedInput( return Err(ShellError::UnsupportedInput(
"Attempted to compute the product of a value that cannot be multiplied" "Attempted to compute the product of a value that cannot be multiplied"
.to_string(), .to_string(),
other.span().unwrap_or(head), "value originates from here".into(),
head,
other.expect_span(),
)); ));
} }
} }

View File

@ -43,6 +43,10 @@ impl Command for SubCommand {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let precision_param: Option<i64> = call.get_flag(engine_state, stack, "precision")?; let precision_param: Option<i64> = call.get_flag(engine_state, stack, "precision")?;
let head = call.head; let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty(head));
}
input.map( input.map(
move |value| operate(value, head, precision_param), move |value| operate(value, head, precision_param),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
@ -89,13 +93,13 @@ fn operate(value: Value, head: Span, precision: Option<i64>) -> Value {
}, },
}, },
Value::Int { .. } => value, Value::Int { .. } => value,
Value::Error { .. } => value,
other => Value::Error { other => Value::Error {
error: ShellError::UnsupportedInput( error: ShellError::OnlySupportsThisInputType(
format!( "numeric".into(),
"Only numerical values are supported, input type: {:?}", other.get_type().to_string(),
other.get_type() head,
), other.expect_span(),
other.span().unwrap_or(head),
), ),
}, },
} }

Some files were not shown because too many files have changed in this diff Show More