summaryrefslogtreecommitdiffstats
path: root/src/tools/jsondoclint/src/json_find.rs
blob: 95ea8866609d09689e969d33fb47dc3b1245bffb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use std::fmt::Write;

use serde_json::Value;

#[derive(Debug, Clone)]
pub enum SelectorPart {
    Field(String),
    Index(usize),
}

pub type Selector = Vec<SelectorPart>;

pub fn to_jsonpath(sel: &Selector) -> String {
    let mut s = String::from("$");
    for part in sel {
        match part {
            SelectorPart::Field(name) => {
                if is_jsonpath_safe(name) {
                    write!(&mut s, ".{}", name).unwrap();
                } else {
                    // This is probably wrong in edge cases, but all Id's are
                    // just ascii alphanumerics, `-` `_`, and `:`
                    write!(&mut s, "[{name:?}]").unwrap();
                }
            }
            SelectorPart::Index(idx) => write!(&mut s, "[{idx}]").unwrap(),
        }
    }
    s
}

fn is_jsonpath_safe(s: &str) -> bool {
    s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}

pub fn find_selector(haystack: &Value, needle: &Value) -> Vec<Selector> {
    let mut result = Vec::new();
    let mut sel = Selector::new();
    find_selector_recursive(haystack, needle, &mut result, &mut sel);
    result
}

fn find_selector_recursive(
    haystack: &Value,
    needle: &Value,
    result: &mut Vec<Selector>,
    pos: &mut Selector,
) {
    if needle == haystack {
        result.push(pos.clone());
        // Haystack cant both contain needle and be needle
    } else {
        match haystack {
            Value::Null => {}
            Value::Bool(_) => {}
            Value::Number(_) => {}
            Value::String(_) => {}
            Value::Array(arr) => {
                for (idx, subhaystack) in arr.iter().enumerate() {
                    pos.push(SelectorPart::Index(idx));
                    find_selector_recursive(subhaystack, needle, result, pos);
                    pos.pop().unwrap();
                }
            }
            Value::Object(obj) => {
                for (key, subhaystack) in obj {
                    pos.push(SelectorPart::Field(key.clone()));
                    find_selector_recursive(subhaystack, needle, result, pos);
                    pos.pop().unwrap();
                }
            }
        }
    }
}