AshOaskit Usage Rules
View SourceOpenAPI specification generator for Ash Framework domains. Supports OpenAPI 3.0 and 3.1.
Spec Modules (preferred)
# Define a cached, oaskit-native spec module
defmodule MyAppWeb.ApiSpec do
use AshOaskit,
domains: [MyApp.Blog],
title: "My API",
api_version: "1.0.0"
end
# Customize the generated spec (result is cached)
defmodule MyAppWeb.ApiSpec do
use AshOaskit, domains: [MyApp.Blog]
@impl AshOaskit.Spec
def modify_spec(spec) do
put_in(spec, ["components", "securitySchemes"], %{
"bearerAuth" => %{"type" => "http", "scheme" => "bearer"}
})
end
end
# Serve it (Phoenix or Plug.Router) with optional Redoc UI
use AshOaskit.Router,
spec: MyAppWeb.ApiSpec,
open_api: "/openapi",
redoc: "/redoc"
# Dual version: two spec modules
use AshOaskit.Router,
spec: [{"3.1", MyAppWeb.ApiSpecV31}, {"3.0", MyAppWeb.ApiSpecV30}],
open_api: "/openapi"
# Export the exact served spec
# mix openapi.dump MyAppWeb.ApiSpec --pretty -o openapi.jsonRules:
- The spec is cached in
:persistent_term; disable in dev withconfig :ash_oaskit, cache_specs: false(or per module withcache: false). - One spec module = one OpenAPI version; define two modules for dual-version output.
- Only
public? trueattributes/calculations/aggregates/relationships appear in specs. - Request body schemas follow the routed action's
acceptlist plus its public arguments.
Programmatic API
# Generate spec (defaults to 3.1)
AshOaskit.spec(domains: [MyApp.Blog])
AshOaskit.spec_30(domains: [MyApp.Blog]) # Force 3.0
AshOaskit.spec_31(domains: [MyApp.Blog]) # Force 3.1
# Full options
AshOaskit.spec(
domains: [MyApp.Blog, MyApp.Accounts],
version: "3.1",
title: "My API",
api_version: "2.0.0",
description: "API description",
servers: [%{"url" => "https://api.example.com"}],
contact: %{"name" => "Support", "email" => "api@example.com"},
license: %{"name" => "MIT"},
security: [%{"bearerAuth" => []}]
)CLI
# Preferred once a spec module exists:
mix openapi.dump MyAppWeb.ApiSpec --pretty -o openapi.json
mix ash_oaskit.generate -d MyApp.Blog -o openapi.json
mix ash_oaskit.generate -d MyApp.Blog,MyApp.Accounts -v 3.0 -o openapi.yaml -f yaml
mix ash_oaskit.generate --domains MyApp.Blog --title "My API" --api-version 1.0.0
Router Macro
# Preferred: spec module mode (cached, supports redoc:)
use AshOaskit.Router,
spec: MyAppWeb.ApiSpec,
open_api: "/openapi",
redoc: "/redoc"
# Plug.Router — same options, place before catch-all `match _`
# DEPRECATED (regenerates spec per request, warns at compile time):
use AshOaskit.Router,
domains: [MyApp.Blog],
open_api: "/openapi",
title: "My API"Domain Setup
defmodule MyApp.Blog do
use Ash.Domain, extensions: [AshJsonApi.Domain]
resources do
resource MyApp.Blog.Post
end
json_api do
routes do
base_route "/posts", MyApp.Blog.Post do
get :read
index :read
post :create
patch :update
delete :destroy
end
end
end
endResource Setup
defmodule MyApp.Blog.Post do
use Ash.Resource, domain: MyApp.Blog, extensions: [AshJsonApi.Resource]
json_api do
type "post"
end
attributes do
uuid_primary_key :id
# public? true is REQUIRED for fields to appear in the spec
attribute :title, :string,
public?: true,
allow_nil?: false,
constraints: [min_length: 1, max_length: 255]
attribute :body, :string, public?: true, description: "Post content"
attribute :status, :atom, public?: true, constraints: [one_of: [:draft, :published]], default: :draft
end
actions do
defaults [:read, :destroy]
create :create, accept: [:title, :body, :status]
update :update, accept: [:title, :body, :status]
end
endType Mapping
| Ash Type | JSON Schema | Format |
|---|---|---|
:string, :ci_string, :atom, :module | string | - |
:integer | integer | - |
:float | number | float |
:decimal | number | double |
:boolean | boolean | - |
:date | string | date |
:time, :time_usec | string | time |
:datetime, :utc_datetime, :utc_datetime_usec, :naive_datetime | string | date-time |
:duration | string | duration |
:uuid, :uuid_v7 | string | uuid |
:binary | string | binary |
:url_encoded_binary, Ash.Type.File | string | byte |
:map, :keyword, :tuple | object | - |
:vector | array of number | - |
:term, :function | {} (any) | - |
{:array, type} | array | - |
Ash.Type.Enum implementors | string + enum | - |
Ash.Type.NewType wrappers | (subtype schema) | - |
Constraint Mapping
| Ash | JSON Schema |
|---|---|
:min_length | minLength |
:max_length | maxLength |
:min | minimum |
:max | maximum |
:match | pattern |
:one_of | enum |
Version Differences
- 3.0:
nullable: truefor nullable fields - 3.1:
type: ["string", "null"]for nullable fields (JSON Schema 2020-12)
Module Structure
lib/ash_oaskit.ex # Main API (spec, validate)
lib/ash_oaskit/
open_api.ex # Version routing
spec.ex # Spec module behaviour (use AshOaskit)
open_api_controller.ex # Controller behaviour
phoenix_introspection.ex # Phoenix router extraction
router.ex # Router macro
spec_builder.ex # SpecBuilder behaviour
spec_builder/default.ex # Default SpecBuilder
core/
config.ex # AshJsonApi DSL reader
route_gathering.ex # Domain + resource route collection
path_utils.ex # Path param conversion
schema_ref.ex # $ref object builder
spec_modifier.ex # Post-generation hooks
type_mapper.ex # Ash → JSON Schema types
generators/
generator.ex # Main orchestrator
info_builder.ex # Info, servers, tags
path_builder.ex # Paths and operations
shared.ex # Entry point (both versions)
v30.ex # OpenAPI 3.0 entry
v31.ex # OpenAPI 3.1 entry
parameters/
filter_builder.ex # Filter query params
query_parameters.ex # page, fields, include, sort
sort_builder.ex # Sort param schemas
resources/
included_resources.ex # Included array schemas
resource_identifier.ex # Type+id linkage
tag_builder.ex # Operation grouping tags
responses/
error_schemas.ex # JSON:API error responses
response_links.ex # Self, related, pagination links
response_meta.ex # Pagination meta schemas
routes/
relationship_routes.ex # Relationship endpoints
route_operations.ex # Operation object builder
route_responses.ex # Response schema builder
schemas/
embedded_schemas.ex # Embedded resource detection
nullable.ex # Version-aware nullable
property_builders.ex # Attrs/calcs/aggregates → schema
relationship_schemas.ex # Relationship linkage schemas
resource_schemas.ex # Resource schema generation
schema_builder.ex # Accumulator + cycle detection
support/
controller.ex # Phoenix controller
multipart_support.ex # File upload schemas
security.ex # Security schemes
router/
plug.ex # Plug for serving specs
mix/tasks/
ash_oaskit.generate.ex # CLI: mix ash_oaskit.generate
ash_oaskit.install.ex # CLI: mix ash_oaskit.installConfiguration
config :ash_oaskit,
version: "3.1",
title: "My API",
api_version: "1.0.0"
# Dev only: regenerate the spec on code reload
config :ash_oaskit, cache_specs: falseTesting
test "generates valid spec" do
spec = AshOaskit.spec(domains: [MyApp.Blog])
assert spec["openapi"] == "3.1.0"
assert is_map(spec["paths"])
assert is_map(spec["components"]["schemas"])
endDevelopment
mix deps.get && mix test && mix check