AshOaskit (AshOasKit v0.2.0)

View Source

OpenAPI specification generator for Ash Framework domains.

AshOaskit automatically generates OpenAPI 3.0 and 3.1 specifications from your Ash domains by introspecting resources, attributes, actions, and AshJsonApi routes.

Define a spec module with use AshOaskit to get a cached, oaskit-native spec that plugs into the whole Oaskit toolchain:

defmodule MyAppWeb.ApiSpec do
  use AshOaskit,
    domains: [MyApp.Blog, MyApp.Accounts],
    title: "My API",
    api_version: "1.0.0"
end

The module implements the Oaskit behaviour, which means:

# Serve it (JSON + Redoc UI) straight from your Phoenix router
get "/openapi.json", Oaskit.SpecController, spec: MyAppWeb.ApiSpec
get "/redoc", Oaskit.SpecController, redoc: "/openapi.json"

# Or via the AshOaskit router macro
use AshOaskit.Router,
  spec: MyAppWeb.ApiSpec,
  open_api: "/openapi",
  redoc: "/redoc"

# Export from the command line
mix openapi.dump MyAppWeb.ApiSpec

# Validate requests in hand-written controllers documented
# alongside the Ash routes
plug Oaskit.Plugs.SpecProvider, spec: MyAppWeb.ApiSpec

The generated spec is cached in :persistent_term — the Ash domain walk runs once, not on every request. See AshOaskit.Spec for all options, the modify_spec/1 customization callback, and cache controls.

Other Levels of Customization

1. Router Macro (Simple)

For quick setup with standard endpoints:

# In your Phoenix router
scope "/api" do
  pipe_through :api

  use AshOaskit.Router,
    domains: [MyApp.Blog, MyApp.Accounts],
    open_api: "/docs/openapi",
    title: "My API",
    version: "1.0.0"
end

This automatically generates routes for both OpenAPI 3.0 and 3.1:

GET /api/docs/openapi.json      -> Default (3.1) spec
GET /api/docs/openapi/3.0.json  -> OpenAPI 3.0 spec
GET /api/docs/openapi/3.1.json  -> OpenAPI 3.1 spec

2. Router Macro + Custom SpecBuilder

For customization (security schemes, feature flags, domain filtering):

use AshOaskit.Router,
  spec_builder: MyApp.OpenApi.SpecBuilder,
  domains: [MyApp.Blog],
  open_api: "/openapi",
  title: "My API"

Where MyApp.OpenApi.SpecBuilder implements AshOaskit.SpecBuilder:

defmodule MyApp.OpenApi.SpecBuilder do
  @behaviour AshOaskit.SpecBuilder

  @impl true
  def spec(openapi_version, opts) do
    AshOaskit.spec(
      domains: opts[:domains],
      version: openapi_version,
      title: opts[:title]
    )
    |> add_security_schemes()
    |> add_feature_flags()
  end

  defp add_security_schemes(spec) do
    put_in(spec, ["components", "securitySchemes"], %{
      "bearerAuth" => %{
        "type" => "http",
        "scheme" => "bearer",
        "bearerFormat" => "JWT"
      }
    })
  end

  defp add_feature_flags(spec) do
    Map.put(spec, "x-features", %{"beta" => true})
  end
end

3. Programmatic API (Advanced)

For complete control over routing and serving:

# Generate spec programmatically in your own controller
spec = AshOaskit.spec(
  domains: [MyApp.Blog],
  version: "3.1",
  title: "My API"
)
|> add_custom_processing()

This is an escape hatch for edge cases not covered by the Router macro.

When to Use Which

Use CaseApproach
Standard API documentationSpec module (use AshOaskit)
Custom security schemesSpec module + modify_spec/1
Feature flags / extensionsSpec module + modify_spec/1
Redoc UI / request validation / mix openapi.dumpSpec module
Version-specific domainsTwo spec modules
Non-standard routingProgrammatic API
Complete custom controlProgrammatic API

Architecture Overview


   Ash Domain   
   (Resources)  

        
        
      
    Config        TypeMapper    
 (metadata)           (Ash -> JSON)   
      
                               
                               
      
 SchemaBuilder   FilterBuilder  
 (components)          SortBuilder    
        QueryParams    
                      
        
      
   Generator     ResponseLinks  
  (v30/v31)            ResponseMeta   
        ErrorSchemas   
                      
        

  OpenAPI Spec 
  (JSON/YAML)  

Features

  • Dual Version Support - Generate OpenAPI 3.0 or 3.1 specifications
  • Automatic Schema Generation - Extracts schemas from Ash resource attributes
  • AshJsonApi Integration - Builds paths from AshJsonApi routes when available
  • JSON:API Compliance - Generates proper JSON:API document structures
  • Type Mapping - Comprehensive Ash type to JSON Schema conversion
  • Filter Support - Generates filter parameter schemas from resource filters
  • Sort Support - Generates sort parameter schemas from resource sorts
  • Pagination - Handles offset, keyset, and cursor pagination styles
  • Relationships - Proper handling of belongs_to, has_many, has_one
  • Router Macro - Quick setup for serving specs in Phoenix
  • SpecBuilder Behaviour - Customizable spec generation via behaviour
  • Plug Controller - Flexible controller for custom configurations
  • Mix Task - Generate spec files from the command line

Quick Start

# Generate OpenAPI 3.1 spec (default)
spec = AshOaskit.spec(domains: [MyApp.Blog])

# Generate OpenAPI 3.0 spec
spec = AshOaskit.spec_30(domains: [MyApp.Blog])

# With full options
spec = AshOaskit.spec(
  domains: [MyApp.Blog, MyApp.Accounts],
  title: "My API",
  api_version: "2.0.0",
  description: "Blog and Accounts API",
  servers: ["https://api.example.com"]
)

Options

All functions accept these common options:

  • :domains - List of Ash domains to include (required)
  • :version - OpenAPI version: "3.0" or "3.1" (default: "3.1")
  • :title - API title (default: "API")
  • :api_version - API version string (default: "1.0.0")
  • :description - API description
  • :servers - List of server URLs or server objects
  • :contact - Contact information map with :name, :email, :url
  • :license - License information map with :name, :url
  • :terms_of_service - Terms of service URL
  • :security - Security requirements list
  • :external_docs - External documentation map

Configuration

Set defaults in your application config:

config :ash_oaskit,
  version: "3.1",
  title: "My API",
  api_version: "1.0.0",
  domains: [MyApp.Blog, MyApp.Accounts]

Mix Task

Generate spec files from the command line:

# Generate JSON spec
mix ash_oaskit.generate --domains MyApp.Blog --output openapi.json

# Generate YAML spec with options
mix ash_oaskit.generate \
  --domains MyApp.Blog,MyApp.Accounts \
  --output openapi.yaml \
  --title "My API" \
  --version 3.0

Module Overview

ModulePurpose
AshOaskit.SpecSpec module behaviour and runtime (use AshOaskit)
AshOaskit.RouterRouter macro for quick Phoenix integration
AshOaskit.SpecBuilderBehaviour for custom spec generation
AshOaskit.SpecBuilder.DefaultDefault SpecBuilder implementation
AshOaskit.ControllerPlug controller for serving specs
AshOaskit.OpenApiMain entry point for spec generation
AshOaskit.ConfigConfiguration and domain introspection
AshOaskit.TypeMapperAsh type to JSON Schema mapping

See individual module documentation for detailed information.

Summary

Functions

Defines an oaskit-compatible spec module from Ash domains.

Generate an OpenAPI specification for the given domains.

Generate an OpenAPI 3.0 specification.

Generate an OpenAPI 3.1 specification.

Validate an OpenAPI specification through Oaskit.

Validate an OpenAPI specification through Oaskit.

Functions

__using__(opts)

(macro)

Defines an oaskit-compatible spec module from Ash domains.

Implements the Oaskit behaviour with a spec/0 that generates (and caches) the OpenAPI spec for the configured domains, plus a default passthrough AshOaskit.Spec.modify_spec/1. Both are overridable, as are Oaskit's cache/1, cache_variant/0, and jsv_opts/0.

See AshOaskit.Spec for the full option reference and examples.

Examples

defmodule MyAppWeb.ApiSpec do
  use AshOaskit,
    domains: [MyApp.Blog],
    title: "My API",
    api_version: "1.0.0"
end

MyAppWeb.ApiSpec.spec()
# => %{"openapi" => "3.1.0", ...}

spec(opts)

Generate an OpenAPI specification for the given domains.

This is the main entry point for generating OpenAPI specifications. It delegates to AshOaskit.OpenApi.spec/1 and supports both OpenAPI 3.0 and 3.1 versions.

Options

  • :domains - List of Ash domains to include (required)
  • :version - OpenAPI version: "3.0" or "3.1" (default: "3.1")
  • :title - API title (default: "API")
  • :api_version - API version string (default: "1.0.0")
  • :servers - List of server URLs
  • :description - API description

Examples

iex> spec = AshOaskit.spec(domains: [AshOaskit.Test.Blog])
...> spec["openapi"]
"3.1.0"

iex> spec = AshOaskit.spec(domains: [AshOaskit.Test.Blog], version: "3.0")
...> spec["openapi"]
"3.0.3"

iex> spec = AshOaskit.spec(domains: [AshOaskit.Test.Blog], title: "My API")
...> spec["info"]["title"]
"My API"

Return Value

Returns a map representing the complete OpenAPI specification that can be encoded to JSON or YAML.

spec_30(opts)

Generate an OpenAPI 3.0 specification.

Convenience function that calls spec/1 with version: "3.0".

OpenAPI 3.0 uses nullable: true for optional fields and has some differences in how types are represented compared to 3.1.

Examples

iex> spec = AshOaskit.spec_30(domains: [AshOaskit.Test.Blog])
...> spec["openapi"]
"3.0.3"

See spec/1 for full options documentation.

spec_31(opts)

Generate an OpenAPI 3.1 specification.

Convenience function that calls spec/1 with version: "3.1".

OpenAPI 3.1 aligns more closely with JSON Schema and uses type: ["string", "null"] for nullable fields.

Examples

iex> spec = AshOaskit.spec_31(domains: [AshOaskit.Test.Blog])
...> spec["openapi"]
"3.1.0"

See spec/1 for full options documentation.

validate(spec)

Validate an OpenAPI specification through Oaskit.

Returns {:ok, %Oaskit.Spec.OpenAPI{}} on success or {:error, error} on failure.

Examples

spec = AshOaskit.spec(domains: [MyApp.Blog])
{:ok, validated} = AshOaskit.validate(spec)

validate!(spec)

Validate an OpenAPI specification through Oaskit.

Returns %Oaskit.Spec.OpenAPI{} on success or raises on failure.

Examples

spec = AshOaskit.spec(domains: [MyApp.Blog])
validated = AshOaskit.validate!(spec)