# `ImagePipe.PresetLookup`
[🔗](https://github.com/hlindset/image_pipe/blob/v0.2.0/image_pipe/lib/image_pipe/preset_lookup.ex#L1)

Resolves presets that the static `:presets` map does not define, while a
request is served.

Configure an implementation with `ImagePipe.config/1` or the
`{ImagePipe, ...}` child spec:

    ImagePipe.config(
      presets: %{"card" => "w=400/h=300/fit=cover"},
      preset_lookup: {MyApp.Presets, repo: MyApp.Repo},
      sources: [...]
    )

[Storing presets in a database](storing-presets-in-a-database.md) walks
through an implementation, and [Presets](presets.md) explains how presets
combine.

## When the lookup is called

Static presets shadow the lookup: a name the map defines is never fetched,
and a request that names only static presets never calls it. Looked-up
presets may reference static and looked-up names. Static presets may
reference only static names. `:request_defaults` never involves the
lookup.

`ImagePipe.Plug` calls `c:fetch/2` while parsing a request, after signature
verification, and `ImagePipe.run/4` and `ImagePipe.validate/2` call it too,
always before any source or cache access. Each call receives the unresolved
names for one nesting level, so a request without nested looked-up
references makes one call. `:max_preset_lookups` (default `32`) caps the
distinct names one request may look up, counting the names in the request
and the names their definitions reference. The URL builder never calls the
lookup.

## Results and statuses

| Lookup result | Plug status | `ImagePipe.run/4` error |
| --- | --- | --- |
| A requested name is missing from the map | `400` | `{:invalid_request, issues}` |
| `{:error, reason}`, a raise, an exit, a value other than `{:ok, map}`, or a non-string fragment | `503` | `{:preset, :lookup_unavailable}` |
| A fragment that doesn't parse, references an unknown preset, or forms a cycle | `500` | `{:preset, :invalid_definition}` |
| The request itself names more than `:max_preset_lookups` presets to look up | `400` | `{:preset, :too_many_presets}` |
| References in looked-up definitions bring the total above `:max_preset_lookups` | `500` | `{:preset, :invalid_definition}` |

Names in the returned map that were not requested are ignored. Each call
emits the `[:image_pipe, :preset, :lookup]` telemetry span.

## Caching

ImagePipe does not cache lookup results. `c:fetch/2` runs on the request
path, so cache results in the implementation (ETS, `:persistent_term`,
Cachex) and bound backend timeouts in the client configuration. Changing a
preset's definition changes the cache key and ETag of requests that use
it.

# `fetch`

```elixir
@callback fetch(names :: [String.t()], options :: keyword()) ::
  {:ok, %{required(String.t()) =&gt; String.t()}} | {:error, term()}
```

Returns the option fragment for each requested name the backend defines.

Fragments use the same grammar as static `:presets` strings, such as
`"w=400/h=300/fit=cover"`. Omit names the backend does not define, and a
request selecting one fails with `400`. Return `""` for a retired name to
keep its URLs working without options. Return `{:error, reason}` when the
backend is unavailable, and the request fails with `503`.

# `validate_options`

```elixir
@callback validate_options(keyword()) :: {:ok, keyword()} | {:error, term()}
```

Validates the lookup's options once, when the configuration is built.

Return `{:ok, options}` with the options `c:fetch/2` receives.
`{:error, reason}` makes `ImagePipe.config/1` raise `ArgumentError`.

---

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