Skip to content

Commit

Permalink
Add ignore_invalid_paths to patch
Browse files Browse the repository at this point in the history
  • Loading branch information
tulinmola committed Sep 8, 2023
1 parent fa991b1 commit 98e01c2
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 4 deletions.
21 changes: 18 additions & 3 deletions lib/jsonpatch.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,37 @@ defmodule Jsonpatch do
iex> target = %{"name" => "Bob", "married" => false, "hobbies" => ["Sport", "Elixir", "Football"], "home" => "Berlin"}
iex> Jsonpatch.apply_patch(patch, target)
{:error, %Jsonpatch.Error{patch: %{"op" => "test", "path" => "/name", "value" => "Alice"}, patch_index: 1, reason: {:test_failed, "Expected value '\\"Alice\\"' at '/name'"}}}
iex> # Patch will succeed, not applying invalid path operations.
iex> patch = [
...> %{op: "replace", path: "/name", value: "Alice"},
...> %{op: "replace", path: "/age", value: 42}
...> ]
iex> target = %{"name" => "Bob"} # No age in target
iex> Jsonpatch.apply_patch(patch, target, ignore_invalid_paths: true)
{:ok, %{"name" => "Alice"}}
"""
@spec apply_patch(t() | [t()], target :: Types.json_container(), Types.opts()) ::
{:ok, Types.json_container()} | {:error, Jsonpatch.Error.t()}
def apply_patch(json_patch, target, opts \\ []) do
# https://datatracker.ietf.org/doc/html/rfc6902#section-3
# > Operations are applied sequentially in the order they appear in the array.
{ignore_invalid_paths, opts} = Keyword.pop(opts, :ignore_invalid_paths, false)

json_patch
|> List.wrap()
|> Enum.with_index()
|> Enum.reduce_while({:ok, target}, fn {patch, patch_index}, {:ok, acc} ->
patch = cast_to_op_map(patch)

case do_apply_patch(patch, acc, opts) do
{:error, reason} ->
error = %Jsonpatch.Error{patch: patch, patch_index: patch_index, reason: reason}
{:halt, {:error, error}}
{:error, {error, _} = reason} ->
if ignore_invalid_paths && error == :invalid_path do
{:cont, {:ok, acc}}
else
error = %Jsonpatch.Error{patch: patch, patch_index: patch_index, reason: reason}
{:halt, {:error, error}}
end

{:ok, res} ->
{:cont, {:ok, res}}
Expand Down
2 changes: 1 addition & 1 deletion lib/jsonpatch/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ defmodule Jsonpatch.Types do
- `:atoms!` - path fragments are converted to existing atoms
- `{:custom, convert_fn}` - path fragments are converted with `convert_fn`
"""
@type opt_keys :: :strings | :atoms | {:custom, convert_fn()}
@type opt_keys :: :strings | :atoms | {:custom, convert_fn()} | {:ignore_invalid_paths, :boolean}

@typedoc """
Types options:
Expand Down
7 changes: 7 additions & 0 deletions test/jsonpatch_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,13 @@ defmodule JsonpatchTest do
reason: {:invalid_path, ""}
}} = Jsonpatch.apply_patch(patch, target)
end

test "ignore invalid paths when asked to" do
patch = %{"op" => "replace", "path" => "/inexistent", "value" => 42}
target = %{"foo" => "bar"}

assert {:ok, %{"foo" => "bar"}} = Jsonpatch.apply_patch(patch, target, ignore_invalid_paths: true)
end
end

defp string_to_existing_atom(data) when is_binary(data) do
Expand Down

0 comments on commit 98e01c2

Please sign in to comment.