servant-gdp: Use Servant and GDP together to create expressive web API types

[ bsd3, lib, library ] [ Propose Tags ]

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


[Skip to Readme]
Versions [RSS] [faq] 0.0.1.2
Dependencies aeson (==1.5.*), base (>=4.7 && <5), gdp (<0.1), servant-server (==0.18.*), text (==1.2.*) [details]
License BSD-3-Clause
Copyright 2021 Mikael Tönnberg
Author Mikael Tönnberg
Maintainer mikael@carboncloud.com
Category Lib
Home page https://github.com/mtonnberg/servant-gdp#readme
Source repo head: git clone https://github.com/mtonnberg/servant-gdp
Uploaded by mtonnberg at 2021-06-16T13:10:05Z
Distributions NixOS:0.0.1.2
Downloads 43 total (6 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs available [build log]
Last success reported on 2021-06-16 [all 1 reports]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

For package maintainers and hackage trustees

Candidates


Readme for servant-gdp-0.0.1.2

[back to package description]

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