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.h> |
18 | #include <folly/json_patch.h> |
19 | #include <folly/json_pointer.h> |
20 | #include <folly/portability/GMock.h> |
21 | #include <folly/portability/GTest.h> |
22 | |
23 | using folly::dynamic; |
24 | using folly::json_patch; |
25 | using folly::json_pointer; |
26 | |
27 | using err_code = folly::json_patch::parse_error_code; |
28 | using op_code = folly::json_patch::patch_operation_code; |
29 | |
30 | class JsonPatchTest : public ::testing::Test {}; |
31 | |
32 | TEST_F(JsonPatchTest, ValidPatch) { |
33 | // from RFC 6902 |
34 | constexpr folly::StringPiece jsonPatchStr = R"( |
35 | [ |
36 | { "op": "test", "path": "/a/b/c", "value": "foo" }, |
37 | { "op": "remove", "path": "/a/b/c" }, |
38 | { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }, |
39 | { "op": "replace", "path": "/a/b/c", "value": 42 }, |
40 | { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }, |
41 | { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" } |
42 | ])" ; |
43 | auto const expected = std::vector<json_patch::patch_operation>{ |
44 | {op_code::test, |
45 | json_pointer::parse("/a/b/c" ), |
46 | folly::none, |
47 | dynamic("foo" )}, |
48 | {op_code::remove, |
49 | json_pointer::parse("/a/b/c" ), |
50 | folly::none, |
51 | folly::none}, |
52 | {op_code::add, |
53 | json_pointer::parse("/a/b/c" ), |
54 | folly::none, |
55 | folly::parseJson(R"(["foo", "bar"])" )}, |
56 | {op_code::replace, |
57 | json_pointer::parse("/a/b/c" ), |
58 | folly::none, |
59 | dynamic(42)}, |
60 | {op_code::move, |
61 | json_pointer::parse("/a/b/d" ), |
62 | json_pointer::parse("/a/b/c" ), |
63 | folly::none}, |
64 | {op_code::copy, |
65 | json_pointer::parse("/a/b/e" ), |
66 | json_pointer::parse("/a/b/d" ), |
67 | folly::none}}; |
68 | auto const parsed = |
69 | json_patch::try_parse(folly::parseJson(jsonPatchStr)).value().ops(); |
70 | EXPECT_EQ(expected, parsed); |
71 | } |
72 | |
73 | TEST_F(JsonPatchTest, InvalidPatches) { |
74 | EXPECT_EQ( |
75 | err_code::invalid_shape, |
76 | json_patch::try_parse(dynamic::object()).error().error_code); |
77 | |
78 | EXPECT_EQ( |
79 | err_code::invalid_shape, |
80 | json_patch::try_parse(dynamic::array(dynamic::array())) |
81 | .error() |
82 | .error_code); |
83 | |
84 | EXPECT_EQ( |
85 | err_code::missing_op, |
86 | json_patch::try_parse(folly::parseJson(R"([{"path": "/a/b/c"}])" )) |
87 | .error() |
88 | .error_code); |
89 | |
90 | EXPECT_EQ( |
91 | err_code::unknown_op, |
92 | json_patch::try_parse( |
93 | folly::parseJson(R"([{"op": "blah", "path": "/a/b/c"}])" )) |
94 | .error() |
95 | .error_code); |
96 | |
97 | EXPECT_EQ( |
98 | err_code::malformed_op, |
99 | json_patch::try_parse( |
100 | folly::parseJson(R"([{"op": ["blah"], "path": "/a/b/c"}])" )) |
101 | .error() |
102 | .error_code); |
103 | |
104 | EXPECT_EQ( |
105 | err_code::missing_path_attr, |
106 | json_patch::try_parse(folly::parseJson(R"([{"op": "test"}])" )) |
107 | .error() |
108 | .error_code); |
109 | |
110 | EXPECT_EQ( |
111 | err_code::malformed_path_attr, |
112 | json_patch::try_parse( |
113 | folly::parseJson(R"([{"op": "test", "path" : "a/z/x"}])" )) |
114 | .error() |
115 | .error_code); |
116 | |
117 | EXPECT_EQ( |
118 | err_code::malformed_path_attr, |
119 | json_patch::try_parse( |
120 | folly::parseJson(R"([{"op": "test", "path" : ["a/z/x"]}])" )) |
121 | .error() |
122 | .error_code); |
123 | |
124 | EXPECT_EQ( |
125 | err_code::missing_from_attr, |
126 | json_patch::try_parse( |
127 | folly::parseJson(R"([{"op": "copy", "path" : "/a/b/c"}])" )) |
128 | .error() |
129 | .error_code); |
130 | |
131 | EXPECT_EQ( |
132 | err_code::malformed_from_attr, |
133 | json_patch::try_parse( |
134 | folly::parseJson( |
135 | R"([{"op": "copy", "from" : "c/d/e", "path" : "/a/b/c"}])" )) |
136 | .error() |
137 | .error_code); |
138 | |
139 | EXPECT_EQ( |
140 | err_code::overlapping_pointers, |
141 | json_patch::try_parse( |
142 | folly::parseJson( |
143 | R"([{"op": "move", "from" : "/a/b/c", "path" : "/a/b/c"}])" )) |
144 | .error() |
145 | .error_code); |
146 | |
147 | EXPECT_EQ( |
148 | err_code::overlapping_pointers, |
149 | json_patch::try_parse( |
150 | folly::parseJson( |
151 | R"([{"op": "move", "from" : "/a/b/c", "path" : "/a/b/c/d"}])" )) |
152 | .error() |
153 | .error_code); |
154 | |
155 | // validate presence of mandatory per-operation attributes |
156 | |
157 | EXPECT_EQ( |
158 | err_code::missing_value_attr, |
159 | json_patch::try_parse( |
160 | folly::parseJson(R"([{"op": "test", "path" : "/a/b/c"}])" )) |
161 | .error() |
162 | .error_code); |
163 | |
164 | EXPECT_EQ( |
165 | err_code::missing_value_attr, |
166 | json_patch::try_parse( |
167 | folly::parseJson(R"([{"op": "replace", "path" : "/a/b/c"}])" )) |
168 | .error() |
169 | .error_code); |
170 | |
171 | EXPECT_EQ( |
172 | err_code::missing_from_attr, |
173 | json_patch::try_parse( |
174 | folly::parseJson(R"([{"op": "move", "path" : "/a/b/c"}])" )) |
175 | .error() |
176 | .error_code); |
177 | |
178 | EXPECT_EQ( |
179 | err_code::missing_from_attr, |
180 | json_patch::try_parse( |
181 | folly::parseJson(R"([{"op": "copy", "path" : "/a/b/c"}])" )) |
182 | .error() |
183 | .error_code); |
184 | |
185 | // test the object reference in error: in patch below, 3rd entry is incorrect |
186 | |
187 | constexpr folly::StringPiece jsonPatchStr = R"( |
188 | [ |
189 | { "op": "test", "path": "/a/b/c", "value": "foo" }, |
190 | { "op": "remove", "path": "/a/b/c" }, |
191 | { "op": "add", "path": "/a/b/c" } |
192 | ])" ; |
193 | auto jsonObj = folly::parseJson(jsonPatchStr); |
194 | auto err = json_patch::try_parse(jsonObj).error(); |
195 | EXPECT_EQ(err_code::missing_value_attr, err.error_code); |
196 | // the invalid entry - check pointers and values they point at |
197 | EXPECT_EQ(&jsonObj[2], err.obj); |
198 | EXPECT_EQ(jsonObj[2], *err.obj); |
199 | } |
200 | |
201 | TEST_F(JsonPatchTest, SuccessfulPatchApplication) { |
202 | constexpr folly::StringPiece jsonPatchStr = R"( |
203 | [ |
204 | { "op": "test", "path": "/a/b/c", "value": "foo" }, |
205 | { "op": "remove", "path": "/a/b/c" }, |
206 | { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }, |
207 | { "op": "replace", "path": "/a/b/c", "value": 42 }, |
208 | { "op": "test", "path": "/a/b/c", "value": 42 }, |
209 | { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }, |
210 | { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }, |
211 | { "op": "add", "path": "/a/b/c", "value": [1, 2, 3] }, |
212 | { "op": "add", "path": "/a/b/c/1", "value": 100 } |
213 | ])" ; |
214 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
215 | |
216 | dynamic objToMutate = |
217 | dynamic::object("a" , dynamic::object("b" , dynamic::object("c" , "foo" ))); |
218 | |
219 | auto res = patch.apply(objToMutate); |
220 | |
221 | EXPECT_FALSE(res.hasError()) << (int)res.error().error_code; |
222 | |
223 | EXPECT_EQ(42, objToMutate["a" ]["b" ]["d" ].asInt()); |
224 | EXPECT_EQ(42, objToMutate["a" ]["b" ]["e" ].asInt()); |
225 | EXPECT_EQ(100, objToMutate["a" ]["b" ]["c" ][1].asInt()); |
226 | EXPECT_EQ(1, objToMutate["a" ]["b" ]["c" ][0].asInt()); |
227 | } |
228 | |
229 | TEST_F(JsonPatchTest, TestOpFailure) { |
230 | constexpr folly::StringPiece jsonPatchStr = R"( |
231 | [ |
232 | { "op": "test", "path": "/a/b/c", "value": "foo" }, |
233 | { "op": "remove", "path": "/a/b/c" } |
234 | ])" ; |
235 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
236 | |
237 | dynamic objToMutate = |
238 | dynamic::object("a" , dynamic::object("b" , dynamic::object("c" , "bar" ))); |
239 | |
240 | auto res = patch.apply(objToMutate); |
241 | |
242 | EXPECT_TRUE(res.hasError()); |
243 | EXPECT_EQ( |
244 | json_patch::patch_application_error_code::test_failed, |
245 | res.error().error_code); |
246 | |
247 | EXPECT_EQ("bar" , objToMutate["a" ]["b" ]["c" ].asString()); |
248 | } |
249 | |
250 | TEST_F(JsonPatchTest, PathNotFound) { |
251 | constexpr folly::StringPiece jsonPatchStr = R"( |
252 | [ |
253 | { "op": "remove", "path": "/a/b/d" } |
254 | ])" ; |
255 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
256 | |
257 | dynamic objToMutate = |
258 | dynamic::object("a" , dynamic::object("b" , dynamic::object("c" , "foo" ))); |
259 | |
260 | auto res = patch.apply(objToMutate); |
261 | |
262 | EXPECT_TRUE(res.hasError()); |
263 | EXPECT_EQ( |
264 | json_patch::patch_application_error_code::path_not_found, |
265 | res.error().error_code); |
266 | |
267 | EXPECT_EQ("foo" , objToMutate["a" ]["b" ]["c" ].asString()); |
268 | } |
269 | |
270 | TEST_F(JsonPatchTest, FromNotFound) { |
271 | constexpr folly::StringPiece jsonPatchStr = R"( |
272 | [ |
273 | { "op": "copy", "from": "/a/c/b", "path": "/a/b/d" } |
274 | ])" ; |
275 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
276 | |
277 | dynamic objToMutate = |
278 | dynamic::object("a" , dynamic::object("b" , dynamic::object("c" , "foo" ))); |
279 | |
280 | auto res = patch.apply(objToMutate); |
281 | |
282 | EXPECT_TRUE(res.hasError()); |
283 | EXPECT_EQ( |
284 | json_patch::patch_application_error_code::from_not_found, |
285 | res.error().error_code); |
286 | |
287 | EXPECT_THROW(objToMutate["a" ]["b" ].at("d" ), std::out_of_range); |
288 | } |
289 | |
290 | TEST_F(JsonPatchTest, RemoveRootObject) { |
291 | constexpr folly::StringPiece jsonPatchStr = R"( |
292 | [ |
293 | { "op": "remove", "path": "" } |
294 | ])" ; |
295 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
296 | |
297 | dynamic objToMutate = |
298 | dynamic::object("a" , dynamic::object("b" , dynamic::object("c" , "foo" ))); |
299 | |
300 | auto res = patch.apply(objToMutate); |
301 | |
302 | EXPECT_TRUE(res.hasError()); |
303 | EXPECT_EQ( |
304 | json_patch::patch_application_error_code::other, res.error().error_code); |
305 | |
306 | EXPECT_EQ("foo" , objToMutate["a" ]["b" ]["c" ].asString()); |
307 | } |
308 | |
309 | TEST_F(JsonPatchTest, AddWithAppend) { |
310 | constexpr folly::StringPiece jsonPatchStr = R"( |
311 | [ |
312 | { "op": "add", "path": "/a/b/-", "value": 100 } |
313 | ])" ; |
314 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
315 | |
316 | dynamic objToMutate = |
317 | dynamic::object("a" , dynamic::object("b" , dynamic::array(1, 2, 3, 4))); |
318 | |
319 | auto res = patch.apply(objToMutate); |
320 | |
321 | EXPECT_TRUE(res.hasValue()); |
322 | EXPECT_EQ(100, objToMutate["a" ]["b" ][4].asInt()); |
323 | } |
324 | |
325 | TEST_F(JsonPatchTest, RemoveWithMinus) { |
326 | constexpr folly::StringPiece jsonPatchStr = R"( |
327 | [ |
328 | { "op": "remove", "path": "/a/b/-" } |
329 | ])" ; |
330 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
331 | |
332 | dynamic objToMutate = |
333 | dynamic::object("a" , dynamic::object("b" , dynamic::array(1, 2, 3, 4))); |
334 | |
335 | auto res = patch.apply(objToMutate); |
336 | |
337 | EXPECT_TRUE(res.hasError()); |
338 | EXPECT_EQ( |
339 | json_patch::patch_application_error_code::path_not_found, |
340 | res.error().error_code); |
341 | } |
342 | |
343 | TEST_F(JsonPatchTest, FailedOpIndex) { |
344 | constexpr folly::StringPiece jsonPatchStr = R"( |
345 | [ |
346 | { "op": "test", "path": "/a/b/0", "value": 1 }, |
347 | { "op": "test", "path": "/a/b/0", "value": 2 } |
348 | ])" ; |
349 | auto patch = json_patch::try_parse(folly::parseJson(jsonPatchStr)).value(); |
350 | |
351 | dynamic objToMutate = |
352 | dynamic::object("a" , dynamic::object("b" , dynamic::array(1, 2, 3, 4))); |
353 | |
354 | auto res = patch.apply(objToMutate); |
355 | |
356 | EXPECT_TRUE(res.hasError()); |
357 | EXPECT_EQ( |
358 | json_patch::patch_application_error_code::test_failed, |
359 | res.error().error_code); |
360 | EXPECT_EQ(1, res.error().index); |
361 | } |
362 | |