1 | /* |
2 | * Copyright 2011-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include <folly/json_patch.h> |
18 | |
19 | #include <folly/container/Enumerate.h> |
20 | |
21 | namespace { |
22 | using folly::StringPiece; |
23 | // JSON patch operation names |
24 | constexpr StringPiece kOperationTest = "test" ; |
25 | constexpr StringPiece kOperationRemove = "remove" ; |
26 | constexpr StringPiece kOperationAdd = "add" ; |
27 | constexpr StringPiece kOperationReplace = "replace" ; |
28 | constexpr StringPiece kOperationMove = "move" ; |
29 | constexpr StringPiece kOperationCopy = "copy" ; |
30 | // field tags in JSON patch |
31 | constexpr StringPiece kOpTag = "op" ; |
32 | constexpr StringPiece kValueTag = "value" ; |
33 | constexpr StringPiece kPathTag = "path" ; |
34 | constexpr StringPiece kFromTag = "from" ; |
35 | } // namespace |
36 | |
37 | namespace folly { |
38 | |
39 | // static |
40 | Expected<json_patch, json_patch::parse_error> json_patch::try_parse( |
41 | dynamic const& obj) noexcept { |
42 | using err_code = parse_error_code; |
43 | |
44 | json_patch patch; |
45 | if (!obj.isArray()) { |
46 | return makeUnexpected(parse_error{err_code::invalid_shape, &obj}); |
47 | } |
48 | for (auto const& elem : obj) { |
49 | if (!elem.isObject()) { |
50 | return makeUnexpected(parse_error{err_code::invalid_shape, &elem}); |
51 | } |
52 | auto const* op_ptr = elem.get_ptr(kOpTag); |
53 | if (!op_ptr) { |
54 | return makeUnexpected(parse_error{err_code::missing_op, &elem}); |
55 | } |
56 | if (!op_ptr->isString()) { |
57 | return makeUnexpected(parse_error{err_code::malformed_op, &elem}); |
58 | } |
59 | auto const op_str = op_ptr->asString(); |
60 | patch_operation op; |
61 | |
62 | // extract 'from' attribute |
63 | { |
64 | auto const* from_ptr = elem.get_ptr(kFromTag); |
65 | if (from_ptr) { |
66 | if (!from_ptr->isString()) { |
67 | return makeUnexpected(parse_error{err_code::invalid_shape, &elem}); |
68 | } |
69 | auto json_ptr = json_pointer::try_parse(from_ptr->asString()); |
70 | if (!json_ptr.hasValue()) { |
71 | return makeUnexpected( |
72 | parse_error{err_code::malformed_from_attr, &elem}); |
73 | } |
74 | op.from = json_ptr.value(); |
75 | } |
76 | } |
77 | |
78 | // extract 'path' attribute |
79 | { |
80 | auto const* path_ptr = elem.get_ptr(kPathTag); |
81 | if (!path_ptr) { |
82 | return makeUnexpected(parse_error{err_code::missing_path_attr, &elem}); |
83 | } |
84 | if (!path_ptr->isString()) { |
85 | return makeUnexpected( |
86 | parse_error{err_code::malformed_path_attr, &elem}); |
87 | } |
88 | auto const json_ptr = json_pointer::try_parse(path_ptr->asString()); |
89 | if (!json_ptr.hasValue()) { |
90 | return makeUnexpected( |
91 | parse_error{err_code::malformed_path_attr, &elem}); |
92 | } |
93 | op.path = json_ptr.value(); |
94 | } |
95 | |
96 | // extract 'value' attribute |
97 | { |
98 | auto const* val_ptr = elem.get_ptr(kValueTag); |
99 | if (val_ptr) { |
100 | op.value = *val_ptr; |
101 | } |
102 | } |
103 | |
104 | // check mandatory attributes - different per operation |
105 | // NOTE: per RFC, the surplus attributes (e.g. 'from' with 'add') |
106 | // should be simply ignored |
107 | |
108 | using op_code = patch_operation_code; |
109 | |
110 | if (op_str == kOperationTest) { |
111 | if (!op.value) { |
112 | return makeUnexpected(parse_error{err_code::missing_value_attr, &elem}); |
113 | } |
114 | op.op_code = op_code::test; |
115 | } else if (op_str == kOperationRemove) { |
116 | op.op_code = op_code::remove; |
117 | } else if (op_str == kOperationAdd) { |
118 | if (!op.value) { |
119 | return makeUnexpected(parse_error{err_code::missing_value_attr, &elem}); |
120 | } |
121 | op.op_code = op_code::add; |
122 | } else if (op_str == kOperationReplace) { |
123 | if (!op.value) { |
124 | return makeUnexpected(parse_error{err_code::missing_value_attr, &elem}); |
125 | } |
126 | op.op_code = op_code::replace; |
127 | } else if (op_str == kOperationMove) { |
128 | if (!op.from) { |
129 | return makeUnexpected(parse_error{err_code::missing_from_attr, &elem}); |
130 | } |
131 | // is from a proper prefix to path? |
132 | if (op.from->is_prefix_of(op.path)) { |
133 | return makeUnexpected( |
134 | parse_error{err_code::overlapping_pointers, &elem}); |
135 | } |
136 | op.op_code = op_code::move; |
137 | } else if (op_str == kOperationCopy) { |
138 | if (!op.from) { |
139 | return makeUnexpected(parse_error{err_code::missing_from_attr, &elem}); |
140 | } |
141 | op.op_code = op_code::copy; |
142 | } |
143 | |
144 | if (op.op_code != op_code::invalid) { |
145 | patch.ops_.emplace_back(std::move(op)); |
146 | } else { |
147 | return makeUnexpected(parse_error{err_code::unknown_op, &elem}); |
148 | } |
149 | } |
150 | return patch; |
151 | } |
152 | |
153 | std::vector<json_patch::patch_operation> const& json_patch::ops() const { |
154 | return ops_; |
155 | } |
156 | |
157 | namespace { |
158 | // clang-format off |
159 | Expected<Unit, json_patch::patch_application_error_code> |
160 | // clang-format on |
161 | do_remove(dynamic::resolved_json_pointer<dynamic>& ptr) { |
162 | using error_code = json_patch::patch_application_error_code; |
163 | |
164 | if (!ptr.hasValue()) { |
165 | return folly::makeUnexpected(error_code::path_not_found); |
166 | } |
167 | |
168 | auto parent = ptr->parent; |
169 | |
170 | if (!parent) { |
171 | return folly::makeUnexpected(error_code::other); |
172 | } |
173 | |
174 | if (parent->isObject()) { |
175 | parent->erase(ptr->parent_key); |
176 | return unit; |
177 | } |
178 | |
179 | if (parent->isArray()) { |
180 | parent->erase(parent->begin() + ptr->parent_index); |
181 | return unit; |
182 | } |
183 | |
184 | return folly::makeUnexpected(error_code::other); |
185 | } |
186 | |
187 | // clang-format off |
188 | Expected<Unit, json_patch::patch_application_error_code> |
189 | // clang-format on |
190 | do_add( |
191 | dynamic::resolved_json_pointer<dynamic>& ptr, |
192 | const dynamic& value, |
193 | const std::string& last_token) { |
194 | using app_err_code = json_patch::patch_application_error_code; |
195 | using res_err_code = dynamic::json_pointer_resolution_error_code; |
196 | |
197 | // element found: see if parent is object or array |
198 | if (ptr.hasValue()) { |
199 | // root element, or key in object - replace (per RFC) |
200 | if (ptr->parent == nullptr || ptr->parent->isObject()) { |
201 | *ptr->value = value; |
202 | } |
203 | // valid index in array: insert at index and shift right |
204 | if (ptr->parent && ptr->parent->isArray()) { |
205 | ptr->parent->insert(ptr->parent->begin() + ptr->parent_index, value); |
206 | } |
207 | } else { |
208 | // see if we can add value, based on pointer resolution state |
209 | switch (ptr.error().error_code) { |
210 | // key not found. can only happen in object - add new key-value |
211 | case res_err_code::key_not_found: { |
212 | DCHECK(ptr.error().context->isObject()); |
213 | ptr.error().context->insert(last_token, value); |
214 | break; |
215 | } |
216 | // special '-' index in array - do append operation |
217 | case res_err_code::append_requested: { |
218 | DCHECK(ptr.error().context->isArray()); |
219 | ptr.error().context->push_back(value); |
220 | break; |
221 | } |
222 | default: |
223 | return folly::makeUnexpected(app_err_code::other); |
224 | } |
225 | } |
226 | return unit; |
227 | } |
228 | } // namespace |
229 | |
230 | // clang-format off |
231 | Expected<Unit, json_patch::patch_application_error> |
232 | // clang-format on |
233 | json_patch::apply(dynamic& obj) { |
234 | using op_code = patch_operation_code; |
235 | using error_code = patch_application_error_code; |
236 | using error = patch_application_error; |
237 | |
238 | for (auto&& it : enumerate(ops_)) { |
239 | auto const index = it.index; |
240 | auto const& op = *it; |
241 | auto resolved_path = obj.try_get_ptr(op.path); |
242 | |
243 | switch (op.op_code) { |
244 | case op_code::test: |
245 | if (!resolved_path.hasValue()) { |
246 | return folly::makeUnexpected( |
247 | error{error_code::path_not_found, index}); |
248 | } |
249 | if (*resolved_path->value != *op.value) { |
250 | return folly::makeUnexpected(error{error_code::test_failed, index}); |
251 | } |
252 | break; |
253 | case op_code::remove: { |
254 | auto ret = do_remove(resolved_path); |
255 | if (ret.hasError()) { |
256 | return makeUnexpected(error{ret.error(), index}); |
257 | } |
258 | break; |
259 | } |
260 | case op_code::add: { |
261 | DCHECK(op.value.hasValue()); |
262 | auto ret = do_add(resolved_path, *op.value, op.path.tokens().back()); |
263 | if (ret.hasError()) { |
264 | return makeUnexpected(error{ret.error(), index}); |
265 | } |
266 | break; |
267 | } |
268 | case op_code::replace: { |
269 | if (resolved_path.hasValue()) { |
270 | *resolved_path->value = *op.value; |
271 | } else { |
272 | return folly::makeUnexpected( |
273 | error{error_code::path_not_found, index}); |
274 | } |
275 | break; |
276 | } |
277 | case op_code::move: { |
278 | DCHECK(op.from.hasValue()); |
279 | auto resolved_from = obj.try_get_ptr(*op.from); |
280 | if (!resolved_from.hasValue()) { |
281 | return makeUnexpected(error{error_code::from_not_found, index}); |
282 | } |
283 | { |
284 | auto ret = do_add( |
285 | resolved_path, *resolved_from->value, op.path.tokens().back()); |
286 | if (ret.hasError()) { |
287 | return makeUnexpected(error{ret.error(), index}); |
288 | } |
289 | } |
290 | { |
291 | auto ret = do_remove(resolved_from); |
292 | if (ret.hasError()) { |
293 | return makeUnexpected(error{ret.error(), index}); |
294 | } |
295 | } |
296 | break; |
297 | } |
298 | case op_code::copy: { |
299 | DCHECK(op.from.hasValue()); |
300 | auto const resolved_from = obj.try_get_ptr(*op.from); |
301 | if (!resolved_from.hasValue()) { |
302 | return makeUnexpected(error{error_code::from_not_found, index}); |
303 | } |
304 | { |
305 | DCHECK(!op.path.tokens().empty()); |
306 | auto ret = do_add( |
307 | resolved_path, *resolved_from->value, op.path.tokens().back()); |
308 | if (ret.hasError()) { |
309 | return makeUnexpected(error{ret.error(), index}); |
310 | } |
311 | } |
312 | break; |
313 | } |
314 | case op_code::invalid: { |
315 | DCHECK(false); |
316 | return makeUnexpected(error{error_code::other, index}); |
317 | } |
318 | } |
319 | } |
320 | return unit; |
321 | } |
322 | |
323 | } // namespace folly |
324 | |