use wasm_bindgen::JsValue;
use crate::scope::StaticPropKind;
pub trait PropValue: Sized {
fn to_prop_js(&self) -> JsValue;
fn from_prop_js(value: JsValue) -> Option<Self>;
fn prop_static_kind() -> StaticPropKind;
}
impl PropValue for String {
fn to_prop_js(&self) -> JsValue {
JsValue::from_str(self)
}
fn from_prop_js(value: JsValue) -> Option<Self> {
value.as_string()
}
fn prop_static_kind() -> StaticPropKind {
StaticPropKind::String
}
}
impl PropValue for bool {
fn to_prop_js(&self) -> JsValue {
JsValue::from_bool(*self)
}
fn from_prop_js(value: JsValue) -> Option<Self> {
value.as_bool()
}
fn prop_static_kind() -> StaticPropKind {
StaticPropKind::Bool
}
}
macro_rules! impl_numeric_prop_value {
($($t:ty),* $(,)?) => { $(
impl PropValue for $t {
fn to_prop_js(&self) -> JsValue {
JsValue::from_f64(*self as f64)
}
fn from_prop_js(value: JsValue) -> Option<Self> {
value.as_f64().map(|n| n as $t)
}
fn prop_static_kind() -> StaticPropKind {
StaticPropKind::Number
}
}
)* };
}
impl_numeric_prop_value!(f32, f64, i8, i16, i32, isize, u8, u16, u32, usize);
impl<T: PropValue> PropValue for Option<T> {
fn to_prop_js(&self) -> JsValue {
match self {
Some(v) => v.to_prop_js(),
None => JsValue::NULL,
}
}
fn from_prop_js(value: JsValue) -> Option<Self> {
if value.is_null() || value.is_undefined() {
return Some(None);
}
if value.as_string().as_deref() == Some("") {
return Some(None);
}
T::from_prop_js(value).map(Some)
}
fn prop_static_kind() -> StaticPropKind {
T::prop_static_kind()
}
}
pub trait Props {
fn prop_leaves() -> &'static [&'static str];
fn prop_get(&self, leaf: &str) -> JsValue;
fn prop_set(&mut self, leaf: &str, value: JsValue);
fn prop_static_kind(leaf: &str) -> StaticPropKind;
}
pub const fn str_eq_const(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
pub const fn str_slice_contains_const(haystack: &[&str], needle: &str) -> bool {
let mut i = 0;
while i < haystack.len() {
if str_eq_const(haystack[i], needle) {
return true;
}
i += 1;
}
false
}
pub const fn str_slice_set_eq_const(a: &[&str], b: &[&str]) -> bool {
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if !str_slice_contains_const(b, a[i]) {
return false;
}
i += 1;
}
let mut i = 0;
while i < b.len() {
if !str_slice_contains_const(a, b[i]) {
return false;
}
i += 1;
}
true
}
#[cfg(test)]
mod tests {
use super::{str_eq_const, str_slice_contains_const, str_slice_set_eq_const};
#[test]
fn str_eq_const_handles_basic_cases() {
assert!(str_eq_const("foo", "foo"));
assert!(!str_eq_const("foo", "bar"));
assert!(!str_eq_const("foo", "fooo"));
assert!(str_eq_const("", ""));
}
#[test]
fn str_slice_contains_const_finds_present_and_rejects_absent() {
assert!(str_slice_contains_const(&["a", "b", "c"], "b"));
assert!(!str_slice_contains_const(&["a", "b", "c"], "z"));
assert!(!str_slice_contains_const(&[], "anything"));
}
#[test]
fn str_slice_set_eq_const_rejects_duplicate_bypass() {
assert!(
!str_slice_set_eq_const(&["x", "x"], &["x", "y"]),
"duplicate bypass: ['x','x'] vs ['x','y'] must NOT compare equal"
);
assert!(!str_slice_set_eq_const(&["x", "y"], &["x", "x"]));
}
#[test]
fn str_slice_set_eq_const_passes_genuine_equality() {
assert!(str_slice_set_eq_const(&["x", "y"], &["y", "x"]));
assert!(str_slice_set_eq_const(&["a", "b", "c"], &["c", "a", "b"]));
assert!(str_slice_set_eq_const(&[], &[]));
}
#[test]
fn str_slice_set_eq_const_fails_on_length_mismatch() {
assert!(!str_slice_set_eq_const(&["x"], &["x", "y"]));
assert!(!str_slice_set_eq_const(&["x", "y"], &["x"]));
}
}