Files
Graphite/frontend/wrapper/src/wasm_value.rs
Timon 6f8c4b2adb Desktop: Stop compiling the editor into the wasm wrapper (#4348)
* Make wasm wrapper not depend on editor on native

* Introduce pkg-native

* Fix tests

* Fix fmt

* Correct span and make cargo fmt work inside editor_commands block

* Review

* Fix lint
2026-07-18 10:33:38 +00:00

449 lines
12 KiB
Rust

//! Serializable stand-in for `JsValue`, used to send `FrontendMessage`s from the native editor process to JS.
//! Keeps the types JSON can't represent (`BigInt`, `Uint8Array`, `Map`, `undefined`, `NaN`) so the wasm side
//! can reconstruct the same `JsValue`s that `serde_wasm_bindgen` produces on web.
use serde::{Deserialize, Serialize};
#[cfg(feature = "editor")]
use serde_json::Error;
use wasm_bindgen::JsValue;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub(crate) enum WasmValue {
Undefined,
Bool(bool),
Number(f64),
NonFinite(NonFinite),
Int(i64),
UInt(u64),
String(String),
Bytes(Vec<u8>),
Seq(Vec<WasmValue>),
Object(Vec<(String, WasmValue)>),
Map(Vec<(WasmValue, WasmValue)>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) enum NonFinite {
Nan,
PositiveInfinity,
NegativeInfinity,
}
impl From<NonFinite> for f64 {
fn from(value: NonFinite) -> Self {
match value {
NonFinite::Nan => f64::NAN,
NonFinite::PositiveInfinity => f64::INFINITY,
NonFinite::NegativeInfinity => f64::NEG_INFINITY,
}
}
}
impl From<WasmValue> for JsValue {
fn from(value: WasmValue) -> Self {
match value {
WasmValue::Undefined => JsValue::UNDEFINED,
WasmValue::Bool(value) => JsValue::from_bool(value),
WasmValue::Number(value) => JsValue::from_f64(value),
WasmValue::NonFinite(value) => JsValue::from_f64(value.into()),
WasmValue::Int(value) => JsValue::from(value),
WasmValue::UInt(value) => JsValue::from(value),
WasmValue::String(value) => JsValue::from_str(&value),
WasmValue::Bytes(bytes) => js_sys::Uint8Array::from(bytes.as_slice()).into(),
WasmValue::Seq(values) => {
let array = js_sys::Array::new();
for value in values {
array.push(&value.into());
}
array.into()
}
WasmValue::Object(fields) => {
let object = js_sys::Object::new();
for (key, value) in fields {
let _ = js_sys::Reflect::set(&object, &JsValue::from_str(&key), &value.into());
}
object.into()
}
WasmValue::Map(entries) => {
let map = js_sys::Map::new();
for (key, value) in entries {
map.set(&key.into(), &value.into());
}
map.into()
}
}
}
}
#[cfg(feature = "editor")]
pub(crate) fn encode<T: Serialize + ?Sized>(value: &T) -> Result<String, serde_json::Error> {
serde_json::to_string(&ser::to_value(value)?)
}
#[cfg(feature = "editor")]
mod ser {
use super::*;
use serde::ser::{self, Error as _};
pub(super) fn to_value<T: Serialize + ?Sized>(value: &T) -> Result<WasmValue, Error> {
value.serialize(ValueSerializer)
}
struct ValueSerializer;
impl ser::Serializer for ValueSerializer {
type Ok = WasmValue;
type Error = Error;
type SerializeSeq = SeqSerializer;
type SerializeTuple = SeqSerializer;
type SerializeTupleStruct = SeqSerializer;
type SerializeTupleVariant = VariantSeqSerializer;
type SerializeMap = MapSerializer;
type SerializeStruct = ObjectSerializer;
type SerializeStructVariant = VariantObjectSerializer;
fn serialize_bool(self, v: bool) -> Result<WasmValue, Error> {
Ok(WasmValue::Bool(v))
}
fn serialize_i8(self, v: i8) -> Result<WasmValue, Error> {
Ok(WasmValue::Number(v as f64))
}
fn serialize_i16(self, v: i16) -> Result<WasmValue, Error> {
Ok(WasmValue::Number(v as f64))
}
fn serialize_i32(self, v: i32) -> Result<WasmValue, Error> {
Ok(WasmValue::Number(v as f64))
}
fn serialize_i64(self, v: i64) -> Result<WasmValue, Error> {
Ok(WasmValue::Int(v))
}
fn serialize_i128(self, v: i128) -> Result<WasmValue, Error> {
if let Ok(v) = i64::try_from(v) {
Ok(WasmValue::Int(v))
} else {
u64::try_from(v)
.map(WasmValue::UInt)
.map_err(|_| Error::custom(format!("i128 value {v} exceeds the wire BigInt range")))
}
}
fn serialize_u8(self, v: u8) -> Result<WasmValue, Error> {
Ok(WasmValue::Number(v as f64))
}
fn serialize_u16(self, v: u16) -> Result<WasmValue, Error> {
Ok(WasmValue::Number(v as f64))
}
fn serialize_u32(self, v: u32) -> Result<WasmValue, Error> {
Ok(WasmValue::Number(v as f64))
}
fn serialize_u64(self, v: u64) -> Result<WasmValue, Error> {
Ok(WasmValue::UInt(v))
}
fn serialize_u128(self, v: u128) -> Result<WasmValue, Error> {
u64::try_from(v)
.map(WasmValue::UInt)
.map_err(|_| Error::custom(format!("u128 value {v} exceeds the wire BigInt range")))
}
fn serialize_f32(self, v: f32) -> Result<WasmValue, Error> {
self.serialize_f64(v as f64)
}
fn serialize_f64(self, v: f64) -> Result<WasmValue, Error> {
Ok(if v.is_finite() {
WasmValue::Number(v)
} else if v.is_nan() {
WasmValue::NonFinite(NonFinite::Nan)
} else if v > 0. {
WasmValue::NonFinite(NonFinite::PositiveInfinity)
} else {
WasmValue::NonFinite(NonFinite::NegativeInfinity)
})
}
fn serialize_char(self, v: char) -> Result<WasmValue, Error> {
Ok(WasmValue::String(v.to_string()))
}
fn serialize_str(self, v: &str) -> Result<WasmValue, Error> {
Ok(WasmValue::String(v.to_string()))
}
fn serialize_bytes(self, v: &[u8]) -> Result<WasmValue, Error> {
Ok(WasmValue::Bytes(v.to_vec()))
}
fn serialize_none(self) -> Result<WasmValue, Error> {
Ok(WasmValue::Undefined)
}
fn serialize_some<T: Serialize + ?Sized>(self, value: &T) -> Result<WasmValue, Error> {
value.serialize(self)
}
fn serialize_unit(self) -> Result<WasmValue, Error> {
Ok(WasmValue::Undefined)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<WasmValue, Error> {
Ok(WasmValue::Undefined)
}
fn serialize_unit_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str) -> Result<WasmValue, Error> {
Ok(WasmValue::String(variant.to_string()))
}
fn serialize_newtype_struct<T: Serialize + ?Sized>(self, _name: &'static str, value: &T) -> Result<WasmValue, Error> {
value.serialize(self)
}
fn serialize_newtype_variant<T: Serialize + ?Sized>(self, _name: &'static str, _variant_index: u32, variant: &'static str, value: &T) -> Result<WasmValue, Error> {
Ok(WasmValue::Object(vec![(variant.to_string(), value.serialize(ValueSerializer)?)]))
}
fn serialize_seq(self, len: Option<usize>) -> Result<SeqSerializer, Error> {
Ok(SeqSerializer(Vec::with_capacity(len.unwrap_or_default())))
}
fn serialize_tuple(self, len: usize) -> Result<SeqSerializer, Error> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result<SeqSerializer, Error> {
self.serialize_seq(Some(len))
}
fn serialize_tuple_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize) -> Result<VariantSeqSerializer, Error> {
Ok(VariantSeqSerializer {
variant,
elements: Vec::with_capacity(len),
})
}
fn serialize_map(self, len: Option<usize>) -> Result<MapSerializer, Error> {
Ok(MapSerializer {
entries: Vec::with_capacity(len.unwrap_or_default()),
pending_key: None,
})
}
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<ObjectSerializer, Error> {
Ok(ObjectSerializer(Vec::with_capacity(len)))
}
fn serialize_struct_variant(self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize) -> Result<VariantObjectSerializer, Error> {
Ok(VariantObjectSerializer {
variant,
fields: Vec::with_capacity(len),
})
}
}
struct SeqSerializer(Vec<WasmValue>);
impl ser::SerializeSeq for SeqSerializer {
type Ok = WasmValue;
type Error = Error;
fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
self.0.push(value.serialize(ValueSerializer)?);
Ok(())
}
fn end(self) -> Result<WasmValue, Error> {
Ok(WasmValue::Seq(self.0))
}
}
impl ser::SerializeTuple for SeqSerializer {
type Ok = WasmValue;
type Error = Error;
fn serialize_element<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<WasmValue, Error> {
ser::SerializeSeq::end(self)
}
}
impl ser::SerializeTupleStruct for SeqSerializer {
type Ok = WasmValue;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<WasmValue, Error> {
ser::SerializeSeq::end(self)
}
}
struct VariantSeqSerializer {
variant: &'static str,
elements: Vec<WasmValue>,
}
impl ser::SerializeTupleVariant for VariantSeqSerializer {
type Ok = WasmValue;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
self.elements.push(value.serialize(ValueSerializer)?);
Ok(())
}
fn end(self) -> Result<WasmValue, Error> {
Ok(WasmValue::Object(vec![(self.variant.to_string(), WasmValue::Seq(self.elements))]))
}
}
struct MapSerializer {
entries: Vec<(WasmValue, WasmValue)>,
pending_key: Option<WasmValue>,
}
impl ser::SerializeMap for MapSerializer {
type Ok = WasmValue;
type Error = Error;
fn serialize_key<T: Serialize + ?Sized>(&mut self, key: &T) -> Result<(), Error> {
self.pending_key = Some(key.serialize(ValueSerializer)?);
Ok(())
}
fn serialize_value<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
let key = self.pending_key.take().ok_or_else(|| Error::custom("Map value serialized without a key"))?;
self.entries.push((key, value.serialize(ValueSerializer)?));
Ok(())
}
fn end(self) -> Result<WasmValue, Error> {
Ok(WasmValue::Map(self.entries))
}
}
struct ObjectSerializer(Vec<(String, WasmValue)>);
impl ser::SerializeStruct for ObjectSerializer {
type Ok = WasmValue;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), Error> {
self.0.push((key.to_string(), value.serialize(ValueSerializer)?));
Ok(())
}
fn end(self) -> Result<WasmValue, Error> {
Ok(WasmValue::Object(self.0))
}
}
struct VariantObjectSerializer {
variant: &'static str,
fields: Vec<(String, WasmValue)>,
}
impl ser::SerializeStructVariant for VariantObjectSerializer {
type Ok = WasmValue;
type Error = Error;
fn serialize_field<T: Serialize + ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), Error> {
self.fields.push((key.to_string(), value.serialize(ValueSerializer)?));
Ok(())
}
fn end(self) -> Result<WasmValue, Error> {
Ok(WasmValue::Object(vec![(self.variant.to_string(), WasmValue::Object(self.fields))]))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wasm_value::encode;
use serde::Serialize;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
enum TestMessage {
UnitVariant,
StructVariant {
#[serde(rename = "nodeId")]
node_id: u64,
width: u32,
scale: f64,
min: f64,
max: f64,
label: Option<String>,
widths: std::collections::BTreeMap<u64, u32>,
flags: Vec<bool>,
bytes: serde_bytes::ByteBuf,
},
}
#[test]
fn mirrors_serde_wasm_bindgen_representations() {
assert_eq!(to_value(&TestMessage::UnitVariant).unwrap(), WasmValue::String("unitVariant".into()));
let message = TestMessage::StructVariant {
node_id: u64::MAX,
width: 8,
scale: f64::NAN,
min: f64::NEG_INFINITY,
max: f64::INFINITY,
label: None,
widths: [(42, 7)].into(),
flags: vec![true, false],
bytes: serde_bytes::ByteBuf::from(vec![0, 255]),
};
let expected = WasmValue::Object(vec![(
"structVariant".into(),
WasmValue::Object(vec![
("nodeId".into(), WasmValue::UInt(u64::MAX)),
("width".into(), WasmValue::Number(8.)),
("scale".into(), WasmValue::NonFinite(NonFinite::Nan)),
("min".into(), WasmValue::NonFinite(NonFinite::NegativeInfinity)),
("max".into(), WasmValue::NonFinite(NonFinite::PositiveInfinity)),
("label".into(), WasmValue::Undefined),
("widths".into(), WasmValue::Map(vec![(WasmValue::UInt(42), WasmValue::Number(7.))])),
("flags".into(), WasmValue::Seq(vec![WasmValue::Bool(true), WasmValue::Bool(false)])),
("bytes".into(), WasmValue::Bytes(vec![0, 255])),
]),
)]);
assert_eq!(to_value(&message).unwrap(), expected);
}
#[test]
fn encode_decode_roundtrip() {
let message = TestMessage::StructVariant {
node_id: 1,
width: 2,
scale: f64::NAN,
min: f64::NEG_INFINITY,
max: 1.5,
label: Some("hi".into()),
widths: [(3, 4)].into(),
flags: vec![true],
bytes: serde_bytes::ByteBuf::from(vec![9]),
};
let json: &str = &encode(&message).unwrap();
let decoded: WasmValue = serde_json::from_str(json).unwrap();
assert_eq!(decoded, to_value(&message).unwrap());
}
}
}