lib/topical/adapters/cowboy/rest_handler.ex

defmodule Topical.Adapters.Cowboy.RestHandler do
  @moduledoc """
  A REST-ish handler adapter for a Cowboy web server.

  ## Options

   - `registry` - The name of the Topical registry. Required.
   - `init` - A function called before starting/capturing the topic, passed the request. The
     function must return `{:ok, context}` for the connection to be accepted. This `context` is
     then passed to topics.

  ## Example

      :cowboy_router.compile([
        {:_,
         [
           # ...
           {"/topics/[...]", RestHandler, registry: MyApp.Topical}
         ]}
      ])
  """

  @doc false
  def init(req, opts) do
    registry = Keyword.fetch!(opts, :registry)
    init = Keyword.get(opts, :init)
    topic = :cowboy_req.path_info(req)

    result = if init, do: init.(req), else: {:ok, nil}

    case result do
      {:ok, context} ->
        case Topical.capture(registry, topic, context) do
          {:ok, value} ->
            case Jason.encode(value) do
              {:ok, json} ->
                req = :cowboy_req.reply(200, %{"content-type" => "application/json"}, json, req)
                {:ok, req, opts}

              {:error, _error} ->
                {:ok, error_response(req, "encode_failure"), opts}
            end

          {:error, :not_found} ->
            {:ok, error_response(req, 404, "not_found"), opts}

          {:error, :unauthorized} ->
            {:ok, error_response(req, 403, "unauthorized"), opts}

          {:error, error} ->
            {:ok, error_response(req, 400, error), opts}
        end
    end
  end

  defp error_response(req, status \\ 500, error) do
    json = Jason.encode!(%{"error" => error})
    :cowboy_req.reply(status, %{"content-type" => "application/json"}, json, req)
  end
end