IIIFImagePlug.V3 behaviour (IIIFImagePlug v0.6.0)

View Source

This plug implements the IIIF Image API version 3 (see also https://iiif.io/api/image/3.0).

Summary

Callbacks

Required callback function invoked on image data requests, that maps the given identifier to an image file.

Optional callback function to override the :host evaluated from the Plug.Conn, useful if your Elixir app runs behind a proxy.

Required callback function invoked on information requests (info.json), that maps the given identifier to an image file.

Optional callback function to override the :port evaluated from the Plug.Conn, useful if your Elixir app runs behind a proxy.

Optional callback function to override the :scheme ("http" or "https") evaluated from the Plug.Conn, useful if your Elixir app runs behind a proxy.

Optional callback function that lets you override the default plug error response.

Callbacks

data_request(identifier)

@callback data_request(identifier :: String.t()) ::
  {:ok, IIIFImagePlug.V3.DataRequest.t()}
  | {:error, IIIFImagePlug.V3.RequestError.t()}

Required callback function invoked on image data requests, that maps the given identifier to an image file.

Returns

Example

def data_request(identifier) do
  MyApp.ContextModule.get_image_path(identifier)
  |> case do
    {:ok, path} ->
      {
        :ok,
        %IIIFImagePlug.V3.DataRequest{
          path: path,
          response_headers: [
            {"cache-control", "public, max-age=31536000, immutable"}
          ]
        }
      }
    {:error, :not_found} ->
      {
        :error,
        %IIIFImagePlug.V3.RequestError{
          status_code: 404,
          msg: :not_found
        }
      }
  end
end

host()

@callback host() :: String.t() | nil

Optional callback function to override the :host evaluated from the Plug.Conn, useful if your Elixir app runs behind a proxy.

Example

def host(), do: "images.example.org"

info_request(identifier)

@callback info_request(identifier :: String.t()) ::
  {:ok, IIIFImagePlug.V3.InfoRequest.t()}
  | {:error, IIIFImagePlug.V3.RequestError.t()}

Required callback function invoked on information requests (info.json), that maps the given identifier to an image file.

Returns

Example

def info_request(identifier) do
  MyApp.ContextModule.get_image_metadata(identifier)
  |> case do
    %{path: path, rights_statement: rights} ->
      {
        :ok,
        %IIIFImagePlug.V3.InfoRequest{
          path: path,
          rights: rights
        }
      }
    {:error, :not_found} ->
      {
        :error,
        %IIIFImagePlug.V3.RequestError{
          status_code: 404,
          msg: :not_found
        }
      }
  end
end

port()

@callback port() :: pos_integer() | nil

Optional callback function to override the :port evaluated from the Plug.Conn, useful if your Elixir app runs behind a proxy.

Example

def port(), do: 1337

scheme()

@callback scheme() :: String.t() | nil

Optional callback function to override the :scheme ("http" or "https") evaluated from the Plug.Conn, useful if your Elixir app runs behind a proxy.

Example

def scheme(), do: "https"

send_error(conn, status_code, msg)

@callback send_error(
  conn :: Plug.Conn.t(),
  status_code :: number(),
  msg :: atom()
) :: Plug.Conn.t()

Optional callback function that lets you override the default plug error response.

Examples

Default implementation

The default response for all errors is defined as follows:

def send_error(conn, status_code, msg) do
  conn
  |> Plug.Conn.put_resp_content_type("application/json")
  |> Plug.Conn.send_resp(
    status_code,
    Jason.encode!(%{error: msg})
  )
end

Rewriting 404 for data requests to serve a fallback image

You can pattern match on specific conn, status_code or msg to overwrite specific cases.

One use case might be sending your own placeholder image instead of the JSON for failed data requests.

First customize your data_request/1 implementation with a specific message (you do not want to return an image on a failed info.json request):

def data_request(identifier) do
  MyApp.ContextModule.get_image_path(identifier)
  |> case do
    {:ok, path} ->
      (...)
    {:error, :not_found} ->
      {
        :error,
        %IIIFImagePlug.V3.RequestError{
          status_code: 404,
          msg: :data_request_not_found
        }
      }
  end
end

Then add a custom send_error/3 that picks up on the status code and message you defined:

def send_error(conn, 404, :data_request_not_found) do
  Plug.Conn.send_file(conn, 404, "#{Application.app_dir(:my_app)}/images/not_found.webp")
end

For all errors that do not match the pattern, the plug will be falling back to the default implementation shown above.

Rewriting errors generated by the plug

This also works for errors the plug generates interally:

def send_error(conn, 400, :invalid_rotation) do
  requested_rotation = MyApp.extract_iiif_parameter(conn, :rotation)

  conn
  |> Plug.Conn.put_resp_content_type("application/json")
  |> Plug.Conn.send_resp(
    status_code,
    Jason.encode!(%{error: "Your rotation parameter '#{requested_rotation}' is invalid!"})
  )
end