# openapi-hs
[](https://hackage.haskell.org/package/openapi-hs)
[](/LICENSE)
A Haskell library for **decoding, encoding, manipulating, and validating
[OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) documents** — the format that
describes HTTP APIs in JSON or YAML. OpenAPI 3.1 adopts the
[JSON Schema 2020-12](https://json-schema.org/specification-links#2020-12) dialect.
> **Fork notice.** `openapi-hs` is a fork of
> [`biocad/openapi3`](https://github.com/biocad/openapi3), which is no longer actively
> maintained and supports only OpenAPI 3.0. This fork brings the library up to OpenAPI 3.1.
> The Haskell module namespace is unchanged (`Data.OpenApi.*`), so migrating is usually just a
> dependency-name swap: `openapi3` → `openapi-hs`. The fork keeps the upstream
> [BSD-3-Clause license](#license) and copyright.
---
## Highlights
- **Full OpenAPI 3.1 / JSON Schema 2020-12 data model** with lossless JSON round-tripping.
- **Type arrays** for nullability (`type: ["string", "null"]`) instead of the removed `nullable`.
- **Numeric** `exclusiveMaximum` / `exclusiveMinimum`, independent of `maximum` / `minimum`.
- **Tuples via `prefixItems`** (+ `items: false`) instead of the removed `items` array form.
- **Conditional & assertion keywords:** `if`/`then`/`else`, `const`, `contains` /
`minContains` / `maxContains`, `dependentSchemas` / `dependentRequired`,
`unevaluatedProperties` / `unevaluatedItems`, content keywords, and `examples`.
- **JSON Schema identification keywords:** `$id`, `$anchor`, `$defs`, `$ref`, `$dynamicRef`,
`$dynamicAnchor`.
- **Top-level 3.1 features:** `webhooks`, `Info.summary`, `License.identifier`, and `$ref` on
`PathItem`.
- **Schema validation** that understands the new 3.1 keywords.
- **`ToSchema` derivation** to generate schemas from your Haskell types via `GHC.Generics`.
- **`lens` and `optics`** accessors for ergonomic reads and updates.
- **3.0 → 3.1 migration helpers** for documents you don't control yet.
## Installation
Add `openapi-hs` to your project's dependencies (Cabal):
```cabal
build-depends: openapi-hs
```
then import the umbrella module, which re-exports everything you typically need:
```haskell
import Data.OpenApi
```
Requires GHC **9.12.4** or **9.14.1**.
## Quick start
### Build and serialize a schema
```haskell
{-# LANGUAGE OverloadedStrings #-}
import Control.Lens
import Data.Aeson (encode)
import Data.OpenApi
-- "a string, or null" — 3.1 nullability via a type array
nullableString :: Schema
nullableString = mempty
& type_ ?~ OpenApiTypeArray [OpenApiString, OpenApiNull]
& description ?~ "an optional name"
-- encode nullableString == "{\"description\":\"an optional name\",\"type\":[\"string\",\"null\"]}"
```
### Derive a schema from a Haskell type
```haskell
{-# LANGUAGE DeriveGeneric #-}
import Data.Aeson (ToJSON)
import Data.Proxy (Proxy (..))
import GHC.Generics (Generic)
import Data.OpenApi
data User = User
{ name :: String
, age :: Int
} deriving (Show, Generic)
instance ToJSON User -- needed for validation (below)
instance ToSchema User
userSchema :: Schema
userSchema = toSchema (Proxy :: Proxy User)
```
### Decode a 3.1 document
```haskell
import Data.Aeson (decode)
import Data.OpenApi (Schema)
-- decode "{\"prefixItems\":[{\"type\":\"string\"},{\"type\":\"number\"}],\"items\":false}"
-- :: Maybe Schema
```
### Validate a value against a schema
```haskell
import Data.OpenApi
import Data.OpenApi.Schema.Validation (validateToJSON)
-- Using the `User` from above (which has both ToJSON and ToSchema):
-- validateToJSON returns [] when the value conforms to its derived schema,
-- or a list of human-readable errors otherwise.
ok :: [ValidationError]
ok = validateToJSON (User "Ada" 36) -- []
```
For validating an arbitrary JSON `Value` against a specific `Schema`, use
`validateJSON :: Definitions Schema -> Schema -> Value -> [ValidationError]`.
## Lenses and optics
Every record field has a generated accessor in both the
[`lens`](https://hackage.haskell.org/package/lens) and
[`optics`](https://hackage.haskell.org/package/optics) styles. Import whichever you prefer:
```haskell
import Data.OpenApi -- lens accessors (Data.OpenApi.Lens)
-- or
import Data.OpenApi.Optics -- optics labels (#type, #properties, …)
```
A few field lenses are suffixed with `_` to avoid clashing with reserved words or `Prelude`
names: `type_`, `enum_`, `minimum_`, `maximum_`, `default_`, `const_`, `if_`, `then_`, `else_`,
`contains_`, `id_`. The corresponding optics labels keep the bare name (`#type`, `#const`, …).
## Migrating from OpenAPI 3.0
The 3.1 data types deliberately cannot represent 3.0-only constructs ("Strategy A"), so a 3.0
document does not decode directly. Rewrite the **parsed JSON** into a 3.1 shape first, using
`Data.OpenApi.Migration`:
```haskell
import Data.Aeson (Value, decode, encode)
import Data.OpenApi (OpenApi)
import Data.OpenApi.Migration (migrate30To31)
bring30Forward :: Value -> Maybe OpenApi
bring30Forward raw30 = decode (encode (migrate30To31 raw30))
```
`migrate30To31` recurses into every nested schema, rewriting `nullable` → type arrays, boolean
exclusive bounds → numeric bounds, and tuple `items` arrays → `prefixItems` + `items: false`.
The single-concern helpers (`migrate30NullableValue`, `migrate30ExclusiveBoundsValue`,
`migrate30ItemsArrayValue`) are also exported. They are intentionally **deprecated** to flag that
3.0 input is transitional.
See **[`MIGRATION_3.0_TO_3.1.md`](/MIGRATION_3.0_TO_3.1.md)** for the full breaking-changes list,
worked examples, and pitfalls.
## Examples
Runnable examples live in the [`examples/`](/examples) directory. Generated specifications can be
explored interactively in any OpenAPI 3.1 viewer or editor.
## Validation
The library's own correctness is checked at three complementary levels:
1. **Round-trip** — the test suite encodes documents and decodes them back through
`FromJSON OpenApi`, which rejects any `openapi` version outside `3.1.0 … 3.1.1`, then
compares for semantic equality.
2. **Schema conformance** — `Data.OpenApi.Schema.Validation` (`validateToJSON` /
`validateJSON`) checks that values conform to their derived 3.1 schemas, including the new
keywords.
3. **Authoritative conformance** — the `example` executable emits a complete OpenAPI 3.1
contract (with `info`, a server, top-level `tags`, and a unique `operationId` per operation)
that lints cleanly under [`vacuum`](https://quobix.com/vacuum/):
```bash
cabal run example > openapi.json
nix run nixpkgs#vacuum-go -- lint -d openapi.json
```
The first two layers are *self-referential* — they confirm a document agrees with this
library's own model of OpenAPI 3.1. `vacuum` is an external, authoritative linter, so it
independently catches encoder output that is valid JSON but non-conformant OpenAPI.
## Building and developing
This repository ships a Nix flake providing a pinned GHC 9.12.4 toolchain. From the repository
root:
```bash
nix develop -c cabal build all
nix develop -c cabal test all
```
If you have a matching `cabal` + GHC 9.12.x on your `PATH`, the same commands work without the
`nix develop -c` prefix. The package is Cabal-only (`build-type: Simple`); there is no `stack.yaml`.
## Documentation
Full API documentation is on
[Hackage](https://hackage.haskell.org/package/openapi-hs). Each module's Haddocks include
worked examples.
The design and implementation strategy behind the 3.1 work is documented in
**[`docs/OPENAPI31_MIGRATION_PLAN.md`](https://github.com/shinzui/openapi-hs/blob/master/docs/OPENAPI31_MIGRATION_PLAN.md)**.
## Contributing
Bug reports, fixes, documentation improvements, and other contributions are welcome. Please open
an issue or pull request on the [GitHub issue tracker](https://github.com/shinzui/openapi-hs/issues).
## License
`openapi-hs` retains the original **BSD-3-Clause** license of the upstream
[`openapi3`](https://github.com/biocad/openapi3) project, including its copyright. See the
[`LICENSE`](/LICENSE) file for the full text; this fork's changes are released under the same
terms.
---
*Originally derived from work by the GetShopTV and Biocad teams.*