From c8bfde7318a06a444f4f9d511ddda3fd406a768b Mon Sep 17 00:00:00 2001 From: Brooklyn Zelenka Date: Fri, 2 Feb 2024 00:43:46 -0800 Subject: [PATCH] Learning about Wasm bindgen --- src/ability.rs | 3 + src/ability/arguments.rs | 18 +-- src/ability/dynamic.rs | 303 ++++++++++++++++++-------------------- src/ability/js.rs | 213 +++++++++++++++++++++++++++ src/delegation/payload.rs | 2 +- src/invocation/payload.rs | 2 +- src/new_wasm.rs | 8 +- 7 files changed, 371 insertions(+), 178 deletions(-) create mode 100644 src/ability/js.rs diff --git a/src/ability.rs b/src/ability.rs index f0fd7c0c..a6b095ad 100644 --- a/src/ability.rs +++ b/src/ability.rs @@ -7,6 +7,9 @@ pub mod wasm; pub mod arguments; pub mod command; +#[cfg(target_arch = "wasm32")] +pub mod js; + // // TODO move to crate::wasm? or hide behind feature flag? #[cfg(target_arch = "wasm32")] pub mod dynamic; diff --git a/src/ability/arguments.rs b/src/ability/arguments.rs index 0d02781a..75dc7bba 100644 --- a/src/ability/arguments.rs +++ b/src/ability/arguments.rs @@ -10,22 +10,20 @@ impl Arguments { Arguments(iterable.into_iter().collect()) } - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - pub fn into_iter(self) -> impl Iterator { - self.0.into_iter() + pub fn get(&self, key: &str) -> Option<&Ipld> { + self.0.get(key) } -} -impl Arguments { pub fn insert(&mut self, key: String, value: Ipld) -> Option { self.0.insert(key, value) } - pub fn get(&self, key: &str) -> Option<&Ipld> { - self.0.get(key) + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn into_iter(self) -> impl Iterator { + self.0.into_iter() } } diff --git a/src/ability/dynamic.rs b/src/ability/dynamic.rs index 0b29664a..dd8bbfb1 100644 --- a/src/ability/dynamic.rs +++ b/src/ability/dynamic.rs @@ -21,180 +21,159 @@ use wasm_bindgen::prelude::*; // NOTE the lack of checking functions! // This is meant to be embedded inside of structs that have e.g. FFI bindings to // a validation function, such as a &js_sys::Function, Ruby magnus::function!, etc etc -#[derive(Clone, PartialEq)] -pub struct Generic { - pub cmd: String, - pub args: Args, - pub is_nonce_meaningful: DefaultTrue, - - pub same_validator: F, - pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void - pub shape_validator: F, // FIXME needs to be a different type -} - -impl Debug for Generic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Generic") - .field("cmd", &self.cmd) - .field("args", &self.args) - .field("is_nonce_meaningful", &self.is_nonce_meaningful) - .finish() - } -} - -pub type Dynamic = Generic; -pub type Promised = Generic, F>; - -impl Serialize for Generic -where - Arguments: From, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(2))?; - map.serialize_entry("cmd", &self.cmd)?; - map.serialize_entry("args", &Arguments::from(self.args.clone()))?; - map.end() - } -} - -impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { - fn deserialize(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - // FIXME - todo!() - // let btree = BTreeMap::deserialize(deserializer)?; - // Ok(Generic { - // cmd: btree.get("cmd")?.to_string(), - // args: btree.get("args")?.clone(), - // is_nonce_meaningful: DefaultTrue::default(), - // - // same_validator: (), - // parent_validator: (), - // shape_validator: (), - // }) - } -} - -impl ToCommand for Generic { - fn to_command(&self) -> String { - self.cmd.clone() - } -} - -impl Delegatable for Dynamic { - type Builder = Dynamic; -} - -impl Resolvable for Dynamic { - type Promised = Promised; -} - -impl From> for Ipld { - fn from(generic: Generic) -> Self { - generic.into() - } -} - -impl TryFrom for Generic { - type Error = SerdeError; - - fn try_from(ipld: Ipld) -> Result { - ipld_serde::from_ipld(ipld) - } -} - -impl, F> From> for Arguments { - fn from(generic: Generic) -> Self { - generic.args.into() - } -} - -impl TryFrom> for Dynamic { - type Error = (); // FIXME - - fn try_from(awaiting: Promised) -> Result { - if let Promise::Resolved(args) = &awaiting.args { - Ok(Dynamic { - cmd: awaiting.cmd, - args: args.clone(), - - same_validator: awaiting.same_validator, - parent_validator: awaiting.parent_validator, - shape_validator: awaiting.shape_validator, - is_nonce_meaningful: awaiting.is_nonce_meaningful, - }) - } else { - Err(()) - } - } -} - -impl From> for Promised { - fn from(d: Dynamic) -> Self { - Promised { - cmd: d.cmd, - args: Promise::Resolved(d.args), - - same_validator: d.same_validator, - parent_validator: d.parent_validator, - shape_validator: d.shape_validator, - is_nonce_meaningful: d.is_nonce_meaningful, - } - } -} - -impl Checkable for Dynamic -where - F: Fn(&String, &Arguments) -> Result<(), String>, -{ - type Hierarchy = Parentless>; // FIXME I bet we can revover parents -} +// #[derive(Clone, PartialEq)] +// pub struct Generic { +// pub cmd: String, +// pub args: Args, +// // pub is_nonce_meaningful: Fn(&String) -> bool, +// // pub same_validator: Fn(&String, &Arguments) -> Result<(), String>, +// // pub parent_validator: F, // FIXME needs to be a different types, and fall back to Void +// // pub shape_validator: Fn(&String, &Arguments) -> Result<(), String>, // FIXME needs to be a different type +// } -// FIXME Actually, we shoudl go back to wrapping? -// impl CheckSame for Dynamic +// // pub struct DynamicValidator { +// // fn check_shape(self) -> (); +// // // fn check_same: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, +// // // fn check_parents: Fn(&String, &Arguments, &String, &Arguments) -> Result<(), String>, +// // } +// // +// // +// // +// // // FIXME Actually, we shoudl go back to wrapping? +// // // impl CheckSame for Dynamic +// // // where +// // // F: Fn(&String, &Arguments) -> Result<(), String>, +// // // { +// // // type Error = String; +// // // +// // // fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { +// // // let chain_checker = &self.same_validator; +// // // let shape_checker = &self.same_validator; +// // // +// // // shape_checker(&proof.cmd, &proof.args)?; +// // // chain_checker(&proof.cmd, &proof.args) +// // // } +// // // } +// +// impl Debug for Generic { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// f.debug_struct("Generic") +// .field("cmd", &self.cmd) +// .field("args", &self.args) +// .field("is_nonce_meaningful", &self.is_nonce_meaningful) +// .finish() +// } +// } +// +// pub type Dynamic = Generic; +// pub type Promised = Generic, F>; +// +// impl Serialize for Generic // where -// F: Fn(&String, &Arguments) -> Result<(), String>, +// Arguments: From, // { -// type Error = String; +// fn serialize(&self, serializer: S) -> Result +// where +// S: Serializer, +// { +// let mut map = serializer.serialize_map(Some(2))?; +// map.serialize_entry("cmd", &self.cmd)?; +// map.serialize_entry("args", &Arguments::from(self.args.clone()))?; +// map.end() +// } +// } // -// fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { -// let chain_checker = &self.same_validator; -// let shape_checker = &self.same_validator; +// impl<'de, Args: Deserialize<'de>> Deserialize<'de> for Generic { +// fn deserialize(deserializer: D) -> Result, D::Error> +// where +// D: Deserializer<'de>, +// { +// // FIXME +// todo!() +// // let btree = BTreeMap::deserialize(deserializer)?; +// // Ok(Generic { +// // cmd: btree.get("cmd")?.to_string(), +// // args: btree.get("args")?.clone(), +// // is_nonce_meaningful: DefaultTrue::default(), +// // +// // same_validator: (), +// // parent_validator: (), +// // shape_validator: (), +// // }) +// } +// } // -// shape_checker(&proof.cmd, &proof.args)?; -// chain_checker(&proof.cmd, &proof.args) +// impl ToCommand for Generic { +// fn to_command(&self) -> String { +// self.cmd.clone() // } // } - -// #[wasm_bindgen(module = "./ability")] -// extern "C" { -// type JsAbility; // -// // FIXME wrap in func that checks the jsval or better: converts form Ipld -// #[wasm_bindgen(constructor)] -// fn new(cmd: String, args: BTreeMap) -> JsAbility; +// impl Delegatable for Dynamic { +// type Builder = Dynamic; +// } // -// #[wasm_bindgen(method, getter)] -// fn command(this: &JsAbility) -> String; +// impl Resolvable for Dynamic { +// type Promised = Promised; +// } // -// #[wasm_bindgen(method, getter)] -// fn arguments(this: &JsAbility) -> Arguments; +// impl From> for Ipld { +// fn from(generic: Generic) -> Self { +// generic.into() +// } +// } // -// #[wasm_bindgen(method, getter)] -// fn is_nonce_meaningful(this: &JsAbility) -> bool; +// impl TryFrom for Generic { +// type Error = SerdeError; // -// // e.g. reject extra fields -// #[wasm_bindgen(method)] -// fn validate_shape(this: &JsAbility) -> bool; +// fn try_from(ipld: Ipld) -> Result { +// ipld_serde::from_ipld(ipld) +// } +// } // -// // FIXME camels to snakes -// #[wasm_bindgen(method)] -// fn check_same(this: &JsAbility, proof: &JsAbility) -> Result<(), String>; +// impl, F> From> for Arguments { +// fn from(generic: Generic) -> Self { +// generic.args.into() +// } +// } // -// fn check_parents(th.......) +// impl TryFrom> for Dynamic { +// type Error = (); // FIXME +// +// fn try_from(awaiting: Promised) -> Result { +// if let Promise::Resolved(args) = &awaiting.args { +// Ok(Dynamic { +// cmd: awaiting.cmd, +// args: args.clone(), +// +// same_validator: awaiting.same_validator, +// parent_validator: awaiting.parent_validator, +// shape_validator: awaiting.shape_validator, +// is_nonce_meaningful: awaiting.is_nonce_meaningful, +// }) +// } else { +// Err(()) +// } +// } +// } +// +// impl From> for Promised { +// fn from(d: Dynamic) -> Self { +// Promised { +// cmd: d.cmd, +// args: Promise::Resolved(d.args), +// +// same_validator: d.same_validator, +// parent_validator: d.parent_validator, +// shape_validator: d.shape_validator, +// is_nonce_meaningful: d.is_nonce_meaningful, +// } +// } +// } +// +// impl Checkable for Dynamic +// where +// F: Fn(&String, &Arguments) -> Result<(), String>, +// { +// type Hierarchy = Parentless>; // FIXME I bet we can revover parents // } diff --git a/src/ability/js.rs b/src/ability/js.rs new file mode 100644 index 00000000..709b69b7 --- /dev/null +++ b/src/ability/js.rs @@ -0,0 +1,213 @@ +use super::arguments::Arguments; +use crate::proof::same::CheckSame; +use js_sys::Object; +use libipld_core::ipld::Ipld; +use std::collections::BTreeMap; +use wasm_bindgen::prelude::*; + +// FIXME dynamic +pub struct Ability { + pub cmd: String, // FIXME don't need this field because it's on the validator? + // FIXME JsCast for Args or WrappedIpld, esp for Cids + pub args: BTreeMap, // FIXME args + // pub args: wasm_bindgen::JsValue, // js_sys::Object, // BTreeMap, // FIXME args +} + +impl From for js_sys::Object { + fn from(ability: Ability) -> Self { + let args = js_sys::Map::new(); + for (k, v) in ability.args { + args.set(&k.into(), &v); + } + + let map = js_sys::Map::new(); + map.set(&"args".into(), &js_sys::Object::from(args).into()); + map.set(&"cmd".into(), &ability.cmd.into()); + map.into() + } +} + +impl TryFrom for Ability { + type Error = JsValue; + + fn try_from(map: js_sys::Map) -> Result { + if let (Some(cmd), js_args) = ( + map.get(&("cmd".into())).as_string(), + &map.get(&("args".into())), + ) { + let obj_args = js_sys::Object::try_from(js_args).ok_or(wasm_bindgen::JsValue::NULL)?; + let keys = Object::keys(obj_args); + let values = Object::values(obj_args); + + // FIXME come back when TryInto is done... if it matters + let mut args = BTreeMap::new(); + for (k, v) in keys.iter().zip(values) { + if let Some(k) = k.as_string() { + args.insert(k, v); + } else { + return Err(k); + } + } + + Ok(Ability { + cmd, + args: args.clone(), // FIXME kill clone + }) + } else { + Err(JsValue::NULL) // FIXME + } + } +} + +#[wasm_bindgen] +#[derive(Debug, Clone, PartialEq)] +pub struct Validator { + #[wasm_bindgen(skip)] + pub cmd: String, + + #[wasm_bindgen(readonly)] + pub is_nonce_meaningful: bool, + + #[wasm_bindgen(skip)] + pub validate_shape: js_sys::Function, + + #[wasm_bindgen(skip)] + pub check_same: js_sys::Function, + + #[wasm_bindgen(skip)] + pub check_parent: Option, // FIXME explore concrete types + an enum +} + +// NOTE more like a config object +#[wasm_bindgen] +impl Validator { + // FIXME wrap in func that checks the jsval or better: converts form Ipld + // FIXME notes about checking shape on the way in + #[wasm_bindgen(constructor)] + pub fn new( + cmd: String, + is_nonce_meaningful: bool, + validate_shape: js_sys::Function, + check_same: js_sys::Function, + check_parent: Option, + ) -> Validator { + // FIXME chec that JsErr doesn't auto-throw + Validator { + cmd, + is_nonce_meaningful, + validate_shape, + check_same, + check_parent, + } + } + + pub fn command(&self) -> String { + self.cmd.clone() + } + + // e.g. reject extra fields + pub fn validate_shape(&self, args: &wasm_bindgen::JsValue) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.validate_shape.call1(&this, args)?; + Ok(()) + } + + // FIXME only dynamic? + pub fn check_same( + &self, + target: &js_sys::Object, + proof: &js_sys::Object, + ) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + self.check_same.call2(&this, target, proof)?; + Ok(()) + } + + pub fn check_parents( + &self, + target: &js_sys::Object, // FIXME better type, esp for TS? + proof: &js_sys::Object, + ) -> Result<(), JsValue> { + let this = wasm_bindgen::JsValue::NULL; + if let Some(checker) = &self.check_parent { + checker.call2(&this, target, proof)?; + return Ok(()); + } + + Err(this) + } +} + +pub struct Foo { + ability: Ability, + validator: Validator, +} + +impl From for Arguments { + fn from(foo: Foo) -> Self { + todo!() // FIXME + } +} + +use crate::delegation::Delegatable; + +impl Delegatable for Foo { + type Builder = Foo; +} + +impl CheckSame for Foo { + type Error = JsValue; + + fn check_same(&self, proof: &Self) -> Result<(), Self::Error> { + let this_it = self.ability.args.iter().map(|(k, v)| (JsValue::from(k), v)); + + let mut this_args = js_sys::Map::new(); + for (k, v) in this_it { + this_args.set(&k, v); + } + + let proof_it = proof + .ability + .args + .iter() + .map(|(k, v)| (JsValue::from(k), v)); + + let mut proof_args = js_sys::Map::new(); + for (k, v) in proof_it { + proof_args.set(&k, v); + } + + self.validator.check_same( + &Object::from_entries(&this_args)?, + &Object::from_entries(&proof_args)?, + ) + } +} + +// pub struct Ability { +// pub cmd: String, +// pub args: BTreeMap, // FIXME args +// pub val: JsValidator, +// } +// +// #[wasm_bindgen] +// impl Ability { +// #[wasm_bindgen(constructor)] +// fn new( +// cmd: String, +// args: BTreeMap, +// validator: JsValidator, +// ) -> Result { +// let args = args +// .iter() +// .map(|(k, v)| (k.clone(), JsValue::from(v.clone()))) +// .collect(); +// +// validator.check_shape(args)?; +// Ok(Ability { cmd, args, val }) +// } +// } +// +// pub struct Pipeline { +// pub validators: Vec, +// } diff --git a/src/delegation/payload.rs b/src/delegation/payload.rs index 2fb55124..2161dc63 100644 --- a/src/delegation/payload.rs +++ b/src/delegation/payload.rs @@ -222,7 +222,7 @@ struct InternalSerializer { #[serde(rename = "aud")] audience: Did, - #[serde(rename = "can")] + #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] arguments: Arguments, diff --git a/src/invocation/payload.rs b/src/invocation/payload.rs index 5e8ea6a8..2ee9805c 100644 --- a/src/invocation/payload.rs +++ b/src/invocation/payload.rs @@ -110,7 +110,7 @@ struct InternalSerializer { #[serde(rename = "aud", skip_serializing_if = "Option::is_none")] audience: Option, - #[serde(rename = "do")] + #[serde(rename = "cmd")] command: String, #[serde(rename = "args")] arguments: Arguments, diff --git a/src/new_wasm.rs b/src/new_wasm.rs index cc8066fe..760bf027 100644 --- a/src/new_wasm.rs +++ b/src/new_wasm.rs @@ -1,7 +1,7 @@ -use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; -use js_sys; -use serde::{Deserialize, Serialize}; -use wasm_bindgen::prelude::*; +// use crate::{ability::dynamic::Dynamic, task::DefaultTrue}; +// use js_sys; +// use serde::{Deserialize, Serialize}; +// use wasm_bindgen::prelude::*; // #[wasm_bindgen] // type JsDynamic = Dynamic;