use std::collections::HashMap;
use wasm_bindgen::prelude::*;
use crate::pattern::{Pattern, ValidationError};
use crate::subject::{RangeValue, Subject, Symbol, Value};
#[wasm_bindgen]
pub struct WasmValidationRules {
#[wasm_bindgen(skip)]
pub max_depth: Option<usize>,
#[wasm_bindgen(skip)]
pub max_elements: Option<usize>,
}
#[wasm_bindgen]
impl WasmValidationRules {
#[wasm_bindgen(constructor)]
pub fn new(max_depth: JsValue, max_elements: JsValue) -> WasmValidationRules {
let max_depth = js_to_option(&max_depth, js_to_i64).map(|v| v as usize);
let max_elements = js_to_option(&max_elements, js_to_i64).map(|v| v as usize);
WasmValidationRules {
max_depth,
max_elements,
}
}
#[wasm_bindgen(getter, js_name = maxDepth)]
pub fn max_depth(&self) -> JsValue {
match self.max_depth {
Some(d) => JsValue::from_f64(d as f64),
None => JsValue::undefined(),
}
}
#[wasm_bindgen(getter, js_name = maxElements)]
pub fn max_elements(&self) -> JsValue {
match self.max_elements {
Some(e) => JsValue::from_f64(e as f64),
None => JsValue::undefined(),
}
}
}
impl WasmValidationRules {
fn to_internal(&self) -> crate::pattern::ValidationRules {
crate::pattern::ValidationRules {
max_depth: self.max_depth,
max_elements: self.max_elements,
required_fields: vec![], }
}
}
#[wasm_bindgen]
pub struct WasmStructureAnalysis {
#[wasm_bindgen(skip)]
inner: crate::pattern::StructureAnalysis,
}
#[wasm_bindgen]
impl WasmStructureAnalysis {
#[wasm_bindgen(getter, js_name = depthDistribution)]
pub fn depth_distribution(&self) -> js_sys::Array {
let arr = js_sys::Array::new();
for count in &self.inner.depth_distribution {
arr.push(&JsValue::from_f64(*count as f64));
}
arr
}
#[wasm_bindgen(getter, js_name = elementCounts)]
pub fn element_counts(&self) -> js_sys::Array {
let arr = js_sys::Array::new();
for count in &self.inner.element_counts {
arr.push(&JsValue::from_f64(*count as f64));
}
arr
}
#[wasm_bindgen(getter, js_name = nestingPatterns)]
pub fn nesting_patterns(&self) -> js_sys::Array {
let arr = js_sys::Array::new();
for pattern in &self.inner.nesting_patterns {
arr.push(&JsValue::from_str(pattern));
}
arr
}
#[wasm_bindgen(getter)]
pub fn summary(&self) -> String {
self.inner.summary.clone()
}
}
pub fn either_right(value: JsValue) -> JsValue {
let obj = js_sys::Object::new();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("_tag"),
&JsValue::from_str("Right"),
)
.expect("set _tag");
js_sys::Reflect::set(&obj, &JsValue::from_str("right"), &value).expect("set right");
obj.into()
}
pub fn either_left(error: JsValue) -> JsValue {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("_tag"), &JsValue::from_str("Left"))
.expect("set _tag");
js_sys::Reflect::set(&obj, &JsValue::from_str("left"), &error).expect("set left");
obj.into()
}
pub fn result_to_either<T, E>(result: Result<T, E>) -> JsValue
where
T: Into<JsValue>,
E: Into<JsValue>,
{
match result {
Ok(value) => either_right(value.into()),
Err(error) => either_left(error.into()),
}
}
pub fn validation_error_to_js(error: &ValidationError) -> JsValue {
let obj = js_sys::Object::new();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("message"),
&JsValue::from_str(&error.message),
)
.expect("set message");
js_sys::Reflect::set(
&obj,
&JsValue::from_str("ruleViolated"),
&JsValue::from_str(&error.rule_violated),
)
.expect("set ruleViolated");
let location_arr = js_sys::Array::new();
for loc in &error.location {
location_arr.push(&JsValue::from_str(loc));
}
js_sys::Reflect::set(&obj, &JsValue::from_str("location"), &location_arr)
.expect("set location");
obj.into()
}
pub fn js_to_string(value: &JsValue) -> Option<String> {
value.as_string()
}
pub fn js_to_i64(value: &JsValue) -> Option<i64> {
value.as_f64().map(|f| f as i64)
}
pub fn js_to_f64(value: &JsValue) -> Option<f64> {
value.as_f64()
}
pub fn js_to_bool(value: &JsValue) -> Option<bool> {
value.as_bool()
}
pub fn js_is_null_or_undefined(value: &JsValue) -> bool {
value.is_null() || value.is_undefined()
}
pub fn js_to_option<T, F>(value: &JsValue, convert: F) -> Option<T>
where
F: FnOnce(&JsValue) -> Option<T>,
{
if js_is_null_or_undefined(value) {
None
} else {
convert(value)
}
}
pub fn js_object_to_value_map(obj: &JsValue) -> Result<HashMap<String, Value>, String> {
if !obj.is_object() || obj.is_null() {
return Err("Expected an object".to_string());
}
let mut map = HashMap::new();
let keys = js_sys::Object::keys(obj.unchecked_ref::<js_sys::Object>());
for i in 0..keys.length() {
let key = keys.get(i);
let key_str = key
.as_string()
.ok_or_else(|| "Object key is not a string".to_string())?;
let value = js_sys::Reflect::get(obj, &key).map_err(|_| "Failed to get property")?;
let rust_value = js_to_value(&value)?;
map.insert(key_str, rust_value);
}
Ok(map)
}
pub fn js_to_value(js: &JsValue) -> Result<Value, String> {
if js.is_null() || js.is_undefined() {
return Err("Cannot convert null/undefined to Value".to_string());
}
if let Some(b) = js.as_bool() {
return Ok(Value::VBoolean(b));
}
if let Some(s) = js.as_string() {
return Ok(Value::VString(s));
}
if let Some(n) = js.as_f64() {
if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
return Ok(Value::VInteger(n as i64));
}
return Ok(Value::VDecimal(n));
}
if js_sys::Array::is_array(js) {
let arr: &js_sys::Array = js.unchecked_ref();
let mut values = Vec::with_capacity(arr.length() as usize);
for i in 0..arr.length() {
let item = arr.get(i);
values.push(js_to_value(&item)?);
}
return Ok(Value::VArray(values));
}
if js.is_object() {
let obj: &js_sys::Object = js.unchecked_ref();
let has_lower = js_sys::Reflect::has(obj, &JsValue::from_str("lower")).unwrap_or(false);
let has_upper = js_sys::Reflect::has(obj, &JsValue::from_str("upper")).unwrap_or(false);
if has_lower || has_upper {
let lower = js_sys::Reflect::get(obj, &JsValue::from_str("lower"))
.ok()
.and_then(|v| js_to_option(&v, js_to_f64));
let upper = js_sys::Reflect::get(obj, &JsValue::from_str("upper"))
.ok()
.and_then(|v| js_to_option(&v, js_to_f64));
if lower.is_some() || upper.is_some() {
return Ok(Value::VRange(RangeValue { lower, upper }));
}
}
let has_value = js_sys::Reflect::has(obj, &JsValue::from_str("value")).unwrap_or(false);
let has_unit = js_sys::Reflect::has(obj, &JsValue::from_str("unit")).unwrap_or(false);
if has_value && has_unit {
let value = js_sys::Reflect::get(obj, &JsValue::from_str("value"))
.ok()
.and_then(|v| js_to_f64(&v));
let unit = js_sys::Reflect::get(obj, &JsValue::from_str("unit"))
.ok()
.and_then(|v| js_to_string(&v));
if let (Some(v), Some(u)) = (value, unit) {
return Ok(Value::VMeasurement { unit: u, value: v });
}
}
let has_tag = js_sys::Reflect::has(obj, &JsValue::from_str("tag")).unwrap_or(false);
let has_content = js_sys::Reflect::has(obj, &JsValue::from_str("content")).unwrap_or(false);
if has_tag && has_content {
let tag = js_sys::Reflect::get(obj, &JsValue::from_str("tag"))
.ok()
.and_then(|v| js_to_string(&v));
let content = js_sys::Reflect::get(obj, &JsValue::from_str("content"))
.ok()
.and_then(|v| js_to_string(&v));
if let (Some(t), Some(c)) = (tag, content) {
return Ok(Value::VTaggedString { tag: t, content: c });
}
}
let type_field = js_sys::Reflect::get(obj, &JsValue::from_str("_type"))
.ok()
.and_then(|v| js_to_string(&v));
if type_field.as_deref() == Some("symbol") {
let symbol_value = js_sys::Reflect::get(obj, &JsValue::from_str("value"))
.ok()
.and_then(|v| js_to_string(&v));
if let Some(s) = symbol_value {
return Ok(Value::VSymbol(s));
}
}
let map = js_object_to_value_map(js)?;
return Ok(Value::VMap(map));
}
Err(format!("Cannot convert JS value to Value: {:?}", js))
}
pub fn value_to_js(value: &Value) -> JsValue {
match value {
Value::VString(s) => JsValue::from_str(s),
Value::VInteger(i) => JsValue::from_f64(*i as f64),
Value::VDecimal(d) => JsValue::from_f64(*d),
Value::VBoolean(b) => JsValue::from_bool(*b),
Value::VSymbol(s) => {
let obj = js_sys::Object::new();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("_type"),
&JsValue::from_str("symbol"),
)
.expect("set _type");
js_sys::Reflect::set(&obj, &JsValue::from_str("value"), &JsValue::from_str(s))
.expect("set value");
obj.into()
}
Value::VTaggedString { tag, content } => {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("tag"), &JsValue::from_str(tag))
.expect("set tag");
js_sys::Reflect::set(
&obj,
&JsValue::from_str("content"),
&JsValue::from_str(content),
)
.expect("set content");
obj.into()
}
Value::VArray(arr) => {
let js_arr = js_sys::Array::new();
for item in arr {
js_arr.push(&value_to_js(item));
}
js_arr.into()
}
Value::VMap(map) => {
let obj = js_sys::Object::new();
for (key, val) in map {
js_sys::Reflect::set(&obj, &JsValue::from_str(key), &value_to_js(val))
.expect("set map entry");
}
obj.into()
}
Value::VRange(range) => {
let obj = js_sys::Object::new();
if let Some(lower) = range.lower {
js_sys::Reflect::set(&obj, &JsValue::from_str("lower"), &JsValue::from_f64(lower))
.expect("set lower");
}
if let Some(upper) = range.upper {
js_sys::Reflect::set(&obj, &JsValue::from_str("upper"), &JsValue::from_f64(upper))
.expect("set upper");
}
obj.into()
}
Value::VMeasurement { unit, value } => {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("unit"), &JsValue::from_str(unit))
.expect("set unit");
js_sys::Reflect::set(
&obj,
&JsValue::from_str("value"),
&JsValue::from_f64(*value),
)
.expect("set value");
obj.into()
}
}
}
pub fn js_array_to_strings(arr: &JsValue) -> Result<Vec<String>, String> {
if !js_sys::Array::is_array(arr) {
return Err("Expected an array".to_string());
}
let arr: &js_sys::Array = arr.unchecked_ref();
let mut result = Vec::with_capacity(arr.length() as usize);
for i in 0..arr.length() {
let item = arr.get(i);
let s = item
.as_string()
.ok_or_else(|| format!("Array item at index {} is not a string", i))?;
result.push(s);
}
Ok(result)
}
pub fn strings_to_js_array(strings: &[String]) -> JsValue {
let arr = js_sys::Array::new();
for s in strings {
arr.push(&JsValue::from_str(s));
}
arr.into()
}
pub fn value_map_to_js(map: &HashMap<String, Value>) -> JsValue {
let obj = js_sys::Object::new();
for (key, val) in map {
js_sys::Reflect::set(&obj, &JsValue::from_str(key), &value_to_js(val))
.expect("set property");
}
obj.into()
}
#[wasm_bindgen(js_name = Value)]
pub struct ValueFactory;
#[wasm_bindgen]
impl ValueFactory {
#[wasm_bindgen(js_name = string)]
pub fn string(s: &str) -> JsValue {
value_to_js(&Value::VString(s.to_string()))
}
#[wasm_bindgen(js_name = int)]
pub fn int(n: f64) -> JsValue {
value_to_js(&Value::VInteger(n as i64))
}
#[wasm_bindgen(js_name = decimal)]
pub fn decimal(n: f64) -> JsValue {
value_to_js(&Value::VDecimal(n))
}
#[wasm_bindgen(js_name = boolean)]
pub fn boolean(b: bool) -> JsValue {
value_to_js(&Value::VBoolean(b))
}
#[wasm_bindgen(js_name = symbol)]
pub fn symbol(s: &str) -> JsValue {
value_to_js(&Value::VSymbol(s.to_string()))
}
#[wasm_bindgen(js_name = array)]
pub fn array(arr: &JsValue) -> Result<JsValue, JsValue> {
if !js_sys::Array::is_array(arr) {
return Err(JsValue::from_str("Expected an array"));
}
let js_arr: &js_sys::Array = arr.unchecked_ref();
let mut values = Vec::with_capacity(js_arr.length() as usize);
for i in 0..js_arr.length() {
let item = js_arr.get(i);
let val = js_to_value(&item).map_err(|e| JsValue::from_str(&e))?;
values.push(val);
}
Ok(value_to_js(&Value::VArray(values)))
}
#[wasm_bindgen(js_name = map)]
pub fn map(obj: &JsValue) -> Result<JsValue, JsValue> {
let map = js_object_to_value_map(obj).map_err(|e| JsValue::from_str(&e))?;
Ok(value_to_js(&Value::VMap(map)))
}
#[wasm_bindgen(js_name = range)]
pub fn range(lower: JsValue, upper: JsValue) -> JsValue {
let lower_val = js_to_option(&lower, js_to_f64);
let upper_val = js_to_option(&upper, js_to_f64);
value_to_js(&Value::VRange(RangeValue {
lower: lower_val,
upper: upper_val,
}))
}
#[wasm_bindgen(js_name = measurement)]
pub fn measurement(value: f64, unit: &str) -> JsValue {
value_to_js(&Value::VMeasurement {
unit: unit.to_string(),
value,
})
}
}
#[wasm_bindgen]
pub struct WasmSubject {
inner: Subject,
}
#[wasm_bindgen]
impl WasmSubject {
#[wasm_bindgen(constructor)]
pub fn new(
identity: &str,
labels: &JsValue,
properties: &JsValue,
) -> Result<WasmSubject, JsValue> {
let labels_vec = js_array_to_strings(labels).map_err(|e| JsValue::from_str(&e))?;
let labels_set: std::collections::HashSet<String> = labels_vec.into_iter().collect();
let props = js_object_to_value_map(properties).map_err(|e| JsValue::from_str(&e))?;
Ok(WasmSubject {
inner: Subject {
identity: Symbol(identity.to_string()),
labels: labels_set,
properties: props,
},
})
}
#[wasm_bindgen(getter)]
pub fn identity(&self) -> String {
self.inner.identity.0.clone()
}
#[wasm_bindgen(getter)]
pub fn labels(&self) -> js_sys::Set {
let set = js_sys::Set::new(&JsValue::undefined());
for label in &self.inner.labels {
set.add(&JsValue::from_str(label));
}
set
}
#[wasm_bindgen(getter)]
pub fn properties(&self) -> JsValue {
value_map_to_js(&self.inner.properties)
}
}
impl WasmSubject {
pub fn to_js_value(&self) -> JsValue {
let obj = js_sys::Object::new();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("identity"),
&JsValue::from_str(&self.inner.identity.0),
)
.expect("set identity");
let labels_set = js_sys::Set::new(&JsValue::undefined());
for label in &self.inner.labels {
labels_set.add(&JsValue::from_str(label));
}
js_sys::Reflect::set(&obj, &JsValue::from_str("labels"), &labels_set).expect("set labels");
js_sys::Reflect::set(
&obj,
&JsValue::from_str("properties"),
&value_map_to_js(&self.inner.properties),
)
.expect("set properties");
js_sys::Reflect::set(
&obj,
&JsValue::from_str("_type"),
&JsValue::from_str("Subject"),
)
.expect("set _type");
obj.into()
}
pub fn from_js_value(value: &JsValue) -> Option<Self> {
if !value.is_object() {
return None;
}
let obj: &js_sys::Object = value.unchecked_ref();
let type_marker = js_sys::Reflect::get(obj, &JsValue::from_str("_type")).ok();
let is_plain_subject = type_marker
.as_ref()
.and_then(|v| v.as_string())
.map(|s| s == "Subject")
.unwrap_or(false);
let has_wbg_ptr = js_sys::Reflect::get(obj, &JsValue::from_str("__wbg_ptr"))
.ok()
.map(|v| !v.is_undefined() && !v.is_null())
.unwrap_or(false);
if !is_plain_subject && !has_wbg_ptr {
return None;
}
let identity_js = js_sys::Reflect::get(obj, &JsValue::from_str("identity")).ok()?;
let identity = identity_js.as_string()?;
let labels_js = js_sys::Reflect::get(obj, &JsValue::from_str("labels")).ok()?;
let labels_set: std::collections::HashSet<String> = if js_sys::Array::is_array(&labels_js) {
js_array_to_strings(&labels_js).ok()?.into_iter().collect()
} else {
let set: &js_sys::Set = labels_js.unchecked_ref();
let mut result = std::collections::HashSet::new();
set.for_each(&mut |v, _, _| {
if let Some(s) = v.as_string() {
result.insert(s);
}
});
result
};
let properties_js = js_sys::Reflect::get(obj, &JsValue::from_str("properties")).ok()?;
let properties = js_object_to_value_map(&properties_js).ok()?;
Some(WasmSubject {
inner: Subject {
identity: Symbol(identity),
labels: labels_set,
properties,
},
})
}
pub fn from_subject(subject: Subject) -> Self {
WasmSubject { inner: subject }
}
pub fn into_subject(self) -> Subject {
self.inner
}
pub fn as_subject(&self) -> &Subject {
&self.inner
}
}
#[wasm_bindgen]
#[derive(Clone)]
pub struct WasmPattern {
inner: Pattern<JsValue>,
}
#[wasm_bindgen]
impl WasmPattern {
#[wasm_bindgen(js_name = point)]
pub fn point(value: JsValue) -> WasmPattern {
WasmPattern {
inner: Pattern::point(value),
}
}
#[wasm_bindgen(js_name = of)]
pub fn of(value: JsValue) -> WasmPattern {
Self::point(value) }
#[wasm_bindgen(js_name = pattern)]
pub fn pattern(value: JsValue) -> WasmPattern {
WasmPattern {
inner: Pattern::pattern(value, vec![]),
}
}
#[wasm_bindgen(js_name = addElement)]
pub fn add_element(&mut self, element: &WasmPattern) {
let mut elements = self.inner.elements().to_vec();
elements.push(element.inner.clone());
self.inner = Pattern::pattern(self.inner.value().clone(), elements);
}
#[wasm_bindgen(js_name = fromValues)]
pub fn from_values(values: &JsValue) -> Result<js_sys::Array, JsValue> {
if !js_sys::Array::is_array(values) {
return Err(JsValue::from_str("Values must be an array"));
}
let arr: &js_sys::Array = values.unchecked_ref();
let result = js_sys::Array::new();
for i in 0..arr.length() {
let item = arr.get(i);
let pattern = WasmPattern::point(item);
result.push(&JsValue::from(pattern));
}
Ok(result)
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> JsValue {
self.inner.value().clone()
}
#[wasm_bindgen(getter)]
pub fn identity(&self) -> Option<String> {
WasmSubject::from_js_value(self.inner.value()).map(|s| s.inner.identity.0.clone())
}
#[wasm_bindgen(getter)]
pub fn elements(&self) -> js_sys::Array {
let result = js_sys::Array::new();
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
result.push(&JsValue::from(wasm_elem));
}
result
}
#[wasm_bindgen(js_name = getElement)]
pub fn get_element(&self, index: usize) -> Option<WasmPattern> {
self.inner.elements().get(index).map(|elem| WasmPattern {
inner: elem.clone(),
})
}
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
self.inner.length()
}
#[wasm_bindgen(js_name = isAtomic)]
pub fn is_atomic(&self) -> bool {
self.inner.is_atomic()
}
#[wasm_bindgen(js_name = size)]
pub fn size(&self) -> usize {
self.inner.size()
}
#[wasm_bindgen(js_name = depth)]
pub fn depth(&self) -> usize {
self.inner.depth()
}
#[wasm_bindgen(js_name = values)]
pub fn values(&self) -> js_sys::Array {
let mut result = js_sys::Array::new();
self.values_recursive(&mut result);
result
}
fn values_recursive(&self, result: &mut js_sys::Array) {
result.push(&self.inner.value().clone());
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
wasm_elem.values_recursive(result);
}
}
#[wasm_bindgen(js_name = anyValue)]
pub fn any_value(&self, predicate: &js_sys::Function) -> Result<bool, JsValue> {
self.any_value_recursive(predicate)
}
fn any_value_recursive(&self, predicate: &js_sys::Function) -> Result<bool, JsValue> {
let this = JsValue::null();
let result = predicate
.call1(&this, &self.inner.value().clone())
.map_err(|e| JsValue::from_str(&format!("Predicate error: {:?}", e)))?;
if let Some(true) = result.as_bool() {
return Ok(true);
}
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
if wasm_elem.any_value_recursive(predicate)? {
return Ok(true);
}
}
Ok(false)
}
#[wasm_bindgen(js_name = allValues)]
pub fn all_values(&self, predicate: &js_sys::Function) -> Result<bool, JsValue> {
self.all_values_recursive(predicate)
}
fn all_values_recursive(&self, predicate: &js_sys::Function) -> Result<bool, JsValue> {
let this = JsValue::null();
let result = predicate
.call1(&this, &self.inner.value().clone())
.map_err(|e| JsValue::from_str(&format!("Predicate error: {:?}", e)))?;
if let Some(false) = result.as_bool() {
return Ok(false);
}
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
if !wasm_elem.all_values_recursive(predicate)? {
return Ok(false);
}
}
Ok(true)
}
#[wasm_bindgen(js_name = filter)]
pub fn filter(&self, predicate: &js_sys::Function) -> Result<js_sys::Array, JsValue> {
let result = js_sys::Array::new();
self.filter_recursive(predicate, &result)?;
Ok(result)
}
fn filter_recursive(
&self,
predicate: &js_sys::Function,
result: &js_sys::Array,
) -> Result<(), JsValue> {
let this = JsValue::null();
let wasm_pattern = WasmPattern {
inner: self.inner.clone(),
};
let matches = predicate
.call1(&this, &JsValue::from(wasm_pattern.clone()))
.map_err(|e| JsValue::from_str(&format!("Predicate error: {:?}", e)))?;
if let Some(true) = matches.as_bool() {
result.push(&JsValue::from(wasm_pattern));
}
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
wasm_elem.filter_recursive(predicate, result)?;
}
Ok(())
}
#[wasm_bindgen(js_name = findFirst)]
pub fn find_first(&self, predicate: &js_sys::Function) -> Result<JsValue, JsValue> {
self.find_first_recursive(predicate)
}
fn find_first_recursive(&self, predicate: &js_sys::Function) -> Result<JsValue, JsValue> {
let this = JsValue::null();
let wasm_pattern = WasmPattern {
inner: self.inner.clone(),
};
let matches = predicate
.call1(&this, &JsValue::from(wasm_pattern.clone()))
.map_err(|e| JsValue::from_str(&format!("Predicate error: {:?}", e)))?;
if let Some(true) = matches.as_bool() {
return Ok(JsValue::from(wasm_pattern));
}
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
let found = wasm_elem.find_first_recursive(predicate)?;
if !found.is_null() {
return Ok(found);
}
}
Ok(JsValue::null())
}
#[wasm_bindgen(js_name = matches)]
pub fn matches(&self, other: &WasmPattern) -> bool {
self.matches_recursive(&other.inner)
}
fn matches_recursive(&self, other: &Pattern<JsValue>) -> bool {
let self_val = self.inner.value();
let other_val = other.value();
if let (Some(s1), Some(s2)) = (self_val.as_string(), other_val.as_string()) {
if s1 != s2 {
return false;
}
} else if let (Some(n1), Some(n2)) = (self_val.as_f64(), other_val.as_f64()) {
if n1 != n2 {
return false;
}
} else if let (Some(b1), Some(b2)) = (self_val.as_bool(), other_val.as_bool()) {
if b1 != b2 {
return false;
}
} else {
let eq = js_sys::Reflect::get(&js_sys::Object::new(), &JsValue::from_str("equals"))
.ok()
.and_then(|_| {
let s1 = js_sys::JSON::stringify(self_val).ok()?;
let s2 = js_sys::JSON::stringify(other_val).ok()?;
Some(s1.as_string()? == s2.as_string()?)
})
.unwrap_or(false);
if !eq {
return false;
}
}
if self.inner.elements().len() != other.elements().len() {
return false;
}
for (self_elem, other_elem) in self.inner.elements().iter().zip(other.elements().iter()) {
let wasm_self = WasmPattern {
inner: self_elem.clone(),
};
if !wasm_self.matches_recursive(other_elem) {
return false;
}
}
true
}
#[wasm_bindgen(js_name = contains)]
pub fn contains(&self, subpattern: &WasmPattern) -> bool {
self.contains_recursive(&subpattern.inner)
}
fn contains_recursive(&self, subpattern: &Pattern<JsValue>) -> bool {
if self.matches_recursive(subpattern) {
return true;
}
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
if wasm_elem.contains_recursive(subpattern) {
return true;
}
}
false
}
#[wasm_bindgen(js_name = map)]
pub fn map(&self, f: &js_sys::Function) -> Result<WasmPattern, JsValue> {
self.map_recursive(f)
}
fn map_recursive(&self, f: &js_sys::Function) -> Result<WasmPattern, JsValue> {
let this = JsValue::null();
let new_value = f
.call1(&this, &self.inner.value().clone())
.map_err(|e| JsValue::from_str(&format!("Map function error: {:?}", e)))?;
let mut new_elements = Vec::new();
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
let mapped_elem = wasm_elem.map_recursive(f)?;
new_elements.push(mapped_elem.inner);
}
Ok(WasmPattern {
inner: Pattern::pattern(new_value, new_elements),
})
}
#[wasm_bindgen(js_name = fold)]
pub fn fold(&self, init: JsValue, f: &js_sys::Function) -> Result<JsValue, JsValue> {
self.fold_recursive(init, f)
}
fn fold_recursive(&self, acc: JsValue, f: &js_sys::Function) -> Result<JsValue, JsValue> {
let this = JsValue::null();
let new_acc = f
.call2(&this, &acc, &self.inner.value().clone())
.map_err(|e| JsValue::from_str(&format!("Fold function error: {:?}", e)))?;
let mut acc = new_acc;
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
acc = wasm_elem.fold_recursive(acc, f)?;
}
Ok(acc)
}
#[wasm_bindgen(js_name = para)]
pub fn para(&self, f: &js_sys::Function) -> Result<JsValue, JsValue> {
self.para_recursive(f)
}
fn para_recursive(&self, f: &js_sys::Function) -> Result<JsValue, JsValue> {
let child_results = js_sys::Array::new();
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
let result = wasm_elem.para_recursive(f)?;
child_results.push(&result);
}
let this = JsValue::null();
let wasm_pattern = WasmPattern {
inner: self.inner.clone(),
};
f.call2(&this, &JsValue::from(wasm_pattern), &child_results)
.map_err(|e| JsValue::from_str(&format!("Para function error: {:?}", e)))
}
#[wasm_bindgen(js_name = combine)]
pub fn combine(
&self,
other: &WasmPattern,
combiner: &js_sys::Function,
) -> Result<WasmPattern, JsValue> {
let this = JsValue::null();
let combined_value = combiner
.call2(
&this,
&self.inner.value().clone(),
&other.inner.value().clone(),
)
.map_err(|e| JsValue::from_str(&format!("Combiner function error: {:?}", e)))?;
let mut combined_elements = self.inner.elements().to_vec();
combined_elements.extend(other.inner.elements().iter().cloned());
Ok(WasmPattern {
inner: Pattern::pattern(combined_value, combined_elements),
})
}
#[wasm_bindgen(js_name = extract)]
pub fn extract(&self) -> JsValue {
self.inner.value().clone()
}
#[wasm_bindgen(js_name = extend)]
pub fn extend(&self, f: &js_sys::Function) -> Result<WasmPattern, JsValue> {
self.extend_recursive(f)
}
fn extend_recursive(&self, f: &js_sys::Function) -> Result<WasmPattern, JsValue> {
let this = JsValue::null();
let wasm_pattern = WasmPattern {
inner: self.inner.clone(),
};
let new_value = f
.call1(&this, &JsValue::from(wasm_pattern))
.map_err(|e| JsValue::from_str(&format!("Extend function error: {:?}", e)))?;
let mut new_elements = Vec::new();
for elem in self.inner.elements() {
let wasm_elem = WasmPattern {
inner: elem.clone(),
};
let extended_elem = wasm_elem.extend_recursive(f)?;
new_elements.push(extended_elem.inner);
}
Ok(WasmPattern {
inner: Pattern::pattern(new_value, new_elements),
})
}
#[wasm_bindgen(js_name = depthAt)]
pub fn depth_at(&self) -> WasmPattern {
let extended = self
.inner
.extend(&|subpattern: &Pattern<JsValue>| JsValue::from_f64(subpattern.depth() as f64));
WasmPattern { inner: extended }
}
#[wasm_bindgen(js_name = sizeAt)]
pub fn size_at(&self) -> WasmPattern {
let extended = self
.inner
.extend(&|subpattern: &Pattern<JsValue>| JsValue::from_f64(subpattern.size() as f64));
WasmPattern { inner: extended }
}
#[wasm_bindgen(js_name = indicesAt)]
pub fn indices_at(&self) -> WasmPattern {
fn go(path: Vec<usize>, pattern: &Pattern<JsValue>) -> Pattern<JsValue> {
let path_arr = js_sys::Array::new();
for idx in &path {
path_arr.push(&JsValue::from_f64(*idx as f64));
}
Pattern {
value: path_arr.into(),
elements: pattern
.elements()
.iter()
.enumerate()
.map(|(i, elem)| {
let mut new_path = path.clone();
new_path.push(i);
go(new_path, elem)
})
.collect(),
}
}
WasmPattern {
inner: go(vec![], &self.inner),
}
}
#[wasm_bindgen(js_name = validate)]
pub fn validate(&self, rules: &WasmValidationRules) -> JsValue {
let internal_rules = rules.to_internal();
match self.inner.validate(&internal_rules) {
Ok(()) => either_right(JsValue::undefined()),
Err(error) => either_left(validation_error_to_js(&error)),
}
}
#[wasm_bindgen(js_name = analyzeStructure)]
pub fn analyze_structure(&self) -> WasmStructureAnalysis {
WasmStructureAnalysis {
inner: self.inner.analyze_structure(),
}
}
}
impl WasmPattern {
pub fn from_pattern(pattern: Pattern<JsValue>) -> Self {
WasmPattern { inner: pattern }
}
pub fn into_pattern(self) -> Pattern<JsValue> {
self.inner
}
pub fn as_pattern(&self) -> &Pattern<JsValue> {
&self.inner
}
}
use crate::graph::graph_classifier::canonical_classifier;
use crate::graph::graph_query::{directed, directed_reverse, undirected, GraphQuery};
use crate::graph::StandardGraph;
use crate::pattern_graph::{from_pattern_graph, from_patterns_with_policy, PatternGraph};
use crate::reconcile::{
ElementMergeStrategy, LabelMerge, PropertyMerge, ReconciliationPolicy, SubjectMergeStrategy,
};
fn subject_pattern_to_wasm(p: &crate::pattern::Pattern<crate::subject::Subject>) -> WasmPattern {
let subject_js = WasmSubject::from_subject(p.value.clone()).to_js_value();
let wasm_p = WasmPattern {
inner: crate::pattern::Pattern {
value: subject_js,
elements: p
.elements
.iter()
.map(|e| subject_pattern_to_wasm(e).inner)
.collect(),
},
};
wasm_p
}
fn wasm_pattern_to_subject_pattern(
p: &WasmPattern,
) -> Option<crate::pattern::Pattern<crate::subject::Subject>> {
let subject = WasmSubject::from_js_value(&p.inner.value)?.into_subject();
let elements: Vec<_> = p
.inner
.elements
.iter()
.filter_map(|e| wasm_pattern_to_subject_pattern(&WasmPattern { inner: e.clone() }))
.collect();
Some(crate::pattern::Pattern {
value: subject,
elements,
})
}
fn js_value_to_subject_pattern(
js: &JsValue,
) -> Option<crate::pattern::Pattern<crate::subject::Subject>> {
if !js.is_object() {
return None;
}
let value_js = js_sys::Reflect::get(js, &JsValue::from_str("value")).ok()?;
let subject = WasmSubject::from_js_value(&value_js)?.into_subject();
let elements_js = js_sys::Reflect::get(js, &JsValue::from_str("elements")).ok()?;
let elements = if js_sys::Array::is_array(&elements_js) {
let arr: &js_sys::Array = elements_js.unchecked_ref();
(0..arr.length())
.filter_map(|i| js_value_to_subject_pattern(&arr.get(i)))
.collect()
} else {
vec![]
};
Some(crate::pattern::Pattern {
value: subject,
elements,
})
}
fn subject_pattern_to_js(p: &crate::pattern::Pattern<crate::subject::Subject>) -> JsValue {
JsValue::from(subject_pattern_to_wasm(p))
}
fn patterns_to_js_array(
patterns: &[crate::pattern::Pattern<crate::subject::Subject>],
) -> js_sys::Array {
let arr = js_sys::Array::new();
for p in patterns {
arr.push(&subject_pattern_to_js(p));
}
arr
}
fn parse_weight(weight_js: &JsValue) -> crate::graph::graph_query::TraversalWeight<Subject> {
if let Some(s) = weight_js.as_string() {
match s.as_str() {
"directed" => directed::<Subject>(),
"directed_reverse" => directed_reverse::<Subject>(),
_ => undirected::<Subject>(), }
} else if weight_js.is_function() {
let func = js_sys::Function::from(weight_js.clone());
std::rc::Rc::new(
move |rel: &crate::pattern::Pattern<Subject>,
dir: crate::graph::graph_query::TraversalDirection| {
let wasm_rel = subject_pattern_to_wasm(rel);
let dir_str = match dir {
crate::graph::graph_query::TraversalDirection::Forward => "forward",
crate::graph::graph_query::TraversalDirection::Backward => "backward",
};
let result = func.call2(
&JsValue::undefined(),
&JsValue::from(wasm_rel),
&JsValue::from_str(dir_str),
);
match result {
Ok(v) => v.as_f64().unwrap_or(1.0),
Err(_) => 1.0,
}
},
)
} else {
undirected::<Subject>()
}
}
#[wasm_bindgen]
pub struct WasmReconciliationPolicy {
#[wasm_bindgen(skip)]
pub inner: ReconciliationPolicy<SubjectMergeStrategy>,
}
#[wasm_bindgen]
impl WasmReconciliationPolicy {
#[wasm_bindgen(js_name = lastWriteWins)]
pub fn last_write_wins() -> WasmReconciliationPolicy {
WasmReconciliationPolicy {
inner: ReconciliationPolicy::LastWriteWins,
}
}
#[wasm_bindgen(js_name = firstWriteWins)]
pub fn first_write_wins() -> WasmReconciliationPolicy {
WasmReconciliationPolicy {
inner: ReconciliationPolicy::FirstWriteWins,
}
}
#[wasm_bindgen(js_name = strict)]
pub fn strict() -> WasmReconciliationPolicy {
WasmReconciliationPolicy {
inner: ReconciliationPolicy::Strict,
}
}
#[wasm_bindgen(js_name = merge)]
pub fn merge_policy(options: JsValue) -> WasmReconciliationPolicy {
let mut element_strategy = ElementMergeStrategy::UnionElements;
let mut label_merge = LabelMerge::UnionLabels;
let mut property_merge = PropertyMerge::ShallowMerge;
if options.is_object() {
if let Ok(es) = js_sys::Reflect::get(&options, &JsValue::from_str("elementStrategy")) {
if let Some(s) = es.as_string() {
element_strategy = match s.as_str() {
"replace" => ElementMergeStrategy::ReplaceElements,
"append" => ElementMergeStrategy::AppendElements,
_ => ElementMergeStrategy::UnionElements,
};
}
}
if let Ok(lm) = js_sys::Reflect::get(&options, &JsValue::from_str("labelMerge")) {
if let Some(s) = lm.as_string() {
label_merge = match s.as_str() {
"intersect" => LabelMerge::IntersectLabels,
"left" | "right" => LabelMerge::ReplaceLabels,
_ => LabelMerge::UnionLabels,
};
}
}
if let Ok(pm) = js_sys::Reflect::get(&options, &JsValue::from_str("propertyMerge")) {
if let Some(s) = pm.as_string() {
property_merge = match s.as_str() {
"left" | "right" => PropertyMerge::ReplaceProperties,
"merge" => PropertyMerge::ShallowMerge,
_ => PropertyMerge::ShallowMerge,
};
}
}
}
let strategy = SubjectMergeStrategy {
label_merge,
property_merge,
};
WasmReconciliationPolicy {
inner: ReconciliationPolicy::Merge(element_strategy, strategy),
}
}
}
#[wasm_bindgen]
pub struct WasmPatternGraph {
#[wasm_bindgen(skip)]
pub inner: std::rc::Rc<PatternGraph<(), Subject>>,
}
#[wasm_bindgen]
impl WasmPatternGraph {
#[wasm_bindgen(js_name = fromPatterns)]
pub fn from_patterns(
patterns: &js_sys::Array,
policy: Option<WasmReconciliationPolicy>,
) -> WasmPatternGraph {
let classifier = canonical_classifier::<Subject>();
let policy_inner = policy
.map(|p| p.inner)
.unwrap_or(ReconciliationPolicy::LastWriteWins);
let subject_patterns: Vec<crate::pattern::Pattern<Subject>> = (0..patterns.length())
.filter_map(|i| {
let item = patterns.get(i);
js_value_to_subject_pattern(&item)
})
.collect();
let graph = from_patterns_with_policy(&classifier, &policy_inner, subject_patterns);
WasmPatternGraph {
inner: std::rc::Rc::new(graph),
}
}
#[wasm_bindgen(js_name = empty)]
pub fn empty() -> WasmPatternGraph {
WasmPatternGraph {
inner: std::rc::Rc::new(PatternGraph::empty()),
}
}
#[wasm_bindgen(getter)]
pub fn nodes(&self) -> js_sys::Array {
patterns_to_js_array(&self.inner.pg_nodes.values().cloned().collect::<Vec<_>>())
}
#[wasm_bindgen(getter)]
pub fn relationships(&self) -> js_sys::Array {
patterns_to_js_array(
&self
.inner
.pg_relationships
.values()
.cloned()
.collect::<Vec<_>>(),
)
}
#[wasm_bindgen(getter)]
pub fn walks(&self) -> js_sys::Array {
patterns_to_js_array(&self.inner.pg_walks.values().cloned().collect::<Vec<_>>())
}
#[wasm_bindgen(getter)]
pub fn annotations(&self) -> js_sys::Array {
patterns_to_js_array(
&self
.inner
.pg_annotations
.values()
.cloned()
.collect::<Vec<_>>(),
)
}
#[wasm_bindgen(getter)]
pub fn conflicts(&self) -> JsValue {
let obj = js_sys::Object::new();
for (id, patterns) in &self.inner.pg_conflicts {
let arr = patterns_to_js_array(patterns);
js_sys::Reflect::set(&obj, &JsValue::from_str(&id.0), &arr).ok();
}
obj.into()
}
#[wasm_bindgen(getter)]
pub fn size(&self) -> usize {
self.inner.pg_nodes.len()
+ self.inner.pg_relationships.len()
+ self.inner.pg_walks.len()
+ self.inner.pg_annotations.len()
+ self.inner.pg_other.len()
}
#[wasm_bindgen(js_name = merge)]
pub fn merge(&self, other: &WasmPatternGraph) -> WasmPatternGraph {
let classifier = canonical_classifier::<Subject>();
let policy = ReconciliationPolicy::LastWriteWins;
let all_patterns: Vec<crate::pattern::Pattern<Subject>> = self
.inner
.pg_nodes
.values()
.chain(self.inner.pg_relationships.values())
.chain(self.inner.pg_walks.values())
.chain(self.inner.pg_annotations.values())
.chain(other.inner.pg_nodes.values())
.chain(other.inner.pg_relationships.values())
.chain(other.inner.pg_walks.values())
.chain(other.inner.pg_annotations.values())
.cloned()
.collect();
let graph = from_patterns_with_policy(&classifier, &policy, all_patterns);
WasmPatternGraph {
inner: std::rc::Rc::new(graph),
}
}
#[wasm_bindgen(js_name = topoSort)]
pub fn topo_sort(&self) -> JsValue {
let query = from_pattern_graph(std::rc::Rc::clone(&self.inner));
match crate::graph::algorithms::topological_sort(&query) {
Some(sorted) => JsValue::from(patterns_to_js_array(&sorted)),
None => JsValue::null(),
}
}
}
#[wasm_bindgen]
pub struct WasmGraphQuery {
#[wasm_bindgen(skip)]
pub inner: GraphQuery<Subject>,
}
#[wasm_bindgen]
impl WasmGraphQuery {
#[wasm_bindgen(js_name = fromPatternGraph)]
pub fn from_pattern_graph(graph: &WasmPatternGraph) -> WasmGraphQuery {
WasmGraphQuery {
inner: from_pattern_graph(std::rc::Rc::clone(&graph.inner)),
}
}
#[wasm_bindgen(js_name = nodes)]
pub fn nodes(&self) -> js_sys::Array {
patterns_to_js_array(&(self.inner.query_nodes)())
}
#[wasm_bindgen(js_name = relationships)]
pub fn relationships(&self) -> js_sys::Array {
patterns_to_js_array(&(self.inner.query_relationships)())
}
#[wasm_bindgen(js_name = source)]
pub fn source(&self, rel: &WasmPattern) -> JsValue {
let subject_rel = match wasm_pattern_to_subject_pattern(rel) {
Some(r) => r,
None => return JsValue::null(),
};
match (self.inner.query_source)(&subject_rel) {
Some(p) => subject_pattern_to_js(&p),
None => JsValue::null(),
}
}
#[wasm_bindgen(js_name = target)]
pub fn target(&self, rel: &WasmPattern) -> JsValue {
let subject_rel = match wasm_pattern_to_subject_pattern(rel) {
Some(r) => r,
None => return JsValue::null(),
};
match (self.inner.query_target)(&subject_rel) {
Some(p) => subject_pattern_to_js(&p),
None => JsValue::null(),
}
}
#[wasm_bindgen(js_name = incidentRels)]
pub fn incident_rels(&self, node: &WasmPattern) -> js_sys::Array {
let subject_node = match wasm_pattern_to_subject_pattern(node) {
Some(n) => n,
None => return js_sys::Array::new(),
};
patterns_to_js_array(&(self.inner.query_incident_rels)(&subject_node))
}
#[wasm_bindgen(js_name = degree)]
pub fn degree(&self, node: &WasmPattern) -> usize {
let subject_node = match wasm_pattern_to_subject_pattern(node) {
Some(n) => n,
None => return 0,
};
(self.inner.query_degree)(&subject_node)
}
#[wasm_bindgen(js_name = nodeById)]
pub fn node_by_id(&self, identity: &str) -> JsValue {
let sym = Symbol(identity.to_string());
match (self.inner.query_node_by_id)(&sym) {
Some(p) => subject_pattern_to_js(&p),
None => JsValue::null(),
}
}
#[wasm_bindgen(js_name = relationshipById)]
pub fn relationship_by_id(&self, identity: &str) -> JsValue {
let sym = Symbol(identity.to_string());
match (self.inner.query_relationship_by_id)(&sym) {
Some(p) => subject_pattern_to_js(&p),
None => JsValue::null(),
}
}
}
#[wasm_bindgen]
pub fn graph_class_constants() -> JsValue {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("NODE"), &JsValue::from_str("node")).ok();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("RELATIONSHIP"),
&JsValue::from_str("relationship"),
)
.ok();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("ANNOTATION"),
&JsValue::from_str("annotation"),
)
.ok();
js_sys::Reflect::set(&obj, &JsValue::from_str("WALK"), &JsValue::from_str("walk")).ok();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("OTHER"),
&JsValue::from_str("other"),
)
.ok();
obj.into()
}
#[wasm_bindgen]
pub fn traversal_direction_constants() -> JsValue {
let obj = js_sys::Object::new();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("FORWARD"),
&JsValue::from_str("forward"),
)
.ok();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("BACKWARD"),
&JsValue::from_str("backward"),
)
.ok();
obj.into()
}
#[wasm_bindgen]
pub fn bfs(query: &WasmGraphQuery, start: &WasmPattern, weight: JsValue) -> js_sys::Array {
let subject_start = match wasm_pattern_to_subject_pattern(start) {
Some(s) => s,
None => return js_sys::Array::new(),
};
let w = parse_weight(&weight);
let result = crate::graph::algorithms::bfs(&query.inner, &w, &subject_start);
patterns_to_js_array(&result)
}
#[wasm_bindgen]
pub fn dfs(query: &WasmGraphQuery, start: &WasmPattern, weight: JsValue) -> js_sys::Array {
let subject_start = match wasm_pattern_to_subject_pattern(start) {
Some(s) => s,
None => return js_sys::Array::new(),
};
let w = parse_weight(&weight);
let result = crate::graph::algorithms::dfs(&query.inner, &w, &subject_start);
patterns_to_js_array(&result)
}
#[wasm_bindgen(js_name = shortestPath)]
pub fn shortest_path(
query: &WasmGraphQuery,
start: &WasmPattern,
end: &WasmPattern,
weight: JsValue,
) -> JsValue {
let subject_start = match wasm_pattern_to_subject_pattern(start) {
Some(s) => s,
None => return JsValue::null(),
};
let subject_end = match wasm_pattern_to_subject_pattern(end) {
Some(e) => e,
None => return JsValue::null(),
};
let w = parse_weight(&weight);
match crate::graph::algorithms::shortest_path(&query.inner, &w, &subject_start, &subject_end) {
Some(path) => JsValue::from(patterns_to_js_array(&path)),
None => JsValue::null(),
}
}
#[wasm_bindgen(js_name = allPaths)]
pub fn all_paths(
query: &WasmGraphQuery,
start: &WasmPattern,
end: &WasmPattern,
weight: JsValue,
) -> js_sys::Array {
let subject_start = match wasm_pattern_to_subject_pattern(start) {
Some(s) => s,
None => return js_sys::Array::new(),
};
let subject_end = match wasm_pattern_to_subject_pattern(end) {
Some(e) => e,
None => return js_sys::Array::new(),
};
let w = parse_weight(&weight);
let paths = crate::graph::algorithms::all_paths(&query.inner, &w, &subject_start, &subject_end);
let outer = js_sys::Array::new();
for path in &paths {
outer.push(&JsValue::from(patterns_to_js_array(path)));
}
outer
}
#[wasm_bindgen(js_name = connectedComponents)]
pub fn connected_components(query: &WasmGraphQuery, weight: JsValue) -> js_sys::Array {
let w = parse_weight(&weight);
let components = crate::graph::algorithms::connected_components(&query.inner, &w);
let outer = js_sys::Array::new();
for component in &components {
outer.push(&JsValue::from(patterns_to_js_array(component)));
}
outer
}
#[wasm_bindgen(js_name = hasCycle)]
pub fn has_cycle(query: &WasmGraphQuery) -> bool {
crate::graph::algorithms::has_cycle(&query.inner)
}
#[wasm_bindgen(js_name = isConnected)]
pub fn is_connected(query: &WasmGraphQuery, weight: JsValue) -> bool {
let w = parse_weight(&weight);
crate::graph::algorithms::is_connected(&query.inner, &w)
}
#[wasm_bindgen(js_name = topologicalSort)]
pub fn topological_sort(query: &WasmGraphQuery) -> JsValue {
match crate::graph::algorithms::topological_sort(&query.inner) {
Some(sorted) => JsValue::from(patterns_to_js_array(&sorted)),
None => JsValue::null(),
}
}
#[wasm_bindgen(js_name = degreeCentrality)]
pub fn degree_centrality(query: &WasmGraphQuery) -> JsValue {
let scores = crate::graph::algorithms::degree_centrality(&query.inner);
let obj = js_sys::Object::new();
for (id, score) in &scores {
js_sys::Reflect::set(&obj, &JsValue::from_str(&id.0), &JsValue::from_f64(*score)).ok();
}
obj.into()
}
#[wasm_bindgen(js_name = betweennessCentrality)]
pub fn betweenness_centrality(query: &WasmGraphQuery, weight: JsValue) -> JsValue {
let w = parse_weight(&weight);
let scores = crate::graph::algorithms::betweenness_centrality(&query.inner, &w);
let obj = js_sys::Object::new();
for (id, score) in &scores {
js_sys::Reflect::set(&obj, &JsValue::from_str(&id.0), &JsValue::from_f64(*score)).ok();
}
obj.into()
}
#[wasm_bindgen(js_name = minimumSpanningTree)]
pub fn minimum_spanning_tree(query: &WasmGraphQuery, weight: JsValue) -> js_sys::Array {
let w = parse_weight(&weight);
let tree = crate::graph::algorithms::minimum_spanning_tree(&query.inner, &w);
patterns_to_js_array(&tree)
}
#[wasm_bindgen(js_name = queryWalksContaining)]
pub fn query_walks_containing(query: &WasmGraphQuery, node: &WasmPattern) -> js_sys::Array {
let subject_node = match wasm_pattern_to_subject_pattern(node) {
Some(n) => n,
None => return js_sys::Array::new(),
};
let classifier = canonical_classifier::<Subject>();
let walks =
crate::graph::algorithms::query_walks_containing(&classifier, &query.inner, &subject_node);
patterns_to_js_array(&walks)
}
#[wasm_bindgen(js_name = queryCoMembers)]
pub fn query_co_members(
query: &WasmGraphQuery,
node: &WasmPattern,
container: &WasmPattern,
) -> js_sys::Array {
let subject_node = match wasm_pattern_to_subject_pattern(node) {
Some(n) => n,
None => return js_sys::Array::new(),
};
let subject_container = match wasm_pattern_to_subject_pattern(container) {
Some(c) => c,
None => return js_sys::Array::new(),
};
let members =
crate::graph::algorithms::query_co_members(&query.inner, &subject_node, &subject_container);
patterns_to_js_array(&members)
}
#[wasm_bindgen(js_name = queryAnnotationsOf)]
pub fn query_annotations_of(query: &WasmGraphQuery, target: &WasmPattern) -> js_sys::Array {
let subject_target = match wasm_pattern_to_subject_pattern(target) {
Some(t) => t,
None => return js_sys::Array::new(),
};
let classifier = canonical_classifier::<Subject>();
let annotations =
crate::graph::algorithms::query_annotations_of(&classifier, &query.inner, &subject_target);
patterns_to_js_array(&annotations)
}
#[wasm_bindgen]
pub struct WasmStandardGraph {
#[wasm_bindgen(skip)]
pub inner: StandardGraph,
}
impl Default for WasmStandardGraph {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen]
impl WasmStandardGraph {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmStandardGraph {
WasmStandardGraph {
inner: StandardGraph::new(),
}
}
#[wasm_bindgen(js_name = fromPatterns)]
pub fn from_patterns(patterns: &js_sys::Array) -> WasmStandardGraph {
let subject_patterns: Vec<crate::pattern::Pattern<Subject>> = (0..patterns.length())
.filter_map(|i| js_value_to_subject_pattern(&patterns.get(i)))
.collect();
WasmStandardGraph {
inner: StandardGraph::from_patterns(subject_patterns),
}
}
#[wasm_bindgen(js_name = fromPatternGraph)]
pub fn from_pattern_graph(graph: &WasmPatternGraph) -> WasmStandardGraph {
let all_patterns: Vec<crate::pattern::Pattern<Subject>> = graph
.inner
.pg_nodes
.values()
.chain(graph.inner.pg_relationships.values())
.chain(graph.inner.pg_walks.values())
.chain(graph.inner.pg_annotations.values())
.cloned()
.collect();
WasmStandardGraph {
inner: StandardGraph::from_patterns(all_patterns),
}
}
#[wasm_bindgen(js_name = addNode)]
pub fn add_node(&mut self, subject: &WasmSubject) {
self.inner.add_node(subject.as_subject().clone());
}
#[wasm_bindgen(js_name = addRelationship)]
pub fn add_relationship(
&mut self,
subject: &WasmSubject,
source: &WasmSubject,
target: &WasmSubject,
) {
self.inner.add_relationship(
subject.as_subject().clone(),
source.as_subject(),
target.as_subject(),
);
}
#[wasm_bindgen(js_name = addWalk)]
pub fn add_walk(&mut self, subject: &WasmSubject, relationships: &js_sys::Array) {
let subjects: Vec<Subject> = (0..relationships.length())
.filter_map(|i| {
let item = relationships.get(i);
let id = js_sys::Reflect::get(&item, &wasm_bindgen::JsValue::from_str("identity"))
.ok()?
.as_string()?;
Some(Subject::from_id(id))
})
.collect();
self.inner.add_walk(subject.as_subject().clone(), &subjects);
}
#[wasm_bindgen(js_name = addAnnotation)]
pub fn add_annotation(&mut self, subject: &WasmSubject, element: &WasmSubject) {
self.inner
.add_annotation(subject.as_subject().clone(), element.as_subject());
}
#[wasm_bindgen(js_name = addPattern)]
pub fn add_pattern(&mut self, pattern: &WasmPattern) {
if let Some(subject_pattern) = wasm_pattern_to_subject_pattern(pattern) {
self.inner.add_pattern(subject_pattern);
}
}
#[wasm_bindgen(js_name = addPatterns)]
pub fn add_patterns(&mut self, patterns: &js_sys::Array) {
let subject_patterns: Vec<crate::pattern::Pattern<Subject>> = (0..patterns.length())
.filter_map(|i| js_value_to_subject_pattern(&patterns.get(i)))
.collect();
self.inner.add_patterns(subject_patterns);
}
#[wasm_bindgen(js_name = node)]
pub fn node(&self, id: &str) -> Option<WasmPattern> {
self.inner
.node(&Symbol(id.to_string()))
.map(subject_pattern_to_wasm)
}
#[wasm_bindgen(js_name = relationship)]
pub fn relationship(&self, id: &str) -> Option<WasmPattern> {
self.inner
.relationship(&Symbol(id.to_string()))
.map(subject_pattern_to_wasm)
}
#[wasm_bindgen(js_name = walk)]
pub fn walk(&self, id: &str) -> Option<WasmPattern> {
self.inner
.walk(&Symbol(id.to_string()))
.map(subject_pattern_to_wasm)
}
#[wasm_bindgen(js_name = annotation)]
pub fn annotation(&self, id: &str) -> Option<WasmPattern> {
self.inner
.annotation(&Symbol(id.to_string()))
.map(subject_pattern_to_wasm)
}
#[wasm_bindgen(getter, js_name = nodeCount)]
pub fn node_count(&self) -> usize {
self.inner.node_count()
}
#[wasm_bindgen(getter, js_name = relationshipCount)]
pub fn relationship_count(&self) -> usize {
self.inner.relationship_count()
}
#[wasm_bindgen(getter, js_name = walkCount)]
pub fn walk_count(&self) -> usize {
self.inner.walk_count()
}
#[wasm_bindgen(getter, js_name = annotationCount)]
pub fn annotation_count(&self) -> usize {
self.inner.annotation_count()
}
#[wasm_bindgen(getter, js_name = isEmpty)]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[wasm_bindgen(getter, js_name = hasConflicts)]
pub fn has_conflicts(&self) -> bool {
self.inner.has_conflicts()
}
#[wasm_bindgen(getter, js_name = nodes)]
pub fn nodes(&self) -> js_sys::Array {
let arr = js_sys::Array::new();
for (id, pattern) in self.inner.nodes() {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("id"), &JsValue::from_str(&id.0))
.unwrap_or_default();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("pattern"),
&JsValue::from(subject_pattern_to_wasm(pattern)),
)
.unwrap_or_default();
arr.push(&obj);
}
arr
}
#[wasm_bindgen(getter, js_name = relationships)]
pub fn relationships(&self) -> js_sys::Array {
let arr = js_sys::Array::new();
for (id, pattern) in self.inner.relationships() {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("id"), &JsValue::from_str(&id.0))
.unwrap_or_default();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("pattern"),
&JsValue::from(subject_pattern_to_wasm(pattern)),
)
.unwrap_or_default();
arr.push(&obj);
}
arr
}
#[wasm_bindgen(getter, js_name = walks)]
pub fn walks(&self) -> js_sys::Array {
let arr = js_sys::Array::new();
for (id, pattern) in self.inner.walks() {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("id"), &JsValue::from_str(&id.0))
.unwrap_or_default();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("pattern"),
&JsValue::from(subject_pattern_to_wasm(pattern)),
)
.unwrap_or_default();
arr.push(&obj);
}
arr
}
#[wasm_bindgen(getter, js_name = annotations)]
pub fn annotations(&self) -> js_sys::Array {
let arr = js_sys::Array::new();
for (id, pattern) in self.inner.annotations() {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &JsValue::from_str("id"), &JsValue::from_str(&id.0))
.unwrap_or_default();
js_sys::Reflect::set(
&obj,
&JsValue::from_str("pattern"),
&JsValue::from(subject_pattern_to_wasm(pattern)),
)
.unwrap_or_default();
arr.push(&obj);
}
arr
}
#[wasm_bindgen(js_name = source)]
pub fn source(&self, rel_id: &str) -> Option<WasmPattern> {
self.inner
.source(&Symbol(rel_id.to_string()))
.map(subject_pattern_to_wasm)
}
#[wasm_bindgen(js_name = target)]
pub fn target(&self, rel_id: &str) -> Option<WasmPattern> {
self.inner
.target(&Symbol(rel_id.to_string()))
.map(subject_pattern_to_wasm)
}
#[wasm_bindgen(js_name = neighbors)]
pub fn neighbors(&self, node_id: &str) -> js_sys::Array {
let neighbors = self.inner.neighbors(&Symbol(node_id.to_string()));
patterns_to_js_array(&neighbors.into_iter().cloned().collect::<Vec<_>>())
}
#[wasm_bindgen(js_name = degree)]
pub fn degree(&self, node_id: &str) -> usize {
self.inner.degree(&Symbol(node_id.to_string()))
}
#[wasm_bindgen(js_name = asPatternGraph)]
pub fn as_pattern_graph(&self) -> WasmPatternGraph {
let pg = self.inner.as_pattern_graph();
WasmPatternGraph {
inner: std::rc::Rc::new(PatternGraph {
pg_nodes: pg.pg_nodes.clone(),
pg_relationships: pg.pg_relationships.clone(),
pg_walks: pg.pg_walks.clone(),
pg_annotations: pg.pg_annotations.clone(),
pg_other: pg.pg_other.clone(),
pg_conflicts: pg.pg_conflicts.clone(),
}),
}
}
#[wasm_bindgen(js_name = asQuery)]
pub fn as_query(&self) -> WasmGraphQuery {
WasmGraphQuery {
inner: self.inner.as_query(),
}
}
}
#[wasm_bindgen]
pub struct WasmSubjectBuilder {
#[wasm_bindgen(skip)]
identity: String,
#[wasm_bindgen(skip)]
labels: Vec<String>,
#[wasm_bindgen(skip)]
properties: HashMap<String, Value>,
}
#[wasm_bindgen]
impl WasmSubjectBuilder {
#[wasm_bindgen(js_name = label)]
pub fn label(&mut self, label: &str) {
self.labels.push(label.to_string());
}
#[wasm_bindgen(js_name = property)]
pub fn property(&mut self, key: &str, value: JsValue) -> Result<(), JsValue> {
let rust_value = js_to_value(&value).map_err(|e| JsValue::from_str(&e))?;
self.properties.insert(key.to_string(), rust_value);
Ok(())
}
#[wasm_bindgen(js_name = done)]
pub fn done(&self) -> WasmSubject {
WasmSubject::from_subject(Subject {
identity: Symbol(self.identity.clone()),
labels: self.labels.iter().cloned().collect(),
properties: self.properties.clone(),
})
}
}
#[wasm_bindgen]
impl WasmSubject {
#[wasm_bindgen(js_name = fromId)]
pub fn from_id(identity: &str) -> WasmSubject {
WasmSubject::from_subject(Subject::from_id(identity))
}
#[wasm_bindgen(js_name = build)]
pub fn build(identity: &str) -> WasmSubjectBuilder {
WasmSubjectBuilder {
identity: identity.to_string(),
labels: Vec::new(),
properties: HashMap::new(),
}
}
}