-
Notifications
You must be signed in to change notification settings - Fork 0
/
to.rs
209 lines (188 loc) · 6.79 KB
/
to.rs
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use std::num::NonZeroUsize;
use jsonptr::{resolve::ResolveError, Pointer, Resolve};
use serde_json::Value;
#[derive(Debug)]
pub struct WalkTo<'p, 'v> {
cursor: &'v Value,
original_value: &'v Value,
full_path: &'p Pointer,
offset: Option<NonZeroUsize>,
}
impl<'p, 'v> WalkTo<'p, 'v> {
pub fn new(to: &'p Pointer, value: &'v Value) -> Self {
Self {
cursor: value,
original_value: value,
full_path: to,
offset: None,
}
}
fn handle_err(
&self,
_err: ResolveError,
path: &'p Pointer,
_resolvable: &'p Pointer,
) -> Option<Result<(&'p Pointer, &'v Value), ResolveError>> {
// TODO: determine appropriate offsets and update error rather than re-resolving
// for now, we are just punting to `Resolve` to recreate the error
Some(Err(self.original_value.resolve(path).unwrap_err()))
}
}
// /some/example
// ^
impl<'p, 'v> Iterator for WalkTo<'p, 'v> {
type Item = Result<(&'p Pointer, &'v Value), ResolveError>;
fn next(&mut self) -> Option<Self::Item> {
// we need to get the offset to determine where we are in the pointer
let offset = if let Some(offset) = self.offset {
// the offset has previously been set, so we use that
offset.get()
} else {
// An empty offset means we are at the beginning of the walk.
// this is a special case where the target path is root
// we need to handle this separately because we later account
// for tokens, which an empty path has none of.
if self.full_path.is_root() {
// the target path is root, so we set the offset to 1 ensures we
// do not send repeats due to the bounds check on offset below.
self.offset = NonZeroUsize::new(1);
return Some(Ok((Pointer::root(), self.cursor)));
}
// if the offset was not previously set, we start at 0
0
};
// checking to make sure we are not out of bounds
if offset > self.full_path.len() {
return None;
}
// split the path at the offset, where everything to the left
// is the full path of the current value to be sent and everything
// to the right is the remaining path to be resolved.
let (path, remaining) = self
.full_path
.split_at(offset)
.unwrap_or((self.full_path, Pointer::root()));
if let Some(next) = remaining.first() {
// if there is a next token, we set the offset to the next token's length
// plus 1 to account for the slash.
self.offset = NonZeroUsize::new(offset + next.encoded().len() + 1);
} else {
// otherwise we intentionally push the offset out of bounds
self.offset = NonZeroUsize::new(offset + 1)
}
// we want the last token as a `&Pointer` so that we can use the resolve logic
// the path is either splittable (contains a token) or is empty (root).
//
// If it is splittable, we either use the token's length to determine the token's
// offset and split the path there or we use the root pointer.
let resolvable = path
.last()
.map(|t| path.split_at(path.len() - t.encoded().len() - 1).unwrap().1)
.unwrap_or(Pointer::root());
// we attempt to resolve the value
let result = self.cursor.resolve(resolvable);
let value = match result {
Ok(ok) => ok,
Err(err) => return self.handle_err(err, path, resolvable),
};
// moving the cursor value to the resolved value
self.cursor = value;
Some(Ok((path, value)))
}
}
#[cfg(test)]
mod test {
use jsonptr::Pointer;
use serde_json::json;
use super::WalkTo;
#[test]
fn valid() {
let value = json!({
"foo": {
"bar": [
{
"baz": {
"qux": 34
}
}
]
}
});
let full_path = Pointer::from_static("/foo/bar/0/baz/qux");
let walk_to = WalkTo::new(full_path, &value);
let foo = value.get("foo").unwrap();
let foo_bar = foo.get("bar").unwrap();
let foo_bar_0 = foo_bar.get(0).unwrap();
let foo_bar_0_baz = foo_bar_0.get("baz").unwrap();
let foo_bar_0_baz_qux = foo_bar_0_baz.get("qux").unwrap();
assert_eq!(
walk_to.collect::<Vec<_>>(),
vec![
Ok((Pointer::from_static(""), &value)),
Ok((Pointer::from_static("/foo"), foo)),
Ok((Pointer::from_static("/foo/bar"), foo_bar)),
Ok((Pointer::from_static("/foo/bar/0"), foo_bar_0)),
Ok((Pointer::from_static("/foo/bar/0/baz"), foo_bar_0_baz)),
Ok((
Pointer::from_static("/foo/bar/0/baz/qux"),
foo_bar_0_baz_qux
)),
]
);
}
#[test]
fn root() {
let value = json!({
"foo": {
"bar": [
{
"baz": {
"qux": 34
}
}
]
}
});
let walk_to = WalkTo::new(Pointer::from_static(""), &value);
assert_eq!(
walk_to.collect::<Vec<_>>(),
vec![Ok((Pointer::from_static(""), &value))]
);
}
#[test]
fn invalid() {
use jsonptr::resolve::ResolveError;
let value = json!({
"foo": {
"bar": [
{
"baz": {
"qux": 34
}
}
]
}
});
let full_path = Pointer::from_static("/invalid");
let walk_to = WalkTo::new(full_path, &value);
let mut results = walk_to.collect::<Vec<_>>();
assert!(results.len() == 2);
assert!(results[0].is_ok()); // root is always valid
assert!(results[1].is_err());
assert_eq!(
results.pop().unwrap().unwrap_err(),
ResolveError::NotFound { offset: 0 }
);
let full_path = Pointer::from_static("/foo/invalid");
let walk_to = WalkTo::new(full_path, &value);
let mut results = walk_to.collect::<Vec<_>>();
assert!(results.len() == 3);
assert!(results[0].is_ok()); // root is always valid
assert!(results[1].is_ok());
assert!(results[2].is_err());
assert_eq!(
results.pop().unwrap().unwrap_err(),
ResolveError::NotFound { offset: 4 }
);
}
}