1use std::collections::HashMap;
2
3use crate::result::CborValue;
4
5pub fn resolve_path(value: &CborValue, path: &str) -> Option<CborValue> {
10 if path.is_empty() {
11 return Some(value.clone());
12 }
13
14 let segments: Vec<&str> = path.split('.').collect();
15 let mut current = value.clone();
16
17 for segment in &segments {
18 match current {
19 CborValue::Map(pairs) => {
20 let found = pairs
21 .into_iter()
22 .find(|(k, _)| matches!(k, CborValue::Text(s) if s == segment));
23 match found {
24 Some((_, v)) => current = v,
25 None => return None,
26 }
27 }
28 _ => return None,
29 }
30 }
31
32 Some(current)
33}
34
35pub fn set_path(path: &str, value: CborValue) -> CborValue {
39 if path.is_empty() {
40 return value;
41 }
42
43 let segments: Vec<&str> = path.split('.').collect();
44 let mut result = value;
45
46 for segment in segments.into_iter().rev() {
48 result = CborValue::Map(vec![(CborValue::Text(segment.to_string()), result)]);
49 }
50
51 result
52}
53
54pub fn merge_maps(base: CborValue, overlay: CborValue) -> CborValue {
58 match (base, overlay) {
59 (CborValue::Map(mut base_pairs), CborValue::Map(overlay_pairs)) => {
60 let mut idx: HashMap<String, usize> = HashMap::new();
62 for (i, (k, _)) in base_pairs.iter().enumerate() {
63 if let CborValue::Text(s) = k {
64 idx.insert(s.clone(), i);
65 }
66 }
67
68 for (ok, ov) in overlay_pairs {
69 if let CborValue::Text(ref ks) = ok {
70 if let Some(&i) = idx.get(ks) {
71 let old = std::mem::replace(
72 &mut base_pairs[i],
73 (CborValue::Null, CborValue::Null),
74 );
75 let (bk, bv) = old;
76 let merged = merge_maps(bv, ov);
77 base_pairs[i] = (bk, merged);
78 } else {
79 idx.insert(ks.clone(), base_pairs.len());
80 base_pairs.push((ok, ov));
81 }
82 } else {
83 base_pairs.push((ok, ov));
85 }
86 }
87
88 CborValue::Map(base_pairs)
89 }
90 (_, overlay) => overlay,
91 }
92}
93
94pub fn cbor_to_json(value: &CborValue) -> serde_json::Value {
96 match value {
97 CborValue::Null => serde_json::Value::Null,
98 CborValue::Bool(b) => serde_json::Value::Bool(*b),
99 CborValue::Integer(n) => serde_json::json!(n),
100 CborValue::Float(f) => serde_json::json!(f),
101 CborValue::Text(s) => serde_json::Value::String(s.clone()),
102 CborValue::Bytes(b) => serde_json::Value::String(hex::encode(b)),
103 CborValue::Array(items) => {
104 serde_json::Value::Array(items.iter().map(cbor_to_json).collect())
105 }
106 CborValue::Map(pairs) => {
107 let mut map = serde_json::Map::new();
108 for (k, v) in pairs {
109 let key = match k {
110 CborValue::Text(s) => s.clone(),
111 other => format!("{other:?}"),
112 };
113 map.insert(key, cbor_to_json(v));
114 }
115 serde_json::Value::Object(map)
116 }
117 }
118}
119
120pub fn extract_confidence(value: &CborValue) -> Option<f64> {
122 match resolve_path(value, "confidence") {
123 Some(CborValue::Float(f)) => Some(f),
124 Some(CborValue::Integer(n)) => Some(n as f64),
125 _ => None,
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn text(s: &str) -> CborValue {
134 CborValue::Text(s.to_string())
135 }
136 fn int(n: i64) -> CborValue {
137 CborValue::Integer(n)
138 }
139
140 fn sample_map() -> CborValue {
141 CborValue::Map(vec![
142 (text("label"), text("positive")),
143 (text("confidence"), CborValue::Float(0.95)),
144 (
145 text("nested"),
146 CborValue::Map(vec![(text("deep"), int(42))]),
147 ),
148 ])
149 }
150
151 #[test]
152 fn resolve_top_level() {
153 let map = sample_map();
154 let val = resolve_path(&map, "label").unwrap();
155 assert!(matches!(val, CborValue::Text(s) if s == "positive"));
156 }
157
158 #[test]
159 fn resolve_nested() {
160 let map = sample_map();
161 let val = resolve_path(&map, "nested.deep").unwrap();
162 assert!(matches!(val, CborValue::Integer(42)));
163 }
164
165 #[test]
166 fn resolve_missing() {
167 let map = sample_map();
168 assert!(resolve_path(&map, "nonexistent").is_none());
169 assert!(resolve_path(&map, "nested.missing").is_none());
170 }
171
172 #[test]
173 fn resolve_empty_path() {
174 let map = sample_map();
175 let val = resolve_path(&map, "").unwrap();
176 assert!(matches!(val, CborValue::Map(_)));
177 }
178
179 #[test]
180 fn set_single_segment() {
181 let result = set_path("label", text("positive"));
182 let resolved = resolve_path(&result, "label").unwrap();
183 assert!(matches!(resolved, CborValue::Text(s) if s == "positive"));
184 }
185
186 #[test]
187 fn set_multi_segment() {
188 let result = set_path("input.sentiment", text("positive"));
189 let resolved = resolve_path(&result, "input.sentiment").unwrap();
190 assert!(matches!(resolved, CborValue::Text(s) if s == "positive"));
191 }
192
193 #[test]
194 fn set_empty_path() {
195 let val = text("hello");
196 let result = set_path("", val);
197 assert!(matches!(result, CborValue::Text(s) if s == "hello"));
198 }
199
200 #[test]
201 fn merge_disjoint() {
202 let a = set_path("x", int(1));
203 let b = set_path("y", int(2));
204 let merged = merge_maps(a, b);
205 assert!(matches!(
206 resolve_path(&merged, "x"),
207 Some(CborValue::Integer(1))
208 ));
209 assert!(matches!(
210 resolve_path(&merged, "y"),
211 Some(CborValue::Integer(2))
212 ));
213 }
214
215 #[test]
216 fn merge_deep() {
217 let a = set_path("input.x", int(1));
218 let b = set_path("input.y", int(2));
219 let merged = merge_maps(a, b);
220 assert!(matches!(
221 resolve_path(&merged, "input.x"),
222 Some(CborValue::Integer(1))
223 ));
224 assert!(matches!(
225 resolve_path(&merged, "input.y"),
226 Some(CborValue::Integer(2))
227 ));
228 }
229
230 #[test]
231 fn merge_preserves_order() {
232 let base = CborValue::Map(vec![
233 (text("a"), int(1)),
234 (text("b"), int(2)),
235 (text("c"), int(3)),
236 ]);
237 let overlay = CborValue::Map(vec![(text("b"), int(20)), (text("d"), int(4))]);
238 let merged = merge_maps(base, overlay);
239 if let CborValue::Map(pairs) = &merged {
240 let keys: Vec<&str> = pairs
241 .iter()
242 .map(|(k, _)| {
243 if let CborValue::Text(s) = k {
244 s.as_str()
245 } else {
246 ""
247 }
248 })
249 .collect();
250 assert_eq!(keys, vec!["a", "b", "c", "d"]);
251 assert!(matches!(
252 resolve_path(&merged, "b"),
253 Some(CborValue::Integer(20))
254 ));
255 } else {
256 panic!("expected map");
257 }
258 }
259
260 #[test]
261 fn extract_confidence_float() {
262 let map = sample_map();
263 assert_eq!(extract_confidence(&map), Some(0.95));
264 }
265
266 #[test]
267 fn extract_confidence_missing() {
268 let map = CborValue::Map(vec![(text("label"), text("positive"))]);
269 assert_eq!(extract_confidence(&map), None);
270 }
271
272 #[test]
273 fn cbor_to_json_roundtrip() {
274 let cbor = sample_map();
275 let json = cbor_to_json(&cbor);
276 assert_eq!(json["label"], "positive");
277 assert_eq!(json["confidence"], 0.95);
278 assert_eq!(json["nested"]["deep"], 42);
279 }
280}