Skip to content

WebIDL: Handle Invalid Enum Returns #477

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

Merged
merged 4 commits into from
Jul 23, 2018

Conversation

Slowki
Copy link
Contributor

@Slowki Slowki commented Jul 14, 2018

As discussed in #260 and #433 there are two main ways to handle invalid enum returns from JS in the WebIDL codegen:

Method 1

Convert enum returns in WebIDL to Result<ENUM, String>s

Advantages

  • Fairly ergonomic and idiomatic
  • You can add special handling logic for browser specific returns by examining the String in Err

Disadvantages

  • Additive changes will break any matchs that don't have a wildcard arm(unlike in Method 2, which forces a wildcard match), so the user might have to change their code when they update their web-sys version

Method 2

Non-exhaustive enums.

Advantages

  • Any additive changes to the WebIDL enums(and as such the web-sys crate's Rust enum variants) don't break code because non-exhaustive enums force wildcard matches

Disadvantages

  • Not very ergonomic on the end user since their code could end up with a lot of wildcard matches even in cases where the enum is coming from their own Rust code
  • Wildcard matches wouldn't give you specific information on the value returned(unless we want to make the enums un-copyable), just that it wasn't one of the known variants, so you couldn't add special handling for browser specific return values

I opted for Method 1 because it seems like it gives the cleanest code when using enums, and I like the idea of being able to handle browser specific returns that aren't document in WebIDL. Do you folks agree, or should we go with Method 2/something else?

Closes #260

@Slowki Slowki force-pushed the handle-invalid-enums branch from 377219c to 02feaf0 Compare July 14, 2018 20:58
///
/// # Panics
/// This function will panic if the `JsValue` isn't a string
#vis fn from_js_value(obj: ::wasm_bindgen::JsValue) -> Result<#name, String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this doesn't actually need ownership, should this take &JsValue?

@@ -624,17 +624,26 @@ impl ToTokens for ast::ImportEnum {

(quote! {
#[allow(bad_style)]
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, PartialEq, Debug)]
Copy link
Contributor

Choose a reason for hiding this comment

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

I've just realized (after the fact, sorry!) that in other places in wasm-bindgen we don't explicitly list derive modes but rather pass through the original annotations. Here there's no #[wasm_bindgen] equivalent for generating an imported enum so it doesn't matter too too much, but long-term we may want this line to be #(#rust_attrs)* and store attributes in the ImportEnum

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

src/convert.rs Outdated
/// A trait for anything that can be recovered by-value from the wasm ABI
/// but whose conversion might error, for example a JS string being converted
/// to a Rust enum.
pub trait TryFromWasmAbi: WasmDescribe + Sized {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little wary of adding a trait like this, I'm afraid it'll be difficult to work with in general. The conversion traits are used in various locations throughout imports/exports/closures/etc. This also necessitates FromWasmAbi for Result which seems unfortunate in the sense that you can't do:

#[wasm_bindgen]
pub fn foo(a: CustomEnumType) {
    // ...
}

but are instead forced to do:

#[wasm_bindgen]
pub fn foo(a: Result<CustomEnumType, String>) {
    // ...
}

I might be leaning a bit towards dealing with nonexhaustive enums due to this interaction of how to add implicit conversions? I would also be ok, though, having a fallible constructor in Rust but panicking in the conversions at the boundary (as it seems like that's what may happen most of the time anyway?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Non-exhaustive enums are fine by me, the only awkward thing with them is the potential panic if the Rust code gets an invalid enum then tries to return it back to JS, which I can't think of a decent way around.

@Slowki Slowki force-pushed the handle-invalid-enums branch from 02feaf0 to e01f16c Compare July 23, 2018 00:34
@alexcrichton alexcrichton merged commit b3ee71c into rustwasm:master Jul 23, 2018
@alexcrichton
Copy link
Contributor

Awesome, thanks!

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