Skip to content

Commit 377219c

Browse files
committed
webidl: use Results for enum returns
Use `Result`s for enum returns so that non-valid strings can be handled without panicking.
1 parent 80384d8 commit 377219c

File tree

5 files changed

+190
-23
lines changed

5 files changed

+190
-23
lines changed

crates/backend/src/codegen.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ impl ToTokens for ast::ImportEnum {
594594
fn to_tokens(&self, tokens: &mut TokenStream) {
595595
let vis = &self.vis;
596596
let name = &self.name;
597-
let expect_string = format!("attempted to convert invalid JSValue into {}", name);
597+
let panic_message = format!("attempted to convert a non-string JSValue into {}", name);
598598
let variants = &self.variants;
599599
let variant_strings = &self.variant_values;
600600

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

624624
(quote! {
625625
#[allow(bad_style)]
626-
#[derive(Copy, Clone, Debug)]
626+
#[derive(Copy, Clone, PartialEq, Debug)]
627627
#vis enum #name {
628628
#(#variants = #variant_indexes_ref,)*
629629
}
630630

631631
impl #name {
632-
#vis fn from_js_value(obj: ::wasm_bindgen::JsValue) -> Option<#name> {
633-
obj.as_string().and_then(|obj_str| match obj_str.as_str() {
634-
#(#variant_strings => Some(#variant_paths_ref),)*
635-
_ => None,
636-
})
632+
/// Attempt to convert a `JSValue` to an enum, returning Ok if
633+
/// the value maps to the enum's variants, or an `Err` containing
634+
/// the `JsValue`'s string value if it doesn't map to any of the
635+
/// variants.
636+
///
637+
/// # Panics
638+
/// This function will panic if the `JsValue` isn't a string
639+
#vis fn from_js_value(obj: ::wasm_bindgen::JsValue) -> Result<#name, String> {
640+
let js_string_value = obj.as_string().expect(#panic_message);
641+
642+
match js_string_value.as_str() {
643+
#(#variant_strings => Ok(#variant_paths_ref),)*
644+
_ => Err(js_string_value),
645+
}
637646
}
638647
}
639648

@@ -652,15 +661,17 @@ impl ToTokens for ast::ImportEnum {
652661
}
653662
}
654663

655-
impl ::wasm_bindgen::convert::FromWasmAbi for #name {
664+
impl ::wasm_bindgen::convert::TryFromWasmAbi for #name {
656665
type Abi = <::wasm_bindgen::JsValue as
657666
::wasm_bindgen::convert::FromWasmAbi>::Abi;
667+
type Error = String;
658668

659-
unsafe fn from_abi(
669+
unsafe fn try_from_abi(
660670
js: Self::Abi,
661671
extra: &mut ::wasm_bindgen::convert::Stack,
662-
) -> Self {
663-
#name::from_js_value(::wasm_bindgen::JsValue::from_abi(js, extra)).expect(#expect_string)
672+
) -> Result<#name, Self::Error> {
673+
#name::from_js_value(<::wasm_bindgen::JsValue as
674+
::wasm_bindgen::convert::FromWasmAbi>::from_abi(js, extra))
664675
}
665676
}
666677

crates/webidl/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ extern crate heck;
1313
#[macro_use]
1414
extern crate log;
1515
extern crate proc_macro2;
16+
#[macro_use]
1617
extern crate quote;
1718
extern crate syn;
1819
extern crate wasm_bindgen_backend as backend;

crates/webidl/src/util.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,14 @@ impl<'a> FirstPassRecord<'a> {
123123
} else if self.dictionaries.contains(id) {
124124
ty
125125
} else if self.enums.contains(id) {
126-
ty
126+
if pos == TypePosition::Return {
127+
// Enums are returned as Results in case their value doesn't match the WebIDL schema
128+
syn::TypeVerbatim {
129+
tts: quote!(Result<#ty, String>),
130+
}.into()
131+
} else {
132+
ty
133+
}
127134
} else {
128135
warn!("unrecognized type {}", id);
129136
ty

crates/webidl/tests/all/enums.rs

Lines changed: 123 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,78 @@
11
use super::project;
22

3+
static SHAPE_IDL: &'static str = r#"
4+
enum ShapeType { "circle", "square" };
5+
6+
[Constructor(ShapeType kind)]
7+
interface Shape {
8+
[Pure]
9+
boolean isSquare();
10+
11+
[Pure]
12+
boolean isCircle();
13+
14+
[Pure]
15+
ShapeType getShape();
16+
};
17+
"#;
18+
319
#[test]
420
fn top_level_enum() {
521
project()
22+
.file("shape.webidl", SHAPE_IDL)
23+
.file(
24+
"shape.mjs",
25+
r#"
26+
export class Shape {
27+
constructor(kind) {
28+
this.kind = kind;
29+
}
30+
31+
isSquare() {
32+
return this.kind === 'square';
33+
}
34+
35+
isCircle() {
36+
return this.kind === 'circle';
37+
}
38+
39+
getShape() {
40+
return this.kind;
41+
}
42+
}
43+
"#,
44+
)
645
.file(
7-
"shape.webidl",
46+
"src/lib.rs",
847
r#"
9-
enum ShapeType { "circle", "square" };
10-
11-
[Constructor(ShapeType kind)]
12-
interface Shape {
13-
[Pure]
14-
boolean isSquare();
15-
16-
[Pure]
17-
boolean isCircle();
18-
};
48+
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
49+
50+
extern crate wasm_bindgen;
51+
52+
use wasm_bindgen::prelude::*;
53+
54+
pub mod shape;
55+
56+
use shape::{Shape, ShapeType};
57+
58+
#[wasm_bindgen]
59+
pub fn test() {
60+
let circle = Shape::new(ShapeType::Circle).unwrap();
61+
let square = Shape::new(ShapeType::Square).unwrap();
62+
assert!(circle.is_circle());
63+
assert!(!circle.is_square());
64+
assert!(square.is_square());
65+
assert!(!square.is_circle());
66+
}
1967
"#,
2068
)
69+
.test();
70+
}
71+
72+
#[test]
73+
fn valid_enum_return() {
74+
project()
75+
.file("shape.webidl", SHAPE_IDL)
2176
.file(
2277
"shape.mjs",
2378
r#"
@@ -33,6 +88,10 @@ fn top_level_enum() {
3388
isCircle() {
3489
return this.kind === 'circle';
3590
}
91+
92+
getShape() {
93+
return this.kind;
94+
}
3695
}
3796
"#,
3897
)
@@ -55,8 +114,61 @@ fn top_level_enum() {
55114
let square = Shape::new(ShapeType::Square).unwrap();
56115
assert!(circle.is_circle());
57116
assert!(!circle.is_square());
117+
assert_eq!(circle.get_shape(), Ok(ShapeType::Circle));
58118
assert!(square.is_square());
59119
assert!(!square.is_circle());
120+
assert_eq!(square.get_shape(), Ok(ShapeType::Square));
121+
}
122+
"#,
123+
)
124+
.test();
125+
}
126+
127+
#[test]
128+
fn invalid_enum_return() {
129+
project()
130+
.file("shape.webidl", SHAPE_IDL)
131+
.file(
132+
"shape.mjs",
133+
r#"
134+
export class Shape {
135+
constructor(kind) {
136+
this.kind = 'triangle'; // <-- invalid ShapeType
137+
}
138+
139+
isSquare() {
140+
return this.kind === 'square';
141+
}
142+
143+
isCircle() {
144+
return this.kind === 'circle';
145+
}
146+
147+
getShape() {
148+
return this.kind;
149+
}
150+
}
151+
"#,
152+
)
153+
.file(
154+
"src/lib.rs",
155+
r#"
156+
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
157+
158+
extern crate wasm_bindgen;
159+
160+
use wasm_bindgen::prelude::*;
161+
162+
pub mod shape;
163+
164+
use shape::{Shape, ShapeType};
165+
166+
#[wasm_bindgen]
167+
pub fn test() {
168+
let actually_a_triangle = Shape::new(ShapeType::Circle).unwrap();
169+
assert!(!actually_a_triangle.is_circle());
170+
assert!(!actually_a_triangle.is_square());
171+
assert_eq!(actually_a_triangle.get_shape(), Err(String::from("triangle")));
60172
}
61173
"#,
62174
)

src/convert.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@ pub trait FromWasmAbi: WasmDescribe {
4646
unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self;
4747
}
4848

49+
/// A trait for anything that can be recovered by-value from the wasm ABI
50+
/// but whose conversion might error, for example a JS string being converted
51+
/// to a Rust enum.
52+
pub trait TryFromWasmAbi: WasmDescribe + Sized {
53+
/// The wasm ABI type that this converts from when coming back out from the
54+
/// ABI boundary.
55+
type Abi: WasmAbi;
56+
57+
/// The error type for Result if the conversion fails
58+
type Error: Sized;
59+
60+
/// Attempt to recover a `Self` from `Self::Abi`.
61+
///
62+
/// # Safety
63+
///
64+
/// This is only safe to call when -- and implementations may assume that --
65+
/// the supplied `Self::Abi` was previously generated by a call to `<Self as
66+
/// IntoWasmAbi>::into_abi()` or the moral equivalent in JS.
67+
unsafe fn try_from_abi(js: Self::Abi, extra: &mut Stack) -> Result<Self, Self::Error>;
68+
}
69+
4970
/// A trait for anything that can be recovered as some sort of shared reference
5071
/// from the wasm ABI boundary.
5172
///
@@ -395,6 +416,21 @@ impl FromWasmAbi for JsValue {
395416
}
396417
}
397418

419+
impl<T: TryFromWasmAbi> WasmDescribe for Result<T, T::Error> {
420+
fn describe() {
421+
T::describe()
422+
}
423+
}
424+
425+
impl<T: TryFromWasmAbi> FromWasmAbi for Result<T, T::Error> {
426+
type Abi = T::Abi;
427+
428+
#[inline]
429+
unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Result<T, T::Error> {
430+
T::try_from_abi(js, extra)
431+
}
432+
}
433+
398434
impl<'a> IntoWasmAbi for &'a JsValue {
399435
type Abi = u32;
400436

0 commit comments

Comments
 (0)