AshOaskit generates OpenAPI 3.0 and 3.1 specifications from Ash Framework domains. It introspects Ash resources for schemas and reads AshJsonApi configurations for routes.
AshOaskit.spec(opts)
│
▼
OpenApi.spec/1 ──► version routing
│
┌───┴───┐
▼ ▼
V31 V30
│ │
└───┬───┘
▼
Shared.generate ──► Generator.generate
│
┌────┼────────────┐
▼ ▼ ▼
InfoBuilder PathBuilder SchemaBuilder
│ │ │
│ │ ┌────┼──────────┐
│ │ ▼ ▼ ▼
│ │ ResourceSchemas RelationshipSchemas
│ │ │ │
│ │ PropertyBuilders EmbeddedSchemas
▼ ▼ ▼
Info + Paths + Components
Tags Operations (Schemas)
Servers
lib/ash_oaskit.ex # Main public API (spec, validate)
lib/ash_oaskit/
open_api.ex # Version routing (spec → V30/V31)
open_api_controller.ex # Behaviour for Phoenix controllers
phoenix_introspection.ex # Extract routes from Phoenix router
router.ex # Router macro for serving specs
spec_builder.ex # SpecBuilder behaviour
spec_builder/default.ex # Default SpecBuilder implementation
core/
config.ex # Config — AshJsonApi DSL reader
path_utils.ex # Core.PathUtils — path param conversion
schema_ref.ex # Core.SchemaRef — $ref object builder
spec_modifier.ex # SpecModifier — post-generation hooks
type_mapper.ex # TypeMapper — Ash → JSON Schema types
generators/
generator.ex # Generator — orchestrates all builders
info_builder.ex # InfoBuilder — info, servers, tags
path_builder.ex # PathBuilder — paths and operations
shared.ex # Shared — entry point for both versions
v30.ex # V30 — OpenAPI 3.0 entry point
v31.ex # V31 — OpenAPI 3.1 entry point
parameters/
filter_builder.ex # FilterBuilder — filter query params
query_parameters.ex # QueryParameters — page, fields, include, sort
sort_builder.ex # SortBuilder — sort param schemas
resources/
included_resources.ex # IncludedResources — included array schemas
resource_identifier.ex # ResourceIdentifier — type+id linkage
tag_builder.ex # TagBuilder — operation grouping tags
responses/
error_schemas.ex # ErrorSchemas — JSON:API error responses
response_links.ex # ResponseLinks — self, related, pagination links
response_meta.ex # ResponseMeta — pagination meta schemas
routes/
relationship_routes.ex # RelationshipRoutes — relationship endpoints
route_operations.ex # RouteOperations — operation object builder
route_responses.ex # RouteResponses — response schema builder
schemas/
embedded_schemas.ex # EmbeddedSchemas — embedded resource detection
nullable.ex # Schemas.Nullable — version-aware nullable
property_builders.ex # PropertyBuilders — attrs/calcs/aggregates
relationship_schemas.ex # RelationshipSchemas — relationship linkage
resource_schemas.ex # ResourceSchemas — resource schema generation
schema_builder.ex # SchemaBuilder — accumulator with cycle detection
support/
controller.ex # Controller — Phoenix controller for specs
multipart_support.ex # MultipartSupport — file upload schemas
security.ex # Security — security scheme generation
router/
plug.ex # Router.Plug — Plug for serving specs
mix/tasks/
ash_oaskit.generate.ex # CLI: mix ash_oaskit.generate
ash_oaskit.install.ex # CLI: mix ash_oaskit.install
Tests mirror the lib/ directory structure 1:1. Each source module has a corresponding _test.exs file in the matching path. Additional cross-cutting integration tests live at the test/ash_oaskit/ root:
advanced_types_test.exs— Cross-cutting TypeMapper coveragecalculations_aggregates_test.exs— PropertyBuilders integrationcomponents_test.exs— Full components objectformat_strings_test.exs— JSON Schema format stringsintegration_test.exs— End-to-end spec generationcross_version_contamination_test.exs— Cross-version feature leakage checksopen_api_test.exs— Core OpenApi module testsopenapi_30_compliance_test.exs— OpenAPI 3.0 spec compliance checksopenapi_31_compliance_test.exs— OpenAPI 3.1 spec compliance checksparameter_styles_test.exs— Parameter serializationphoenix_introspection_test.exs— Phoenix router extractionresponse_codes_test.exs— HTTP response codesrouter_test.exs— Router macro testsspec_builder_test.exs— SpecBuilder behaviour testswebhooks_test.exs— OpenAPI 3.1 webhooks
- 3.0:
nullable: truefor optional fields - 3.1:
type: ["string", "null"]for optional fields
Handled by Schemas.Nullable (atom-key schemas) and TypeMapper (string-key schemas).
All type conversions go through TypeMapper. When adding new types:
- Add to
@simple_type_schemasmap for basic types - Handle in
complex_type_schema/1for compound types - Add to
@ash_type_to_atomfor Ash.Type.* module normalization
PropertyBuilders also maintains a parallel @type_to_schema_map for calculation/aggregate type resolution.
SchemaBuilder uses an accumulator pattern:
mark_seen/2prevents infinite recursion on self-referential typesadd_schema/3deduplicates (first definition wins)- Separate tracking for input vs output schemas
- Sub-modules:
ResourceSchemas,RelationshipSchemas,EmbeddedSchemas,PropertyBuilders
Core.SchemaRef builds $ref objects with string keys ("$ref" not :$ref). This is required because the Oaskit normalizer detects references by checking for the "$ref" string key.
Core.PathUtils provides shared functions for Phoenix-to-OpenAPI path conversion (:id → {id}), parameter extraction, and humanization. Used by PathBuilder, PhoenixIntrospection, and RouteOperations.
SpecModifier enables post-generation customization via function callbacks or MFA tuples. Used for adding security schemes, custom headers, webhooks, deprecation markers, etc.
SpecBuilder defines a behaviour for custom spec generation. The default implementation delegates to AshOaskit.spec/1. Custom implementations can add security schemes, feature flags, or domain filtering.
AshOaskit.Router provides a use macro for both Phoenix Router and Plug.Router that auto-generates versioned spec endpoints. Automatically detects the router type and generates appropriate routes inline. Supports custom SpecBuilder, format selection (JSON/YAML), and Phoenix router introspection.
- Test resources defined in
test/support/test_resources.ex - Relationship resources in
test/support/relationship_resources.ex - Use
AshOaskit.Test.Blogfor full feature testing - Use
AshOaskit.Test.SimpleDomain/AshOaskit.Test.EdgeCaseDomainfor edge cases - Warning-producing tests use
ExUnit.CaptureLogto keep output clean
- Update
TypeMapper(lib/ash_oaskit/core/type_mapper.ex) with the mapping - Update
PropertyBuilders(lib/ash_oaskit/schemas/property_builders.ex) if needed for calculation/aggregate support - Add tests in
test/ash_oaskit/core/type_mapper_test.exs - Update type mapping table in README.md and usage-rules.md
- Implement in
Generatoror the appropriate sub-module - Handle version differences via
Schemas.Nullableor direct version branching - Add tests for both versions (use existing V30/V31 test patterns)
- Update the appropriate sub-module under
schemas/ - Ensure cycle detection still works (
SchemaBuilder.mark_seen/2/seen?/2) - Test with
embedded_schemas_test.exsandrelationship_schemas_test.exs
- Add to
SpecModifier(lib/ash_oaskit/core/spec_modifier.ex) - Add catch-all with
Logger.warningfor invalid inputs - Add tests in
test/ash_oaskit/core/spec_modifier_test.exs
- For controller features: update
Controller(lib/ash_oaskit/support/controller.ex) - For router features: update
Router(lib/ash_oaskit/router.ex) andRouter.Plug - For controller introspection: update
PhoenixIntrospectionandOpenApiController
All code must pass:
mix check # Runs all 10 tools:mix compile --warnings-as-errorsmix credo --strictmix dialyzermix doctormix format --check-formattedmix hex.auditmix deps.auditmix sobelowmix testmix deps.unlock --check-unused
All commits must follow the Conventional Commits specification. git_ops parses these to auto-generate CHANGELOG.md and determine version bumps.
Format: type(optional scope): description
Do not add Co-Authored-By or any AI/Claude attribution to commit messages.
| Type | Version bump | Changelog |
|---|---|---|
feat: |
minor | "Features" |
fix: |
patch | "Bug Fixes" |
feat!: / fix!: / BREAKING CHANGE: |
major | shown |
chore:, docs:, ci:, refactor:, style:, test:, build: |
none | hidden |
mix git_ops.release— updates changelog, bumps version in mix.exs and README.md, commits, and tagsgit push --follow-tags— pushes commit and tag- CI (
publish.yml) triggers onv*tag → runs checks →mix hex.publish
- Do not modify generated specs after generation (use SpecModifier instead)
- Do not assume AshJsonApi is always present (graceful degradation)
- Do not add dependencies without discussion
- Use
Core.SchemaRef.schema_ref/1for all$refconstruction (string keys required) - Use
Schemas.Nullablefor nullable handling (not inline version branching) - Use
Core.PathUtilsfor path parameter operations (not inline regex) - Use
Logger.warningin catch-all clauses for unknown inputs - Use
ExUnit.CaptureLogin tests that trigger Logger.warning - Keep test files mirroring lib/ directory structure