OP

OpenAPI/Swagger

protocol v3.0

OpenAPI/Swagger

Version: 3.0

Overview

OpenAPI (formerly Swagger) is the API documentation standard used by the Prismatic Platform's auto-introspecting REST API. The platform generates a complete OpenAPI 3.0 specification automatically by scanning all Prismatic facade modules at boot time, mapping Elixir @spec type annotations to JSON Schema, and exposing the result through Swagger UI for interactive API exploration. This approach represents a fundamental shift from traditional API documentation workflows where specifications are authored manually and inevitably drift from the actual implementation.

The Prismatic API's auto-introspection approach means the OpenAPI specification is always synchronized with the actual codebase. When a module's @spec changes, the API documentation updates automatically on the next application restart. This eliminates the common problem of documentation drift and ensures that API consumers always have accurate, up-to-date documentation without any manual authoring. The specification serves as both documentation and a runtime validation contract -- incoming requests are validated against the schema before reaching handler code, preventing malformed data from propagating through the system.

The OpenApiSpex library generates schemas, validates request parameters at runtime, and serves the interactive Swagger UI at /api/swagger-ui, providing developers with a complete API exploration and testing interface. The Prismatic API application on port 4004 is the sole consumer of this OpenAPI infrastructure, serving as the unified gateway through which external systems interact with the platform's capabilities.

Key Features

  • Auto-Introspection: Elixir module scanning discovers all public functions and maps @spec AST to JSON Schema automatically, eliminating manual specification authoring
  • Schema Definition: JSON Schema types for request/response validation, derived directly from Elixir typespecs with support for nested structures, enums, and union types
  • Path Documentation: Endpoint descriptions, parameters, and examples generated from module @doc annotations and function signatures
  • Authentication Schemes: Security scheme documentation covering API keys, JWT tokens, and OAuth flows with per-endpoint authorization requirements
  • Runtime Validation: Request parameters validated against the OpenAPI schema before reaching handler code, providing early rejection of malformed requests
  • Interactive UI: Swagger UI at /api/swagger-ui for exploring and testing every endpoint interactively with authentication support
  • Code Generation: Clients can generate SDKs in any language from the exported specification using OpenAPI Generator or similar tools
  • Versioned Endpoints: API versioning through URL path prefixes enables breaking changes without disrupting existing consumers

Platform Integration

OpenAPI powers the platform's auto-documented REST API. The spec module, scanner, and type mapper work together to produce the complete specification. The architecture follows a three-phase pipeline: discovery, mapping, and serving.

defmodule PrismaticApi.ApiSpec do
  alias OpenApiSpex.{Info, OpenApi, Paths, Server}

  @behaviour OpenApi

  @impl OpenApi
  def spec do
    %OpenApi{
      info: %Info{
        title: "Prismatic Platform API",
        version: "1.0.0",
        description: "Auto-introspecting REST API for the Prismatic Platform"
      },
      servers: [%Server{url: "http://localhost:4004"}],
      paths: Paths.from_router(PrismaticApi.Router)
    }
    |> OpenApiSpex.resolve_schema_modules()
  end
end

defmodule PrismaticApi.Scanner do
  @doc "Discovers all Prismatic facade modules and their public functions"
  def scan_modules do
    :code.all_loaded()
    |> Enum.filter(fn {mod, _} -> prismatic_facade?(mod) end)
    |> Enum.flat_map(fn {mod, _} -> discover_endpoints(mod) end)
    |> Enum.map(&build_endpoint_spec/1)
  end

  defp prismatic_facade?(mod) do
    mod
    |> Atom.to_string()
    |> String.starts_with?("Elixir.Prismatic")
  end

  defp discover_endpoints(mod) do
    case Code.fetch_docs(mod) do
      {:docs_v1, _, _, _, _, _, docs} ->
        Enum.filter(docs, fn {{:function, _, _}, _, _, doc, _} -> doc != :hidden end)

      _ -> []
    end
  end
end

The type mapper converts Elixir typespec AST into OpenAPI JSON Schema definitions, handling the full range of Elixir's type system including maps, lists, tuples, atoms, and custom structs:

defmodule PrismaticApi.TypeMapper do
  @doc "Maps Elixir typespec AST nodes to OpenAPI JSON Schema objects"
  def elixir_to_json_schema({:type, _, :string, []}), do: %{"type" => "string"}
  def elixir_to_json_schema({:type, _, :integer, []}), do: %{"type" => "integer"}
  def elixir_to_json_schema({:type, _, :boolean, []}), do: %{"type" => "boolean"}
  def elixir_to_json_schema({:type, _, :float, []}), do: %{"type" => "number", "format" => "double"}
  def elixir_to_json_schema({:type, _, :list, [inner]}),
    do: %{"type" => "array", "items" => elixir_to_json_schema(inner)}
  def elixir_to_json_schema({:type, _, :map, fields}),
    do: %{"type" => "object", "properties" => map_fields(fields)}
  def elixir_to_json_schema({:type, _, :atom, []}),
    do: %{"type" => "string", "description" => "Elixir atom value"}
  def elixir_to_json_schema({:remote_type, _, [{:atom, _, DateTime}, {:atom, _, :t}, []]}),
    do: %{"type" => "string", "format" => "date-time"}
end

Architecture

The OpenAPI infrastructure occupies a specific position in the platform's request processing pipeline:

LayerComponentResponsibility
DiscoveryPrismaticApi.ScannerBoot-time module scanning, function enumeration, doc extraction
MappingPrismaticApi.TypeMapperElixir typespec AST to JSON Schema conversion
RegistryPrismaticApi.EndpointRegistryETS-cached endpoint metadata for fast lookup
SpecificationPrismaticApi.ApiSpecOpenApiSpex behaviour implementation, spec assembly
ValidationOpenApiSpex.Plug.CastAndValidateRequest parameter validation against schema
DispatchPrismaticApi.DispatchControllerGeneric function dispatch after validation
DocumentationOpenApiSpex.Plug.SwaggerUIInteractive API explorer at /api/swagger-ui

The scanner runs once at application startup and caches results in ETS. Subsequent requests serve the cached specification without re-scanning, ensuring sub-millisecond spec retrieval even with hundreds of discovered endpoints.

Performance Characteristics

The auto-introspection approach introduces no runtime overhead for API consumers because the scanning phase occurs only during application startup.

MetricValueNotes
Boot-time scan duration~200msScanning all Prismatic modules
Spec retrieval latency<1msETS-cached after initial scan
Request validation overhead~0.5msSchema validation per request
Spec JSON size~150KBComplete specification with all endpoints
Discovered endpoints50+Across all Prismatic facade modules
Swagger UI load time<500msIncluding spec fetch and rendering

Configuration

The API routes expose the OpenAPI specification and Swagger UI alongside the generic dispatch endpoint.

# API routes in PrismaticApi.Router
scope "/api" do
  pipe_through [:api]

  get "/openapi", OpenApiSpex.Plug.RenderSpec, []
  get "/swaggerui", OpenApiSpex.Plug.SwaggerUI,
    path: "/api/openapi",
    default_model_expand_depth: 3

  get "/v1/health", PrismaticApi.HealthController, :index
  get "/v1/endpoints", PrismaticApi.EndpointController, :index
  match :*, "/v1/:app/:action", PrismaticApi.DispatchController, :dispatch
end

Environment-specific configuration controls the API server and scanner behavior:

# config/config.exs
config :prismatic_api, PrismaticApi.Endpoint,
  url: [host: "localhost"],
  http: [port: 4004],
  adapter: Bandit.PhoenixAdapter

config :prismatic_api, PrismaticApi.Scanner,
  module_prefix: "Elixir.Prismatic",
  exclude_modules: [PrismaticApi, PrismaticWeb],
  cache_table: :api_endpoint_registry

Best Practices

  • Let introspection generate the spec -- never write OpenAPI YAML manually; the scanner ensures accuracy and eliminates documentation drift entirely
  • Add @doc and @spec to all facade functions -- these are the source material for API documentation; missing typespecs produce incomplete schemas
  • Use OpenApiSpex cast and validate plugs -- validate incoming requests against the schema before handler code executes, providing clear error messages for invalid input
  • Version the API path -- /api/v1/ prefix allows future breaking changes without disrupting existing clients while maintaining backward compatibility
  • Test the spec -- verify that the generated OpenAPI JSON is valid using OpenApiSpex.TestAssertions in your test suite
  • Keep facade functions pure -- functions exposed through the API should accept simple types (strings, integers, maps) that map cleanly to JSON, avoiding complex Elixir-specific types
  • Document error responses -- use @doc metadata to describe possible error conditions so the specification includes error response schemas
  • Monitor endpoint count -- track the number of discovered endpoints across deployments to detect accidental exposure or missing modules

Comparison with Alternatives

FeatureOpenAPI 3.0 (Prismatic)GraphQLgRPCJSON:API
Schema LanguageJSON SchemaSDLProtocol BuffersJSON Schema subset
Auto-IntrospectionYes (Elixir scanning)Requires schema definitionRequires proto filesManual
Interactive ExplorerSwagger UIGraphiQL/PlaygroundBloomRPCNone standard
Runtime ValidationBuilt-in via OpenApiSpexBuilt-inBuilt-inManual
Code Generation50+ language targetsLimitedExcellentLimited
CachingHTTP native (ETags, 304)Complex (POST-based)N/AHTTP native
Learning CurveLow (REST familiarity)MediumHighMedium
Platform UsagePrimary API gatewayVia AbsintheNot usedNot used

The Prismatic Platform chose OpenAPI as the primary API specification because its auto-introspection capability eliminates manual specification authoring, and REST's simplicity reduces the cognitive load for API consumers who may not be familiar with the platform's internals.

  • GraphQL - Alternative API paradigm via Absinthe for flexible queries
  • Phoenix Framework - Web framework hosting the API endpoints and routing
  • Plug - Middleware pipeline for request validation and authentication
  • JOSE - JWT authentication documented in the security schemes
  • Elixir - Source language whose typespecs drive the schema generation
  • ETS - In-memory cache for the endpoint registry
  • prismatic_api - The auto-introspecting REST API gateway on port 4004
  • prismatic_web - The main web application whose facades are exposed through the API
  • prismatic_storage_core - Storage abstractions whose traits are discoverable via the scanner

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL