ImagePipe.URL.Helpers (image_pipe_url v0.1.0)

Copy Markdown View Source

An image_url/2 macro that builds image URLs in templates and checks their options at compile time.

In an app that serves its own images with image_pipe, name the ImagePipe instance:

# lib/my_app_web.ex, in html_helpers
use ImagePipe.URL.Helpers, instance: MyApp.Images
<img src={image_url("cat.jpg", group: [resize: [width: 400, height: 300, fit: :cover]])} />
<img src={image_url(@photo.path, group: [resize: [width: @width]], output: [format: :webp])} />

In an app that builds URLs for an image service running elsewhere, give a function that returns the URL configuration. The helper calls this function for every URL, so build the configuration once when the app starts and return the stored copy:

# lib/my_app/image_urls.ex
defmodule MyApp.ImageURLs do
  def put_config do
    config =
      ImagePipe.URL.config(
        base_url: "https://images.example.com",
        keys: [System.fetch_env!("IMAGE_PIPE_SIGNING_KEY")]
      )

    :persistent_term.put(__MODULE__, config)
  end

  def config, do: :persistent_term.get(__MODULE__)
end

# lib/my_app/application.ex, in start/2, before the endpoint starts
MyApp.ImageURLs.put_config()

# lib/my_app_web.ex, in html_helpers
use ImagePipe.URL.Helpers, config: {MyApp.ImageURLs, :config, []}

Calling put_config/0 again replaces the stored configuration without a restart. Signing URLs and rotating keys gives the order for rotating a key.

Options

  • :instance - the :name of an ImagePipe instance. Each URL uses the instance's URL settings from ImagePipe.url_config/2: its base URL, signing keys, source encryption settings, and the presets, request defaults, and watermark settings it checks options against. Building a URL raises ArgumentError when the instance isn't running. Needs the image_pipe package.
  • :mount - with :instance, one of the instance's :mounts, whose URL options the URLs use instead of the instance's own.
  • :config - {module, function, args}, called for each URL, that returns an ImagePipe.URL.Config. Use it in place of :instance.
  • :on_invalid - :warn (the default) or :raise. See Checks when the page renders.

The configuration is read each time a URL is built, so signing keys stay out of the compiled code. Without :instance or :config, URLs use ImagePipe.URL.config([]): no base URL, signing, or encryption.

Summary

Functions

Builds the URL for source with the options of ImagePipe.URL.

Functions

image_url(source, options)

(macro)

Builds the URL for source with the options of ImagePipe.URL.

image_url("cat.jpg",
  new: [filename: "cat"],
  group: [resize: [width: 400, height: 300, fit: :cover]],
  group: [blur: 2],
  output: [format: :webp]
)
# "/w=400/h=300/fit=cover/-/blur=2/format=webp/filename=cat/src/cat.jpg"

new: takes the request controls of ImagePipe.URL.new/2, each group: the options of one ImagePipe.URL.group/2 call, in order, and output: the options of ImagePipe.URL.output/2. new: applies first wherever it is in the call. The option names must be written out in the call, and a list that isn't fails to compile. Use ImagePipe.URL for options built at runtime. Values can be variables or assigns.

Checks at compile time

Each option name and value written out in the call is checked when the module compiles. A list value, such as gradient: or jpeg_options:, is checked only when all of it is written out, except resize:, whose options are checked one by one. A mistake is a compiler warning at the call site:

warning: image_url: invalid_value at {:group, 0, :fit}: invalid value for :fit option: expected one of [:contain, :cover, :stretch, :auto], got: :fill

With mix compile --warnings-as-errors, a mistake fails the build. Values that are variables are checked when the page renders. How the options combine depends on the server's presets and request defaults, so how the options combine is checked at render, with :instance or a configuration that has :validate_against.

Checks when the page renders

The URL is built with ImagePipe.URL.url_with_issues/3, so an error in the options or the source never raises. The URL includes it, the server rejects the URL with 400, and the browser shows a broken image. Each error is logged at :warning, and each warning, such as a mistake the builder repaired or an option that has no effect, at :debug. The log names the call's file and line, the reason, and the option, not the source or values:

[warning] image_url (lib/my_app_web/controllers/page_html/home.html.heex:3): invalid_value at {:group, 0, :fit}

These messages come from the ImagePipe.URL.Helpers module, so Logger's module levels control them. To turn them off in production, set Logger.put_module_level(ImagePipe.URL.Helpers, :none) when the app starts.

With on_invalid: :raise, an error raises ArgumentError instead. To raise only in tests, read the setting from the app's configuration:

# config/test.exs
config :my_app, :image_url_on_invalid, :raise

# lib/my_app_web.ex, in html_helpers
use ImagePipe.URL.Helpers,
  instance: MyApp.Images,
  on_invalid: Application.compile_env(:my_app, :image_url_on_invalid, :warn)