# `Storehouse.Metadata`
[🔗](https://github.com/hattoriai/storehouse/blob/v0.2.0/lib/storehouse/metadata.ex#L1)

A file's own metadata (`Storehouse.File`'s `:metadata`): a map the app
keeps with the file, in its record's column and in the JSON it sends,
of what a person entered (a caption, a credit, a description for
readers who cannot see it) and what the app decided.

It holds what JSON holds, so that it goes into a `jsonb` column and a
response as it is: string keys; strings (UTF-8, with no NUL byte, which
PostgreSQL's `jsonb` cannot keep), numbers, booleans and `nil`; lists
and maps of them. `normalize/1` turns atoms (keys and values) into
strings, and dates and times into ISO 8601 strings; anything else is refused, with where it
is.

What analysis reads from the file itself is not metadata: it is the
file's `:analysis` (`Storehouse.Analysis`), so the two never overwrite
each other.

# `path`

```elixir
@type path() :: [String.t() | non_neg_integer()]
```

Where a value that is refused is: the keys and indexes down to it.

# `merge`

```elixir
@spec merge(map(), map()) :: map()
```

`into` with `from` merged in: maps merged key by key, all the way down;
any other value, `nil` too, replaced.

## Examples

    iex> Storehouse.Metadata.merge(%{"credit" => %{"name" => "Ana", "year" => 2025}}, %{"credit" => %{"year" => 2026}})
    %{"credit" => %{"name" => "Ana", "year" => 2026}}

# `normalize`

```elixir
@spec normalize(map()) :: {:ok, map()} | {:error, {:invalid_metadata, path()}}
```

Metadata as `Storehouse.File` keeps it, or `{:error, {:invalid_metadata,
path}}` with where the first value it refuses is. At most 8
levels deep, and 65536 bytes of JSON.

## Examples

    iex> Storehouse.Metadata.normalize(%{caption: "The quay", rating: 4, taken: ~D[2026-10-01]})
    {:ok, %{"caption" => "The quay", "rating" => 4, "taken" => "2026-10-01"}}

    iex> Storehouse.Metadata.normalize(%{"gear" => %{"lens" => {35, :mm}}})
    {:error, {:invalid_metadata, ["gear", "lens"]}}

    iex> Storehouse.Metadata.normalize(%{"title" => "a\0b"})
    {:error, {:invalid_metadata, ["title"]}}

---

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