paradox-servant

Generate Servant API types from Paradox URI specifications.

Define your endpoints in .dox, and paradox-servant extracts the URI declarations and generates a Haskell module with a Servant type API ready to wire up.

Usage

As a Library

import Paradox.Servant (GenerateConfig(..), generateFromFile)

main :: IO ()
main = do
  let cfg = GenerateConfig
        { gcModuleName  = "Api.Servant"
        , gcApiTypeName = "API"
        }
  result <- generateFromFile cfg "api.dox"
  case result of
    Left err  -> putStrLn $ "Error: " <> show err
    Right src -> writeFile "Api/Servant.hs" (show src)

As an Executable

paradox-servant \
  --path api.dox \
  --module Api.Servant \
  --api-name API \
  --output Api/Servant.hs

All flags:

Flag Default Description

--path

(required)

Path to the .dox file

--module

Dox.Servant

Output Haskell module name

--api-name

API

Name of the generated Servant API type alias

Example

Given this .dox specification:

import std

type User
  id: Text
  name: Text
  email: Text

type CreateUserRequest
  name: Text
  email: Text

users: URI
  http://localhost/api/users GET (Unit. [User])

createUser: URI
  http://localhost/api/users POST (CreateUserRequest. User)

getUser: URI
  http://localhost/api/users/${Integer} GET (Unit. User)

paradox-servant generates:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}

module Api.Servant (API) where

import Data.Text (Text)
import Servant
import Dox (CreateUserRequest, User)

type API =
       "api" :> "users" :> Get '[JSON] [User]
  :<|> "api" :> "users" :> ReqBody '[JSON] CreateUserRequest :> Post '[JSON] User
  :<|> "api" :> "users" :> Capture "integer" Integer :> Get '[JSON] User

How It Works

paradox-servant operates in three stages:

  1. Parse and type-check — Runs the Paradox compiler on the .dox file via Paradox.Strap

  2. Extract endpoints — Walks the typed specification and pulls out every URI-typed constant, reading its path, meta (HTTP methods), io (request/response types), and accept (MIME types)

  3. Generate Servant types — Maps each endpoint to a Servant route combinator chain

Path Segments

Path segments are mapped to Servant combinators:

  • Literal segments like users become "users"

  • Interpolated type parameters like ${Integer} become Capture "integer" Integer

  • Named captures like ${Integer id} become Capture "id" Integer

HTTP Methods

The meta field maps to Servant verbs:

.dox Method Servant Verb

GET

Get

POST

Post

PUT

Put

DELETE

Delete

PATCH

Patch

When the output type is Unit or (), the NoContent variants are used (GetNoContent, PostNoContent, etc.).

MIME Types

The accept field maps to Servant content types:

MIME Type Servant Content Type

application/json

JSON

text/plain

PlainText

text/html

HTML

application/octet-stream

OctetStream

application/x-www-form-urlencoded

FormUrlEncoded

If no accept is specified, JSON is the default.

Request Bodies

POST, PUT, and PATCH endpoints with an input type get a ReqBody combinator. The content types from accept are reused for the request body encoding.

API Reference

generateFromFile

generateFromFile :: GenerateConfig -> FilePath -> IO (Either Text Text)

Parse a .dox file, type-check it, extract endpoints, and generate the Servant module. Returns Left on parse/type-check failure, Right with the generated source on success.

generateFromSpec

generateFromSpec :: GenerateConfig -> Map TypeIdentifier Text -> Specification Typed -> Text

Generate from an already-parsed specification. Useful when you have the specification in memory from another pipeline stage.

extractEndpoints

extractEndpoints :: Specification ann -> [URIEndpoint]

Extract the list of URI endpoints from a specification without generating code. Useful for inspection or custom code generation.

GenerateConfig

data GenerateConfig = GenerateConfig
  { gcModuleName  :: Text  -- ^ Output module name
  , gcApiTypeName :: Text  -- ^ Name of the API type alias
  }

See Also