Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions jsonpatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ class JsonPatchTestFailed(JsonPatchException, AssertionError):
""" A Test operation failed """


def _raise_if_string_index(document, part):
if isinstance(document, basestring):
raise JsonPointerException(
"unable to resolve json pointer against string value, part {0}".format(part))


def multidict(ordered_pairs):
"""Convert duplicate keys values to lists."""
# read all values into lists
Expand Down Expand Up @@ -240,6 +246,7 @@ class RemoveOperation(PatchOperation):

def apply(self, obj):
subobj, part = self.pointer.to_last(obj)
_raise_if_string_index(subobj, part)

if isinstance(subobj, Sequence) and not isinstance(part, int):
raise JsonPointerException("invalid array index '{0}'".format(part))
Expand Down Expand Up @@ -378,8 +385,9 @@ def apply(self, obj):

subobj, part = from_ptr.to_last(obj)
try:
_raise_if_string_index(subobj, part)
value = subobj[part]
except (KeyError, IndexError) as ex:
except (KeyError, IndexError, JsonPointerException) as ex:
raise JsonPatchConflict(str(ex))

# If source and target are equal, this is a no-op
Expand Down Expand Up @@ -458,6 +466,7 @@ def apply(self, obj):
if part is None:
val = subobj
else:
_raise_if_string_index(subobj, part)
val = self.pointer.walk(subobj, part)
except JsonPointerException as ex:
raise JsonPatchTestFailed(str(ex))
Expand Down Expand Up @@ -488,8 +497,9 @@ def apply(self, obj):

subobj, part = from_ptr.to_last(obj)
try:
_raise_if_string_index(subobj, part)
value = copy.deepcopy(subobj[part])
except (KeyError, IndexError) as ex:
except (KeyError, IndexError, JsonPointerException) as ex:
raise JsonPatchConflict(str(ex))

obj = AddOperation({
Expand Down
24 changes: 24 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ def test_remove_array_item(self):
res = jsonpatch.apply_patch(obj, [{'op': 'remove', 'path': '/foo/1'}])
self.assertEqual(res['foo'], ['bar', 'baz'])

def test_remove_does_not_index_string(self):
obj = {'foo': 'should-not-be-indexable'}
patch_obj = [{'op': 'remove', 'path': '/foo/0'}]
self.assertRaises(jsonpointer.JsonPointerException,
jsonpatch.apply_patch, obj, patch_obj)

def test_remove_invalid_item(self):
obj = {'foo': ['bar', 'qux', 'baz']}
with self.assertRaises(jsonpointer.JsonPointerException):
Expand Down Expand Up @@ -134,6 +140,12 @@ def test_move_array_item(self):
res = jsonpatch.apply_patch(obj, [{'op': 'move', 'from': '/foo/1', 'path': '/foo/3'}])
self.assertEqual(res, {'foo': ['all', 'cows', 'eat', 'grass']})

def test_move_does_not_index_string(self):
obj = {'foo': 'should-not-be-indexable', 'bar': []}
patch_obj = [{'op': 'move', 'from': '/foo/0', 'path': '/bar/0'}]
self.assertRaises(jsonpatch.JsonPatchConflict,
jsonpatch.apply_patch, obj, patch_obj)

def test_move_array_item_into_other_item(self):
obj = [{"foo": []}, {"bar": []}]
patch = [{"op": "move", "from": "/0", "path": "/0/bar/0"}]
Expand All @@ -159,6 +171,12 @@ def test_copy_array_item(self):
res = jsonpatch.apply_patch(obj, [{'op': 'copy', 'from': '/foo/1', 'path': '/foo/3'}])
self.assertEqual(res, {'foo': ['all', 'grass', 'cows', 'grass', 'eat']})

def test_copy_does_not_index_string(self):
obj = {'foo': 'should-not-be-indexable'}
patch_obj = [{'op': 'copy', 'from': '/foo/0', 'path': '/bar'}]
self.assertRaises(jsonpatch.JsonPatchConflict,
jsonpatch.apply_patch, obj, patch_obj)


def test_copy_mutable(self):
""" test if mutable objects (dicts and lists) are copied by value """
Expand All @@ -177,6 +195,12 @@ def test_test_success(self):
jsonpatch.apply_patch(obj, [{'op': 'test', 'path': '/baz', 'value': 'qux'},
{'op': 'test', 'path': '/foo/1', 'value': 2}])

def test_test_does_not_index_string(self):
obj = {'foo': 'should-not-be-indexable'}
patch_obj = [{'op': 'test', 'path': '/foo/0', 'value': 's'}]
self.assertRaises(jsonpatch.JsonPatchTestFailed,
jsonpatch.apply_patch, obj, patch_obj)

def test_test_whole_obj(self):
obj = {'baz': 1}
jsonpatch.apply_patch(obj, [{'op': 'test', 'path': '', 'value': obj}])
Expand Down