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
31 changes: 1 addition & 30 deletions config/config.exs
Original file line numberDiff line numberDiff line change
@@ -1,30 +1 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
# config :json_patch, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:json_patch, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
import Mix.Config
102 changes: 47 additions & 55 deletions lib/json_patch.ex
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
defmodule JSONPatch do
@moduledoc ~S"""
@moduledoc """
JSONPatch is an Elixir implementation of the JSON Patch format,
described in [RFC 6902](http://tools.ietf.org/html/rfc6902).

Expand DownExpand Up@@ -57,7 +57,7 @@ defmodule JSONPatch do

@type status_code :: non_neg_integer

@doc ~S"""
@doc """
Applies JSON Patch (RFC 6902) patches to the given JSON document.
Returns `{:ok, patched_map}` or `{:error, error_type, description}`.

Expand DownExpand Up@@ -89,7 +89,7 @@ defmodule JSONPatch do
end
end

@doc ~S"""
@doc """
Converts a `t:return_value/0` or `t:error_type/0' to an HTTP status code.

The HTTP status codes emitted are:
Expand DownExpand Up@@ -126,28 +126,26 @@ defmodule JSONPatch do
cond do
!Map.has_key?(patch, "op") -> {:error, :syntax_error, "missing `op`"}
!Map.has_key?(patch, "path") -> {:error, :syntax_error, "missing `path`"}
:else -> apply_op(patch["op"], doc, patch)
true -> apply_op(patch["op"], doc, patch)
end
end

@spec apply_op(String.t(), json_document, patch) :: return_value
defp apply_op("test", doc, patch) do
cond do
!Map.has_key?(patch, "value") ->
{:error, :syntax_error, "missing `value`"}

:else ->
case Path.get_value_at_path(doc, patch["path"]) do
{:ok, path_value} ->
if path_value == patch["value"] do
{:ok, doc}
else
{:error, :test_failed, "test failed"}
end

err ->
err
end
if Map.has_key?(patch, "value") do
case Path.get_value_at_path(doc, patch["path"]) do
{:ok, path_value} ->
if path_value == patch["value"] do
{:ok, doc}
else
{:error, :test_failed, "test failed"}
end

err ->
err
end
else
{:error, :syntax_error, "missing `value`"}
end
end

Expand All@@ -156,55 +154,49 @@ defmodule JSONPatch do
end

defp apply_op("add", doc, patch) do
cond do
!Map.has_key?(patch, "value") ->
{:error, :syntax_error, "missing `value`"}

:else ->
Path.add_value_at_path(doc, patch["path"], patch["value"])
if Map.has_key?(patch, "value") do
Path.add_value_at_path(doc, patch["path"], patch["value"])
else
{:error, :syntax_error, "missing `value`"}
end
end

defp apply_op("replace", doc, patch) do
cond do
!Map.has_key?(patch, "value") ->
{:error, :syntax_error, "missing `value`"}

:else ->
with {:ok, data} <- Path.remove_value_at_path(doc, patch["path"]) do
if Map.has_key?(patch, "value") do
case Path.remove_value_at_path(doc, patch["path"]) do
{:ok, data} ->
Path.add_value_at_path(data, patch["path"], patch["value"])
else
err -> err
end

err ->
err
end
else
{:error, :syntax_error, "missing `value`"}
end
end

defp apply_op("move", doc, patch) do
cond do
!Map.has_key?(patch, "from") ->
{:error, :syntax_error, "missing `from`"}

:else ->
with {:ok, value} <- Path.get_value_at_path(doc, patch["from"]),
{:ok, data} <- Path.remove_value_at_path(doc, patch["from"]) do
Path.add_value_at_path(data, patch["path"], value)
else
err -> err
end
with true <- Map.has_key?(patch, "from"),
{:ok, value} <- Path.get_value_at_path(doc, patch["from"]),
{:ok, data} <- Path.remove_value_at_path(doc, patch["from"]) do
Path.add_value_at_path(data, patch["path"], value)
else
false -> {:error, :syntax_error, "missing `from`"}
err -> err
end
end

defp apply_op("copy", doc, patch) do
cond do
!Map.has_key?(patch, "from") ->
{:error, :syntax_error, "missing `from`"}

:else ->
with {:ok, value} <- Path.get_value_at_path(doc, patch["from"]) do
if Map.has_key?(patch, "from") do
case Path.get_value_at_path(doc, patch["from"]) do
{:ok, value} ->
Path.add_value_at_path(doc, patch["path"], value)
else
err -> err
end

err ->
err
end
else
{:error, :syntax_error, "missing `from`"}
end
end

Expand Down
66 changes: 38 additions & 28 deletions lib/json_patch/path.ex
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
defmodule JSONPatch.Path do
@moduledoc false

@doc ~S"""
@doc """
Splits a JSON Pointer (RFC 6901) path into its components.
Path keys are converted to integers if possible, otherwise
remaining strings.
Expand All@@ -17,13 +17,21 @@ defmodule JSONPatch.Path do
@spec split_path(String.t()) :: [String.t() | non_neg_integer]
def split_path(path)

def split_path(nil), do: {:error, :path_error, "null is not valid value for 'path'"}

def split_path(""), do: []

def split_path(path) do
path
|> String.replace_leading("/", "")
|> String.split("/")
|> Enum.map(&convert_number/1)
case path do
"/" <> _remainder ->
path
|> String.replace_leading("/", "")
|> String.split("/")
|> Enum.map(&convert_number/1)

_otherwise ->
{:error, :path_error, "JSON Pointer should start with a slash"}
end
end

## Converts string-formatted integers back to integers.
Expand All@@ -40,7 +48,7 @@ defmodule JSONPatch.Path do
end
end

@doc ~S"""
@doc """
Traverses `data` according to the given `path`, returning `{:ok, value}`
if a value was found at that path, or `{:error, reason}` otherwise.
"""
Expand All@@ -62,7 +70,7 @@ defmodule JSONPatch.Path do
defp value_at_path(data, ["-" | rest]) when is_list(data) do
case Enum.count(data) do
0 -> {:error, :path_error, "can't use index '-' with empty array"}
c -> value_at_path(data, [c-1 | rest])
c -> value_at_path(data, [c - 1 | rest])
end
end

Expand All@@ -81,7 +89,7 @@ defmodule JSONPatch.Path do
{:error, :path_error, "can't index into value #{data}"}
end

@doc ~S"""
@doc """
Attempts to remove the value at the given path. Returns the updated
`{:ok, data}`, otherwise `{:error, reason}.

Expand DownExpand Up@@ -120,7 +128,7 @@ defmodule JSONPatch.Path do
defp remove_at_path(data, ["-" | rest]) when is_list(data) do
case Enum.count(data) do
0 -> {:error, :path_error, "can't use index '-' with empty array"}
c -> remove_at_path(data, [c-1 | rest])
c -> remove_at_path(data, [c - 1 | rest])
end
end

Expand All@@ -131,22 +139,22 @@ defmodule JSONPatch.Path do
defp remove_at_path(%{} = data, [key | rest]) do
keystr = to_string(key)

if !Map.has_key?(data, keystr) do
{:error, :path_error, "missing key #{keystr}"}
else
if Map.has_key?(data, keystr) do
case remove_at_path(data[keystr], rest) do
{:ok, :removed} -> {:ok, Map.delete(data, keystr)}
{:ok, value} -> {:ok, Map.put(data, keystr, value)}
err -> err
end
else
{:error, :path_error, "missing key #{keystr}"}
end
end

defp remove_at_path(data, _) do
{:error, :path_error, "can't index into value #{data}"}
end

@doc ~S"""
@doc """
Attempts to add the value at the given path. Returns the updated
`{:ok, data}`, otherwise `{:error, reason}.

Expand DownExpand Up@@ -176,11 +184,13 @@ defmodule JSONPatch.Path do
rest == [] ->
{:ok, List.insert_at(data, key, value)}

:else ->
with {:ok, v} <- add_at_path(Enum.at(data, key), rest, value) do
{:ok, List.replace_at(data, key, v)}
else
err -> err
true ->
case add_at_path(Enum.at(data, key), rest, value) do
{:ok, v} ->
{:ok, List.replace_at(data, key, v)}

err ->
err
end
end
end
Expand All@@ -196,20 +206,20 @@ defmodule JSONPatch.Path do
defp add_at_path(%{} = data, [key | rest], value) do
keystr = to_string(key)

cond do
rest == [] ->
{:ok, Map.put(data, keystr, value)}

:else ->
with {:ok, v} <- add_at_path(data[keystr], rest, value) do
if rest == [] do
{:ok, Map.put(data, keystr, value)}
else
case add_at_path(data[keystr], rest, value) do
{:ok, v} ->
{:ok, Map.put(data, keystr, v)}
else
err -> err
end

err ->
err
end
end
end

defp add_at_path(data, _, _) do
{:error, :path_error, "can't index into value #{data}"}
{:error, :path_error, "can't index into value #{inspect(data)}"}
end
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,9 +55,10 @@ defmodule JSONPatch.MixProject do
[
{:dialyxir, "~> 0.5", only: :dev},
{:ex_doc, "~> 0.18", only: :dev},
{:jason, "~> 1.0", only: :test},
{:jason, "~> 1.0", only: [:test, :dev]},
{:ex_spec, "~> 2.0", only: :test},
{:excoveralls, "~> 0.8", only: :test},
{:credo, "~> 1.7", only: [:test, :dev]}
]
end
end
28 changes: 13 additions & 15 deletions mix.lock
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
%{
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.18.2", "993e0a95e9fbb790ac54ea58e700b45b299bd48bc44b4ae0404f28161f37a83e", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"ex_spec": {:hex, :ex_spec, "2.0.1", "8bdbd6fa85995fbf836ed799571d44be6f9ebbcace075209fd0ad06372c111cf", [:mix], [], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.8.1", "0bbf67f22c7dbf7503981d21a5eef5db8bbc3cb86e70d3798e8c802c74fa5e27", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm", "6c32a70ed5d452c6650916555b1f96c79af5fc4bf286997f8b15f213de786f73"},
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
"ex_spec": {:hex, :ex_spec, "2.0.1", "8bdbd6fa85995fbf836ed799571d44be6f9ebbcace075209fd0ad06372c111cf", [:mix], [], "hexpm", "b44fe5054497411a58341ece5bf7756c219d9d6c1303b5ac467f557a0a4c31ac"},
"excoveralls": {:hex, :excoveralls, "0.18.3", "bca47a24d69a3179951f51f1db6d3ed63bca9017f476fe520eb78602d45f7756", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "746f404fcd09d5029f1b211739afb8fb8575d775b21f6a3908e7ce3e640724c6"},
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
}
Loading