mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
fix(core) Include causes when converting anyhow errors to JS exceptions (#16397)
When an op returns an `anyhow` error with a cause (usually added using the `.context()` method), the `Error` thrown into JavaScript contains only the message of the outernmost error in the chain. This PR simply changes the formatting of `anyhow::Error` from `"{}"` to `"{:#}"`: This significantly improves errors for code that embeds Deno and defines custom ops. For example, in [chiselstrike/chiselstrike](https://github.com/chiselstrike/chiselstrike), this PR improves an error message like ``` Error: could not plan migration ``` to ``` Error: could not plan migration: could not migrate table for entity "E": could not add column for field "title": the field does not have a default value ```
This commit is contained in:
parent
a57faa8a0a
commit
642118fdeb
3 changed files with 69 additions and 2 deletions
|
@ -105,7 +105,7 @@ pub fn to_v8_error<'a>(
|
|||
let cb = cb.open(scope);
|
||||
let this = v8::undefined(scope).into();
|
||||
let class = v8::String::new(scope, get_class(error)).unwrap();
|
||||
let message = v8::String::new(scope, &error.to_string()).unwrap();
|
||||
let message = v8::String::new(scope, &format!("{:#}", error)).unwrap();
|
||||
let mut args = vec![class.into(), message.into()];
|
||||
if let Some(code) = crate::error_codes::get_error_code(error) {
|
||||
args.push(v8::String::new(scope, code).unwrap().into());
|
||||
|
|
|
@ -133,7 +133,7 @@ impl OpError {
|
|||
pub fn new(get_class: GetErrorClassFn, err: Error) -> Self {
|
||||
Self {
|
||||
class_name: (get_class)(&err),
|
||||
message: err.to_string(),
|
||||
message: format!("{:#}", err),
|
||||
code: crate::error_codes::get_error_code(&err),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3094,6 +3094,73 @@ main();
|
|||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_context() {
|
||||
use anyhow::anyhow;
|
||||
|
||||
#[op]
|
||||
fn op_err_sync() -> Result<(), Error> {
|
||||
Err(anyhow!("original sync error").context("higher-level sync error"))
|
||||
}
|
||||
|
||||
#[op]
|
||||
async fn op_err_async() -> Result<(), Error> {
|
||||
Err(anyhow!("original async error").context("higher-level async error"))
|
||||
}
|
||||
|
||||
run_in_task(|cx| {
|
||||
let ext = Extension::builder()
|
||||
.ops(vec![op_err_sync::decl(), op_err_async::decl()])
|
||||
.build();
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![ext],
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
runtime
|
||||
.execute_script(
|
||||
"test_error_context_sync.js",
|
||||
r#"
|
||||
let errMessage;
|
||||
try {
|
||||
Deno.core.ops.op_err_sync();
|
||||
} catch (err) {
|
||||
errMessage = err.message;
|
||||
}
|
||||
if (errMessage !== "higher-level sync error: original sync error") {
|
||||
throw new Error("unexpected error message from op_err_sync: " + errMessage);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let promise = runtime
|
||||
.execute_script(
|
||||
"test_error_context_async.js",
|
||||
r#"
|
||||
(async () => {
|
||||
let errMessage;
|
||||
try {
|
||||
await Deno.core.opAsync("op_err_async");
|
||||
} catch (err) {
|
||||
errMessage = err.message;
|
||||
}
|
||||
if (errMessage !== "higher-level async error: original async error") {
|
||||
throw new Error("unexpected error message from op_err_async: " + errMessage);
|
||||
}
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
match runtime.poll_value(&promise, cx) {
|
||||
Poll::Ready(Ok(_)) => {}
|
||||
Poll::Ready(Err(err)) => panic!("{:?}", err),
|
||||
_ => panic!(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pump_message_loop() {
|
||||
run_in_task(|cx| {
|
||||
|
|
Loading…
Reference in a new issue