# `PhoenixGenApi.Structs.FunConfig`
[🔗](https://github.com/ohhi-vn/phoenix_gen_api/blob/main/lib/phoenix_gen_api/structs/fun_config.ex#L1)

Defines the configuration for a function that can be called through the API.

This struct holds all the necessary information to route, validate, and execute
a function call based on an incoming request.

## 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, `version/1` returns
`nil` and the config is stored with a `nil` version key in the cache.

## Argument Types (arg_types)

The `arg_types` field supports two formats:

### Simple Format (Backward Compatible)

Uses just the type atom:

    arg_types: %{"user_id" => :string, "age" => :num}

### Extended Format (New)

Uses a keyword list with `:type` and optional parameters:

    arg_types: %{
      "user_id" => [type: :string, max_bytes: 255, allow_nil?: true],
      "age" => [type: :num, default_value: 18],
      "tags" => [type: :list_string, max_items: 10, max_item_bytes: 100],
      "gps_list" => [type: :list_map, max_items: 100],
      "metadata" => [type: :map, max_items: 200, required: ["name"], accept: ["name", "email", "age"]]
    }

#### Extended Format Options

- `type:` - Required. The argument type (`:string`, `:num`, `:boolean`, etc.)
- `allow_nil?:` - Optional. When `true`, allows nil values (default: `false`)
- `default_value:` - Optional. Default value if argument is missing from request
- `max_bytes:` - For `:string` type, max byte size
- `max_items:` - For list/map types, max number of items
- `max_item_bytes:` - For `:list_string`, max bytes per item
- `required:` - For `:map` type only, list of required key names (e.g., `["name", "email"]`)
- `accept:` - For `:map` type only, list of accepted key names — any key not in this list causes an error

#### Validation

- Default values are validated to match their declared type during config validation
- Invalid default values will cause `FunConfig.validate_with_details/1` to return an error

## Retry

The `retry` field configures retry behavior when request execution fails
(returns `{:error, _}` or `{:error, _, _}`).

Possible values:

- `nil` - No retry (default, backward compatible)
- A positive number (e.g., `3`) - Equivalent to `{:all_nodes, 3}`.
  Retry across all available nodes.
- `{:same_node, positive_number}` (e.g., `{:same_node, 2}`) - Retry on the
  same node(s) that were originally selected by the `choose_node_mode` strategy.
  Useful when the failure might be transient.
- `{:all_nodes, positive_number}` (e.g., `{:all_nodes, 3}`) - Retry across
  all available nodes in the cluster. Useful when a node might be down.

For `nodes: :local`, both `:same_node` and `:all_nodes` retry on the same
local machine since there's only one node.

Use `normalize_retry/1` to convert a raw config value to the standard tuple
format. Zero, negative numbers, strings, and other formats are invalid.

## Hooks

The `before_execute` and `after_execute` fields allow you to run custom code
before and/or after a function is executed through the API. Hooks are specified
as MFA tuples:

  - `{module, function}` — Called as `module.function(request, fun_config)` (before)
    or `module.function(request, fun_config, result)` (after).
  - `{module, function, extra_args}` — Extra arguments are appended.

### Before execute

Must return one of:

  - `{:ok, request, fun_config}` — Proceed with (possibly modified) request/config.
  - `{:error, reason}` — Abort execution and return an error response.

### After execute

Must return the (possibly modified) result. Any other return value is ignored and
the original result is preserved.

Hooks emit telemetry events at `[:phoenix_gen_api, :hook, :before|:after, :start|:stop|:exception]`.

## Permission Callback

The `permission_callback` field allows a custom MFA to override the built-in
`check_permission` modes. When set to `{module, function, args}` (or
`{module, function}`), it is called as `apply(module, function, [request | args])`
and must return `true` or `false`. Any other return, exception, or catch is
treated as `false` (denied). When `permission_callback` is set, `check_permission`
is ignored.

## Security Considerations

- MFA tuples are validated to ensure modules are loaded and functions exist
- Node lists are validated to prevent routing to invalid destinations
- Permission modes are checked against available request fields
- Timeout values are bounded to prevent resource exhaustion
- Hook failures in `before_execute` abort the request; hook failures in `after_execute`
  are silently ignored (the original result is preserved)
- Permission callbacks that raise exceptions are treated as denied (fail-closed)

# `t`

```elixir
@type t() :: %PhoenixGenApi.Structs.FunConfig{
  after_execute: {module(), atom()} | {module(), atom(), args :: list()} | nil,
  arg_orders: [String.t()] | :map,
  arg_types: map() | nil,
  before_execute: {module(), atom()} | {module(), atom(), args :: list()} | nil,
  check_permission:
    false | :any_authenticated | {:arg, String.t()} | {:role, list()},
  choose_node_mode:
    :random | :hash | {:hash, String.t()} | :round_robin | {:sticky, String.t()},
  disabled: boolean(),
  hook_timeout: pos_integer(),
  mfa: {module(), function(), args :: list()},
  nodes: [atom()] | {module(), function(), args :: list()} | :local,
  permission_callback: {module(), atom(), args :: list()} | nil,
  request_info: boolean(),
  request_type: String.t(),
  response_type: :sync | :async | :stream | :none,
  retry: {:same_node, number()} | {:all_nodes, number()} | number() | nil,
  service: atom() | String.t(),
  timeout: integer() | :infinity,
  version: String.t()
}
```

# `check_permission!`

Checks if the request has the necessary permissions to be executed.

# `convert_args!`

Validates and converts the request arguments based on the `arg_types` and `arg_orders` configuration.

# `get_node`

Selects a target node for the request based on the `choose_node_mode` strategy.

# `local_service?`

Checks if the service is configured to run locally.

# `normalize_retry`

```elixir
@spec normalize_retry(
  nil
  | number()
  | {:same_node, number()}
  | {:all_nodes, number()}
) ::
  nil | {:same_node, pos_integer()} | {:all_nodes, pos_integer()}
```

Normalizes the retry configuration into a standard tuple format.

- `nil` remains `nil` (no retry)
- A number `n` is converted to `{:all_nodes, n}`
- `{:same_node, n}` and `{:all_nodes, n}` are returned as-is

## Examples

    iex> FunConfig.normalize_retry(nil)
    nil

    iex> FunConfig.normalize_retry(3)
    {:all_nodes, 3}

    iex> FunConfig.normalize_retry({:same_node, 2})
    {:same_node, 2}

    iex> FunConfig.normalize_retry({:all_nodes, 5})
    {:all_nodes, 5}

# `valid?`

Validates the function configuration.

Returns `true` if all configuration fields are valid, `false` otherwise.
Logs detailed error messages for each invalid field.

## Validation Checks

- `request_type` must be a non-empty string
- `service` must not be nil
- `nodes` must be a valid list, MFA tuple, or `:local`
- `choose_node_mode` must be a recognized strategy
- `timeout` must be a positive integer or `:infinity`
- `mfa` must be a valid `{module, function, args}` tuple
- `arg_types` and `arg_orders` must be consistent
- `response_type` must be one of `:sync`, `:async`, `:stream`, or `:none`
- `check_permission` must be a valid permission mode
- `request_info` must be a boolean

# `validate_with_details`

Validates the function configuration and returns detailed error information.

Returns `{:ok, config}` if valid, or `{:error, [error_messages]}` if invalid.

# `version`

```elixir
@spec version(t()) :: String.t() | nil
```

Returns the version of the function configuration.
If the version is not set, is `"0.0.0"` (reserved sentinel), or is empty,
returns `nil` to indicate no version was specified.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
