use js_sys::{Function, Object, Reflect};
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
type JsGetter = Rc<dyn Fn(&JsValue) -> JsValue>;
type JsSetter = Rc<dyn Fn(&JsValue, JsValue) -> JsValue>;
type JsOverAll = Rc<dyn Fn(&JsValue, &Function) -> JsValue>;
#[wasm_bindgen]
pub struct JsLens {
pub(crate) get_fn: JsGetter,
pub(crate) set_fn: JsSetter,
}
#[wasm_bindgen]
impl JsLens {
#[wasm_bindgen]
pub fn get(&self, source: &JsValue) -> JsValue {
(self.get_fn)(source)
}
#[wasm_bindgen]
pub fn set(&self, source: &JsValue, value: JsValue) -> JsValue {
(self.set_fn)(source, value)
}
#[wasm_bindgen]
pub fn over(&self, source: &JsValue, f: &Function) -> JsValue {
let current = self.get(source);
let this = JsValue::null();
let updated = f.call1(&this, ¤t).unwrap_or_else(|_| current.clone());
self.set(source, updated)
}
#[wasm_bindgen]
pub fn compose(&self, other: &JsLens) -> JsLens {
let self_get = self.get_fn.clone();
let self_set = self.set_fn.clone();
let other_get = other.get_fn.clone();
let other_set = other.set_fn.clone();
let self_get_2 = self_get.clone();
JsLens {
get_fn: Rc::new(move |source: &JsValue| {
let intermediate = self_get(source);
other_get(&intermediate)
}),
set_fn: Rc::new(move |source: &JsValue, value: JsValue| {
let intermediate = self_get_2(source);
let updated_intermediate = other_set(&intermediate, value);
self_set(source, updated_intermediate)
}),
}
}
}
#[wasm_bindgen]
pub fn lens(prop: &str) -> JsLens {
let prop_get = prop.to_string();
let prop_set = prop.to_string();
JsLens {
get_fn: Rc::new(move |source: &JsValue| {
if let Some(obj) = source.dyn_ref::<Object>() {
Reflect::get(obj, &JsValue::from_str(&prop_get)).unwrap_or(JsValue::undefined())
} else {
JsValue::undefined()
}
}),
set_fn: Rc::new(move |source: &JsValue, value: JsValue| {
if let Some(obj) = source.dyn_ref::<Object>() {
let new_obj = Object::assign(&Object::new(), obj);
let _ = Reflect::set(&new_obj, &JsValue::from_str(&prop_set), &value);
new_obj.into()
} else {
source.clone()
}
}),
}
}
#[wasm_bindgen(js_name = lensPath)]
pub fn lens_path(path: &JsValue) -> Result<JsLens, JsValue> {
let arr = path
.dyn_ref::<js_sys::Array>()
.ok_or_else(|| JsValue::from_str("path must be an array"))?;
if arr.length() == 0 {
return Err(JsValue::from_str("path cannot be empty"));
}
let first_prop = arr
.get(0)
.as_string()
.ok_or_else(|| JsValue::from_str("path elements must be strings"))?;
let mut result = lens(&first_prop);
for i in 1..arr.length() {
let prop = arr
.get(i)
.as_string()
.ok_or_else(|| JsValue::from_str("path elements must be strings"))?;
let next_lens = lens(&prop);
result = result.compose(&next_lens);
}
Ok(result)
}
#[wasm_bindgen]
pub struct JsOptional {
pub(crate) get_fn: JsGetter,
pub(crate) set_fn: JsSetter,
}
#[wasm_bindgen]
impl JsOptional {
#[wasm_bindgen]
pub fn get(&self, source: &JsValue) -> JsValue {
(self.get_fn)(source)
}
#[wasm_bindgen(js_name = getOr)]
pub fn get_or(&self, source: &JsValue, default: JsValue) -> JsValue {
let value = self.get(source);
if value.is_undefined() || value.is_null() {
default
} else {
value
}
}
#[wasm_bindgen]
pub fn set(&self, source: &JsValue, value: JsValue) -> JsValue {
(self.set_fn)(source, value)
}
#[wasm_bindgen]
pub fn over(&self, source: &JsValue, f: &Function) -> JsValue {
let current = self.get(source);
if current.is_undefined() || current.is_null() {
source.clone()
} else {
let this = JsValue::null();
let updated = f.call1(&this, ¤t).unwrap_or_else(|_| current.clone());
self.set(source, updated)
}
}
}
#[wasm_bindgen]
pub fn optional(prop: &str) -> JsOptional {
let prop_get = prop.to_string();
let prop_set = prop.to_string();
JsOptional {
get_fn: Rc::new(move |source: &JsValue| {
if let Some(obj) = source.dyn_ref::<Object>() {
Reflect::get(obj, &JsValue::from_str(&prop_get)).unwrap_or(JsValue::undefined())
} else {
JsValue::undefined()
}
}),
set_fn: Rc::new(move |source: &JsValue, value: JsValue| {
if let Some(obj) = source.dyn_ref::<Object>() {
let new_obj = Object::assign(&Object::new(), obj);
let _ = Reflect::set(&new_obj, &JsValue::from_str(&prop_set), &value);
new_obj.into()
} else {
source.clone()
}
}),
}
}
#[wasm_bindgen]
pub struct JsPrism {
preview_fn: Rc<dyn Fn(&JsValue) -> JsValue>,
review_fn: Rc<dyn Fn(JsValue) -> JsValue>,
}
#[wasm_bindgen]
impl JsPrism {
#[wasm_bindgen]
pub fn preview(&self, source: &JsValue) -> JsValue {
(self.preview_fn)(source)
}
#[wasm_bindgen]
pub fn review(&self, value: JsValue) -> JsValue {
(self.review_fn)(value)
}
#[wasm_bindgen]
pub fn over(&self, source: &JsValue, f: &Function) -> JsValue {
let current = self.preview(source);
if current.is_undefined() || current.is_null() {
source.clone()
} else {
let this = JsValue::null();
let updated = f.call1(&this, ¤t).unwrap_or_else(|_| current.clone());
self.review(updated)
}
}
}
#[wasm_bindgen]
pub fn prism(match_fn: &Function, build_fn: &Function) -> JsPrism {
let match_fn = match_fn.clone();
let build_fn = build_fn.clone();
JsPrism {
preview_fn: Rc::new(move |source: &JsValue| {
let this = JsValue::null();
match_fn
.call1(&this, source)
.unwrap_or(JsValue::undefined())
}),
review_fn: Rc::new(move |value: JsValue| {
let this = JsValue::null();
build_fn
.call1(&this, &value)
.unwrap_or(JsValue::undefined())
}),
}
}
#[wasm_bindgen]
pub struct JsIso {
to_fn: Rc<dyn Fn(&JsValue) -> JsValue>,
from_fn: Rc<dyn Fn(JsValue) -> JsValue>,
}
#[wasm_bindgen]
impl JsIso {
#[wasm_bindgen]
pub fn to(&self, source: &JsValue) -> JsValue {
(self.to_fn)(source)
}
#[wasm_bindgen]
pub fn from(&self, value: JsValue) -> JsValue {
(self.from_fn)(value)
}
#[wasm_bindgen]
pub fn over(&self, source: &JsValue, f: &Function) -> JsValue {
let target = self.to(source);
let this = JsValue::null();
let updated = f.call1(&this, &target).unwrap_or_else(|_| target.clone());
self.from(updated)
}
#[wasm_bindgen]
pub fn reverse(&self) -> JsIso {
let original_to = self.to_fn.clone();
let original_from = self.from_fn.clone();
JsIso {
to_fn: Rc::new(move |a: &JsValue| (original_from)(a.clone())),
from_fn: Rc::new(move |s: JsValue| (original_to)(&s)),
}
}
}
#[wasm_bindgen]
pub fn iso(to_fn: &Function, from_fn: &Function) -> JsIso {
let to_fn = to_fn.clone();
let from_fn = from_fn.clone();
JsIso {
to_fn: Rc::new(move |source: &JsValue| {
let this = JsValue::null();
to_fn.call1(&this, source).unwrap_or(JsValue::undefined())
}),
from_fn: Rc::new(move |value: JsValue| {
let this = JsValue::null();
from_fn.call1(&this, &value).unwrap_or(JsValue::undefined())
}),
}
}
#[wasm_bindgen]
pub struct JsFold {
fold_fn: Rc<dyn Fn(&JsValue) -> JsValue>,
}
#[wasm_bindgen]
impl JsFold {
#[wasm_bindgen(js_name = foldOf)]
pub fn fold_of(&self, source: &JsValue) -> JsValue {
(self.fold_fn)(source)
}
#[wasm_bindgen(js_name = isEmpty)]
pub fn is_empty(&self, source: &JsValue) -> bool {
let result = self.fold_of(source);
if let Some(arr) = result.dyn_ref::<js_sys::Array>() {
arr.length() == 0
} else {
true
}
}
#[wasm_bindgen]
pub fn length(&self, source: &JsValue) -> u32 {
let result = self.fold_of(source);
if let Some(arr) = result.dyn_ref::<js_sys::Array>() {
arr.length()
} else {
0
}
}
#[wasm_bindgen]
pub fn first(&self, source: &JsValue) -> JsValue {
let result = self.fold_of(source);
if let Some(arr) = result.dyn_ref::<js_sys::Array>() {
if arr.length() > 0 {
arr.get(0)
} else {
JsValue::undefined()
}
} else {
JsValue::undefined()
}
}
}
#[wasm_bindgen]
pub fn fold(extract_fn: &Function) -> JsFold {
let extract_fn = extract_fn.clone();
JsFold {
fold_fn: Rc::new(move |source: &JsValue| {
let this = JsValue::null();
extract_fn
.call1(&this, source)
.unwrap_or(JsValue::undefined())
}),
}
}
#[wasm_bindgen]
pub struct JsTraversal {
get_all_fn: Rc<dyn Fn(&JsValue) -> JsValue>,
over_all_fn: JsOverAll,
}
#[wasm_bindgen]
impl JsTraversal {
#[wasm_bindgen(js_name = getAll)]
pub fn get_all(&self, source: &JsValue) -> JsValue {
(self.get_all_fn)(source)
}
#[wasm_bindgen(js_name = overAll)]
pub fn over_all(&self, source: &JsValue, f: &Function) -> JsValue {
(self.over_all_fn)(source, f)
}
#[wasm_bindgen(js_name = setAll)]
pub fn set_all(&self, source: &JsValue, value: JsValue) -> JsValue {
let const_fn = Function::new_with_args("_", "return this");
let bound: Function = const_fn
.bind1(&value, &JsValue::undefined())
.unchecked_into();
(self.over_all_fn)(source, &bound)
}
}
#[wasm_bindgen]
pub fn traversal(get_all_fn: &Function, over_all_fn: &Function) -> JsTraversal {
let get_all_fn = get_all_fn.clone();
let over_all_fn = over_all_fn.clone();
JsTraversal {
get_all_fn: Rc::new(move |source: &JsValue| {
let this = JsValue::null();
get_all_fn
.call1(&this, source)
.unwrap_or(JsValue::undefined())
}),
over_all_fn: Rc::new(move |source: &JsValue, f: &Function| {
let this = JsValue::null();
over_all_fn
.call2(&this, source, f)
.unwrap_or(source.clone())
}),
}
}
#[cfg(test)]
mod tests {
}