Skip to main content

vv_agent/checkpoint/canonical/
json_pointer.rs

1use super::*;
2
3pub fn resolve_json_pointer<'a>(value: &'a Value, pointer: &str) -> CheckpointResult<&'a Value> {
4    let tokens = pointer_tokens(pointer)?;
5    let mut current = value;
6    for token in tokens {
7        current = match current {
8            Value::Object(object) => object.get(&token).ok_or_else(|| {
9                CheckpointError::new(
10                    "checkpoint_credential_slot_unresolved",
11                    format!("JSON pointer {pointer} does not resolve"),
12                )
13            })?,
14            Value::Array(array) => {
15                let index = token.parse::<usize>().map_err(|_| {
16                    CheckpointError::new(
17                        "checkpoint_credential_slot_unresolved",
18                        format!("JSON pointer {pointer} has an invalid array index"),
19                    )
20                })?;
21                array.get(index).ok_or_else(|| {
22                    CheckpointError::new(
23                        "checkpoint_credential_slot_unresolved",
24                        format!("JSON pointer {pointer} does not resolve"),
25                    )
26                })?
27            }
28            _ => {
29                return Err(CheckpointError::new(
30                    "checkpoint_credential_slot_unresolved",
31                    format!("JSON pointer {pointer} does not resolve"),
32                ));
33            }
34        };
35    }
36    Ok(current)
37}
38
39pub fn set_json_pointer(
40    value: &mut Value,
41    pointer: &str,
42    replacement: Value,
43) -> CheckpointResult<()> {
44    let tokens = pointer_tokens(pointer)?;
45    if tokens.is_empty() {
46        *value = replacement;
47        return Ok(());
48    }
49    let mut current = value;
50    for token in &tokens[..tokens.len() - 1] {
51        current = match current {
52            Value::Object(object) => object.get_mut(token).ok_or_else(|| {
53                CheckpointError::new(
54                    "checkpoint_credential_slot_unresolved",
55                    format!("JSON pointer {pointer} does not resolve"),
56                )
57            })?,
58            Value::Array(array) => {
59                let index = token.parse::<usize>().map_err(|_| {
60                    CheckpointError::new(
61                        "checkpoint_credential_slot_unresolved",
62                        format!("JSON pointer {pointer} has an invalid array index"),
63                    )
64                })?;
65                array.get_mut(index).ok_or_else(|| {
66                    CheckpointError::new(
67                        "checkpoint_credential_slot_unresolved",
68                        format!("JSON pointer {pointer} does not resolve"),
69                    )
70                })?
71            }
72            _ => {
73                return Err(CheckpointError::new(
74                    "checkpoint_credential_slot_unresolved",
75                    format!("JSON pointer {pointer} does not resolve"),
76                ));
77            }
78        };
79    }
80    let last = tokens.last().expect("non-empty pointer tokens");
81    match current {
82        Value::Object(object) => {
83            if !object.contains_key(last) {
84                return Err(CheckpointError::new(
85                    "checkpoint_credential_slot_unresolved",
86                    format!("JSON pointer {pointer} does not resolve"),
87                ));
88            }
89            object.insert(last.clone(), replacement);
90        }
91        Value::Array(array) => {
92            let index = last.parse::<usize>().map_err(|_| {
93                CheckpointError::new(
94                    "checkpoint_credential_slot_unresolved",
95                    format!("JSON pointer {pointer} has an invalid array index"),
96                )
97            })?;
98            let Some(item) = array.get_mut(index) else {
99                return Err(CheckpointError::new(
100                    "checkpoint_credential_slot_unresolved",
101                    format!("JSON pointer {pointer} does not resolve"),
102                ));
103            };
104            *item = replacement;
105        }
106        _ => {
107            return Err(CheckpointError::new(
108                "checkpoint_credential_slot_unresolved",
109                format!("JSON pointer {pointer} does not resolve"),
110            ));
111        }
112    }
113    Ok(())
114}
115
116pub(in crate::checkpoint) fn validate_pointer(pointer: &str) -> CheckpointResult<()> {
117    pointer_tokens(pointer).map(|_| ())
118}
119
120fn pointer_tokens(pointer: &str) -> CheckpointResult<Vec<String>> {
121    if pointer.is_empty() {
122        return Ok(Vec::new());
123    }
124    if !pointer.starts_with('/') {
125        return Err(CheckpointError::new(
126            "checkpoint_credential_slots_invalid",
127            "JSON pointer must be empty or start with '/'",
128        ));
129    }
130    pointer
131        .split('/')
132        .skip(1)
133        .map(|raw| {
134            let mut token = String::with_capacity(raw.len());
135            let bytes = raw.as_bytes();
136            let mut index = 0;
137            while index < bytes.len() {
138                if bytes[index] != b'~' {
139                    let character = raw[index..].chars().next().expect("valid UTF-8");
140                    token.push(character);
141                    index += character.len_utf8();
142                    continue;
143                }
144                if index + 1 >= bytes.len() {
145                    return Err(CheckpointError::new(
146                        "checkpoint_credential_slots_invalid",
147                        "JSON pointer contains an invalid escape",
148                    ));
149                }
150                match bytes[index + 1] {
151                    b'0' => token.push('~'),
152                    b'1' => token.push('/'),
153                    _ => {
154                        return Err(CheckpointError::new(
155                            "checkpoint_credential_slots_invalid",
156                            "JSON pointer contains an invalid escape",
157                        ));
158                    }
159                }
160                index += 2;
161            }
162            Ok(token)
163        })
164        .collect()
165}