openapi-hs: OpenAPI 3.1 data model

[ bsd3, library, openapi, program, web ] [ Propose Tags ] [ Report a vulnerability ]

openapi-hs (a fork of the openapi3 library) is intended to be used for decoding and encoding OpenAPI 3.1 API specifications as well as manipulating them. The Haskell module namespace remains Data.OpenApi.*, so only the package dependency name changes for downstream users migrating from openapi3. . The OpenAPI 3.1 specification is available at https://spec.openapis.org/oas/v3.1.0


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 4.0.0
Change log CHANGELOG.md
Dependencies aeson (>=2.0.1.0 && <2.3), aeson-pretty (>=0.8.7 && <0.9), base (>=4.11.1.0 && <4.23), base-compat-batteries (>=0.11.1 && <0.16), bytestring (>=0.10.8.2 && <0.13), containers (>=0.5.11.0 && <0.9), cookie (>=0.4.3 && <0.6), generics-sop (>=0.5.1.0 && <0.6), hashable (>=1.2.7.0 && <1.6), http-media (>=0.8.0.0 && <0.9), insert-ordered-containers (>=0.2.3 && <0.4), lens (>=4.16.1 && <5.4), mtl (>=2.2.2 && <2.4), openapi-hs, optics-core (>=0.2 && <0.5), optics-th (>=0.2 && <0.5), QuickCheck (>=2.10.1 && <2.19), scientific (>=0.3.6.2 && <0.4), template-haskell (>=2.13.0.0 && <2.25), text (>=1.2.3.1 && <2.2), time (>=1.8.0.2 && <1.16), transformers (>=0.5.5.0 && <0.7), unordered-containers (>=0.2.9.0 && <0.3), uuid-types (>=1.0.3 && <1.1), vector (>=0.12.0.1 && <0.14) [details]
Tested with ghc ==9.12.4 || ==9.14.1
License BSD-3-Clause
Copyright (c) 2015-2016, GetShopTV, (c) 2020, Biocad, (c) 2026, Nadeem Bitar
Author Nickolay Kudasov, Maxim Koltsov, Nadeem Bitar
Maintainer nadeem@gmail.com
Uploaded by shinzui at 2026-06-18T22:43:17Z
Category Web, OpenApi
Home page https://github.com/shinzui/openapi-hs
Bug tracker https://github.com/shinzui/openapi-hs/issues
Source repo head: git clone https://github.com/shinzui/openapi-hs.git
Distributions
Executables example
Downloads 2 total (2 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for openapi-hs-4.0.0

[back to package description]

openapi-hs

Hackage License BSD-3-Clause

A Haskell library for decoding, encoding, manipulating, and validating OpenAPI 3.1 documents — the format that describes HTTP APIs in JSON or YAML. OpenAPI 3.1 adopts the JSON Schema 2020-12 dialect.

Fork notice. openapi-hs is a fork of 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: openapi3openapi-hs. The fork keeps the upstream BSD-3-Clause 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):

build-depends: openapi-hs

then import the umbrella module, which re-exports everything you typically need:

import Data.OpenApi

Requires GHC 9.12.4 or 9.14.1.

Quick start

Build and serialize a schema

{-# 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

{-# 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

import Data.Aeson (decode)
import Data.OpenApi (Schema)

-- decode "{\"prefixItems\":[{\"type\":\"string\"},{\"type\":\"number\"}],\"items\":false}"
--   :: Maybe Schema

Validate a value against a schema

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 and optics styles. Import whichever you prefer:

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:

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 for the full breaking-changes list, worked examples, and pitfalls.

Examples

Runnable examples live in the 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 conformanceData.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:

    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:

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. 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.

Contributing

Bug reports, fixes, documentation improvements, and other contributions are welcome. Please open an issue or pull request on the GitHub issue tracker.

License

openapi-hs retains the original BSD-3-Clause license of the upstream openapi3 project, including its copyright. See the 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.