structfs_core_store/
conformance.rs1use crate::{path, Path, Reader, Record, Store, Value};
21
22pub fn check_conventions<S: Store>(store: &mut S) {
24 check_leaf_roundtrip(store);
25 check_missing_reads_none(store);
26 check_deep_write_creates_intermediates(store);
27 check_prefix_read_returns_children(store);
28 check_read_children(store);
29 check_null_write_deletes_subtree(store);
30 check_map_write_replaces_subtree(store);
31}
32
33fn read_value<S: Reader>(store: &mut S, path: &Path) -> Option<Value> {
34 store
35 .read(path)
36 .unwrap_or_else(|e| panic!("read {} failed: {}", path, e))
37 .map(|record| {
38 record
39 .as_value()
40 .unwrap_or_else(|| panic!("read {} returned a raw record", path))
41 .clone()
42 })
43}
44
45pub fn check_leaf_roundtrip<S: Store>(store: &mut S) {
47 let p = path!("conformance_leaf/value");
48 store
49 .write(&p, Record::parsed(Value::from("roundtrip")))
50 .expect("leaf write failed");
51 assert_eq!(
52 read_value(store, &p),
53 Some(Value::from("roundtrip")),
54 "leaf write/read did not roundtrip"
55 );
56}
57
58pub fn check_missing_reads_none<S: Store>(store: &mut S) {
60 assert_eq!(
61 read_value(store, &path!("conformance_missing/never_written")),
62 None,
63 "missing path must read as None, not an error or a value"
64 );
65}
66
67pub fn check_deep_write_creates_intermediates<S: Store>(store: &mut S) {
69 store
70 .write(
71 &path!("conformance_deep/a/b/c"),
72 Record::parsed(Value::from(1i64)),
73 )
74 .expect("deep write must create intermediate maps");
75 for prefix in [
76 "conformance_deep",
77 "conformance_deep/a",
78 "conformance_deep/a/b",
79 ] {
80 let p = Path::parse(prefix).unwrap();
81 assert!(
82 matches!(read_value(store, &p), Some(Value::Map(_))),
83 "intermediate {} must exist as a map after a deep write",
84 prefix
85 );
86 }
87}
88
89pub fn check_prefix_read_returns_children<S: Store>(store: &mut S) {
91 store
92 .write(
93 &path!("conformance_tree/users/alice"),
94 Record::parsed(Value::from(1i64)),
95 )
96 .expect("write failed");
97 store
98 .write(
99 &path!("conformance_tree/users/bob"),
100 Record::parsed(Value::from(2i64)),
101 )
102 .expect("write failed");
103
104 match read_value(store, &path!("conformance_tree/users")) {
105 Some(Value::Map(map)) => {
106 assert!(
107 map.contains_key("alice") && map.contains_key("bob"),
108 "prefix read must include children as map keys, got: {:?}",
109 map.keys().collect::<Vec<_>>()
110 );
111 }
112 other => panic!(
113 "prefix read must return a map of children, got: {:?}",
114 other
115 ),
116 }
117}
118
119pub fn check_read_children<S: Store>(store: &mut S) {
121 store
122 .write(
123 &path!("conformance_children/x"),
124 Record::parsed(Value::from(1i64)),
125 )
126 .expect("write failed");
127 store
128 .write(
129 &path!("conformance_children/y"),
130 Record::parsed(Value::from(2i64)),
131 )
132 .expect("write failed");
133
134 let mut children = store
135 .read_children(&path!("conformance_children"))
136 .expect("read_children failed")
137 .expect("read_children must return Some at an existing prefix");
138 children.sort();
139 assert_eq!(
140 children,
141 vec!["x".to_string(), "y".to_string()],
142 "read_children must enumerate direct children"
143 );
144
145 assert_eq!(
146 store
147 .read_children(&path!("conformance_children_missing"))
148 .expect("read_children failed"),
149 None,
150 "read_children at a missing path must return None"
151 );
152}
153
154pub fn check_null_write_deletes_subtree<S: Store>(store: &mut S) {
157 store
158 .write(
159 &path!("conformance_del/accounts/personal/key"),
160 Record::parsed(Value::from("secret")),
161 )
162 .expect("write failed");
163 store
164 .write(
165 &path!("conformance_del/accounts_other"),
166 Record::parsed(Value::from("survivor")),
167 )
168 .expect("write failed");
169
170 store
171 .write(
172 &path!("conformance_del/accounts"),
173 Record::parsed(Value::Null),
174 )
175 .expect("null write failed");
176
177 assert_eq!(
178 read_value(store, &path!("conformance_del/accounts")),
179 None,
180 "null write must delete the node"
181 );
182 assert_eq!(
183 read_value(store, &path!("conformance_del/accounts/personal/key")),
184 None,
185 "null write must delete the entire subtree"
186 );
187 assert_eq!(
188 read_value(store, &path!("conformance_del/accounts_other")),
189 Some(Value::from("survivor")),
190 "null write must be component-wise: string-prefix siblings survive"
191 );
192}
193
194pub fn check_map_write_replaces_subtree<S: Store>(store: &mut S) {
197 store
198 .write(
199 &path!("conformance_replace/cfg/stale"),
200 Record::parsed(Value::from("old")),
201 )
202 .expect("write failed");
203
204 let mut fresh = std::collections::BTreeMap::new();
205 fresh.insert("fresh".to_string(), Value::from("new"));
206 store
207 .write(
208 &path!("conformance_replace/cfg"),
209 Record::parsed(Value::Map(fresh)),
210 )
211 .expect("map write failed");
212
213 assert_eq!(
214 read_value(store, &path!("conformance_replace/cfg/stale")),
215 None,
216 "map write at a parent must sweep stale descendants"
217 );
218 assert_eq!(
219 read_value(store, &path!("conformance_replace/cfg/fresh")),
220 Some(Value::from("new")),
221 "map write must install the new state"
222 );
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::MemoryStore;
229
230 #[test]
231 fn memory_store_is_conformant() {
232 check_conventions(&mut MemoryStore::new());
233 }
234}