oaspec

HexCI

Generate Gleam code from OpenAPI 3.x specifications with strict codegen for a large practical subset.

Install

From GitHub Release

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

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> (server) and <dir>_client/<package> (client). Both directory basenames must match package so that Gleam imports resolve correctly. Copy or symlink the output into src/ to use it.

Field Required Default Description
input yes - Path to OpenAPI 3.x spec (YAML or JSON)
package no api Gleam module namespace prefix
mode no bothserver, client, or both
output.dir no ./gen Base output directory
output.server no <dir>/<package> Server code output path
output.client no <dir>_client/<package> Client code output path

The directory basename must match package so that import my_api/types resolves. The CLI --output flag works the same as output.dir. A mismatch is an early error.

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

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_client/my_api/          # client
  types.gleam
  decode.gleam
  encode.gleam
  middleware.gleam
  client.gleam              # HTTP client functions
  request_types.gleam
  response_types.gleam

Generated code examples

Given a Petstore OpenAPI spec:

Types

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

pub type PetStatus {
  PetStatusAvailable
  PetStatusPending
  PetStatusSold
}

Server handlers

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

Client

pub fn create_pet(config: ClientConfig, body: types.CreatePetRequest)
  -> Result(response_types.CreatePetResponse, ClientError) {
  // ...
}

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

Not yet supported

The AST now parses and preserves all standard OpenAPI 3.x fields (lossless parse). The following features are parsed and stored but not yet used by codegen (preserved for downstream tools or future codegen use):

The following features are not supported at all:

Schema-to-type mapping

OpenAPI type Gleam type
stringString
integerInt
numberFloat
booleanBool
arrayList(T)
object Custom type
enum Custom type with variants
nullable Option(T)
allOf Merged custom type
oneOf/anyOf ($ref variants) Sum type

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

Command Tool What it tests
just test gleeunit Parser, validator, naming, config, collision detection
just shellspec ShellSpec CLI behaviour, file generation, content, unsupported feature detection
just integration gleeunit Generated code compiles, types/decoders/encoders/handlers/middleware work

License

MIT