1use crate::changes::CaughtNode;
24use crate::flat::{FlatChange, FlatOp, WireNode};
25use crate::view::ViewData;
26
27pub use rindle_value::js_safe::unsafe_int_in_cells;
32pub(crate) use rindle_value::js_safe::unsafe_int_in_row;
33
34fn unsafe_int_in_wire_node(node: &WireNode) -> Option<i64> {
35 unsafe_int_in_cells(&node.row).or_else(|| {
36 node.rels
37 .iter()
38 .flat_map(|(_, children)| children.iter())
39 .find_map(unsafe_int_in_wire_node)
40 })
41}
42
43pub fn unsafe_int_in_flat_changes(changes: &[FlatChange]) -> Option<i64> {
46 changes.iter().find_map(|c| {
47 c.path
48 .iter()
49 .find_map(|seg| unsafe_int_in_cells(&seg.parent_row))
50 .or_else(|| match &c.op {
51 FlatOp::Add(node) => unsafe_int_in_wire_node(node),
52 FlatOp::Remove { row } => unsafe_int_in_cells(row),
53 FlatOp::Edit { old, new } => {
54 unsafe_int_in_cells(old).or_else(|| unsafe_int_in_cells(new))
55 }
56 })
57 })
58}
59
60#[cfg_attr(not(feature = "wasm"), allow(dead_code))]
62pub(crate) fn unsafe_int_in_view_data(data: &ViewData) -> Option<i64> {
63 data.items.iter().find_map(|e| {
64 unsafe_int_in_row(&e.row).or_else(|| e.rels.iter().find_map(unsafe_int_in_view_data))
65 })
66}
67
68#[cfg_attr(not(feature = "wasm"), allow(dead_code))]
70pub(crate) fn unsafe_int_in_caught_node(node: &CaughtNode) -> Option<i64> {
71 unsafe_int_in_row(&node.row).or_else(|| {
72 node.relationships
73 .values()
74 .flat_map(|v| v.iter())
75 .find_map(unsafe_int_in_caught_node)
76 })
77}
78
79#[cfg(test)]
80mod tests {
81 use std::collections::BTreeMap;
82 use std::sync::Arc;
83
84 use super::*;
85 use crate::flat::PathSeg;
86 use crate::value::{owned_row, OwnedValue, RelId};
87 use crate::view::{Entry, EntryListInner, TxnGen};
88
89 const UNSAFE: i64 = (1_i64 << 53) + 1;
90
91 #[test]
92 fn flat_changes_are_walked_to_every_row_position() {
93 let node = |cell: i64| WireNode {
94 row: vec![OwnedValue::Int(cell)],
95 rels: Vec::new(),
96 };
97 let deep = FlatChange {
99 path: vec![PathSeg {
100 rel: RelId(0),
101 parent_row: vec![OwnedValue::Int(1)],
102 }],
103 op: FlatOp::Add(WireNode {
104 row: vec![OwnedValue::Int(2)],
105 rels: vec![(RelId(0), vec![node(UNSAFE)])],
106 }),
107 };
108 assert_eq!(unsafe_int_in_flat_changes(&[deep]), Some(UNSAFE));
109 let edit = FlatChange {
111 path: Vec::new(),
112 op: FlatOp::Edit {
113 old: vec![OwnedValue::Int(UNSAFE)],
114 new: vec![OwnedValue::Int(3)],
115 },
116 };
117 assert_eq!(unsafe_int_in_flat_changes(&[edit]), Some(UNSAFE));
118 let in_path = FlatChange {
120 path: vec![PathSeg {
121 rel: RelId(0),
122 parent_row: vec![OwnedValue::Int(UNSAFE)],
123 }],
124 op: FlatOp::Remove {
125 row: vec![OwnedValue::Int(4)],
126 },
127 };
128 assert_eq!(unsafe_int_in_flat_changes(&[in_path]), Some(UNSAFE));
129 let clean = FlatChange {
131 path: Vec::new(),
132 op: FlatOp::Remove {
133 row: vec![OwnedValue::Int(5), OwnedValue::Float(1e300)],
134 },
135 };
136 assert_eq!(unsafe_int_in_flat_changes(&[clean]), None);
137 }
138
139 #[test]
140 fn view_trees_and_caught_nodes_are_walked_recursively() {
141 let leaf = Arc::new(Entry {
142 row: owned_row(vec![OwnedValue::Int(UNSAFE)]),
143 rc: 1,
144 created: TxnGen(0),
145 id: None,
146 rels: Box::new([]),
147 });
148 let root = Arc::new(Entry {
149 row: owned_row(vec![OwnedValue::Int(1)]),
150 rc: 1,
151 created: TxnGen(0),
152 id: None,
153 rels: Box::new([Arc::new(EntryListInner {
154 created: TxnGen(0),
155 items: vec![leaf],
156 })]),
157 });
158 let data: ViewData = Arc::new(EntryListInner {
159 created: TxnGen(0),
160 items: vec![root],
161 });
162 assert_eq!(unsafe_int_in_view_data(&data), Some(UNSAFE));
163
164 let child = CaughtNode {
165 row: owned_row(vec![OwnedValue::Int(UNSAFE)]),
166 relationships: BTreeMap::new(),
167 };
168 let parent = CaughtNode {
169 row: owned_row(vec![OwnedValue::Int(1)]),
170 relationships: BTreeMap::from([(RelId(0), vec![child])]),
171 };
172 assert_eq!(unsafe_int_in_caught_node(&parent), Some(UNSAFE));
173 }
174}