FunConfig is the central configuration struct. Each FunConfig maps one {service, request_type} pair to one function call.
Schema
| Field | Type | Default | Description |
|---|
request_type | String.t() | required | API endpoint name (e.g. "get_user") |
service | atom | String.t() | required | Service group name (e.g. "user_service") |
nodes | [atom] | {m,f,a} | :local | required | Target nodes or :local for same-node execution |
choose_node_mode | atom | :random | Node selection strategy (see below) |
timeout | integer | :infinity | required | Execution timeout in ms (100–300_000 or :infinity) |
mfa | {module, function, args} | required | The function to call |
arg_types | map() | nil | nil | Argument type declarations for validation |
arg_orders | [String.t()] | :map | [] | Argument ordering (or :map to pass a map) |
response_type | atom | :sync | :sync | :async | :stream | :none |
check_permission | atom | tuple | false | Permission mode (see below) |
permission_callback | {m,f,a} | nil | nil | Custom permission check MFA |
version | String.t() | nil | nil | API version. "0.0.0" is reserved as a sentinel |
disabled | boolean | false | Soft-delete flag |
retry | nil | number | tuple | nil | Retry configuration |
before_execute | tuple | nil | nil | Hook called before execution |
after_execute | tuple | nil | nil | Hook called after execution |
hook_timeout | pos_integer() | 5_000 | Per-hook timeout in ms |
request_info | boolean | false | Legacy field, currently unused in execution |
Node Selection Strategies (choose_node_mode)
| Strategy | Value | Description |
|---|
| Random | :random | Pick a random node |
| Hash (request_id) | :hash | Hash the request_id to pick a node |
| Hash (arg value) | {:hash, "user_id"} | Hash the value of the named arg |
| Round-robin | :round_robin | Cycle through nodes in order |
| Sticky | {:sticky, "user_id"} | Same key value always maps to the same node (persisted via ETS) |
Permission Modes (check_permission)
| Mode | Value | Description |
|---|
| Disabled | false | No permission check (default) |
| Any authenticated | :any_authenticated | Requires user_id to be non-nil |
| Arg-based | {:arg, "user_id"} | Compares user_id from socket to the named arg value |
| Role-based | {:role, ["admin"]} | Checks if any user role is in the allowed list |
When permission_callback is set to an MFA tuple, it overrides check_permission entirely.
Response Types (response_type)
| Type | Description |
|---|
:sync | Execute and return the result immediately |
:async | Acknowledge immediately, send result later via {:async_call, result} |
:stream | Start a StreamCall GenServer that sends chunks via {:stream_response, result} |
:none | Fire-and-forget; no response sent to the client |
Retry Configuration (retry)
| Value | Description |
|---|
nil | No retry (default) |
3 | Equivalent to {:all_nodes, 3} |
{:same_node, 2} | Retry on the originally selected node(s) |
{:all_nodes, 3} | Retry across all available nodes |
For nodes: :local, both :same_node and :all_nodes retry locally.
Argument Types (arg_types)
arg_types: %{
"user_id" => :string,
"age" => :num,
"active" => :boolean
}
arg_types: %{
"title" => [type: :string, max_bytes: 200],
"tags" => [type: :list_string, max_items: 10, max_item_bytes: 50],
"published" => [type: :boolean, default_value: false],
"metadata" => [type: :map, max_items: 50, required: ["author"], accept: ["author", "email"]]
}
Available types
| Type | Description |
|---|
:string | UTF-8 binary |
:num | Integer or float |
:boolean | true or false |
:uuid | UUID string |
:datetime | ISO 8601 datetime string |
:naive_datetime | ISO 8601 naive datetime string |
:list | List of any values |
:list_string | List of strings |
:list_num | List of numbers |
:list_uuid | List of UUIDs |
:list_map | List of maps |
:map | String-keyed map |
:any | Skip type checking |
| Option | Applies to | Description |
|---|
max_bytes: | :string | Maximum byte length |
max_items: | All list/map types | Maximum number of items |
max_item_bytes: | :list_string | Max bytes per list item |
allow_nil?: | All types | Allow nil values (default: false) |
default_value: | All types | Default if arg is missing |
required: | :map only | List of required map keys |
accept: | :map only | List of allowed map keys (rejects unknown keys) |
Version
The version field is a string (e.g., "1.0.0"). The value "0.0.0" is reserved as a sentinel and cannot be explicitly registered — it is used internally to mean "no version specified". If a config has no version set, it is stored with a nil version key in the cache.
Validation
Use FunConfig.valid?/1 for a quick boolean check or FunConfig.validate_with_details/1 for detailed error messages:
case FunConfig.validate_with_details(config) do
{:ok, _} -> :valid
{:error, errors} -> IO.inspect(errors)
end
Validation checks include: request_type is non-empty, service is not nil, nodes is valid, choose_node_mode is recognized, timeout is within bounds, mfa is a valid tuple, arg_types and arg_orders are consistent, response_type is valid, check_permission is valid, retry is valid, hooks are valid MFAs, hook_timeout is positive.
Example
alias PhoenixGenApi.Structs.FunConfig
%FunConfig{
request_type: "get_user",
service: "user_service",
nodes: [:"node1@host", :"node2@host"],
choose_node_mode: {:sticky, "user_id"},
timeout: 5_000,
mfa: {MyApp.Api, :get_user, []},
arg_types: %{
"user_id" => :string,
"fields" => [type: :list_string, max_items: 10]
},
arg_orders: ["user_id", "fields"],
response_type: :sync,
version: "2.0.0",
check_permission: {:arg, "user_id"},
retry: {:all_nodes, 3},
before_execute: {MyApp.Hooks, :validate_quota},
after_execute: {MyApp.Hooks, :log_response}
}
What's Next