Add JsValue::{from_serde, into_serde}

These functions are activated with the `serde-serialization` feature of the
`wasm-bindgen` crate. When activated they will allow passing any arbitrary value
into JS that implements the `Serialize` trait and receiving any value from JS
using the `Deserialize` trait. The interchange between JS and Rust is JSON.

Closes #96
This commit is contained in:
Alex Crichton 2018-04-26 19:03:46 -07:00
parent acb5eac96a
commit b8895b3a95
5 changed files with 412 additions and 229 deletions

View File

@ -20,9 +20,12 @@ doctest = false
default = ["spans", "std"]
spans = ["wasm-bindgen-macro/spans"]
std = []
serde-serialize = ["serde", "serde_json", "std"]
[dependencies]
wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.6" }
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
[dev-dependencies]
wasm-bindgen-cli-support = { path = "crates/cli-support", version = '=0.2.6' }

View File

@ -83,213 +83,230 @@ impl<'a> Context<'a> {
}
}
bail!("\n\nthe exported function `{}` is required to generate bindings \
bail!("the exported function `{}` is required to generate bindings \
but it was not found in the wasm file, perhaps the `std` feature \
of the `wasm-bindgen` crate needs to be enabled?\n\n",
of the `wasm-bindgen` crate needs to be enabled?",
name);
}
pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> {
self.write_classes()?;
self.bind("__wbindgen_object_clone_ref", &|me| {
me.expose_add_heap_object();
me.expose_get_object();
let bump_cnt = if me.config.debug {
String::from("
if (typeof(val) === 'number')
throw new Error('corrupt slab');
val.cnt += 1;
")
} else {
String::from("val.cnt += 1;")
};
Ok(format!("
function(idx) {{
// If this object is on the stack promote it to the heap.
if ((idx & 1) === 1)
return addHeapObject(getObject(idx));
// Otherwise if the object is on the heap just bump the
// refcount and move on
const val = slab[idx >> 1];
{}
return idx;
}}
", bump_cnt))
})?;
self.bind("__wbindgen_object_drop_ref", &|me| {
me.expose_drop_ref();
Ok("function(i) { dropRef(i); }".to_string())
})?;
self.bind("__wbindgen_string_new", &|me| {
me.expose_add_heap_object();
me.expose_get_string_from_wasm();
Ok(String::from("
function(p, l) {
return addHeapObject(getStringFromWasm(p, l));
}
"))
})?;
self.bind("__wbindgen_number_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("function(i) { return addHeapObject(i); }"))
})?;
self.bind("__wbindgen_number_get", &|me| {
me.expose_get_object();
me.expose_uint8_memory();
Ok(format!("
function(n, invalid) {{
let obj = getObject(n);
if (typeof(obj) === 'number')
return obj;
getUint8Memory()[invalid] = 1;
return 0;
}}
"))
})?;
self.bind("__wbindgen_undefined_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("function() { return addHeapObject(undefined); }"))
})?;
self.bind("__wbindgen_null_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("
function() {
return addHeapObject(null);
}
"))
})?;
self.bind("__wbindgen_is_null", &|me| {
me.expose_get_object();
Ok(String::from("
function(idx) {
return getObject(idx) === null ? 1 : 0;
}
"))
})?;
self.bind("__wbindgen_is_undefined", &|me| {
me.expose_get_object();
Ok(String::from("
function(idx) {
return getObject(idx) === undefined ? 1 : 0;
}
"))
})?;
self.bind("__wbindgen_boolean_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("
function(v) {
return addHeapObject(v === 1);
}
"))
})?;
self.bind("__wbindgen_boolean_get", &|me| {
me.expose_get_object();
Ok(String::from("
function(i) {
let v = getObject(i);
if (typeof(v) === 'boolean') {
return v ? 1 : 0;
} else {
return 2;
}
}
"))
})?;
self.bind("__wbindgen_symbol_new", &|me| {
me.expose_get_string_from_wasm();
me.expose_add_heap_object();
Ok(format!("
function(ptr, len) {{
let a;
if (ptr === 0) {{
a = Symbol();
}} else {{
a = Symbol(getStringFromWasm(ptr, len));
}}
return addHeapObject(a);
}}
"))
})?;
self.bind("__wbindgen_is_symbol", &|me| {
me.expose_get_object();
Ok(String::from("
function(i) {
return typeof(getObject(i)) === 'symbol' ? 1 : 0;
}
"))
})?;
self.bind("__wbindgen_string_get", &|me| {
me.expose_pass_string_to_wasm()?;
me.expose_get_object();
me.expose_uint32_memory();
Ok(String::from("
function(i, len_ptr) {
let obj = getObject(i);
if (typeof(obj) !== 'string')
return 0;
const [ptr, len] = passStringToWasm(obj);
getUint32Memory()[len_ptr / 4] = len;
return ptr;
}
"))
})?;
self.bind("__wbindgen_cb_drop", &|me| {
me.expose_drop_ref();
Ok(String::from("
function(i) {
let obj = getObject(i).original;
obj.a = obj.b = 0;
dropRef(i);
}
"))
})?;
self.bind("__wbindgen_cb_forget", &|me| {
me.expose_drop_ref();
Ok(String::from("
function(i) {
dropRef(i);
}
"))
})?;
self.bind("__wbindgen_json_parse", &|me| {
me.expose_add_heap_object();
me.expose_get_string_from_wasm();
Ok(String::from("
function(ptr, len) {
return addHeapObject(JSON.parse(getStringFromWasm(ptr, len)));
}
"))
})?;
self.bind("__wbindgen_json_serialize", &|me| {
me.expose_get_object();
me.expose_pass_string_to_wasm()?;
me.expose_uint32_memory();
Ok(String::from("
function(idx, ptrptr) {
const [ptr, len] = passStringToWasm(JSON.stringify(getObject(idx)));
getUint32Memory()[ptrptr / 4] = ptr;
return len;
}
"))
})?;
self.unexport_unused_internal_exports();
self.gc()?;
self.write_classes()?;
{
let mut bind = |name: &str, f: &Fn(&mut Self) -> Result<String, Error>|
-> Result<(), Error>
{
if !self.wasm_import_needed(name) {
return Ok(());
}
let contents = f(self)?;
self.export(name, &contents);
Ok(())
};
bind("__wbindgen_object_clone_ref", &|me| {
me.expose_add_heap_object();
me.expose_get_object();
let bump_cnt = if me.config.debug {
String::from("
if (typeof(val) === 'number')
throw new Error('corrupt slab');
val.cnt += 1;
")
} else {
String::from("val.cnt += 1;")
};
Ok(format!("
function(idx) {{
// If this object is on the stack promote it to the heap.
if ((idx & 1) === 1)
return addHeapObject(getObject(idx));
// Otherwise if the object is on the heap just bump the
// refcount and move on
const val = slab[idx >> 1];
{}
return idx;
}}
", bump_cnt))
})?;
bind("__wbindgen_object_drop_ref", &|me| {
me.expose_drop_ref();
Ok("function(i) { dropRef(i); }".to_string())
})?;
bind("__wbindgen_string_new", &|me| {
me.expose_add_heap_object();
me.expose_get_string_from_wasm();
Ok(String::from("
function(p, l) {
return addHeapObject(getStringFromWasm(p, l));
}
"))
})?;
bind("__wbindgen_number_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("function(i) { return addHeapObject(i); }"))
})?;
bind("__wbindgen_number_get", &|me| {
me.expose_get_object();
me.expose_uint8_memory();
Ok(format!("
function(n, invalid) {{
let obj = getObject(n);
if (typeof(obj) === 'number')
return obj;
getUint8Memory()[invalid] = 1;
return 0;
}}
"))
})?;
bind("__wbindgen_undefined_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("function() { return addHeapObject(undefined); }"))
})?;
bind("__wbindgen_null_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("
function() {
return addHeapObject(null);
}
"))
})?;
bind("__wbindgen_is_null", &|me| {
me.expose_get_object();
Ok(String::from("
function(idx) {
return getObject(idx) === null ? 1 : 0;
}
"))
})?;
bind("__wbindgen_is_undefined", &|me| {
me.expose_get_object();
Ok(String::from("
function(idx) {
return getObject(idx) === undefined ? 1 : 0;
}
"))
})?;
bind("__wbindgen_boolean_new", &|me| {
me.expose_add_heap_object();
Ok(String::from("
function(v) {
return addHeapObject(v === 1);
}
"))
})?;
bind("__wbindgen_boolean_get", &|me| {
me.expose_get_object();
Ok(String::from("
function(i) {
let v = getObject(i);
if (typeof(v) === 'boolean') {
return v ? 1 : 0;
} else {
return 2;
}
}
"))
})?;
bind("__wbindgen_symbol_new", &|me| {
me.expose_get_string_from_wasm();
me.expose_add_heap_object();
Ok(format!("
function(ptr, len) {{
let a;
if (ptr === 0) {{
a = Symbol();
}} else {{
a = Symbol(getStringFromWasm(ptr, len));
}}
return addHeapObject(a);
}}
"))
})?;
bind("__wbindgen_is_symbol", &|me| {
me.expose_get_object();
Ok(String::from("
function(i) {
return typeof(getObject(i)) === 'symbol' ? 1 : 0;
}
"))
})?;
bind("__wbindgen_throw", &|me| {
me.expose_get_string_from_wasm();
Ok(format!("
function(ptr, len) {{
throw new Error(getStringFromWasm(ptr, len));
}}
"))
})?;
bind("__wbindgen_string_get", &|me| {
me.expose_pass_string_to_wasm()?;
me.expose_get_object();
me.expose_uint32_memory();
Ok(String::from("
function(i, len_ptr) {
let obj = getObject(i);
if (typeof(obj) !== 'string')
return 0;
const [ptr, len] = passStringToWasm(obj);
getUint32Memory()[len_ptr / 4] = len;
return ptr;
}
"))
})?;
bind("__wbindgen_cb_drop", &|me| {
me.expose_drop_ref();
Ok(String::from("
function(i) {
let obj = getObject(i).original;
obj.a = obj.b = 0;
dropRef(i);
}
"))
})?;
bind("__wbindgen_cb_forget", &|me| {
me.expose_drop_ref();
Ok(String::from("
function(i) {
dropRef(i);
}
"))
})?;
}
// Note that it's important `throw` comes last *after* we gc. The
// `__wbindgen_malloc` function may call this but we only want to
// generate code for this if it's actually live (and __wbindgen_malloc
// isn't gc'd).
self.bind("__wbindgen_throw", &|me| {
me.expose_get_string_from_wasm();
Ok(format!("
function(ptr, len) {{
throw new Error(getStringFromWasm(ptr, len));
}}
"))
})?;
self.rewrite_imports(module_name);
@ -352,6 +369,20 @@ impl<'a> Context<'a> {
Ok((js, self.typescript.clone()))
}
fn bind(&mut self, name: &str, f: &Fn(&mut Self) -> Result<String, Error>)
-> Result<(), Error>
{
if !self.wasm_import_needed(name) {
return Ok(());
}
let contents = f(self)
.with_context(|_| {
format!("failed to generate internal JS function `{}`", name)
})?;
self.export(name, &contents);
Ok(())
}
fn write_classes(&mut self) -> Result<(), Error> {
let classes = mem::replace(&mut self.exported_classes, Default::default());
for (class, exports) in classes {

View File

@ -8,6 +8,11 @@
#![feature(use_extern_macros, wasm_import_module, try_reserve, unsize)]
#![no_std]
#[cfg(feature = "serde-serialize")]
extern crate serde;
#[cfg(feature = "serde-serialize")]
extern crate serde_json;
extern crate wasm_bindgen_macro;
use core::cell::UnsafeCell;
@ -112,22 +117,58 @@ impl JsValue {
}
}
// #[doc(hidden)]
// pub unsafe fn __from_idx(idx: u32) -> JsValue {
// JsValue { idx }
// }
//
// #[doc(hidden)]
// pub fn __get_idx(&self) -> u32 {
// self.idx
// }
//
// #[doc(hidden)]
// pub fn __into_idx(self) -> u32 {
// let ret = self.idx;
// mem::forget(self);
// return ret
// }
/// Creates a new `JsValue` from the JSON serialization of the object `t`
/// provided.
///
/// This function will serialize the provided value `t` to a JSON string,
/// send the JSON string to JS, parse it into a JS object, and then return
/// a handle to the JS object. This is unlikely to be super speedy so it's
/// not recommended for large payloads, but it's a nice to have in some
/// situations!
///
/// Usage of this API requires activating the `serde-serialize` feature of
/// the `wasm-bindgen` crate.
///
/// # Errors
///
/// Returns any error encountered when serializing `T` into JSON.
#[cfg(feature = "serde-serialize")]
pub fn from_serde<T>(t: &T) -> serde_json::Result<JsValue>
where T: serde::ser::Serialize + ?Sized,
{
let s = serde_json::to_string(t)?;
unsafe {
Ok(JsValue {
idx: __wbindgen_json_parse(s.as_ptr(), s.len()),
})
}
}
/// Invokes `JSON.stringify` on this value and then parses the resulting
/// JSON into an arbitrary Rust value.
///
/// This function will first call `JSON.stringify` on the `JsValue` itself.
/// The resulting string is then passed into Rust which then parses it as
/// JSON into the resulting value.
///
/// Usage of this API requires activating the `serde-serialize` feature of
/// the `wasm-bindgen` crate.
///
/// # Errors
///
/// Returns any error encountered when parsing the JSON into a `T`.
#[cfg(feature = "serde-serialize")]
pub fn into_serde<T>(&self) -> serde_json::Result<T>
where T: for<'a> serde::de::Deserialize<'a>,
{
unsafe {
let mut ptr = ptr::null_mut();
let len = __wbindgen_json_serialize(self.idx, &mut ptr);
let s = Vec::from_raw_parts(ptr, len, len);
let s = String::from_utf8_unchecked(s);
serde_json::from_str(&s)
}
}
/// Returns the `f64` value of this JS value if it's an instance of a
/// number.
@ -252,18 +293,13 @@ extern {
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
fn __wbindgen_cb_arity0(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_arity1(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_arity2(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_arity3(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_arity4(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_arity5(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_arity6(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_arity7(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_cb_drop(idx: u32);
fn __wbindgen_cb_forget(idx: u32);
fn __wbindgen_describe(v: u32);
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
}
impl Clone for JsValue {

View File

@ -244,3 +244,90 @@ fn another_vector_return() {
"#)
.test();
}
#[test]
fn serde() {
project()
.serde(true)
.depend("serde = '1.0'")
.depend("serde_derive = '1.0'")
.file("src/lib.rs", r#"
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
#[macro_use]
extern crate serde_derive;
use wasm_bindgen::prelude::*;
#[derive(Deserialize, Serialize)]
pub struct Foo {
a: u32,
b: String,
c: Option<Bar>,
d: Bar,
}
#[derive(Deserialize, Serialize)]
pub struct Bar {
a: u32,
}
#[wasm_bindgen(module = "./test")]
extern {
fn verify(a: JsValue) -> JsValue;
}
#[wasm_bindgen]
pub fn run() {
let js = JsValue::from_serde("foo").unwrap();
assert_eq!(js.as_string(), Some("foo".to_string()));
let ret = verify(JsValue::from_serde(&Foo {
a: 0,
b: "foo".to_string(),
c: None,
d: Bar { a: 1 },
}).unwrap());
let foo = ret.into_serde::<Foo>().unwrap();
assert_eq!(foo.a, 2);
assert_eq!(foo.b, "bar");
assert!(foo.c.is_some());
assert_eq!(foo.c.as_ref().unwrap().a, 3);
assert_eq!(foo.d.a, 4);
}
#[wasm_bindgen]
pub fn parse(j: &JsValue) {
let s = j.into_serde::<String>().unwrap();
assert_eq!(s, "bar");
}
"#)
.file("test.ts", r#"
import { run, parse } from "./out";
import * as assert from "assert";
export function verify(a: any) {
assert.deepStrictEqual(a, {
a: 0,
b: 'foo',
c: null,
d: { a: 1 }
});
return {
a: 2,
b: 'bar',
c: { a: 3 },
d: { a: 4 },
}
}
export function test() {
run();
parse('bar');
}
"#)
.test();
}

View File

@ -16,6 +16,7 @@ struct Project {
debug: bool,
node: bool,
no_std: bool,
serde: bool,
}
fn project() -> Project {
@ -27,6 +28,7 @@ fn project() -> Project {
debug: true,
node: false,
no_std: false,
serde: false,
files: vec![
("Cargo.toml".to_string(), format!(r#"
[package]
@ -151,6 +153,23 @@ impl Project {
self
}
fn serde(&mut self, serde: bool) -> &mut Project {
self.serde = serde;
self
}
fn depend(&mut self, dep: &str) -> &mut Project {
{
let cargo_toml = self.files
.iter_mut()
.find(|f| f.0 == "Cargo.toml")
.expect("should have Cargo.toml file!");
cargo_toml.1.push_str(dep);
cargo_toml.1.push_str("\n");
}
self
}
fn add_local_dependency(&mut self, name: &str, path: &str) -> &mut Project {
{
let cargo_toml = self.files
@ -173,10 +192,12 @@ impl Project {
.expect("should have Cargo.toml file!");
cargo_toml.1.push_str("wasm-bindgen = { path = '");
cargo_toml.1.push_str(env!("CARGO_MANIFEST_DIR"));
cargo_toml.1.push_str("'");
if self.no_std {
cargo_toml.1.push_str("', default-features = false");
} else {
cargo_toml.1.push_str("'");
cargo_toml.1.push_str(", default-features = false");
}
if self.serde {
cargo_toml.1.push_str(", features = ['serde-serialize']");
}
cargo_toml.1.push_str(" }\n");
}
@ -207,13 +228,18 @@ impl Project {
let as_a_module = root.join("out.wasm");
fs::copy(&out, &as_a_module).unwrap();
cli::Bindgen::new()
let res = cli::Bindgen::new()
.input_path(&as_a_module)
.typescript(true)
.nodejs(self.node)
.debug(self.debug)
.generate(&root)
.expect("failed to run bindgen");
.generate(&root);
if let Err(e) = res {
for e in e.causes() {
println!("- {}", e);
}
panic!("failed");
}
let mut wasm = Vec::new();
File::open(root.join("out_bg.wasm")).unwrap()