structfs_core_store/
reference.rs1use std::collections::BTreeMap;
9
10use crate::Value;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct TypeInfo {
15 pub name: String,
17
18 pub schema: Option<BTreeMap<String, TypeDescriptor>>,
20}
21
22impl TypeInfo {
23 pub fn new(name: impl Into<String>) -> Self {
25 Self {
26 name: name.into(),
27 schema: None,
28 }
29 }
30
31 pub fn to_value(&self) -> Value {
33 let mut map = BTreeMap::new();
34 map.insert("name".to_string(), Value::String(self.name.clone()));
35
36 if let Some(ref schema) = self.schema {
37 let schema_map: BTreeMap<String, Value> = schema
38 .iter()
39 .map(|(k, v)| (k.clone(), v.to_value()))
40 .collect();
41 map.insert("schema".to_string(), Value::Map(schema_map));
42 }
43
44 Value::Map(map)
45 }
46
47 pub fn from_value(value: &Value) -> Option<Self> {
49 let map = match value {
50 Value::Map(m) => m,
51 _ => return None,
52 };
53
54 let name = match map.get("name") {
55 Some(Value::String(s)) => s.clone(),
56 _ => return None,
57 };
58
59 let schema = map.get("schema").and_then(|v| {
60 if let Value::Map(schema_map) = v {
61 let mut result = BTreeMap::new();
62 for (k, v) in schema_map {
63 if let Some(td) = TypeDescriptor::from_value(v) {
64 result.insert(k.clone(), td);
65 }
66 }
67 Some(result)
68 } else {
69 None
70 }
71 });
72
73 Some(Self { name, schema })
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct TypeDescriptor {
80 pub type_info: TypeInfo,
82}
83
84impl TypeDescriptor {
85 pub fn new(type_name: impl Into<String>) -> Self {
87 Self {
88 type_info: TypeInfo::new(type_name),
89 }
90 }
91
92 pub fn to_value(&self) -> Value {
94 let mut map = BTreeMap::new();
95 map.insert("type".to_string(), self.type_info.to_value());
96 Value::Map(map)
97 }
98
99 pub fn from_value(value: &Value) -> Option<Self> {
101 let map = match value {
102 Value::Map(m) => m,
103 _ => return None,
104 };
105
106 let type_info = map.get("type").and_then(TypeInfo::from_value)?;
107
108 Some(Self { type_info })
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct Reference {
134 pub path: String,
136
137 pub type_info: Option<TypeInfo>,
139}
140
141impl Reference {
142 pub fn new(path: impl Into<String>) -> Self {
144 Self {
145 path: path.into(),
146 type_info: None,
147 }
148 }
149
150 pub fn with_type(path: impl Into<String>, type_name: impl Into<String>) -> Self {
152 Self {
153 path: path.into(),
154 type_info: Some(TypeInfo::new(type_name)),
155 }
156 }
157
158 pub fn to_value(&self) -> Value {
165 let mut map = BTreeMap::new();
166 map.insert("path".to_string(), Value::String(self.path.clone()));
167
168 if let Some(ref ti) = self.type_info {
169 map.insert("type".to_string(), ti.to_value());
170 }
171
172 Value::Map(map)
173 }
174
175 pub fn from_value(value: &Value) -> Option<Self> {
180 let map = match value {
181 Value::Map(m) => m,
182 _ => return None,
183 };
184
185 let path = match map.get("path") {
186 Some(Value::String(s)) => s.clone(),
187 _ => return None,
188 };
189
190 let type_info = map.get("type").and_then(TypeInfo::from_value);
191
192 Some(Self { path, type_info })
193 }
194
195 pub fn is_reference(value: &Value) -> bool {
200 match value {
201 Value::Map(m) => matches!(m.get("path"), Some(Value::String(_))),
202 _ => false,
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn reference_new() {
213 let r = Reference::new("handles/0");
214 assert_eq!(r.path, "handles/0");
215 assert!(r.type_info.is_none());
216 }
217
218 #[test]
219 fn reference_with_type() {
220 let r = Reference::with_type("handles/0", "handle");
221 assert_eq!(r.path, "handles/0");
222 assert_eq!(r.type_info.as_ref().unwrap().name, "handle");
223 }
224
225 #[test]
226 fn reference_to_value_minimal() {
227 let r = Reference::new("handles/0");
228 let v = r.to_value();
229
230 if let Value::Map(map) = v {
231 assert_eq!(map.get("path"), Some(&Value::String("handles/0".into())));
232 assert!(!map.contains_key("type"));
233 } else {
234 panic!("Expected map");
235 }
236 }
237
238 #[test]
239 fn reference_to_value_with_type() {
240 let r = Reference::with_type("handles/0", "handle");
241 let v = r.to_value();
242
243 if let Value::Map(map) = v {
244 assert_eq!(map.get("path"), Some(&Value::String("handles/0".into())));
245 if let Some(Value::Map(type_map)) = map.get("type") {
246 assert_eq!(type_map.get("name"), Some(&Value::String("handle".into())));
247 } else {
248 panic!("Expected type map");
249 }
250 } else {
251 panic!("Expected map");
252 }
253 }
254
255 #[test]
256 fn reference_from_value_minimal() {
257 let mut map = BTreeMap::new();
258 map.insert("path".to_string(), Value::String("handles/0".into()));
259 let v = Value::Map(map);
260
261 let r = Reference::from_value(&v).unwrap();
262 assert_eq!(r.path, "handles/0");
263 assert!(r.type_info.is_none());
264 }
265
266 #[test]
267 fn reference_from_value_with_type() {
268 let mut type_map = BTreeMap::new();
269 type_map.insert("name".to_string(), Value::String("handle".into()));
270
271 let mut map = BTreeMap::new();
272 map.insert("path".to_string(), Value::String("handles/0".into()));
273 map.insert("type".to_string(), Value::Map(type_map));
274 let v = Value::Map(map);
275
276 let r = Reference::from_value(&v).unwrap();
277 assert_eq!(r.path, "handles/0");
278 assert_eq!(r.type_info.as_ref().unwrap().name, "handle");
279 }
280
281 #[test]
282 fn reference_from_value_not_map() {
283 assert!(Reference::from_value(&Value::String("x".into())).is_none());
284 }
285
286 #[test]
287 fn reference_from_value_no_path() {
288 let mut map = BTreeMap::new();
289 map.insert("other".to_string(), Value::String("x".into()));
290 assert!(Reference::from_value(&Value::Map(map)).is_none());
291 }
292
293 #[test]
294 fn reference_from_value_path_not_string() {
295 let mut map = BTreeMap::new();
296 map.insert("path".to_string(), Value::Integer(42));
297 assert!(Reference::from_value(&Value::Map(map)).is_none());
298 }
299
300 #[test]
301 fn is_reference_true() {
302 let mut map = BTreeMap::new();
303 map.insert("path".to_string(), Value::String("x".into()));
304 assert!(Reference::is_reference(&Value::Map(map)));
305 }
306
307 #[test]
308 fn is_reference_false_not_map() {
309 assert!(!Reference::is_reference(&Value::String("x".into())));
310 }
311
312 #[test]
313 fn is_reference_false_no_path() {
314 let mut map = BTreeMap::new();
315 map.insert("other".to_string(), Value::String("x".into()));
316 assert!(!Reference::is_reference(&Value::Map(map)));
317 }
318
319 #[test]
320 fn is_reference_false_path_not_string() {
321 let mut map = BTreeMap::new();
322 map.insert("path".to_string(), Value::Integer(42));
323 assert!(!Reference::is_reference(&Value::Map(map)));
324 }
325
326 #[test]
327 fn reference_roundtrip() {
328 let original = Reference::with_type("meta/handles/0", "handle");
329 let value = original.to_value();
330 let parsed = Reference::from_value(&value).unwrap();
331 assert_eq!(original, parsed);
332 }
333
334 #[test]
335 fn type_info_new() {
336 let ti = TypeInfo::new("handle");
337 assert_eq!(ti.name, "handle");
338 assert!(ti.schema.is_none());
339 }
340
341 #[test]
342 fn type_info_to_value() {
343 let ti = TypeInfo::new("integer");
344 let v = ti.to_value();
345
346 if let Value::Map(map) = v {
347 assert_eq!(map.get("name"), Some(&Value::String("integer".into())));
348 } else {
349 panic!("Expected map");
350 }
351 }
352
353 #[test]
354 fn type_info_with_schema() {
355 let mut schema = BTreeMap::new();
356 schema.insert("position".to_string(), TypeDescriptor::new("integer"));
357
358 let ti = TypeInfo {
359 name: "handle".to_string(),
360 schema: Some(schema),
361 };
362
363 let v = ti.to_value();
364 if let Value::Map(map) = v {
365 assert!(map.contains_key("schema"));
366 } else {
367 panic!("Expected map");
368 }
369 }
370
371 #[test]
372 fn type_info_from_value() {
373 let mut map = BTreeMap::new();
374 map.insert("name".to_string(), Value::String("handle".into()));
375 let v = Value::Map(map);
376
377 let ti = TypeInfo::from_value(&v).unwrap();
378 assert_eq!(ti.name, "handle");
379 }
380
381 #[test]
382 fn type_info_from_value_not_map() {
383 assert!(TypeInfo::from_value(&Value::String("x".into())).is_none());
384 }
385
386 #[test]
387 fn type_info_from_value_no_name() {
388 let map = BTreeMap::new();
389 assert!(TypeInfo::from_value(&Value::Map(map)).is_none());
390 }
391
392 #[test]
393 fn type_descriptor_new() {
394 let td = TypeDescriptor::new("string");
395 assert_eq!(td.type_info.name, "string");
396 }
397
398 #[test]
399 fn type_descriptor_to_value() {
400 let td = TypeDescriptor::new("integer");
401 let v = td.to_value();
402
403 if let Value::Map(map) = v {
404 assert!(map.contains_key("type"));
405 } else {
406 panic!("Expected map");
407 }
408 }
409
410 #[test]
411 fn type_descriptor_from_value() {
412 let mut type_map = BTreeMap::new();
413 type_map.insert("name".to_string(), Value::String("integer".into()));
414
415 let mut map = BTreeMap::new();
416 map.insert("type".to_string(), Value::Map(type_map));
417
418 let td = TypeDescriptor::from_value(&Value::Map(map)).unwrap();
419 assert_eq!(td.type_info.name, "integer");
420 }
421
422 #[test]
423 fn type_descriptor_from_value_not_map() {
424 assert!(TypeDescriptor::from_value(&Value::String("x".into())).is_none());
425 }
426
427 #[test]
428 fn type_descriptor_from_value_no_type() {
429 let map = BTreeMap::new();
430 assert!(TypeDescriptor::from_value(&Value::Map(map)).is_none());
431 }
432}