DynamicRoutes.Resolver behaviour (DynamicRoutes v0.1.1)

Copy Markdown View Source

The behaviour a host application implements to say which paths it owns.

A resolver is asked about a path only when no declared route matched it — which is to say, only about requests that would otherwise be a 404. It answers :pass to let the 404 happen, or {:match, term} to claim the request, and whatever it returns comes back to the controller through DynamicRoutes.resolution/1.

defmodule MyApp.Pages do
  @behaviour DynamicRoutes.Resolver

  @impl true
  def resolve(["blog", slug]) do
    case MyApp.Content.page_by_slug(slug) do
      nil -> :pass
      page -> {:match, page}
    end
  end

  def resolve([slug]) do
    case MyApp.Content.page_by_slug(slug) do
      nil -> :pass
      page -> {:match, page}
    end
  end

  def resolve(_path), do: :pass
end

Returning the record itself, rather than just true, is worth doing: the controller would otherwise look it up a second time on the same request.

Caching

Results are cached, so resolve/1 is not called on every request for the same path — see DynamicRoutes.Cache. Call DynamicRoutes.invalidate/0 when pages change, or DynamicRoutes.invalidate/1 for a single path.

Because the cache holds whatever {:match, term} carries, a resolver that returns a large struct is holding that struct in memory until the entry expires. Return an id, or something small, if the record is big.

Summary

Types

The answer to "is this path yours?"

Callbacks

Decides whether a path is one this application serves dynamically.

Types

resolution()

@type resolution() :: :pass | {:match, term()}

The answer to "is this path yours?"

{:match, term} claims the request and hands term to the controller. :pass declines, and the request 404s as it normally would.

Callbacks

resolve(path_info)

@callback resolve(path_info :: [String.t()]) :: resolution()

Decides whether a path is one this application serves dynamically.

Receives the request path split on /, with no leading empty segment and already percent-decoded — so a request for /caf%C3%A9 arrives as ["café"], matching what the controller will see in its params.

Do not decode again. %2520 is a literal %20, and decoding it twice turns it into a space the client never asked for.

This runs on requests that matched no declared route, which on a public site includes every scanner probing for /wp-login.php. Keep it cheap, and return :pass early for shapes that cannot be yours.