Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(js-connectors): Proper JS error propagation #4186

Merged
merged 3 commits into from
Aug 31, 2023

Conversation

SevInf
Copy link
Member

@SevInf SevInf commented Aug 30, 2023

The way it is implemented:

  1. On TS side, we catch all errors from connector methods. In case we
    encounterd an error, we store it on per-connector error registry and
    return it's numeric id to the engine.
  2. Engine propogates that numeric id all the way throuhg quaint up to
    query-engine-node-api, where it becomes new kind of error,
    ExternalError with a code of P2036 and error id in the meta field.
  3. Client is then expected to pick the error from the registry and
    re-throw it. This PR implements this for smoke-tests, similar
    implementation needs to be done in the client.

Implementation is done this way, rather than propogating napi::Error
instance, to avoid dependencies on napi types in all intermediate crates
other than js-connectors and query-engine-node-api.

To better accomodate this pattern, Result type was introduced to the
connectors. It allows to explicitly indicate if particular call
succeeded or failed via return value. On Rust side, those values get
parsed and converted into std::result::Result values.

On Rust side, AsyncJsFunction wrapper over ThreadsafeFunction is
introduced. It handles promises and JS result type conversions
automatically and simplifies Proxy code.

This also lays the foundation for supporting "Known" errors - JsError
type could be extended in a future with new error types, that could be
converted into standard prisma errors with normal error codes.

Fix #prisma/team-orm#260

The way it is implemented:
1. On TS side, we catch all errors from connector methods. In case we
   encounterd an error, we store it on per-connector error registry and
   return it's numeric id to the engine.
2. Engine propogates that numeric id all the way throuhg quaint up to
   query-engine-node-api, where it becomes new kind of error,
   `ExternalError` with a code of P2036 and error id in the meta field.
3. Client is then expected to pick the error from the registry and
   re-throw it. This PR implements this for smoke-tests, similar
   implementation needs to be done in the client.

Implementation is done this way, rather than propogating `napi::Error`
instance, to avoid dependencies on napi types in all intermediate crates
other than `js-connectors` and `query-engine-node-api`.

To better accomodate this pattern, `Result` type was introduced to the
connectors. It allows to explicitly indicate if particular call
succeeded or failed via return value. On Rust side, those values get
parsed and converted into `std::result::Result` values.

On Rust side, `AsyncJsFunction` wrapper over `ThreadsafeFunction` is
introduced. It handles promises and JS result type conversions
automatically and simplifies `Proxy` code.

This also lays the foundation for supporting "Known" errors - JsError
type could be extended in a future with new error types, that could be
converted into standard prisma errors with normal error codes.

Fix #prisma/team-orm#260
@SevInf SevInf added this to the 5.3.0 milestone Aug 30, 2023
@SevInf
Copy link
Member Author

SevInf commented Aug 30, 2023

I am also happy to discuss naming of some of the things this PR introduces. In particuallary, I am not 100% with those names:

  • ExternalError for this kind of error that is stored elsewhere and all engine knows about it is it's id
  • BoundConnecter for describing Connector + publicly readable error registry. It's "bound" only because binder creates it

Comment on lines +310 to +315
if (error.error_code === 'P2036') {
const jsError = this.connector.errorRegistry.consumeError(error.meta.id)
if (!jsError) {
throw new Error(`Something went wrong. Engine reported external error with id ${error.meta.id}, but it was not registered.`)
}
throw jsError.error
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is rougly what Prisma client has to do to get errors too.

ArgType: ToNapiValue + 'static,
ReturnType: FromNapiValue + 'static,
{
unsafe fn from_napi_value(napi_env: napi::sys::napi_env, napi_val: napi::sys::napi_value) -> napi::Result<Self> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is pretty heavy on the usage of this trait because it plays nicely with napi-rs build-in methods for automatic conversions and there is no safe alternative. In all cases we immediately wrap it into safe wrappers and work with them afterwards. I beleive safe alternative could be added to napi-rs pretty easily, so if you think it's a problem I can try to send them a PR.

@SevInf SevInf marked this pull request as ready for review August 30, 2023 16:39
@SevInf SevInf requested a review from a team as a code owner August 30, 2023 16:39
@millsp millsp changed the title feat(js-connectors): Proper JS error propogation feat(js-connectors): Proper JS error propagation Aug 30, 2023
@codspeed-hq
Copy link

codspeed-hq bot commented Aug 31, 2023

CodSpeed Performance Report

Merging #4186 will not alter performance

Comparing feat/js-error-propogation (0a79073) with main (1c5aa64)

Summary

✅ 11 untouched benchmarks

Copy link
Contributor

@jkomyno jkomyno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@jkomyno jkomyno merged commit bb89459 into main Aug 31, 2023
59 of 61 checks passed
@jkomyno jkomyno deleted the feat/js-error-propogation branch August 31, 2023 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants