Skip to content

Commit

Permalink
fix(core) Include causes when converting anyhow errors to JS exceptio…
Browse files Browse the repository at this point in the history
…ns (denoland#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
```
  • Loading branch information
honzasp authored Oct 26, 2022
1 parent a57faa8 commit 642118f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 2 deletions.
2 changes: 1 addition & 1 deletion core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
2 changes: 1 addition & 1 deletion core/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
Expand Down
67 changes: 67 additions & 0 deletions core/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down

0 comments on commit 642118f

Please sign in to comment.