# `JSCalendar.Patch`
[🔗](https://github.com/elixir-tempo/jscalendar/blob/v0.1.0/lib/jscalendar/patch.ex#L1)

A set of changes to a JSCalendar object
([RFC 8984 §1.4.9](https://www.rfc-editor.org/rfc/rfc8984.html#section-1.4.9)).

A PatchObject is how JSCalendar says "the same as that, but different
here". Each key is a path and each value is what to put there, so a
recurring event that moves one week states only the move rather than
restating the event:

    %{"start" => "2020-01-22T14:00:00", "locations/room/name" => "Room B"}

Paths are a restricted [JSON Pointer](https://www.rfc-editor.org/rfc/rfc6901),
with the leading `/` implied. A `null` value removes the property; any
other value sets it.

### Why this is a module and not a map lookup

Four rules decide whether a patch may be applied at all, and getting
them wrong corrupts the object rather than failing loudly:

1. A pointer must not reach inside an **array**. Arrays are replaced
   whole, never spliced — there is no way to say "insert at index 2"
   and no way to mean it unambiguously if two patches disagree.

2. Every part **before** the last must already exist. A patch is a
   change to something, not a way to conjure structure.

3. No pointer may be a **prefix** of another. `alerts` and
   `alerts/1/offset` in one patch have no defined order, so the
   result would depend on which was applied first.

4. `null` may only remove a property that is **optional**.

The specification is emphatic about what happens when one fails:
implementations MUST reject the patch **in its entirety** and MUST
NOT apply it partially. A half-applied patch is an object that never
existed and that nobody asked for, so `apply/2` builds the result
and only returns it once every pointer has succeeded.

# `t`

```elixir
@type t() :: %{optional(String.t()) =&gt; term()}
```

An unordered set of changes, keyed by path.

# `apply`

```elixir
@spec apply(map(), t(), keyword()) :: {:ok, map()} | {:error, term()}
```

Apply `patch` to `target`.

### Arguments

* `target` is the map being patched — a decoded JSCalendar object.

* `patch` is a `t:t/0`.

### Options

* `:ignore` is a list of path prefixes to skip rather than reject.
  RFC 8984 requires certain pointers to be *ignored* in
  `recurrenceOverrides` and `localizations`, which is different from
  rejecting them.

* `:only` is a list of path suffixes to keep; anything else is
  skipped. `localizations` allows only `title`, `description` and
  `name`.

### Returns

* `{:ok, patched}`; or

* `{:error, reason}` when any pointer is invalid, in which case
  nothing has been applied.

### Examples

    iex> JSCalendar.Patch.apply(%{"title" => "Standup"}, %{"title" => "Retro"})
    {:ok, %{"title" => "Retro"}}

    iex> JSCalendar.Patch.apply(%{"title" => "Standup", "color" => "red"}, %{"color" => nil})
    {:ok, %{"title" => "Standup"}}

    iex> JSCalendar.Patch.apply(%{"a" => %{"b" => 1}}, %{"a/b" => 2})
    {:ok, %{"a" => %{"b" => 2}}}

    iex> JSCalendar.Patch.apply(%{}, %{"a/b" => 2})
    {:error, {:missing_parent, "a/b"}}

    iex> JSCalendar.Patch.apply(%{"a" => [1, 2]}, %{"a/0" => 9})
    {:error, {:points_into_array, "a/0"}}

# `validate`

```elixir
@spec validate(t()) :: :ok | {:error, term()}
```

Check that `patch` is well formed, without applying it.

Only the rules that can be decided from the patch alone are checked
here — whether a parent exists, and whether a pointer reaches into an
array, depend on the object being patched and are settled by
`apply/3`.

### Arguments

* `patch` is a `t:t/0`.

### Returns

* `:ok`; or

* `{:error, {:overlapping_pointers, a, b}}` when one pointer is a
  prefix of another.

### Examples

    iex> JSCalendar.Patch.validate(%{"title" => "Retro", "color" => "red"})
    :ok

    iex> JSCalendar.Patch.validate(%{"alerts" => %{}, "alerts/1/offset" => "-PT5M"})
    {:error, {:overlapping_pointers, "alerts", "alerts/1/offset"}}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
