Error Specification implementation for Elixir.
Quick Start
defmodule MyApp.NotFoundError do
use Trogon.Error,
domain: "com.myapp.resources",
reason: "not_found",
code: :NOT_FOUND,
message: "Resource not found"
end
# Create an error instance with dynamic metadata
MyApp.NotFoundError.new!(
metadata: Trogon.Error.Metadata.new(%{
"resource_type" => "user",
"resource_id" => "123"
})
)Design Philosophy
Trogon errors follow the "Error as Type" pattern where domain/0 + reason/0
uniquely identify the error type, message/0 is a static compile-time description,
and metadata/0 carries instance-specific dynamic values.
The message is intentionally not overridable at runtime to ensure consistent messages for logging/monitoring and predictable error contracts for API consumers.
Key Types
domain/0+reason/0- Unique error identifiercode/0- Standard error code (Google RPC compatible)message/0- Static error descriptionmetadata/0- Dynamic instance dataerror_opt/0- Instance creation options
Summary
Types
The standard error code. See Google RPC error codes for more details.
Debug information for troubleshooting, including stack traces and details.
The error domain identifying the service or component that generated the error.
Instance options for new!/1. See the type definition for available keys.
Help information containing links to documentation or support resources.
A single help link with a description and URL.
Unique identifier for this error instance.
Localized message for internationalization (i18n) support.
The error message template describing what went wrong.
Instance-specific metadata as key-value pairs.
A unique identifier for the specific error within the domain.
Retry information for recoverable errors, indicating when to retry.
Source identifier indicating where the error originated.
A pointer to the field or element that caused the error.
The Trogon error struct type, parameterized by the error module.
A single template option for defining an error module with use Trogon.Error.
Timestamp when the error occurred.
Whether the error should be visible to end users or kept internal.
Functions
See template_opt/0 for available options.
Guard that checks if a term is a Trogon error.
Creates a new Trogon error at runtime with dynamic values.
Converts a code atom or error struct to its integer value.
Converts a code atom or error struct to its HTTP status code.
Updates the help links for a Trogon error.
Types
@type code() ::
:CANCELLED
| :UNKNOWN
| :INVALID_ARGUMENT
| :DEADLINE_EXCEEDED
| :NOT_FOUND
| :ALREADY_EXISTS
| :PERMISSION_DENIED
| :RESOURCE_EXHAUSTED
| :FAILED_PRECONDITION
| :ABORTED
| :OUT_OF_RANGE
| :UNIMPLEMENTED
| :INTERNAL
| :UNAVAILABLE
| :DATA_LOSS
| :UNAUTHENTICATED
The standard error code. See Google RPC error codes for more details.
Debug information for troubleshooting, including stack traces and details.
@type domain() :: String.t()
The error domain identifying the service or component that generated the error.
Examples: "com.myapp.payments", "com.stripe.api"
@type error_opt() :: {:metadata, metadata()} | {:causes, [t(module())]} | {:subject, subject() | nil} | {:debug_info, debug_info() | nil} | {:localized_message, localized_message() | nil} | {:retry_info, retry_info() | nil} | {:id, id() | nil} | {:time, time() | nil} | {:source_id, source_id() | nil}
Instance options for new!/1. See the type definition for available keys.
MyApp.NotFoundError.new!(
metadata: Trogon.Error.Metadata.new(%{"user_id" => "123"}),
subject: "user:123",
localized_message: %{locale: "es", message: "Usuario no encontrado"}
)The :message option is not supported
Use :localized_message for i18n or :metadata for dynamic values.
@type help() :: %{links: [help_link()]}
Help information containing links to documentation or support resources.
A single help link with a description and URL.
@type id() :: String.t()
Unique identifier for this error instance.
Localized message for internationalization (i18n) support.
The locale follows IETF BCP-47
(e.g., "en-US", "fr-CH", "es-MX").
@type message() :: String.t()
The error message template describing what went wrong.
Defined at compile time and cannot be overridden at runtime. For dynamic values,
treat the message as a template and pass runtime data via :metadata.
@type metadata() :: Trogon.Error.Metadata.t()
Instance-specific metadata as key-value pairs.
Trogon.Error.Metadata.new(%{
"user_id" => "123",
"resource_type" => "order"
})See Trogon.Error.Metadata for visibility controls and advanced usage.
@type reason() :: String.t()
A unique identifier for the specific error within the domain.
Combined with domain/0, this creates a globally unique error identifier:
# domain: "com.myapp.payments", reason: "card_declined"
# domain: "com.myapp.users", reason: "not_found"
@type retry_info() :: retry_info_duration()
Retry information for recoverable errors, indicating when to retry.
retry_info: %{retry_offset: %Duration{second: 60}}
@type source_id() :: String.t()
Source identifier indicating where the error originated.
@type subject() :: String.t()
A pointer to the field or element that caused the error.
Examples: "email", "user.address.zipCode", "/items/0/quantity" (JSON Pointer)
@type t(struct) :: %{ __struct__: struct, __exception__: true, specversion: non_neg_integer(), code: code(), message: message(), domain: domain(), reason: reason(), metadata: metadata(), causes: [t(module())], visibility: visibility(), subject: subject() | nil, id: id() | nil, time: time() | nil, help: help() | nil, debug_info: debug_info() | nil, localized_message: localized_message() | nil, retry_info: retry_info() | nil, source_id: source_id() | nil }
The Trogon error struct type, parameterized by the error module.
@spec find_user(String.t()) ::
{:ok, User.t()}
| {:error, Trogon.Error.t(MyApp.NotFoundError)}
@type template_opt() :: {:domain, domain()} | {:reason, reason()} | {:message, atom() | message()} | {:metadata, Trogon.Error.Metadata.raw()} | {:code, code()} | {:visibility, visibility()} | {:help, help()}
A single template option for defining an error module with use Trogon.Error.
defmodule MyApp.NotFoundError do
use Trogon.Error,
domain: "com.myapp.resources",
reason: "not_found",
code: :NOT_FOUND,
message: "Resource not found"
endOptions
:domain(String.t/0) - Required. Seedomain/0.:reason(String.t/0) - Required. Seereason/0.:message- Seemessage/0. Use:codeif not provided as a string.:metadata- Default metadata merged with instance metadata. SeeTrogon.Error.Metadata.raw/0. The default value is%{}.:code- Seecode/0. The default value is:UNKNOWN.:visibility- Seevisibility/0. The default value is:INTERNAL.:links(list ofmap/0)
@type time() :: DateTime.t()
Timestamp when the error occurred.
@type using_opt() :: template_opt() | {:proto, module()}
@type visibility() :: :INTERNAL | :PRIVATE | :PUBLIC
Whether the error should be visible to end users or kept internal.
:INTERNAL- The error is not visible from outside the application.:PRIVATE- The error is visible from applications belonging to the same organization.:PUBLIC- The error is visible to everyone.
Functions
See template_opt/0 for available options.
Guard that checks if a term is a Trogon error.
Examples
iex> error = TestSupport.TestError.new!()
iex> require Trogon.Error
iex> Trogon.Error.is_trogon_error?(error)
true
iex> require Trogon.Error
iex> Trogon.Error.is_trogon_error?(%{})
false
@spec new!([template_opt() | error_opt()]) :: t(Trogon.Error)
Creates a new Trogon error at runtime with dynamic values.
Useful for handling external errors from services you don't control, without needing predefined error modules.
Examples
# Basic external error
Trogon.Error.new!(
domain: "com.stripe.payment",
reason: "card_declined",
message: "Your card was declined"
)
# With additional metadata and options
Trogon.Error.new!(
domain: "com.external.api",
reason: "rate_limit_exceeded",
code: :RESOURCE_EXHAUSTED,
message: "Rate limit exceeded",
metadata: Trogon.Error.Metadata.new(%{
"limit" => "100",
"window" => "3600"
}),
subject: "api-client-123",
retry_info: %{retry_offset: %Duration{second: 60}}
)
@spec to_code_int(atom() | t(module())) :: non_neg_integer()
Converts a code atom or error struct to its integer value.
Examples
iex> Trogon.Error.to_code_int(:CANCELLED)
1
iex> err = TestSupport.InvalidCurrencyError.new!()
iex> Trogon.Error.to_code_int(err)
2
@spec to_http_status_code(atom() | t(module())) :: non_neg_integer()
Converts a code atom or error struct to its HTTP status code.
Examples
iex> Trogon.Error.to_http_status_code(:CANCELLED)
499
iex> err = TestSupport.InvalidCurrencyError.new!()
iex> Trogon.Error.to_http_status_code(err)
500
Updates the help links for a Trogon error.
The updater receives the error and the current links list, normalized from nil
to [], and must return the replacement links list.
Examples
iex> defmodule MyApp.Error.InsufficientInventoryError do
...> use Trogon.Error,
...> domain: "com.myapp.inventory",
...> reason: "INSUFFICIENT_INVENTORY"
...> end
iex> updater_links = fn err, links, principal_role ->
...> path = "/runbooks/" <> err.domain <> "/" <> err.reason <> "?role=#{principal_role}"
...> url = "https://docs.example.com" <> path
...> links ++ [%{description: "Runbook", url: url}]
...> end
iex> err = MyApp.Error.InsufficientInventoryError.new!()
iex> err = Trogon.Error.update_help_links(err, &updater_links.(&1, &2, :ai_agent))
iex> err.help
%{links: [
%{description: "Runbook", url: "https://docs.example.com/runbooks/com.myapp.inventory/INSUFFICIENT_INVENTORY?role=ai_agent"}
]}