# `ALLM.Providers.Support.ImageMime`
[🔗](https://github.com/cykod/ALLM/blob/v0.3.0/lib/allm/providers/support/image_mime.ex#L1)

Per-provider MIME and size validation for `ALLM.ImagePart` content.

Layer B helper used by the OpenAI and (future) Anthropic vision pre-flight
to gate `%ALLM.ImagePart{}` content against per-provider accept-sets and a
shared 20-MB byte-size ceiling. See spec §35.6 and Phase 17 design §3.1.

## Per-image validation (`validate/2`)

Resolves the part's `%ALLM.Image{}` to bytes via
`ALLM.Image.to_binary/1` and applies two checks:

  * MIME membership against the supplied accept-set.
  * `byte_size/1` of the resolved bytes against `@max_bytes` (20 MB).

URL sources (`{:url, _}`) skip the size check — the adapter does NOT
fetch the URL during pre-flight (Decision #1); size validation is
deferred to the provider. MIME enforcement still applies (the URL's
`:mime_type` is always `nil` per `ALLM.Image.from_url/1`, so a URL-source
`%ImagePart{}` is accepted regardless of accept-set today; future
divergence is captured by the per-provider arg).

## Request-level pre-flight (`validate_request/2`)

Walks every `%ALLM.ImagePart{}` in `request.messages`, calls `validate/2`
for each, accumulates `{[:content, msg_idx, part_idx], reason}` field
errors. Returns `:ok` when no part fails, otherwise an
`%ALLM.Error.ValidationError{reason: :invalid_message}` with all
per-image errors in `:errors`. Adapters call this once in pre-flight;
per-provider divergence is isolated to `accept_mimes/1`.

## Provider accept-sets

    iex> ALLM.Providers.Support.ImageMime.accept_mimes(:openai)
    ["image/png", "image/jpeg", "image/webp", "image/gif"]

    iex> ALLM.Providers.Support.ImageMime.accept_mimes(:anthropic)
    ["image/png", "image/jpeg", "image/webp", "image/gif"]

Both providers' published 2026-04 accept-sets match; the per-provider
arg keeps the seam open for future divergence.

# `validate_result`

```elixir
@type validate_result() ::
  :ok
  | {:error,
     {:unsupported_image_format, mime :: String.t() | nil}
     | {:image_too_large, byte_size :: non_neg_integer()}
     | :missing_mime_type}
```

Per-image validation result.

# `accept_mimes`

```elixir
@spec accept_mimes(:openai | :anthropic) :: [String.t()]
```

Provider accept-set for `image/*` MIME types.

## Examples

    iex> ALLM.Providers.Support.ImageMime.accept_mimes(:openai)
    ["image/png", "image/jpeg", "image/webp", "image/gif"]

# `validate`

```elixir
@spec validate(ALLM.ImagePart.t(), [String.t()]) :: validate_result()
```

Validate a single `%ImagePart{}` against an accept-set and the 20-MB
size ceiling.

## Examples

    iex> img = ALLM.Image.from_binary("hi", "image/png")
    iex> part = ALLM.ImagePart.new(img)
    iex> ALLM.Providers.Support.ImageMime.validate(part, ["image/png"])
    :ok

    iex> img = ALLM.Image.from_binary("hi", "image/svg+xml")
    iex> part = ALLM.ImagePart.new(img)
    iex> ALLM.Providers.Support.ImageMime.validate(part, ["image/png"])
    {:error, {:unsupported_image_format, "image/svg+xml"}}

    iex> img = ALLM.Image.from_url("https://example.com/x.png")
    iex> part = ALLM.ImagePart.new(img)
    iex> ALLM.Providers.Support.ImageMime.validate(part, ["image/png"])
    :ok

# `validate_request`

```elixir
@spec validate_request(ALLM.Request.t(), :openai | :anthropic) ::
  :ok | {:error, ALLM.Error.ValidationError.t()}
```

Walk every `%ImagePart{}` in `request.messages`, validate, and return
either `:ok` or a `%ValidationError{reason: :invalid_message}` carrying
per-image field errors.

Field paths: `[:content, msg_idx, part_idx]` per spec §35.6 / Phase 17
design §7.

## Examples

    iex> img = ALLM.Image.from_binary("hi", "image/png")
    iex> req = ALLM.Request.new([%ALLM.Message{role: :user, content: [%ALLM.ImagePart{image: img}]}])
    iex> ALLM.Providers.Support.ImageMime.validate_request(req, :openai)
    :ok

---

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