oaspec

CI Integration Tests

Not fully supporting the entire OpenAPI 3.x specification, oaspec can only perform limited code generation at this stage. Support will be expanded incrementally.

Generate strongly typed Gleam code from OpenAPI 3.x specifications.

Install

From GitHub Release (recommended)

Download the oaspec escript binary from the Releases page. Requires Erlang/OTP 27+ runtime.

# Download (replace URL with the latest release)
curl -fSL -o oaspec https://github.com/nao1215/oaspec/releases/latest/download/oaspec
chmod +x oaspec
sudo mv oaspec /usr/local/bin/

From source

Requires Gleam 1.15+, Erlang/OTP 27+, and rebar3.

git clone https://github.com/nao1215/oaspec.git
cd oaspec
gleam deps download
gleam run -m gleescript    # produces ./oaspec escript binary
sudo mv oaspec /usr/local/bin/

Usage

1. Create a config file

oaspec init

This creates oaspec.yaml with a commented template. Edit it for your project:

input: openapi.yaml
package: my_api
output:
  dir: ./gen          # base directory (default: ./gen)

Generated code is placed at <dir>/<package> and <dir>/<package>_client. To use the generated code in your Gleam project, copy or symlink the output into src/.

FieldRequiredDefaultDescription
inputyes-Path to OpenAPI 3.x spec (YAML or JSON)
packagenoapiGleam module namespace prefix
modenobothGeneration mode: server, client, or both
output.dirno./genBase output directory
output.serverno<dir>/<package>Server code output (overrides dir-based default)
output.clientno<dir>/<package>_clientClient code output (overrides dir-based default)

The directory basename must match package so that Gleam imports (import my_api/types) resolve correctly. The CLI --output flag works the same as output.dir in the config file.

2. Run the generator

oaspec generate --config=oaspec.yaml

Options:

--config=<path>   Path to config file (default: ./oaspec.yaml)
--mode=<mode>     server, client, or both (default: both)
--output=<path>   Override output base directory

When developing oaspec itself, you can also run via gleam run -- generate --config=oaspec.yaml.

3. Generated output

gen/my_api/                 # server (package = "my_api")
  types.gleam               # Domain model types
  request_types.gleam       # Request parameter types
  response_types.gleam      # Response types (tagged unions by status code)
  decode.gleam              # JSON decoders
  encode.gleam              # JSON encoders
  middleware.gleam           # Middleware types and utilities
  handlers.gleam            # Handler stubs (TODO placeholders)
  router.gleam              # Route dispatcher skeleton

gen/my_api_client/          # client
  types.gleam               # Same domain types
  decode.gleam              # Same decoders
  encode.gleam              # Same encoders
  middleware.gleam           # Same middleware (with retry)
  client.gleam              # HTTP client functions
  request_types.gleam
  response_types.gleam

Generated code examples

Given a Petstore OpenAPI spec, oaspec generates:

Types

/// A pet in the store
pub type Pet {
  Pet(
    id: Int,
    name: String,
    status: PetStatus,
    tag: Option(String)
  )
}

/// The status of a pet in the store
pub type PetStatus {
  PetStatusAvailable
  PetStatusPending
  PetStatusSold
}

Server handlers

/// List all pets
pub fn list_pets(req: request_types.ListPetsRequest) -> response_types.ListPetsResponse {
  let _ = req
  // TODO: Implement list_pets
  todo
}

Client SDK

pub fn get_pet(config: ClientConfig, pet_id: Int) -> Result(ClientResponse, ClientError) {
  let path = "/pets/{petId}"
  let path = string.replace(path, "{petId}", int.to_string(pet_id))
  let assert Ok(req) = request.to(config.base_url <> path)
  let req = request.set_method(req, http.Get)
  config.send(req)
}

Middleware

pub type Handler(req, res) =
  fn(req) -> Result(res, MiddlewareError)

pub type Middleware(req, res) =
  fn(Handler(req, res)) -> Handler(req, res)

pub fn compose(first: Middleware(req, res), second: Middleware(req, res)) -> Middleware(req, res)
pub fn apply(middlewares: List(Middleware(req, res)), handler: Handler(req, res)) -> Handler(req, res)
pub fn retry(max_retries: Int) -> Middleware(req, res)

OpenAPI support

Supported

Explicitly unsupported (generator exits with error)

These patterns are detected before code generation. The generator prints a clear error message and exits non-zero instead of generating broken code.

Not yet supported

Schema-to-type mapping

OpenAPI typeGleam type
stringString
integerInt
numberFloat
booleanBool
arrayList(T)
objectCustom type
enumCustom type with variants
nullableOption(T)

Development

This project uses mise for tool versions and just as a task runner.

mise install          # install Gleam, Erlang, rebar3
just check            # format check, typecheck, build, unit tests
just shellspec        # CLI integration tests (ShellSpec)
just integration      # generated code compile + roundtrip tests

Test structure

CommandToolWhat it tests
just testgleeunitUnit tests (parser, naming, config)
just shellspecShellSpecCLI behaviour, file generation, content verification
just integrationgleeunitGenerated code compiles, types/decoders/encoders/handlers/middleware work

License

MIT

Search Document