use serde::Serialize;
use tensogram::{self as core, EncodeOptions};
use wasm_bindgen::prelude::*;
pub(crate) fn js_err<E: std::fmt::Display>(e: E) -> JsError {
JsError::new(&e.to_string())
}
pub(crate) fn build_encode_options(hash: Option<bool>) -> EncodeOptions {
build_encode_options_full(hash, None, None, None, None, None, None)
.expect("build_encode_options_full with no method names cannot error")
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn build_encode_options_full(
hash: Option<bool>,
allow_nan: Option<bool>,
allow_inf: Option<bool>,
nan_mask_method: Option<&str>,
pos_inf_mask_method: Option<&str>,
neg_inf_mask_method: Option<&str>,
small_mask_threshold_bytes: Option<usize>,
) -> Result<EncodeOptions, JsError> {
use core::encode::MaskMethod;
let defaults = EncodeOptions::default();
let parse = |s: Option<&str>, d: MaskMethod| -> Result<MaskMethod, JsError> {
match s {
Some(name) => MaskMethod::from_name(name).map_err(js_err),
None => Ok(d),
}
};
Ok(EncodeOptions {
hash_algorithm: if hash.unwrap_or(true) {
Some(core::hash::HashAlgorithm::Xxh3)
} else {
None
},
emit_preceders: false,
allow_nan: allow_nan.unwrap_or(false),
allow_inf: allow_inf.unwrap_or(false),
nan_mask_method: parse(nan_mask_method, defaults.nan_mask_method.clone())?,
pos_inf_mask_method: parse(pos_inf_mask_method, defaults.pos_inf_mask_method.clone())?,
neg_inf_mask_method: parse(neg_inf_mask_method, defaults.neg_inf_mask_method.clone())?,
small_mask_threshold_bytes: small_mask_threshold_bytes
.unwrap_or(defaults.small_mask_threshold_bytes),
..defaults
})
}
pub(crate) fn extract_descriptor_data_pairs(
objects_js: &js_sys::Array,
) -> Result<(Vec<core::DataObjectDescriptor>, Vec<Vec<u8>>), JsError> {
let len = objects_js.length();
let mut descriptors = Vec::with_capacity(len as usize);
let mut data_vec = Vec::with_capacity(len as usize);
for i in 0..len {
let entry = objects_js.get(i);
let desc_val = js_sys::Reflect::get(&entry, &"descriptor".into())
.map_err(|_| JsError::new("each object must have a 'descriptor' field"))?;
let data_val = js_sys::Reflect::get(&entry, &"data".into())
.map_err(|_| JsError::new("each object must have a 'data' field"))?;
let desc: core::DataObjectDescriptor =
serde_wasm_bindgen::from_value(desc_val).map_err(js_err)?;
let data_bytes = typed_array_or_u8_to_bytes(&data_val)
.ok_or_else(|| JsError::new("data must be a TypedArray, DataView, or Uint8Array"))?;
descriptors.push(desc);
data_vec.push(data_bytes);
}
Ok((descriptors, data_vec))
}
pub(crate) fn to_js<T: Serialize>(val: &T) -> Result<JsValue, JsError> {
let serializer = serde_wasm_bindgen::Serializer::json_compatible();
val.serialize(&serializer)
.map_err(|e| JsError::new(&e.to_string()))
}
pub(crate) fn view_as_f32(data: &[u8]) -> Result<js_sys::Float32Array, JsError> {
if !data.len().is_multiple_of(4) {
return Err(JsError::new(&format!(
"data length {} is not a multiple of 4 (Float32)",
data.len()
)));
}
if data.is_empty() {
return Ok(js_sys::Float32Array::new_with_length(0));
}
let byte_offset = data.as_ptr() as u32;
let length = (data.len() / 4) as u32;
let memory = wasm_bindgen::memory().unchecked_into::<js_sys::WebAssembly::Memory>();
Ok(js_sys::Float32Array::new_with_byte_offset_and_length(
&memory.buffer(),
byte_offset,
length,
))
}
pub(crate) fn view_as_f64(data: &[u8]) -> Result<js_sys::Float64Array, JsError> {
if !data.len().is_multiple_of(8) {
return Err(JsError::new(&format!(
"data length {} is not a multiple of 8 (Float64)",
data.len()
)));
}
if data.is_empty() {
return Ok(js_sys::Float64Array::new_with_length(0));
}
let byte_offset = data.as_ptr() as u32;
let length = (data.len() / 8) as u32;
let memory = wasm_bindgen::memory().unchecked_into::<js_sys::WebAssembly::Memory>();
Ok(js_sys::Float64Array::new_with_byte_offset_and_length(
&memory.buffer(),
byte_offset,
length,
))
}
pub(crate) fn view_as_i32(data: &[u8]) -> Result<js_sys::Int32Array, JsError> {
if !data.len().is_multiple_of(4) {
return Err(JsError::new(&format!(
"data length {} is not a multiple of 4 (Int32)",
data.len()
)));
}
if data.is_empty() {
return Ok(js_sys::Int32Array::new_with_length(0));
}
let byte_offset = data.as_ptr() as u32;
let length = (data.len() / 4) as u32;
let memory = wasm_bindgen::memory().unchecked_into::<js_sys::WebAssembly::Memory>();
Ok(js_sys::Int32Array::new_with_byte_offset_and_length(
&memory.buffer(),
byte_offset,
length,
))
}
pub(crate) fn view_as_u8(data: &[u8]) -> js_sys::Uint8Array {
if data.is_empty() {
return js_sys::Uint8Array::new_with_length(0);
}
unsafe { js_sys::Uint8Array::view(data) }
}
pub(crate) fn copy_as_f32(data: &[u8]) -> Result<js_sys::Float32Array, JsError> {
let view = view_as_f32(data)?;
Ok(js_sys::Float32Array::new(&view))
}
pub(crate) fn typed_array_or_u8_to_bytes(val: &JsValue) -> Option<Vec<u8>> {
if let Some(arr) = val.dyn_ref::<js_sys::Uint8Array>() {
return Some(arr.to_vec());
}
if let Some(dv) = val.dyn_ref::<js_sys::DataView>() {
let u8 = js_sys::Uint8Array::new_with_byte_offset_and_length(
&dv.buffer(),
dv.byte_offset() as u32,
dv.byte_length() as u32,
);
return Some(u8.to_vec());
}
macro_rules! try_typed {
($ty:ty) => {
if let Some(arr) = val.dyn_ref::<$ty>() {
let u8 = js_sys::Uint8Array::new_with_byte_offset_and_length(
&arr.buffer(),
arr.byte_offset(),
arr.byte_length(),
);
return Some(u8.to_vec());
}
};
}
try_typed!(js_sys::Int8Array);
try_typed!(js_sys::Uint8ClampedArray);
try_typed!(js_sys::Int16Array);
try_typed!(js_sys::Uint16Array);
try_typed!(js_sys::Int32Array);
try_typed!(js_sys::Uint32Array);
try_typed!(js_sys::Float32Array);
try_typed!(js_sys::Float64Array);
try_typed!(js_sys::BigInt64Array);
try_typed!(js_sys::BigUint64Array);
None
}