Better generic errors for plugins (and perhaps scripts) (#12236)

# Description
This makes `LabeledError` much more capable of representing close to
everything a `miette::Diagnostic` can, including `ShellError`, and
allows plugins to generate multiple error spans, codes, help, etc.

`LabeledError` is now embeddable within `ShellError` as a transparent
variant.

This could also be used to improve `error make` and `try/catch` to
reflect `LabeledError` exactly in the future.

Also cleaned up some errors in existing plugins.

# User-Facing Changes
Breaking change for plugins. Nicer errors for users.
This commit is contained in:
Devyn Cairns
2024-03-21 04:27:21 -07:00
committed by GitHub
parent 8237d15683
commit efe25e3f58
42 changed files with 453 additions and 307 deletions

View File

@ -3,8 +3,8 @@ use crate::query_web::QueryWeb;
use crate::query_xml::QueryXml;
use nu_engine::documentation::get_flags_section;
use nu_plugin::{EvaluatedCall, LabeledError, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, Value};
use nu_plugin::{EvaluatedCall, Plugin, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, PluginSignature, Value};
use std::fmt::Write;
#[derive(Default)]

View File

@ -1,6 +1,8 @@
use gjson::Value as gjValue;
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginSignature, Record, Span, Spanned, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, PluginSignature, Record, Span, Spanned, SyntaxShape, Value,
};
use crate::Query;
@ -39,22 +41,15 @@ pub fn execute_json_query(
let input_string = match input.coerce_str() {
Ok(s) => s,
Err(e) => {
return Err(LabeledError {
span: Some(call.head),
msg: e.to_string(),
label: "problem with input data".to_string(),
})
return Err(LabeledError::new("Problem with input data").with_inner(e));
}
};
let query_string = match &query {
Some(v) => &v.item,
None => {
return Err(LabeledError {
msg: "problem with input data".to_string(),
label: "problem with input data".to_string(),
span: Some(call.head),
})
return Err(LabeledError::new("Problem with input data")
.with_label("query string missing", call.head));
}
};
@ -62,11 +57,9 @@ pub fn execute_json_query(
let is_valid_json = gjson::valid(&input_string);
if !is_valid_json {
return Err(LabeledError {
msg: "invalid json".to_string(),
label: "invalid json".to_string(),
span: Some(call.head),
});
return Err(
LabeledError::new("Invalid JSON").with_label("this is not valid JSON", call.head)
);
}
let val: gjValue = gjson::get(&input_string, query_string);

View File

@ -1,6 +1,9 @@
use crate::{web_tables::WebTable, Query};
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{Category, PluginExample, PluginSignature, Record, Span, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, PluginExample, PluginSignature, Record, Span, Spanned, SyntaxShape,
Value,
};
use scraper::{Html, Selector as ScraperSelector};
pub struct QueryWeb;
@ -99,10 +102,7 @@ impl Default for Selector {
pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
let head = call.head;
let query: String = match call.get_flag("query")? {
Some(q2) => q2,
None => "".to_string(),
};
let query: Option<Spanned<String>> = call.get_flag("query")?;
let as_html = call.has_flag("as-html")?;
let attribute = call.get_flag("attribute")?.unwrap_or_default();
let as_table: Value = call
@ -111,16 +111,20 @@ pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Valu
let inspect = call.has_flag("inspect")?;
if !&query.is_empty() && ScraperSelector::parse(&query).is_err() {
return Err(LabeledError {
msg: "Cannot parse this query as a valid css selector".to_string(),
label: "Parse error".to_string(),
span: Some(head),
});
if let Some(query) = &query {
if let Err(err) = ScraperSelector::parse(&query.item) {
return Err(LabeledError::new("CSS query parse error")
.with_label(err.to_string(), query.span)
.with_help("cannot parse this query as a valid CSS selector"));
}
} else {
return Err(
LabeledError::new("Missing query argument").with_label("add --query here", call.head)
);
}
let selector = Selector {
query,
query: query.map(|q| q.item).unwrap_or_default(),
as_html,
attribute,
as_table,
@ -130,11 +134,8 @@ pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Valu
let span = input.span();
match input {
Value::String { val, .. } => Ok(begin_selector_query(val.to_string(), selector, span)),
_ => Err(LabeledError {
label: "requires text input".to_string(),
msg: "Expected text from pipeline".to_string(),
span: Some(span),
}),
_ => Err(LabeledError::new("Requires text input")
.with_label("expected text from pipeline", span)),
}
}

View File

@ -1,5 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, LabeledError, SimplePluginCommand};
use nu_protocol::{record, Category, PluginSignature, Record, Span, Spanned, SyntaxShape, Value};
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
record, Category, LabeledError, PluginSignature, Record, Span, Spanned, SyntaxShape, Value,
};
use sxd_document::parser;
use sxd_xpath::{Context, Factory};
@ -38,11 +40,9 @@ pub fn execute_xpath_query(
let (query_string, span) = match &query {
Some(v) => (&v.item, v.span),
None => {
return Err(LabeledError {
msg: "problem with input data".to_string(),
label: "problem with input data".to_string(),
span: Some(call.head),
})
return Err(
LabeledError::new("problem with input data").with_label("query missing", call.head)
)
}
};
@ -50,12 +50,10 @@ pub fn execute_xpath_query(
let input_string = input.coerce_str()?;
let package = parser::parse(&input_string);
if package.is_err() {
return Err(LabeledError {
label: "invalid xml document".to_string(),
msg: "invalid xml document".to_string(),
span: Some(call.head),
});
if let Err(err) = package {
return Err(
LabeledError::new("Invalid XML document").with_label(err.to_string(), input.span())
);
}
let package = package.expect("invalid xml document");
@ -107,29 +105,20 @@ pub fn execute_xpath_query(
Ok(Value::list(records, call.head))
}
Err(_) => Err(LabeledError {
label: "xpath query error".to_string(),
msg: "xpath query error".to_string(),
span: Some(call.head),
}),
Err(err) => {
Err(LabeledError::new("xpath query error").with_label(err.to_string(), call.head))
}
}
}
fn build_xpath(xpath_str: &str, span: Span) -> Result<sxd_xpath::XPath, LabeledError> {
let factory = Factory::new();
if let Ok(xpath) = factory.build(xpath_str) {
xpath.ok_or_else(|| LabeledError {
label: "invalid xpath query".to_string(),
msg: "invalid xpath query".to_string(),
span: Some(span),
})
} else {
Err(LabeledError {
label: "expected valid xpath query".to_string(),
msg: "expected valid xpath query".to_string(),
span: Some(span),
})
match factory.build(xpath_str) {
Ok(xpath) => xpath.ok_or_else(|| {
LabeledError::new("invalid xpath query").with_label("the query must not be empty", span)
}),
Err(err) => Err(LabeledError::new("invalid xpath query").with_label(err.to_string(), span)),
}
}