Skip to content

Commit dbdc34b

Browse files
authored
Merge pull request #7 from corka149/bugfix/#6-remove-sorting
Bugfix/#6 remove sorting
2 parents d7dc1cd + 93f32d3 commit dbdc34b

7 files changed

Lines changed: 42 additions & 50 deletions

File tree

lib/jsonpatch.ex

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,16 @@ defmodule Jsonpatch do
3030

3131
@doc """
3232
Apply a Jsonpatch or a list of Jsonpatches to a map or struct. The whole patch will not be applied
33-
when any path is invalid or any other error occured.
33+
when any path is invalid or any other error occured. When a list is provided, the operations are
34+
applied in the order as they appear in the list.
3435
3536
## Examples
3637
iex> patch = [
3738
...> %Jsonpatch.Operation.Add{path: "/age", value: 33},
3839
...> %Jsonpatch.Operation.Replace{path: "/hobbies/0", value: "Elixir!"},
3940
...> %Jsonpatch.Operation.Replace{path: "/married", value: true},
40-
...> %Jsonpatch.Operation.Remove{path: "/hobbies/1"},
4141
...> %Jsonpatch.Operation.Remove{path: "/hobbies/2"},
42+
...> %Jsonpatch.Operation.Remove{path: "/hobbies/1"},
4243
...> %Jsonpatch.Operation.Copy{from: "/name", path: "/surname"},
4344
...> %Jsonpatch.Operation.Move{from: "/home", path: "/work"},
4445
...> %Jsonpatch.Operation.Test{path: "/name", value: "Bob"}
@@ -61,14 +62,10 @@ defmodule Jsonpatch do
6162
def apply_patch(json_patch, target)
6263

6364
def apply_patch(json_patch, %{} = target) when is_list(json_patch) do
64-
# Operatons MUST be sorted before applying because a remove operation for path "/foo/2" must be done
65-
# before the remove operation for path "/foo/1". Without order it could be possible that the wrong
66-
# value will be removed or only one value instead of two.
65+
# https://datatracker.ietf.org/doc/html/rfc6902#section-3
66+
# > Operations are applied sequentially in the order they appear in the array.
6767
result =
6868
json_patch
69-
|> Enum.map(&create_sort_value/1)
70-
|> Enum.sort(fn {sort_value_1, _}, {sort_value_2, _} -> sort_value_1 >= sort_value_2 end)
71-
|> Enum.map(fn {_, patch} -> patch end)
7269
|> Enum.reduce(target, &Jsonpatch.Operation.apply_op/2)
7370

7471
case result do
@@ -88,7 +85,8 @@ defmodule Jsonpatch do
8885

8986
@doc """
9087
Apply a Jsonpatch or a list of Jsonpatches to a map or struct. In case of an error
91-
it will raise an exception.
88+
it will raise an exception. When a list is provided, the operations are applied in
89+
the order as they appear in the list.
9290
9391
(See Jsonpatch.apply_patch/2 for more details)
9492
"""
@@ -219,25 +217,4 @@ defmodule Jsonpatch do
219217
defp escape(subpath) do
220218
subpath
221219
end
222-
223-
# Create once a easy sortable value for a operation
224-
defp create_sort_value(%{path: path} = operation) do
225-
fragments = String.split(path, "/")
226-
227-
x = Jsonpatch.PathUtil.operation_sort_value?(operation) * 1_000_000 * 100_000_000
228-
y = length(fragments) * 100_000_000
229-
230-
z =
231-
case List.last(fragments) |> Integer.parse() do
232-
:error -> 0
233-
{int, _} -> int
234-
end
235-
236-
# Structure of recorde sort value
237-
# x = Kind of PathUtil
238-
# y = Amount of fragments (how deep goes the path?)
239-
# z = At which position in a list?
240-
# xxxxyyyyyyzzzzzzzz
241-
{x + y + z, operation}
242-
end
243220
end

lib/jsonpatch/path_util.ex

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ defmodule Jsonpatch.PathUtil do
44
# ===== Internal documentation =====
55
# Helper module for handling JSON paths.
66

7-
alias Jsonpatch.Operation.Add
8-
alias Jsonpatch.Operation.Remove
9-
alias Jsonpatch.Operation.Replace
10-
alias Jsonpatch.Operation.Test
11-
127
@doc """
138
Uses a JSON patch path to get the last map that this path references.
149
@@ -61,20 +56,6 @@ defmodule Jsonpatch.PathUtil do
6156
do_update_final_destination(target, new_destination, fragments)
6257
end
6358

64-
@doc """
65-
Determines the sort value for the operation of a patch. This value
66-
assure in which order patches are applied. (Example: shall remove
67-
patches be applied before add patches?)
68-
"""
69-
@spec operation_sort_value?(Jsonpatch.t()) :: integer()
70-
def operation_sort_value?(patch)
71-
72-
def operation_sort_value?(%Test{}), do: 600
73-
def operation_sort_value?(%Add{}), do: 500
74-
def operation_sort_value?(%Replace{}), do: 400
75-
def operation_sort_value?(%Remove{}), do: 300
76-
def operation_sort_value?(_), do: 0
77-
7859
@doc """
7960
Unescape `~1` to `/` and `~0` to `~`.
8061
"""

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ defmodule Jsonpatch.MixProject do
66
app: :jsonpatch,
77
name: "Jsonpatch",
88
description: "Implementation of RFC 6902 in pure Elixir",
9-
version: "0.11.2",
9+
version: "0.12.0",
1010
elixir: "~> 1.10",
1111
start_permanent: Mix.env() == :prod,
1212
deps: deps(),

test/jsonpatch/operation/add_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,11 @@ defmodule Jsonpatch.Operation.AddTest do
7373

7474
assert %{"a" => [0, 1, 2, 3]} = Operation.apply_op(patch, target)
7575
end
76+
77+
test "Return error when patch error was provided to add operation" do
78+
patch = %Add{path: "/a", value: false}
79+
error = {:error, :invalid_index, "4"}
80+
81+
assert ^error = Operation.apply_op(patch, error)
82+
end
7683
end

test/jsonpatch/operation/remove_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,11 @@ defmodule RemoveTest do
8383
remove_patch = %Remove{path: "/hobbies/1/description"}
8484
assert {:error, :invalid_index, "1"} = Jsonpatch.apply_patch(remove_patch, target)
8585
end
86+
87+
test "Return error when patch error was provided to remove operation" do
88+
patch = %Remove{path: "/a"}
89+
error = {:error, :invalid_index, "4"}
90+
91+
assert ^error = Operation.apply_op(patch, error)
92+
end
8693
end

test/jsonpatch/operation/replace_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,11 @@ defmodule Jsonpatch.Operation.ReplaceTest do
8181

8282
assert {:error, :invalid_index, "c"} = patched_target
8383
end
84+
85+
test "Return error when patch error was provided to replace operation" do
86+
patch = %Replace{path: "/a", value: true}
87+
error = {:error, :invalid_index, "4"}
88+
89+
assert ^error = Operation.apply_op(patch, error)
90+
end
8491
end

test/jsonpatch_test.exs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,19 @@ defmodule JsonpatchTest do
179179
)
180180
end
181181

182+
test "Apply patch with multilple operations" do
183+
patch = [
184+
%Jsonpatch.Operation.Remove{path: "/age"},
185+
%Jsonpatch.Operation.Add{path: "/age", value: 34},
186+
%Jsonpatch.Operation.Replace{path: "/age", value: 35}
187+
]
188+
189+
target = %{"age" => "33"}
190+
patched = Jsonpatch.apply_patch!(patch, target)
191+
192+
assert %{"age" => 35} = patched
193+
end
194+
182195
test "Apply patch with invalid target source path and expect error" do
183196
target = %{
184197
"name" => "Bob",

0 commit comments

Comments
 (0)