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

Enum support for TryIntoJs, structured errors from Result #155

Merged
merged 24 commits into from May 18, 2021

Conversation

mkawalec
Copy link
Contributor

As the title says, this adds support for auto-conversion of enums into JS. Representation used is the same as the default one in Serde. I took the same choices when it comes to unit variants' representation: they are represented by strings with the value being the variant name in PascalCase. Unit structs are also supported, and represented by nulls (see examples/json for a hands on outline of different representation possibilities).

There is one somewhat breaking change buried in here. The representation of TryIntoJs for Result<T, E> no longer demands E: ToString, but requires E: TryIntoJs. This allows the user to read structured errors in JS, no longer having to rely on just their string representation. If you see a way of achieving the same thing without introducing a breaking change, let me know in the review.

@nicholastmosher
Copy link
Contributor

Hello again @mkawalec, thanks for the PR! I'll review it soon and get back with any comments!

{
fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
match self {
Ok(val) => val.try_to_js(&js_env),
Err(err) => Err(NjError::Other(err.to_string())),
Err(err) => Err(NjError::Native(err.try_to_js(&js_env)?)),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll have to add TryIntoJs for NjError

Copy link
Contributor

Choose a reason for hiding this comment

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

Are you able to do this? I'm running into this error when building the examples

error[E0599]: the method `try_to_js` exists for enum `Result<i32, NjError>`, but its trait bounds were not satisfied
   --> function/src/lib.rs:15:1
    |
15  | #[node_bindgen]
    | ^^^^^^^^^^^^^^^ method cannot be called on `Result<i32, NjError>` due to unsatisfied trait bounds
    |
   ::: /Users/nick/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:241:1
    |
241 | pub enum Result<T, E> {
    | --------------------- doesn't satisfy `Result<i32, NjError>: TryIntoJs`
    |
   ::: /Users/nick/infinyon/node-bindgen/nj-core/src/error.rs:12:1
    |
12  | pub enum NjError {
    | ---------------- doesn't satisfy `NjError: TryIntoJs`
    |
    = note: the following trait bounds were not satisfied:
            `NjError: TryIntoJs`
            which is required by `Result<i32, NjError>: TryIntoJs`
    = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0599`.
error: could not compile `nj-example-function`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, not yet, I'll update the PR tomorrow.

@sehz
Copy link
Collaborator

sehz commented May 13, 2021

rebase from master

CHANGELOG.md Outdated
@@ -3,6 +3,12 @@
## Unreleased
### Improvements

## [4.5.0] - TBD
Copy link
Collaborator

Choose a reason for hiding this comment

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

4.4.0 wasn't release, so we can merged into 4.5 release!

README.md Outdated
assert.deepStrictEqual(addon.withUnit(), "UnitErrorType")
```

Unnamed variants will be converted to lists, named to objects and unit variants to strings equal to their name in PascalCase.
Copy link
Contributor

Choose a reason for hiding this comment

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

The wording here is a bit confusing at first glance. How about

Tuple variants will be converted into lists, struct variants converted to objects, and unit variants converted into strings matching the variant's name in PascalCase.

@sehz
Copy link
Collaborator

sehz commented May 16, 2021

rebase please

@sehz
Copy link
Collaborator

sehz commented May 16, 2021

To avoid breaking changes, it is possible add auto impl for TryIntoJs for string?

@mkawalec
Copy link
Contributor Author

@sehz so string-containing errors are converted to strings, but other kinds of errors keep their structure? I think there's a possible solution out there, I'll find one that isn't breaking.

@mkawalec
Copy link
Contributor Author

To avoid breaking changes, it is possible add auto impl for TryIntoJs for string?

@sehz Unfortunately not, impl<T: ToString> TryIntoJs for T is too wide and encompasses virtually all the types, including the ones which have TryIntoJs defined already. Would you be open to having this breaking change here?

I'll keep on poking on this today, asking in case I don't find a non-breaking solution.

@sehz
Copy link
Collaborator

sehz commented May 18, 2021

@nicholastmosher idea on how to keep compatibility? If not we will do major semversion. That willl also give up opportunity to clean up other API as well

@nicholastmosher
Copy link
Contributor

Looking now, need to catch up

@nicholastmosher
Copy link
Contributor

Hmm I don't see a way we can do this, because for any given type T, T may implement both ToString and may also implement TryIntoJs for itself, so there would not be a single choice of implementation to use and the compiler would reject it.

I did take a stab at a conversion anyways but it did not work. This is the code I was experimenting with that failed:

enum ErrorConversion<E> {
    StringError(String),
    NativeError(E),
}

impl<S: ToString> From<S> for ErrorConversion<()> {
    fn from(s: S) -> Self {
        Self::StringError(s.to_string())
    }
}

impl<E: TryIntoJs> From<E> for ErrorConversion<E> {
    fn from(e: E) -> Self {
        Self::NativeError(e)
    }
}

impl<T, E, C> TryIntoJs for Result<T, C>
where
    T: TryIntoJs,
    E: TryIntoJs,
    C: Into<ErrorConversion<E>>,
{
    fn try_to_js(self, js_env: &JsEnv) -> Result<napi_value, NjError> {
        match self {
            Ok(val) => val.try_to_js(&js_env),
            Err(err) => {
                let conv = err.into();
                match conv {
                    ErrorConversion::StringError(string) => Err(NjError::Other(string)),
                    ErrorConversion::NativeError(e) => Err(NjError::Native(e.try_to_js(&js_env))),
                }
            }
        }
    }
}

This fails to compile with the following error:

error[E0207]: the type parameter `E` is not constrained by the impl trait, self type, or predicates
  --> nj-core/src/convert.rs:93:9
   |
93 | impl<T, E, C> TryIntoJs for Result<T, C<E>>
   |         ^ unconstrained type parameter

error: aborting due to 2 previous errors

I think we should probably accept that this will be a breaking change and move forward with the major version bump. I think the new enum conversion for errors is better than what we had previously.

@sehz
Copy link
Collaborator

sehz commented May 18, 2021

K. Let's bump up major version

@sehz
Copy link
Collaborator

sehz commented May 18, 2021

Since we are bump up major version, let's see what else can be clean up in terms of API.
We should create issue for it

@mkawalec
Copy link
Contributor Author

Ok, I'll bump the version here and create the issue shortly...

@mkawalec
Copy link
Contributor Author

Created a stub issue at #156, please comment with any improvements you have in mind.

@sehz sehz linked an issue May 18, 2021 that may be closed by this pull request
5 tasks
@mkawalec
Copy link
Contributor Author

I've noticed there is no example/test which tests the new structured errors from Err, I'll add one before merging.

@sehz
Copy link
Collaborator

sehz commented May 18, 2021

Great job!

@sehz sehz merged commit a354bb1 into infinyon:master May 18, 2021
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.

Automatically derive TryIntoJs for structs
3 participants