neon-bindings / examples

A collection of examples of Neon
324 stars 43 forks source link

`errors` example: demonstrate error translation #67

Closed vtenfys closed 5 months ago

vtenfys commented 3 years ago

This PR adds a function to the errors example which shows how to use or_else to translate library-specific errors to JavaScript errors.

This partially addresses https://github.com/neon-bindings/neon/issues/166 although I'm not sure how to go about implementing From<CustomError> (where CustomError is an error type created by the author of a Neon project) for neon::result::Throw or if this is possible. Throwing errors seems to require a context object which isn't available from the required from function.

So I would like to ask, is it possible to do the above with the latest Neon?

kjvalencik commented 3 years ago

@davidbailey00 Latest Neon has the same implementation. It is not possible to implement From<T> for Throw because it requires a Context. Thread local storage could be used to store a context, but this would be error prone since it could fail at runtime.

An alternative might be a wrapper that expects a custom error type that has a variant of Throw.

enum MyError {
    SomeError,
    OtherError,
    Throw,
}

impl From<Throw> for MyError {
    fn from(other: Throw) -> Self {
        MyError::Throw
    }
}

trait ResultExt<T> {
    fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult<T>;
}

impl<T> ResultExt<T> for Result<T, MyError> {
    fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult<T> {
        match self {
            Ok(v) => Ok(v),
            Err(MyError::SomeError) => cx.throw_error("MyError"),
            Err(MyError::OtherError) => cx.throw_error("OtherError"),
            Err(MyError::Throw) => Err(Throw),
        }
    }
}

fn my_error_function(mut cx: FunctionContext) -> JsResult<JsString> {
    fn my_error_function<'a>(cx: &mut FunctionContext<'a>) -> Result<Handle<'a, JsString>, MyError> {
        // The `?` operator can be used in this method with both exceptions and Rust errors    
        let _ = cx.throw_error("Oh, no!")?;

        Ok(cx.string("Hello"))
    }

    my_error_function(&mut cx).or_throw(&mut cx)
}

The boilerplate of wrapping the real function with a function that calls .or_throw(&mut cx) could be done with a macro.

vtenfys commented 3 years ago

How about this? (where Result is std::result::Result)

trait OrThrow<T> {
    fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult<T>;
}

impl<T, E: Display> OrThrow<T> for Result<T, E> {
    fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult<T> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => cx.throw_error(e.to_string()),
        }
    }
}
vtenfys commented 3 years ago

From the docs on neon::result::Throw

Throw deliberately does not implement std::error::Error, because it's generally not a good idea to chain JavaScript exceptions with other kinds of Rust errors, since entering into the throwing state means that the JavaScript engine is unavailable until the exception is handled.

I'm not sure I understand what the last part of this sentence means, and whether the same is true for my example above? It seems like implementing std::error::Error for Throw, or using my code above, would both result in converting a foreign error to a Neon Throw error (in different ways) - is my understanding correct?

Apologies for lots of questions, I come from a JS background but rather new to Rust and still have lots to learn!

vtenfys commented 3 years ago

Ah, I realise in my above comment I'm mixing up impl Error for Throw (which is what the docs refer to) with impl From<Error> for Throw (which is what would be similar to the .or_throw() method but doesn't actually exist in the Neon codebase). I think I now understand the reasoning behind removing impl Error for Throw to avoid accidentally converting JS errors to Rust errors, whereas the code samples above do the opposite. Hopefully my understanding of this is now correct?

I realise now that impl From<Error> for Throw might not be a good idea either because Throw alone doesn't include any information about the error - a JsResult is needed for this to bubble up a meaningful error to JS. That said, perhaps I could write an RFC to include the code in https://github.com/neon-bindings/examples/pull/67#issuecomment-794629880 directly in Neon?