defmodule Image.Plug.Provider.Cloudflare do @moduledoc """ Cloudflare Images URL provider. Wires `Image.Plug.Provider.Cloudflare.URL` (URL-shape recognition) and `Image.Plug.Provider.Cloudflare.Options` (option parsing) into the `Image.Plug.Provider` behaviour. ### Options * `:mount` — string path prefix the plug is mounted under. Defaults to `""`. See `Image.Plug.Provider.Cloudflare.URL`. * `:hosted_account_hash` — when set, also recognises the hosted-image delivery form at `///`. * `:strict?` — if `true` (default), unknown option keys produce an `:unknown_option` error. If `false`, unknown keys are logged and ignored. * `:variants_enabled?` — defaults to `true`. When `false`, a hosted URL whose tail looks like a variant name produces a `:variant_not_found` error instead of returning a `:variant` result. ### URL forms recognised * `/cdn-cgi/image//` * `/cdn-cgi/image//` * `///` (when `:hosted_account_hash` is configured) """ @behaviour Image.Plug.Provider alias Image.Plug.Error alias Image.Plug.Provider.Cloudflare.{Options, URL} @impl Image.Plug.Provider def parse(%Plug.Conn{} = conn, options) when is_list(options) do case URL.parse(conn, Keyword.take(options, [:mount, :hosted_account_hash])) do {:ok, recognised} -> build_result(recognised, options) # The URL is not addressed to this plug; ask the host application # to handle the request by returning the passthrough signal. :unrecognised -> {:ok, :skip} {:error, _reason} = error -> error end end defp build_result(%{variant: variant, source: source}, options) when is_binary(variant) do if Keyword.get(options, :variants_enabled?, true) do {:ok, {:variant, variant, [], source}} else {:error, Error.new(:variant_not_found, "variants are disabled on this provider", details: %{variant: variant} )} end end defp build_result(%{options: options_string, source: source}, options) when is_binary(options_string) do with {:ok, pipeline} <- Options.parse(options_string, Keyword.take(options, [:strict?])) do result = case pipeline.ops do [] -> {:passthrough, source} _ -> {:pipeline, pipeline, source} end {:ok, result} end end # No options string and no variant — only happens for the cdn-cgi # form when the source is missing, but URL.parse already rejects # that. Defensive `:malformed_url` here for completeness. defp build_result(_recognised, _options) do {:error, Error.new(:malformed_url, "URL produced neither options nor a variant")} end end