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
21namespace {
22using folly::StringPiece;
23// JSON patch operation names
24constexpr StringPiece kOperationTest = "test";
25constexpr StringPiece kOperationRemove = "remove";
26constexpr StringPiece kOperationAdd = "add";
27constexpr StringPiece kOperationReplace = "replace";
28constexpr StringPiece kOperationMove = "move";
29constexpr StringPiece kOperationCopy = "copy";
30// field tags in JSON patch
31constexpr StringPiece kOpTag = "op";
32constexpr StringPiece kValueTag = "value";
33constexpr StringPiece kPathTag = "path";
34constexpr StringPiece kFromTag = "from";
35} // namespace
36
37namespace folly {
38
39// static
40Expected<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
153std::vector<json_patch::patch_operation> const& json_patch::ops() const {
154 return ops_;
155}
156
157namespace {
158// clang-format off
159Expected<Unit, json_patch::patch_application_error_code>
160// clang-format on
161do_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
188Expected<Unit, json_patch::patch_application_error_code>
189// clang-format on
190do_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
231Expected<Unit, json_patch::patch_application_error>
232// clang-format on
233json_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