use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
#[derive(Clone)]
pub struct ErrorMake;
impl Command for ErrorMake {
fn name(&self) -> &str {
"error make"
}
fn signature(&self) -> Signature {
Signature::build("error make")
.input_output_types(vec![(Type::Nothing, Type::Error)])
.required(
"error_struct",
SyntaxShape::Record(vec![]),
"the error to create",
)
.switch(
"unspanned",
"remove the origin label from the error",
Some('u'),
)
.category(Category::Core)
}
fn usage(&self) -> &str {
"Create an error."
}
fn search_terms(&self) -> Vec<&str> {
vec!["panic", "crash", "throw"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let arg: Value = call.req(engine_state, stack, 0)?;
let unspanned = call.has_flag("unspanned");
let throw_error = if unspanned { None } else { Some(span) };
Err(make_error(&arg, throw_error).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a simple custom error",
example: r#"error make {msg: "my custom error message"}"#,
result: Some(Value::Error {
error: Box::new(ShellError::GenericError(
"my custom error message".to_string(),
"".to_string(),
None,
None,
Vec::new(),
)),
}),
},
Example {
description: "Create a more complex custom error",
example: r#"error make {
msg: "my custom error message"
label: {
text: "my custom label text" # not mandatory unless $.label exists
start: 123 # not mandatory unless $.label.end is set
end: 456 # not mandatory unless $.label.start is set
}
}"#,
result: Some(Value::Error {
error: Box::new(ShellError::GenericError(
"my custom error message".to_string(),
"my custom label text".to_string(),
Some(Span::new(123, 456)),
None,
Vec::new(),
)),
}),
},
Example {
description:
"Create a custom error for a custom command that shows the span of the argument",
example: r#"def foo [x] {
let span = (metadata $x).span;
error make {
msg: "this is fishy"
label: {
text: "fish right here"
start: $span.start
end: $span.end
}
}
}"#,
result: None,
},
]
}
}
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
if let Value::Record { span, .. } = &value {
let msg = value.get_data_by_key("msg");
let label = value.get_data_by_key("label");
match (msg, &label) {
(Some(Value::String { val: message, .. }), Some(label)) => {
let label_start = label.get_data_by_key("start");
let label_end = label.get_data_by_key("end");
let label_text = label.get_data_by_key("text");
let label_span = match label.span() {
Ok(lspan) => Some(lspan),
Err(_) => None,
};
match (label_start, label_end, label_text) {
(
Some(Value::Int { val: start, .. }),
Some(Value::Int { val: end, .. }),
Some(Value::String {
val: label_text, ..
}),
) => {
if start > end {
Some(ShellError::GenericError(
"invalid error format.".into(),
"`$.label.start` should be smaller than `$.label.end`".into(),
label_span,
Some(format!("{} > {}", start, end)),
Vec::new(),
))
} else {
Some(ShellError::GenericError(
message,
label_text,
Some(Span::new(start as usize, end as usize)),
None,
Vec::new(),
))
}
}
(
None,
None,
Some(Value::String {
val: label_text, ..
}),
) => Some(ShellError::GenericError(
message,
label_text,
throw_span,
None,
Vec::new(),
)),
(_, _, None) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.label.text`".into(),
label_span,
None,
Vec::new(),
)),
(Some(Value::Int { .. }), None, _) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.label.end`".into(),
label_span,
Some("required because `$.label.start` is set".to_string()),
Vec::new(),
)),
(None, Some(Value::Int { .. }), _) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.label.start`".into(),
label_span,
Some("required because `$.label.end` is set".to_string()),
Vec::new(),
)),
_ => None,
}
}
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
message,
"originates from here".to_string(),
throw_span,
None,
Vec::new(),
)),
(None, _) => Some(ShellError::GenericError(
"Unable to parse error format.".into(),
"missing required member `$.msg`".into(),
Some(*span),
None,
Vec::new(),
)),
_ => None,
}
} else {
None
}
}