cesride/core/
pather.rs

1use crate::{
2    core::{
3        bexter::{rawify, tables as bexter, Bext},
4        matter::{tables as matter, Matter},
5        sadder::Sadder,
6        saider::Saider,
7        serder::Serder,
8        util::REB64_STRING,
9    },
10    data::Value,
11    error::{err, Error, Result},
12};
13
14use lazy_static::lazy_static;
15use regex::Regex;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct Pather {
19    code: String,
20    raw: Vec<u8>,
21    size: u32,
22}
23
24impl Default for Pather {
25    fn default() -> Self {
26        Pather { code: matter::Codex::StrB64_L0.to_string(), raw: vec![], size: 0 }
27    }
28}
29
30fn validate_code(code: &str) -> Result<()> {
31    if !bexter::Codex::has_code(code) {
32        return err!(Error::UnexpectedCode(code.to_string()));
33    }
34
35    Ok(())
36}
37
38fn pather_from_bext(bext: &str, code: &str) -> Result<Pather> {
39    lazy_static! {
40        static ref REB64: Regex = Regex::new(REB64_STRING).unwrap();
41    }
42
43    if !REB64.is_match(bext) {
44        return err!(Error::Value("invalid base64".to_string()));
45    }
46
47    let raw = rawify(bext)?;
48
49    Matter::new(Some(code), Some(&raw), None, None, None)
50}
51
52impl Pather {
53    pub fn new(
54        path: Option<&Value>,
55        bext: Option<&str>,
56        code: Option<&str>,
57        raw: Option<&[u8]>,
58        qb64b: Option<&[u8]>,
59        qb64: Option<&str>,
60        qb2: Option<&[u8]>,
61    ) -> Result<Self> {
62        let code = code.unwrap_or(matter::Codex::StrB64_L0);
63
64        let pather: Pather = if bext.is_none()
65            && raw.is_none()
66            && qb64b.is_none()
67            && qb64.is_none()
68            && qb2.is_none()
69        {
70            if let Some(path) = path {
71                pather_from_bext(&Self::bextify(path)?, code)?
72            } else {
73                return err!(Error::EmptyMaterial("missing bext string".to_string()));
74            }
75        } else if let Some(bext) = bext {
76            pather_from_bext(bext, code)?
77        } else {
78            Matter::new(Some(code), raw, qb64b, qb64, qb2)?
79        };
80
81        validate_code(&pather.code())?;
82
83        Ok(pather)
84    }
85
86    pub fn new_with_path(path: &Value) -> Result<Self> {
87        Self::new(Some(path), None, None, None, None, None, None)
88    }
89
90    pub fn new_with_bext(bext: &str) -> Result<Self> {
91        Self::new(None, Some(bext), None, None, None, None, None)
92    }
93
94    pub fn new_with_raw(raw: &[u8], code: Option<&str>) -> Result<Self> {
95        Self::new(None, None, code, Some(raw), None, None, None)
96    }
97
98    pub fn new_with_qb64b(qb64b: &[u8]) -> Result<Self> {
99        Self::new(None, None, None, None, Some(qb64b), None, None)
100    }
101
102    pub fn new_with_qb64(qb64: &str) -> Result<Self> {
103        Self::new(None, None, None, None, None, Some(qb64), None)
104    }
105
106    pub fn new_with_qb2(qb2: &[u8]) -> Result<Self> {
107        Self::new(None, None, None, None, None, None, Some(qb2))
108    }
109
110    pub fn path(&self) -> Result<Value> {
111        let bext = self.bext()?;
112
113        if !bext.starts_with('-') {
114            return err!(Error::Value("invalid sad pointer".to_string()));
115        }
116
117        let result: Vec<Value> = bext[1..].split('-').map(|p| dat!(p)).collect();
118
119        if result[0].to_string()?.is_empty() {
120            Ok(dat!([]))
121        } else {
122            Ok(dat!(result.as_slice()))
123        }
124    }
125
126    pub fn root(&self, root: &Self) -> Result<Self> {
127        let mut path = root.path()?.to_vec()?;
128        let mut to_append = self.path()?.to_vec()?;
129
130        path.append(&mut to_append);
131        let path = dat!(path.as_slice());
132
133        Pather::new_with_path(&path)
134    }
135
136    pub fn strip(&self, root: &Self) -> Result<Self> {
137        let mut root_path = root.path()?.to_vec()?;
138        let mut path = self.path()?.to_vec()?;
139
140        let hashmap: std::collections::HashMap<String, usize> =
141            path.iter().enumerate().map(|(x, y)| (y.to_string().unwrap(), x)).collect();
142
143        if root_path.len() > path.len() {
144            return Ok(self.clone());
145        }
146
147        root_path.reverse();
148        for p in root_path {
149            path.remove(hashmap[&p.to_string()?]);
150        }
151
152        Pather::new_with_path(&dat!(path.as_slice()))
153    }
154
155    pub fn starts_with(&self, path: &Self) -> Result<bool> {
156        Ok(self.bext()?.starts_with(&path.bext()?))
157    }
158
159    pub fn resolve(&self, sad: &Value) -> Result<Value> {
160        Self::_resolve(sad, &self.path()?)
161    }
162
163    pub fn tail(&self, serder: &Serder) -> Result<String> {
164        let val = self.resolve(&serder.ked())?;
165
166        if val.to_string().is_ok() {
167            let result = val.to_string()?;
168            // validate said
169            Saider::new(None, None, None, None, None, None, None, Some(&result), None)?;
170            Ok(result)
171        } else if val.to_map().is_ok() || val.to_vec().is_ok() {
172            val.to_json()
173        } else {
174            return err!(Error::Value("bad tail value".to_string()));
175        }
176    }
177
178    fn bextify(path: &Value) -> Result<String> {
179        lazy_static! {
180            static ref REB64: Regex = Regex::new(REB64_STRING).unwrap();
181        }
182
183        let mut vath = vec![];
184        let path = path.to_vec()?;
185        for e in &path {
186            let p = e.to_string();
187            let p = if let Ok(p) = p { p } else { e.to_i64()?.to_string() };
188
189            if !REB64.is_match(&p) {
190                return err!(Error::Value("invalid base64".to_string()));
191            }
192
193            vath.push(p);
194        }
195
196        Ok("-".to_string() + &vath.join("-"))
197    }
198
199    fn _resolve(val: &Value, ptr: &Value) -> Result<Value> {
200        let mut ptr = ptr.to_vec()?;
201        if ptr.is_empty() {
202            return Ok(val.clone());
203        }
204
205        let idx = ptr.remove(0).to_string()?;
206
207        let cur = if val.to_map().is_ok() {
208            let val = val.to_map()?;
209            let result = idx.parse::<usize>();
210            if result.is_ok() {
211                let i = result?;
212                if i >= val.len() {
213                    return err!(Error::Value(format!("invalid map index {i}, larger than size")));
214                }
215                val[i].clone()
216            } else if idx.is_empty() {
217                return Ok(dat!(&val));
218            } else {
219                if !val.contains_key(&idx) {
220                    return err!(Error::Value(format!("invalid index {idx} for map")));
221                }
222                val[&idx].clone()
223            }
224        } else if val.to_vec().is_ok() {
225            let val = val.to_vec()?;
226            let i = idx.parse::<usize>()?;
227            if i >= val.len() {
228                return err!(Error::Value(format!("invalid array index {i}, larger than size")));
229            }
230            val[i].clone()
231        } else {
232            return err!(Error::Value("invalid traversal type".to_string()));
233        };
234
235        Self::_resolve(&cur, &dat!(ptr.as_slice()))
236    }
237}
238
239impl Bext for Pather {}
240
241impl Matter for Pather {
242    fn code(&self) -> String {
243        self.code.clone()
244    }
245
246    fn raw(&self) -> Vec<u8> {
247        self.raw.clone()
248    }
249
250    fn size(&self) -> u32 {
251        self.size
252    }
253
254    fn set_code(&mut self, code: &str) {
255        self.code = code.to_string();
256    }
257
258    fn set_raw(&mut self, raw: &[u8]) {
259        self.raw = raw.to_vec();
260    }
261
262    fn set_size(&mut self, size: u32) {
263        self.size = size;
264    }
265}
266
267#[cfg(test)]
268mod test {
269    use super::Pather;
270    use crate::{
271        core::{
272            bexter::Bext,
273            matter::{tables as matter, Matter},
274            saider::Saider,
275            serder::Serder,
276        },
277        data::dat,
278    };
279
280    #[test]
281    fn convenience() {
282        let pather = Pather::new_with_bext("-a").unwrap();
283
284        assert!(Pather::new_with_bext(&pather.bext().unwrap()).is_ok());
285        assert!(Pather::new_with_path(&pather.path().unwrap()).is_ok());
286        assert!(Pather::new_with_qb2(&pather.qb2().unwrap()).is_ok());
287        assert!(Pather::new_with_qb64(&pather.qb64().unwrap()).is_ok());
288        assert!(Pather::new_with_qb64b(&pather.qb64b().unwrap()).is_ok());
289        assert!(Pather::new_with_raw(&pather.raw(), None).is_ok());
290    }
291
292    #[test]
293    fn unhappy() {
294        assert!(Pather::new(
295            None,
296            None,
297            Some(matter::Codex::Blake3_256),
298            Some(b"00000000000000000000000000000000"),
299            None,
300            None,
301            None
302        )
303        .is_err());
304        assert!(Pather::new(None, Some("@!"), None, None, None, None, None).is_err());
305        assert!(Pather::new(None, None, None, None, None, None, None).is_err());
306
307        let pather =
308            Pather::new(None, None, Some(matter::Codex::StrB64_L0), Some(b"00"), None, None, None)
309                .unwrap();
310        assert!(pather.path().is_err());
311
312        let sad = dat!({
313            "a": [1]
314        });
315
316        let pather = Pather::new(None, Some("-1"), None, None, None, None, None).unwrap();
317        assert!(pather.resolve(&sad).is_err());
318
319        let pather = Pather::new(None, Some("-b"), None, None, None, None, None).unwrap();
320        assert!(pather.resolve(&sad).is_err());
321
322        let pather = Pather::new(None, Some("-a-1"), None, None, None, None, None).unwrap();
323        assert!(pather.resolve(&sad).is_err());
324
325        // invalid traversal
326        let sad = dat!(2);
327        let pather = Pather::new(None, Some("-0"), None, None, None, None, None).unwrap();
328        assert!(pather.resolve(&sad).is_err());
329    }
330
331    #[test]
332    fn resolve() {
333        let sad = dat!({
334            "a": [2]
335        });
336
337        let pather = Pather::new(None, Some("-a-0"), None, None, None, None, None).unwrap();
338        assert_eq!(pather.resolve(&sad).unwrap(), dat!(2));
339
340        assert_eq!(Pather::_resolve(&sad, &dat!([""])).unwrap(), sad);
341    }
342
343    #[test]
344    fn root() {
345        let pather = Pather::new_with_bext("-a").unwrap();
346        let root = Pather::new_with_bext("-r").unwrap();
347        assert_eq!(pather.root(&root).unwrap().bext().unwrap(), "-r-a");
348    }
349
350    #[test]
351    fn tail() {
352        let _vs = "KERI10JSON000000_";
353        let e1 = dat!({
354            "v": _vs,
355            "d": "",
356            "i": "ABCDEFG",
357            "s": {},
358            "t": "rot",
359            "x": 1
360        });
361        let (_, e1) = Saider::saidify(&e1, None, None, None, None).unwrap();
362        let serder = Serder::new(None, None, None, Some(&e1), None).unwrap();
363
364        let pather = Pather::new_with_bext("-d").unwrap();
365        assert_eq!(pather.tail(&serder).unwrap(), e1["d"].to_string().unwrap());
366
367        let pather = Pather::new_with_bext("-s").unwrap();
368        assert_eq!(pather.tail(&serder).unwrap(), "{}");
369
370        let pather = Pather::new_with_bext("-x").unwrap();
371        assert!(pather.tail(&serder).is_err());
372    }
373
374    #[test]
375    fn python_interop() {
376        let sad = dat!({
377            "a": {
378                "z": "value",
379                "b": {
380                    "x": 1,
381                    "y": 2,
382                    "c": "test"
383                }
384            }
385        });
386
387        let path = dat!([]);
388        let pather = Pather::new(Some(&path), None, None, None, None, None, None).unwrap();
389        assert_eq!(pather.bext().unwrap(), "-");
390        assert_eq!(pather.qb64().unwrap(), "6AABAAA-");
391        assert_eq!(pather.raw(), b">");
392        assert_eq!(pather.resolve(&sad).unwrap(), sad);
393        assert_eq!(pather.path().unwrap(), path);
394
395        let path = dat!(["a", "b", "c"]);
396        let pather = Pather::new(Some(&path), None, None, None, None, None, None).unwrap();
397        assert_eq!(pather.bext().unwrap(), "-a-b-c");
398        assert_eq!(pather.qb64().unwrap(), "5AACAA-a-b-c");
399        assert_eq!(pather.raw(), b"\x0f\x9a\xf9\xbf\x9c");
400        assert_eq!(pather.resolve(&sad).unwrap().to_string().unwrap(), "test");
401        assert_eq!(pather.path().unwrap(), path);
402
403        let path = dat!(["0", "1", "2"]);
404        let pather = Pather::new(Some(&path), None, None, None, None, None, None).unwrap();
405        assert_eq!(pather.bext().unwrap(), "-0-1-2");
406        assert_eq!(pather.qb64().unwrap(), "5AACAA-0-1-2");
407        assert_eq!(pather.raw(), b"\x0f\xb4\xfb_\xb6");
408        assert_eq!(pather.resolve(&sad).unwrap().to_string().unwrap(), "test");
409        assert_eq!(pather.path().unwrap(), path);
410
411        let sad = dat!({
412            "field0": {
413                "z": "value",
414                "field1": {
415                    "field2": 1,
416                    "field3": 2,
417                    "c": "test"
418                }
419            }
420        });
421
422        let path = dat!(["field0"]);
423        let pather = Pather::new(Some(&path), None, None, None, None, None, None).unwrap();
424        assert_eq!(pather.bext().unwrap(), "-field0");
425        assert_eq!(pather.qb64().unwrap(), "4AACA-field0");
426        assert_eq!(pather.raw(), b"\x03\xe7\xe2zWt");
427        assert_eq!(
428            pather.resolve(&sad).unwrap(),
429            dat!({
430                "z": "value",
431                "field1": {
432                    "field2": 1,
433                    "field3": 2,
434                    "c": "test"
435                }
436            })
437        );
438        assert_eq!(pather.path().unwrap(), path);
439
440        let path = dat!(["field0", "field1", "field3"]);
441        let pather = Pather::new(Some(&path), None, None, None, None, None, None).unwrap();
442        assert_eq!(pather.bext().unwrap(), "-field0-field1-field3");
443        assert_eq!(pather.qb64().unwrap(), "6AAGAAA-field0-field1-field3");
444        assert_eq!(pather.raw(), b">~'\xa5wO\x9f\x89\xe9]\xd7\xe7\xe2zWw");
445        assert_eq!(pather.resolve(&sad).unwrap().to_i64().unwrap(), 2);
446        assert_eq!(pather.path().unwrap(), path);
447
448        let path = dat!(["field0", "1", "0"]);
449        let pather = Pather::new(Some(&path), None, None, None, None, None, None).unwrap();
450        assert_eq!(pather.bext().unwrap(), "-field0-1-0");
451        assert_eq!(pather.qb64().unwrap(), "4AADA-field0-1-0");
452        assert_eq!(pather.raw(), b"\x03\xe7\xe2zWt\xfb_\xb4");
453        assert_eq!(pather.resolve(&sad).unwrap().to_i64().unwrap(), 1);
454        assert_eq!(pather.path().unwrap(), path);
455
456        let sad = dat!({
457            "field0": {
458                "z": {
459                    "field2": 1,
460                    "field3": 2,
461                    "c": "test"
462                },
463                "field1": "value"
464            }
465        });
466
467        let text = "-0-z-2";
468        let pather = Pather::new(None, Some(text), None, None, None, None, None).unwrap();
469        assert_eq!(pather.bext().unwrap(), text);
470        assert_eq!(pather.qb64().unwrap(), "5AACAA-0-z-2");
471        assert_eq!(pather.raw(), b"\x0f\xb4\xfb?\xb6");
472        assert_eq!(pather.resolve(&sad).unwrap().to_string().unwrap(), "test");
473        assert_eq!(pather.path().unwrap(), dat!(["0", "z", "2"]));
474
475        let text = "-0-a";
476        let pather = Pather::new(None, Some(text), None, None, None, None, None).unwrap();
477        assert_eq!(pather.bext().unwrap(), text);
478        assert_eq!(pather.qb64().unwrap(), "4AAB-0-a");
479        assert_eq!(pather.raw(), b"\xfbO\x9a");
480        assert!(pather.resolve(&sad).is_err());
481        assert_eq!(pather.path().unwrap(), dat!(["0", "a"]));
482
483        let text = "-0-field1-0";
484        let pather = Pather::new(None, Some(text), None, None, None, None, None).unwrap();
485        assert_eq!(pather.bext().unwrap(), text);
486        assert_eq!(pather.qb64().unwrap(), "4AADA-0-field1-0");
487        assert_eq!(pather.raw(), b"\x03\xed>~'\xa5w_\xb4");
488        assert!(pather.resolve(&sad).is_err());
489        assert_eq!(pather.path().unwrap(), dat!(["0", "field1", "0"]));
490
491        let path = dat!(["Not$Base64", "@moreso", "*again"]);
492        assert!(Pather::new(Some(&path), None, None, None, None, None, None).is_err());
493
494        let text = "-a";
495        let a = Pather::new(None, Some(text), None, None, None, None, None).unwrap();
496        let b = Pather::new(None, Some("-a-b"), None, None, None, None, None).unwrap();
497
498        let pather = Pather::new(None, Some(text), None, None, None, None, None).unwrap();
499        assert!(pather.starts_with(&a).unwrap());
500        assert!(!pather.starts_with(&b).unwrap());
501
502        let pnew = pather.strip(&a).unwrap();
503        assert_eq!(pnew.path().unwrap(), dat!([]));
504
505        let pnew = pather.strip(&b).unwrap();
506        assert_eq!(pnew.path().unwrap(), pather.path().unwrap());
507
508        let pather = Pather::new(None, Some("-a-b-c-d-e-f"), None, None, None, None, None).unwrap();
509        assert!(pather.starts_with(&a).unwrap());
510        assert!(pather.starts_with(&b).unwrap());
511
512        let pnew = pather.strip(&a).unwrap();
513        assert_eq!(pnew.path().unwrap(), dat!(["b", "c", "d", "e", "f"]));
514
515        let pnew = pather.strip(&b).unwrap();
516        assert_eq!(pnew.path().unwrap(), dat!(["c", "d", "e", "f"]));
517    }
518}