Plugin that provides status and error response management with extensible callbacks.
This plugin works with Malla.Status to provide a complete status handling system.
To use the system, include this plugin in your service's plugin list, and use
Malla.Status.status/2 or Malla.Status.public/2 to convert statuses.
This plugin provides default implementations for the the following callbacks:
See Malla.Status for details.
Summary
Callbacks
Converts a status term into a standardized status description.
Adds or modifies metadata in a generated status structure.
Handles unmatched statuses when using Malla.Status.public/1.
Types
@type status_return() :: String.t() | [Malla.Status.status_opt()] | :cont
Callbacks
@callback status(Malla.Status.user_status()) :: status_return()
Converts a status term into a standardized status description.
This callback receives any term representing a status or error and returns
either a status description or :cont to delegate to the next plugin.
Return Values
The callback can return:
Keyword list with status description options:
:info(string) - Human-readable message (becomesinfofield):code(integer) - Numeric code, typically HTTP status code:status(string/atom) - Override the status identifier:data(map/keyword) - Additional structured data:metadata(map/keyword) - Service metadata
String - Sets the
infofield directly (simple case):cont- Skip this handler and try the next plugin
Examples
# Simple message
defcb status(:not_found), do: [info: "Resource not found", code: 404]
# With data
defcb status({:invalid_field, field}),
do: [info: "Invalid field", code: 400, data: [field: field]]
# String return
defcb status(:timeout), do: "Operation timed out"
# Continue to next plugin
defcb status(_), do: :contRemember to always include a catch-all clause returning :cont to allow other plugins to handle unknown statuses.
Implemented Status Codes
This base implementation provides out-of-the-box a number of status patterns that are converted to statuses, but you can implement it too to support new ones or override the default ones.
| Status Pattern | HTTP Code | Message |
|---|---|---|
:ok | 200 | Success |
{:ok_data, data} | 200 | Success (with data) |
:created | 201 | Created |
:deleted | 200 | Deleted |
:normal_termination | 200 | Normal termination |
:redirect | 307 | Redirect |
:bad_request | 400 | Bad Request |
:content_type_invalid | 400 | Content type is invalid |
{:field_invalid, field} | 400 | Field is invalid |
{:field_missing, field} | 400 | Field is missing |
{:field_unknown, field} | 400 | Field is unknown |
:file_too_large | 400 | File too large |
:invalid_parameters | 400 | Invalid parameters |
{:parameter_invalid, param} | 400 | Invalid parameter |
{:parameter_missing, param} | 400 | Missing parameter |
{:request_op_unknown, op} | 400 | Request OP unknown |
:request_body_invalid | 400 | The request body is invalid |
{:syntax_error, field} | 400 | Syntax error |
:token_invalid | 400 | Token is invalid |
:token_expired | 400 | Token is expired |
:unauthorized | 401 | Unauthorized |
:forbidden | 403 | Forbidden |
:not_found | 404 | Not found |
:resource_invalid | 404 | Invalid resource |
{:method_not_allowed, method} | 405 | Method not allowed |
:verb_not_allowed | 405 | Verb is not allowed |
:timeout | 408 | Timeout |
:conflict | 409 | Conflict |
:not_allowed | 409 | Not allowed |
:service_not_found | 409 | Service not found |
{:service_not_found, service} | 409 | Service not found (specific) |
:gone | 410 | Gone |
:unprocessable | 422 | Unprocessable |
:internal_error | 500 | Internal error |
{:internal_error, ref} | 500 | Internal error (with ref) |
:service_not_available | 500 | RPC service unavailable |
:not_implemented | 501 | Not implemented |
{:service_not_available, service} | 503 | Service not available (specific) |
@callback status_metadata(Malla.Status.t()) :: Malla.Status.t()
Adds or modifies metadata in a generated status structure.
This callback is invoked after a status has been fully constructed, allowing plugins to inject additional metadata based on the service context or status content.
Examples
# Conditional metadata based on status
defcb status_metadata(%{status: "internal_error"} = status) do
metadata = Map.put(status.metadata, "support_contact", "support@example.com")
%{status | metadata: metadata}
end
# Add service information to metadata
defcb status_metadata(status) do
metadata = Map.merge(status.metadata, %{
"service" => "MyService",
"version" => "1.0.0",
"node" => to_string(node())
})
%{status | metadata: metadata}
end
defcb status_metadata(status), do: statusUse Cases
- Adding service identification (name, version, node)
- Including timestamps or request IDs
- Adding environment information (production, staging, etc.)
- Injecting support contact information for errors
- Adding trace IDs for distributed tracing
@callback status_public(Malla.Status.user_status()) :: Malla.Status.t() | :cont
Handles unmatched statuses when using Malla.Status.public/1.
This callback is invoked when public/2 encounters a status that has no
matching status/1 callback implementation. It allows services to customize
how internal errors are exposed to external consumers.
By default, this callback:
- Generates a unique reference number
- Logs a warning with the full status details and reference
- Returns a sanitized status struct with "internal_error" and the reference
Examples
# Default behavior (log and hide details)
defcb status_public(user_status) do
ref = rem(:erlang.phash2(:erlang.make_ref()), 10000)
Logger.warning("Internal error: #{inspect(ref)}: #{inspect(user_status)} (#{srv_id})")
%Malla.Status{
status: "internal_error",
info: "Internal reference #{ref}",
code: 500
}
end
defcb status_public(user_status) do
# Fall back to default for everything else
:cont
endUse Cases
- Customizing which errors are safe to expose externally
- Adding custom logging or monitoring for unhandled statuses
- Providing different sanitization strategies per service
- Routing certain error patterns to external error tracking services