json-spec-0.2.1.0: json-spec.cabal
cabal-version: 3.0
name: json-spec
version: 0.2.1.0
synopsis: Type-level JSON specification
maintainer: rick@owensmurray.com
description: = Motivation
This package provides a way to specify the shape of
your JSON data at the type level. The particular use
cases we focus on are enabling (but not providing
in this package):
1. Auto-generating documentation to ensure it
is correct.
2. Auto-generating client code in front-end languages
to ensure it is correct.
There are already tools available to achieve this,
but they all have one major drawback: they rely on
generically derived Aeson instances. Some people
strongly object to using generically derived Aeson
instances for encoding/decoding http api data because
of how brittle it is. It can be surprisingly easy
accidentally break your API without noticing because
you don't realize that a small change to some type
somewhere affects the API representation. Avoiding
this requires very strict discipline about how you
organize and maintain your code. E.g. you will see
a lot of comments like
> --| BEWARE, Changing any of the types in this file will change the API
> -- representation!!
> module My.API (...) where
But then the types in this api might reference
types in in other modules where it isn't as obvious
that you might be changing the api when you make
an update.
I have even seen people go so far as to mandate
that /every/ type appearing on the API must be
in some similar \"API\" module. This usually ends
badly because you end up with a bunch of seemingly
spurious (and quite tedious) translations between
between \"business\" types and almost identical
\"API\" types.
The other option is to simply not use generically
derived instances and code all or some of your
'ToJSON'/'FromJSON' instances by hand. That
(sometimes) helps solve the problem of making it a
little more obvious when you are making a breaking
api change. And it definitely helps with the ability
to update the haskell type for some business purpose
while keeping the encoding backwards compatible.
The problem now though is that you can't take
advantage of any of the above tooling without
writing every instance by hand. Writing all the
individual instances by hand defeat's the purpose
because you are back to being unsure whether they
are all in sync!
The approach this library takes is to take a cue
from `servant` and provide a way to specify the
JSON encoding at the type level. You must manually
specify the encoding, but you only have to do so
once (at the type level). Other tools can then
inspect the type using either type families or
type classes to generate the appropriate artifacts
or behavior. Aeson integration (provided by this
package) works by using a type family to transform
the spec into a new Haskell type whose structure
is analogous to the specification. You are then
required to transform your regular business
value into a value of this \"structural type\"
(I strongly recommend using type holes to make this
easier). Values of the structural type will always
encode into specification-complient JSON.
= Example
> data User = User
> { name :: Text
> , lastLogin :: UTCTime
> }
> deriving stock (Show, Eq)
> deriving (ToJSON, FromJSON) via (SpecJSON User)
> instance HasJsonEncodingSpec User where
> type EncodingSpec User =
> JsonObject
> '[ '("name", JsonString)
> , '("last-login", JsonDateTime)
> ]
> toJSONStructure user =
> (Field @"name" (name user),
> (Field @"last-login" (lastLogin user),
> ()))
> instance HasJsonDecodingSpec User where
> type DecodingSpec User = EncodingSpec User
> fromJSONStructure
> (Field @"name" name,
> (Field @"last-login" lastLogin,
> ()))
> =
> pure User { name , lastLogin }
homepage: https://github.com/owensmurray/json-spec
license: MIT
license-file: LICENSE
author: Rick Owens
category: JSON
build-type: Simple
extra-source-files:
README.md
LICENSE
common dependencies
build-depends:
, aeson >= 2.2.0.0 && < 2.3
, base >= 4.17.1.0 && < 4.18
, containers >= 0.6.7 && < 0.7
, scientific >= 0.3.7.0 && < 0.4
, text >= 2.0.2 && < 2.1
, time >= 1.12.2 && < 1.13
, vector >= 0.13.0.0 && < 0.14
common warnings
ghc-options:
-Wmissing-deriving-strategies
-Wmissing-export-lists
-Wmissing-import-lists
-Wredundant-constraints
-Wall
library
import: dependencies, warnings
exposed-modules:
Data.JsonSpec
other-modules:
Data.JsonSpec.Encode
Data.JsonSpec.Decode
Data.JsonSpec.Spec
-- other-extensions:
hs-source-dirs: src
default-language: Haskell2010
test-suite jsonspec
import: dependencies, warnings
main-is: jsonspec.hs
type: exitcode-stdio-1.0
hs-source-dirs: test
default-language: Haskell2010
build-depends:
, json-spec
, bytestring >= 0.11.4.0 && < 0.12
, hspec >= 2.11.1 && < 2.12