Packages

Run WebAssembly from Elixir. Load WASM modules in Rust, Go, C — call them like native functions.

Current section

Files

Jump to
firebird lib firebird phoenix error_handler.ex
Raw

lib/firebird/phoenix/error_handler.ex

defmodule Firebird.Phoenix.ErrorHandler do
@moduledoc """
Error handling and error page generation for Phoenix WASM.
Provides standardized error responses for common HTTP errors,
with customizable error templates and JSON error formatting.
## Usage
# HTML error page
html = ErrorHandler.render_error(404, :html)
# JSON error
json = ErrorHandler.render_error(404, :json)
# Custom error templates
handler = ErrorHandler.new()
|> ErrorHandler.put_template(404, "<h1>Custom 404</h1><p>{{message}}</p>")
|> ErrorHandler.put_template(500, "<h1>Server Error</h1>")
html = ErrorHandler.render(handler, 404, %{"message" => "Page not found"})
"""
alias Firebird.Phoenix.Conn
@type t :: %__MODULE__{
templates: %{integer() => String.t()},
format: :html | :json
}
defstruct templates: %{}, format: :html
@default_messages %{
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
406 => "Not Acceptable",
408 => "Request Timeout",
409 => "Conflict",
413 => "Payload Too Large",
415 => "Unsupported Media Type",
422 => "Unprocessable Entity",
429 => "Too Many Requests",
500 => "Internal Server Error",
502 => "Bad Gateway",
503 => "Service Unavailable",
504 => "Gateway Timeout"
}
@doc "Create a new error handler."
@spec new(keyword()) :: t()
def new(opts \\ []) do
%__MODULE__{
format: Keyword.get(opts, :format, :html)
}
end
@doc "Set a custom template for a status code."
@spec put_template(t(), integer(), String.t()) :: t()
def put_template(%__MODULE__{} = handler, status, template) do
%{handler | templates: Map.put(handler.templates, status, template)}
end
@doc """
Render an error response.
Uses custom templates if defined, otherwise falls back to defaults.
"""
@spec render(t(), integer(), map()) :: String.t()
def render(%__MODULE__{} = handler, status, assigns \\ %{}) do
message = Map.get(assigns, "message", Map.get(@default_messages, status, "Error"))
case Map.get(handler.templates, status) do
nil ->
case handler.format do
:json -> default_json_error(status, message)
:html -> default_html_error(status, message)
end
template ->
all_assigns = Map.merge(%{"status" => "#{status}", "message" => message}, assigns)
Enum.reduce(all_assigns, template, fn {k, v}, acc ->
String.replace(acc, "{{#{k}}}", to_string(v))
end)
end
end
@doc "Render a default error page for a status code."
@spec render_error(integer(), :html | :json) :: String.t()
def render_error(status, format \\ :html) do
message = Map.get(@default_messages, status, "Error")
case format do
:json -> default_json_error(status, message)
:html -> default_html_error(status, message)
end
end
@doc "Build an error connection response."
@spec error_conn(integer(), :html | :json) :: Conn.t()
def error_conn(status, format \\ :html) do
{content_type, body} =
case format do
:json ->
{"application/json",
default_json_error(status, Map.get(@default_messages, status, "Error"))}
:html ->
{"text/html", default_html_error(status, Map.get(@default_messages, status, "Error"))}
end
Conn.new("GET", "/")
|> Conn.put_status(status)
|> Conn.put_resp_header("content-type", content_type)
|> Conn.put_resp_body(body)
end
@doc "Get the default message for a status code."
@spec status_message(integer()) :: String.t()
def status_message(status) do
Map.get(@default_messages, status, "Unknown Error")
end
defp default_html_error(status, message) do
"""
<!DOCTYPE html>
<html>
<head><title>#{status} #{message}</title></head>
<body>
<h1>#{status}</h1>
<p>#{message}</p>
</body>
</html>
"""
|> String.trim()
end
defp default_json_error(status, message) do
~s({"error":{"status":#{status},"message":"#{message}"}})
end
end