| 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 | |