dpp/util/
json_path.rs

1use std::convert::TryFrom;
2
3use anyhow::{anyhow, bail};
4
5/// JsonPath represents a deserialized [JsonPathLiteral]. The JsonPath is made
6/// of [JsonPathStep]. [JsonPath] can be created from string
7/// ## Example
8///  ```
9/// use dpp::util::json_path::{JsonPath, JsonPathLiteral};
10/// use std::convert::TryFrom;
11///
12/// let json_path = JsonPath::try_from(JsonPathLiteral("contract.data.collection[0]")).unwrap();
13/// assert_eq!(4, json_path.len());
14/// ```
15pub type JsonPath = Vec<JsonPathStep>;
16
17/// Single step in [`JsonPath`]. The step can be a `String` - to access data in objects
18/// or `usize` - to access data in collections
19// TODO To reduce memory allocation, the String should be replaced with the &str
20#[derive(Debug, Clone)]
21pub enum JsonPathStep {
22    Key(String),
23    Index(usize),
24}
25
26/// JsonPathLiteral represents the path in JSON structure.
27pub struct JsonPathLiteral<'a>(pub &'a str);
28
29impl<'a> std::ops::Deref for JsonPathLiteral<'a> {
30    type Target = &'a str;
31
32    fn deref(&self) -> &Self::Target {
33        &self.0
34    }
35}
36
37impl<'a> From<&'a str> for JsonPathLiteral<'a> {
38    fn from(s: &'a str) -> Self {
39        JsonPathLiteral(s)
40    }
41}
42
43impl<'a> TryFrom<JsonPathLiteral<'a>> for JsonPath {
44    type Error = anyhow::Error;
45
46    // TODO include validation:
47    // - validation: empty steps.
48    // - Valid and invalid characters should take into account the Schema
49    // - no path steps
50    fn try_from(path: JsonPathLiteral<'a>) -> Result<Self, Self::Error> {
51        let mut steps: Vec<JsonPathStep> = vec![];
52        let raw_steps = path.split('.');
53
54        for step in raw_steps {
55            if let Ok((step_key, step_index)) = try_parse_indexed_field(step) {
56                steps.push(JsonPathStep::Key(step_key.to_string()));
57                steps.push(JsonPathStep::Index(step_index));
58            } else {
59                steps.push(JsonPathStep::Key(step.to_string()))
60            };
61        }
62        Ok(steps)
63    }
64}
65
66// try to parse indexed step path. i.e: "property_name[0]"
67fn try_parse_indexed_field(step: &str) -> Result<(String, usize), anyhow::Error> {
68    let chars: Vec<char> = step.chars().collect();
69    let index_open = chars.iter().position(|c| c == &'[');
70    let index_close = chars.iter().position(|c| c == &']');
71
72    if index_open.is_none() {
73        bail!("open index bracket not found");
74    }
75    if index_close.is_none() {
76        bail!("close index bracket not found");
77    }
78    if index_open > index_close {
79        bail!("open bracket is ahead of close bracket")
80    }
81    if index_close.unwrap() != chars.len() - 1 {
82        bail!("the close bracket must be the last character")
83    }
84
85    let index_str: String = chars[index_open.unwrap() + 1..index_close.unwrap()]
86        .iter()
87        .collect();
88
89    let index: usize = index_str
90        .parse()
91        .map_err(|e| anyhow!("unable to parse '{}' into usize: {}", index_str, e))?;
92    let key: String = chars[0..index_open.unwrap()].iter().collect();
93
94    Ok((key, index))
95}
96
97#[cfg(test)]
98mod test {
99    use super::*;
100
101    #[test]
102    fn test_parse_indexed_field() {
103        let input = "data[1]";
104        let (key, index) = try_parse_indexed_field(input).unwrap();
105
106        assert_eq!("data", key);
107        assert_eq!(1, index);
108
109        let input = "数据[3]";
110        let (key, index) = try_parse_indexed_field(input).unwrap();
111
112        assert_eq!("数据", key);
113        assert_eq!(3, index);
114
115        let input = "data---__[1]";
116        let (key, index) = try_parse_indexed_field(input).unwrap();
117
118        assert_eq!("data---__", key);
119        assert_eq!(1, index);
120
121        let input = "";
122        assert!(try_parse_indexed_field(input).is_err());
123        assert_eq!(
124            try_parse_indexed_field(input).unwrap_err().to_string(),
125            "open index bracket not found"
126        );
127
128        let input = "da[0]ta";
129        assert!(try_parse_indexed_field(input).is_err());
130        assert_eq!(
131            try_parse_indexed_field(input).unwrap_err().to_string(),
132            "the close bracket must be the last character"
133        );
134
135        let input = "data[string]";
136        assert!(try_parse_indexed_field(input).is_err());
137        assert_eq!(
138            try_parse_indexed_field(input).unwrap_err().to_string(),
139            "unable to parse 'string' into usize: invalid digit found in string"
140        );
141    }
142}