use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use crate::protocol::TreeNode;
pub use plushie_core::tree_walk::MAX_TREE_DEPTH;
const MAX_HASH_DEPTH: usize = 256;
pub const MAX_TEXT_INPUT_BYTES: usize = 64 * 1024;
pub const MAX_TEXT_EDITOR_BYTES: usize = 1024 * 1024;
pub const MAX_MARKDOWN_BYTES: usize = 2 * 1024 * 1024;
pub fn enforce_content_cap(widget_id: &str, field: &str, raw: String, cap: usize) -> String {
if raw.len() <= cap {
return raw;
}
let actual = raw.len();
let mut end = cap;
while end > 0 && !raw.is_char_boundary(end) {
end -= 1;
}
crate::diagnostics::warn(plushie_core::Diagnostic::ContentLengthExceeded {
id: widget_id.to_string(),
field: field.to_string(),
actual,
cap,
truncated: end,
});
raw[..end].to_owned()
}
pub struct SharedState {
pub(crate) style_overrides: HashMap<String, (u64, crate::widget::helpers::StyleOverrides)>,
pub interpolated_props: HashMap<String, serde_json::Map<String, serde_json::Value>>,
}
impl SharedState {
pub fn new() -> Self {
Self {
style_overrides: HashMap::new(),
interpolated_props: HashMap::new(),
}
}
pub fn clear(&mut self) {
self.style_overrides.clear();
self.interpolated_props.clear();
}
pub fn prune_shared(&mut self, live_ids: &HashSet<String>) {
self.style_overrides.retain(|id, _| live_ids.contains(id));
self.interpolated_props
.retain(|id, _| live_ids.contains(id));
}
}
impl Default for SharedState {
fn default() -> Self {
Self::new()
}
}
pub(crate) fn ensure_style_overrides_cache(node: &TreeNode, caches: &mut SharedState) {
let style_val = match node.props.get_value("style") {
Some(serde_json::Value::Object(obj)) => obj,
_ => return,
};
let mut hasher = DefaultHasher::new();
hash_json_map(&style_val, &mut hasher);
let hash = hasher.finish();
if let Some((cached_hash, _)) = caches.style_overrides.get(&node.id)
&& *cached_hash == hash
{
return;
}
let overrides = crate::widget::helpers::parse_style_overrides(&style_val);
caches
.style_overrides
.insert(node.id.clone(), (hash, overrides));
}
fn hash_json_map(map: &serde_json::Map<String, serde_json::Value>, h: &mut impl std::hash::Hasher) {
5u8.hash(h);
map.len().hash(h);
for (k, v) in map {
k.hash(h);
hash_json_value(v, h);
}
}
pub(crate) fn cached_style_overrides<'a>(
caches: &'a SharedState,
node_id: &str,
) -> Option<&'a crate::widget::helpers::StyleOverrides> {
caches.style_overrides.get(node_id).map(|(_, ov)| ov)
}
pub fn hash_json_value(v: &serde_json::Value, h: &mut impl std::hash::Hasher) {
hash_json_value_inner(v, h, 0);
}
fn hash_json_value_inner(v: &serde_json::Value, h: &mut impl std::hash::Hasher, depth: usize) {
if depth > MAX_HASH_DEPTH {
6u8.hash(h);
return;
}
match v {
serde_json::Value::Null => 0u8.hash(h),
serde_json::Value::Bool(b) => {
1u8.hash(h);
b.hash(h);
}
serde_json::Value::Number(n) => {
2u8.hash(h);
if let Some(f) = n.as_f64() {
f.to_bits().hash(h);
} else if let Some(i) = n.as_i64() {
i.hash(h);
} else if let Some(u) = n.as_u64() {
u.hash(h);
}
}
serde_json::Value::String(s) => {
3u8.hash(h);
s.hash(h);
}
serde_json::Value::Array(arr) => {
4u8.hash(h);
arr.len().hash(h);
for item in arr {
hash_json_value_inner(item, h, depth + 1);
}
}
serde_json::Value::Object(obj) => {
5u8.hash(h);
obj.len().hash(h);
for (k, v) in obj {
k.hash(h);
hash_json_value_inner(v, h, depth + 1);
}
}
}
}
pub(crate) fn hash_str(s: &str) -> u64 {
let mut hasher = DefaultHasher::new();
s.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shared_state_new_is_empty() {
let c: SharedState = SharedState::new();
assert!(c.style_overrides.is_empty());
assert!(c.interpolated_props.is_empty());
}
#[test]
fn shared_state_clear_empties_maps() {
let mut c: SharedState = SharedState::new();
c.interpolated_props
.insert("w1".into(), serde_json::Map::new());
c.clear();
assert!(c.interpolated_props.is_empty());
}
#[test]
fn hash_json_value_same_input_same_hash() {
use std::collections::hash_map::DefaultHasher;
let val = serde_json::json!({"shapes": [{"type": "rect", "x": 0, "y": 0}]});
let h1 = {
let mut h = DefaultHasher::new();
hash_json_value(&val, &mut h);
h.finish()
};
let h2 = {
let mut h = DefaultHasher::new();
hash_json_value(&val, &mut h);
h.finish()
};
assert_eq!(h1, h2);
}
#[test]
fn hash_json_value_different_input_different_hash() {
use std::collections::hash_map::DefaultHasher;
let a = serde_json::json!({"type": "rect"});
let b = serde_json::json!({"type": "circle"});
let hash_a = {
let mut h = DefaultHasher::new();
hash_json_value(&a, &mut h);
h.finish()
};
let hash_b = {
let mut h = DefaultHasher::new();
hash_json_value(&b, &mut h);
h.finish()
};
assert_ne!(hash_a, hash_b);
}
#[test]
fn hash_json_value_type_discrimination() {
use std::collections::hash_map::DefaultHasher;
let vals = [
serde_json::json!(null),
serde_json::json!(false),
serde_json::json!(0),
serde_json::json!(""),
serde_json::json!([]),
serde_json::json!({}),
];
let hashes: Vec<u64> = vals
.iter()
.map(|v| {
let mut h = DefaultHasher::new();
hash_json_value(v, &mut h);
h.finish()
})
.collect();
for (i, h1) in hashes.iter().enumerate() {
for (j, h2) in hashes.iter().enumerate() {
if i != j {
assert_ne!(h1, h2, "type {i} and {j} should hash differently");
}
}
}
}
}