defmodule Solicit.Plugs.ValidatePathParam do @moduledoc """ Casts the given path param to the given type. If the cast fails, returns a 404. Uses Solicit for 404 responses, so only API routes are supported. """ @typedoc """ Plug options. - `:param` - the string name of the parameter to be cast. **Required.** - `:type` - the type to cast to using `Ecto.Type.cast/2`. **Required.** - `:required` - if true, requires the parameter to not be nil or an empty string after casting. **Default:** `true`. """ @type options :: [ param: binary(), type: Ecto.Type.t(), required: boolean() ] @spec init(keyword()) :: keyword() def init(opts) do unless is_binary(Keyword.get(opts, :param)) do raise ":param is required and must be a string" end unless Keyword.has_key?(opts, :type) do raise ":type is required" end opts end @spec call(Plug.Conn.t(), keyword) :: Plug.Conn.t() def call(conn, opts) do param_name = Keyword.get(opts, :param) type = Keyword.get(opts, :type) required = Keyword.get(opts, :required, true) value = Map.get(conn.path_params, param_name) case Ecto.Type.cast(type, value) do {:ok, cast_value} -> if required && (is_nil(cast_value) || cast_value == "") do Solicit.Response.not_found(conn) else # don't update conn because changing path_params breaks spandex_phoenix conn end :error -> Solicit.Response.not_found(conn) end end end