Mooncore.Action
(mooncore v0.2.4)
Copy Markdown
Action dispatcher framework.
use Mooncore.Action in your app's action module to get the standard
dispatcher pattern with role checking, deep merge, and command fallback.
Usage
defmodule MyApp.Action do
@actions %{
"task.create" => %{handler: {MyApp.Action.Task, :create}, roles: ~w(user)},
"task.list" => %{handler: {MyApp.Action.Task, :list}, roles: ~w(user)},
"echo" => %{handler: {MyApp.Action.Echo, :echo}},
}
use Mooncore.Action
endImportant:
@actionsmust be defined beforeuse Mooncore.Action. The macro captures@actionsat compile time — if it's defined after,actions_map/0will returnniland nothing will dispatch.
Actions are defined as maps with the following keys:
:handler—{Module, :function}tuple (required):roles— list of role strings. Omit or set[]for public (no auth needed).:overrides— map deep-merged into the request before calling the handler, overriding any incoming params with the same keys. Use this to force server-controlled values (e.g.%{format: "pdf"}) regardless of what the caller sends.:validate— aMooncore.Validateschema (keyword list offield: [rules]) applied toparamsbefore the handler is called. If validation fails, an error is returned immediately without reaching the handler.
Overrides Example
Both routes call the same handler but force different config:
@actions %{
"report.pdf" => %{handler: {MyApp.Action.Report, :generate}, roles: ~w(admin), overrides: %{format: "pdf"}},
"report.preview" => %{handler: {MyApp.Action.Report, :generate}, roles: ~w(user), overrides: %{format: "html"}},
}The handler reads req[:format] — callers cannot override it.
Validate Example
@actions %{
"task.create" => %{
handler: {MyApp.Action.Task, :create},
roles: ~w(user),
validate: [
{"title", [:required, :string, {:min_length, 2}]},
{"priority", [:integer, {:in, [1, 2, 3]}]}
]
}
}On failure the caller receives %{error: "validation_failed", errors: %{"title" => ["is required"]}}.
Use string keys to match HTTP/WebSocket params; atom keys for internal Elixir calls;
list paths (["address", "city"]) for nested maps.
Request Map Structure
The request map passed to handlers looks like:
%{
auth: %{"user" => "alice", "app" => "myapp", "roles" => ["user"], ...},
params: %{ # the FULL request body / message
"action" => "task.create", # action name lives here too
"title" => "My Task", # user data is at the top level
"rayid" => "abc-123" # (WebSocket only) correlation id
}
}req[:params] is the entire parsed request body (HTTP) or the full
WebSocket message. User-supplied fields sit alongside "action".
Calling Actions
# Via the pipeline (runs before/after middlewares):
Mooncore.Action.execute("task.create", request_map)
# Direct (no middlewares):
MyApp.Action.run("task.create", request_map)
Summary
Functions
Check if user has any of the required roles.
Execute an action through the full pipeline (before hooks → dispatch → after hooks). Called by transport adapters (HTTP, WebSocket).
Format action responses for transport.
Functions
Check if user has any of the required roles.
Execute an action through the full pipeline (before hooks → dispatch → after hooks). Called by transport adapters (HTTP, WebSocket).
When params["mooncore_log"] is truthy, logs the entire lifecycle with timestamps.
Format action responses for transport.