use js_sys::{Array, ArrayBuffer, Uint8Array};
use sqlx_core::error::Error;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use crate::{safe_integer, DatabaseError, Value};
pub fn to_js_array(values: &[Value]) -> Result<Array, Error> {
values.iter().map(to_js).collect()
}
pub fn to_js(value: &Value) -> Result<JsValue, Error> {
Ok(match value {
Value::Null => JsValue::NULL,
Value::Integer(value) => {
#[allow(
clippy::cast_precision_loss,
reason = "`safe_integer` has just checked the conversion is exact"
)]
let value = safe_integer(*value).map_err(Error::Encode)? as f64;
JsValue::from_f64(value)
}
Value::Real(value) => JsValue::from_f64(*value),
Value::Text(value) => JsValue::from_str(value),
Value::Blob(value) => Uint8Array::from(value.as_slice()).buffer().into(),
})
}
pub fn from_js(value: &JsValue) -> Result<Value, Error> {
Ok(if value.is_null() || value.is_undefined() {
Value::Null
} else if let Some(number) = value.as_f64() {
Value::from_number(number)
} else if let Some(text) = value.as_string() {
Value::Text(text)
} else if let Some(boolean) = value.as_bool() {
Value::Integer(i64::from(boolean))
} else if let Some(buffer) = value.dyn_ref::<ArrayBuffer>() {
Value::Blob(Uint8Array::new(buffer).to_vec())
} else if let Some(bytes) = value.dyn_ref::<Uint8Array>() {
Value::Blob(bytes.to_vec())
} else if let Some(numbers) = value.dyn_ref::<Array>() {
Value::Blob(bytes_from_numbers(numbers)?)
} else {
return Err(Error::Decode(
format!("a value of an unexpected JavaScript type came back: {value:?}").into(),
));
})
}
fn bytes_from_numbers(numbers: &Array) -> Result<Vec<u8>, Error> {
numbers
.iter()
.map(|number| {
number
.as_f64()
.filter(|number| number.fract() == 0.0 && (0.0..=255.0).contains(number))
.map(|number| {
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "the filter has just checked it is a whole number in 0..=255"
)]
let byte = number as u8;
byte
})
.ok_or_else(|| Error::Decode("a BLOB element came back that is not a byte".into()))
})
.collect()
}
#[must_use]
pub fn database_error(error: &JsValue) -> Error {
let message = match error.dyn_ref::<js_sys::Error>() {
Some(error) => {
let message = String::from(error.message());
match error.cause().dyn_ref::<js_sys::Error>() {
Some(cause) => {
let cause = String::from(cause.message());
if message.contains(&cause) {
message
} else {
format!("{message}: {cause}")
}
}
None => message,
}
}
None => error.as_string().unwrap_or_else(|| format!("{error:?}")),
};
Error::Database(Box::new(DatabaseError::new(message)))
}