Current section

Files

Jump to
ash_phoenix_gen_api lib ash_phoenix_gen_api.ex
Raw

lib/ash_phoenix_gen_api.ex

defmodule AshPhoenixGenApi do
@moduledoc """
Ash extension for generating PhoenixGenApi function configurations from Ash resources.
`AshPhoenixGenApi` bridges the Ash Framework and PhoenixGenApi by allowing you to
define PhoenixGenApi endpoints directly in your Ash resource and domain DSLs. It
automatically generates `PhoenixGenApi.Structs.FunConfig` structs from your Ash
actions, including type mappings, argument ordering, and configuration defaults.
## Architecture
The extension consists of two main parts:
### Resource Extension (`AshPhoenixGenApi.Resource`)
Added to Ash resources to define which actions should be exposed as PhoenixGenApi
endpoints. Each action is configured with routing, timeout, permission, and other
settings.
### Domain Extension (`AshPhoenixGenApi.Domain`)
Added to Ash domains to provide domain-level defaults and auto-generate a
"supporter" module that aggregates FunConfigs from all resources in the domain.
The supporter module implements the PhoenixGenApi client config interface
(`get_config/1`, `get_config_version/1`), allowing gateway nodes to pull API
configurations from service nodes.
## Quick Start
### 1. Add the extensions to your resource and domain
# In your resource:
defmodule MyApp.Chat.DirectMessage do
use Ash.Resource,
extensions: [AshPhoenixGenApi.Resource]
gen_api do
service "chat"
nodes {ClusterHelper, :get_nodes, [:chat]}
choose_node_mode :random
timeout 5_000
response_type :async
request_info true
action :send_direct_message do
request_type "send_direct_message"
timeout 10_000
check_permission {:arg, "from_user_id"}
end
action :get_conversation do
timeout 5_000
end
action :create do
# Minimal config — request_type and args are auto-derived
end
end
attributes do
uuid_primary_key :id
attribute :from_user_id, :uuid
attribute :to_user_id, :uuid
attribute :content, :string
attribute :reply_to_id, :uuid
attribute :file_id, :uuid
end
actions do
create :create do
accept [:from_user_id, :to_user_id, :content, :reply_to_id, :file_id]
end
read :read do
primary? true
end
end
end
# In your domain:
defmodule MyApp.Chat do
use Ash.Domain,
extensions: [AshPhoenixGenApi.Domain]
gen_api do
service "chat"
nodes {ClusterHelper, :get_nodes, [:chat]}
choose_node_mode :random
version "0.0.1"
supporter_module MyApp.Chat.GenApiSupporter
end
resources do
resource MyApp.Chat.DirectMessage
resource MyApp.Chat.GroupMessage
end
end
### 2. The supporter module is auto-generated
After compilation, `MyApp.Chat.GenApiSupporter` will be available with:
# Get all FunConfigs (for PhoenixGenApi pull)
MyApp.Chat.GenApiSupporter.fun_configs()
#=> [%PhoenixGenApi.Structs.FunConfig{request_type: "send_direct_message", ...}, ...]
# Get config for remote pull
MyApp.Chat.GenApiSupporter.get_config(:gateway_1)
#=> {:ok, [%PhoenixGenApi.Structs.FunConfig{...}, ...]}
# Get config version
MyApp.Chat.GenApiSupporter.get_config_version(:gateway_1)
#=> {:ok, "0.0.1"}
# Find a specific FunConfig by request_type
MyApp.Chat.GenApiSupporter.get_fun_config("send_direct_message")
#=> %PhoenixGenApi.Structs.FunConfig{request_type: "send_direct_message", ...}
# List all request types
MyApp.Chat.GenApiSupporter.list_request_types()
#=> ["send_direct_message", "get_conversation", ...]
### 3. Configure the gateway node
On the Phoenix gateway node, configure `phoenix_gen_api` in `config.exs`:
config :phoenix_gen_api, :gen_api,
service_configs: [
%{
service: "chat",
nodes: {ClusterHelper, :get_nodes, [:chat]},
module: MyApp.Chat.GenApiSupporter,
function: :get_config,
args: [:gateway_1]
}
]
## Type Mapping
Ash types are automatically mapped to PhoenixGenApi argument types:
| Ash Type | PhoenixGenApi Type |
|----------|-------------------|
| `:string`, `:uuid`, `:boolean`, `:date`, etc. | `:string` |
| `:integer`, `:float`, `:decimal` | `:num` |
| `{:array, :string}`, `{:array, :uuid}` | `{:list_string, max_items, max_item_length}` |
| `{:array, :integer}`, `{:array, :float}` | `{:list_num, max_items}` |
See `AshPhoenixGenApi.TypeMapper` for the complete mapping table.
## Resolution Order
Configuration values are resolved in this order (highest priority first):
1. **Action-level explicit config** — e.g., `action :foo do timeout 10_000 end`
2. **Resource section-level defaults** — e.g., `gen_api do timeout 5_000 end`
3. **Domain section-level defaults** — e.g., `gen_api do timeout 5_000 end`
4. **Built-in defaults** — e.g., timeout defaults to `5000`
## Modules
- `AshPhoenixGenApi.Resource` — Resource-level DSL extension
- `AshPhoenixGenApi.Resource.Info` — Resource introspection helpers
- `AshPhoenixGenApi.Resource.ActionConfig` — Action configuration struct
- `AshPhoenixGenApi.Domain` — Domain-level DSL extension
- `AshPhoenixGenApi.Domain.Info` — Domain introspection helpers
- `AshPhoenixGenApi.TypeMapper` — Ash type to PhoenixGenApi type mapping
- `AshPhoenixGenApi.Transformers.DefineFunConfigs` — Resource transformer
- `AshPhoenixGenApi.Transformers.DefineDomainSupporter` — Domain transformer
- `AshPhoenixGenApi.Verifiers.VerifyActionConfigs` — Resource verifier
- `AshPhoenixGenApi.Verifiers.VerifyDomainConfig` — Domain verifier
"""
@doc """
Lists all modules that are part of the AshPhoenixGenApi extension.
Returns a list of module atoms for the resource extension, domain extension,
and their supporting modules.
## Examples
iex> AshPhoenixGenApi.modules()
[
AshPhoenixGenApi.Resource,
AshPhoenixGenApi.Resource.Info,
AshPhoenixGenApi.Resource.ActionConfig,
AshPhoenixGenApi.Domain,
AshPhoenixGenApi.Domain.Info,
AshPhoenixGenApi.TypeMapper,
...
]
"""
@spec modules() :: [module()]
def modules do
[
AshPhoenixGenApi.Resource,
AshPhoenixGenApi.Resource.Info,
AshPhoenixGenApi.Resource.ActionConfig,
AshPhoenixGenApi.Domain,
AshPhoenixGenApi.Domain.Info,
AshPhoenixGenApi.TypeMapper,
AshPhoenixGenApi.Transformers.DefineFunConfigs,
AshPhoenixGenApi.Transformers.DefineDomainSupporter,
AshPhoenixGenApi.Verifiers.VerifyActionConfigs,
AshPhoenixGenApi.Verifiers.VerifyDomainConfig
]
end
@doc """
Returns the list of Spark DSL extensions provided by AshPhoenixGenApi.
These are the extensions you add to your Ash resources and domains:
- `AshPhoenixGenApi.Resource` — Add to Ash resources
- `AshPhoenixGenApi.Domain` — Add to Ash domains
## Examples
iex> AshPhoenixGenApi.extensions()
[AshPhoenixGenApi.Resource, AshPhoenixGenApi.Domain]
"""
@spec extensions() :: [module()]
def extensions do
[
AshPhoenixGenApi.Resource,
AshPhoenixGenApi.Domain
]
end
@doc """
Returns the built-in defaults for the gen_api DSL section.
These defaults are used when no explicit value is provided at any level:
- `timeout``5000` (5 seconds)
- `response_type``:async`
- `request_info``true`
- `check_permission``false`
- `choose_node_mode``:random`
- `nodes``:local`
- `version``"0.0.1"`
- `retry``nil` (no retry)
"""
@spec defaults() :: map()
def defaults do
%{
timeout: 5_000,
response_type: :async,
request_info: true,
check_permission: false,
choose_node_mode: :random,
nodes: :local,
version: "0.0.1",
retry: nil
}
end
@doc """
Returns the version of the AshPhoenixGenApi library.
"""
@spec version() :: String.t()
def version do
unquote(Mix.Project.config()[:version] || "0.1.0")
end
end