MyNixOS website logo
Description

Use Servant and GDP together to create expressive web API types.

Servant (A web api framework) and GDP (Ghosts of Departed Proofs) is here combined to allow for quite expressive API declarations. This is achieved by parsing captured API-input as named variables, making the named contexts span the entire request. This in turn, makes it possible to express a lot of domain knowledge or requirements in the Servant API type. For an example of how this can look like check out https://github.com/mtonnberg/gdp-demo

Servant ❤️ GDP

In a nutshell

To get more productive, produce high-quality web APIs and reduce the number of needed tests we can combine Servant (A web api framework) and GDP (Ghosts of Departed Proofs). This allows for quite expressive API declarations which leads to more knowledge captured. See a full example.

-- http://localhost/div/10/5 would return 2
type DivWebApi numerator denominator =
  "div"
    :> CaptureNamed (Int ~~ numerator 
                    ::: IsPositive numerator)
    :> CaptureNamed (Int ~~ denominator
                    ::: IsPositive denominator)
    :> Get
         '[JSON]
         ( Int ? IsEqualOrLessThan numerator
         )

or

-- http://localhost/habitats/savanna/animals/3
-- could return ["lion", "elephant"]
type GetAnimalsForHabitat user habitat pagesize =
  AuthProtect "normalUser"
    :> "habitats"
    :> CaptureNamed (String ~~ habitat ::: IsNonEmpty habitat && IsTrimmed habitat)
    :> "animals"
    :> CaptureNamed (Int ~~ pagesize ::: IsPositive pagesize)
    :> Get
         '[JSON]
         ( ( [String ? IsAValidatedAnimal habitat] ? HasAMaximumLengthOf pagesize
           )
             ::: user `HasAccessToHabitat` habitat
         )

See https://github.com/mtonnberg/gdp-demo for a full, working example.

How does it work

API-input is captured as named variables, making the named contexts span the entire request. This in turn, makes it possible to express a lot of domain knowledge/requirements in the Servant API type.

Why does this exist?

To both make the API-capabilities clearer and to make it easier to implement.

It is often quite easy to identify domain rules, invariants and preconditions for the API but hard to capture that knowledge in the types. Instead a lot of domain rules and requirements are hidden in the implementation logic.

Moving information to the types are in line with Knowledge-as-Code, read more about the benefits here: Knowledge-as-Code.

Metadata

Version

0.0.1.2

Platforms (75)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-darwin
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-darwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-windows