Servant.API.TypeLevel

servant-0.20.2: A family of combinators for defining webservices APIs

Safe Haskell | None
Language | Haskell2010

Servant.API.TypeLevel

Contents

- Lax inclusion
- Strict inclusion
- Helpers
  - Lists
  - Logic
  - Fragment

Description

This module collects utilities for manipulating `servant` API types. The functionality in this module is for advanced usage.

The code samples in this module use the following type synonym:

    type SampleAPI = "hello" :> Get '[JSON] Int
                :<|> "bye" :> Capture "name" String :> Post '[JSON, PlainText] Bool

Synopsis

- type family Endpoints api :: [Type] where ...
- type family IsElem' a s
- type family IsElem endpoint api where ...
- type family IsSubAPI sub api where ...
- type family AllIsElem (xs :: [Type]) api where ...
- type family IsIn endpoint api where ...
- type family IsStrictSubAPI sub api where ...
- type family AllIsIn (xs :: [Type]) api where ...
- type family MapSub (e :: k) (xs :: [Type]) :: [Type] where ...
- type family AppendList (xs :: [a]) (ys :: [a]) :: [a] where ...
- type family IsSubList (a :: [t]) (b :: [t]) where ...
- type Elem (e :: t) (es :: [t]) = ElemGo e es es
- type family ElemGo (e :: t) (es :: [t]) (orig :: t1) where ...
- type family Or a b where ...
- type family And a b where ...
- type family FragmentUnique api where ...
- class FragmentUnique api => AtMostOneFragment api

# Documentation

The doctests in this module are run with following preamble:

    >>> :set -XPolyKinds
    >>> :set -XGADTs
    >>> :set -XTypeSynonymInstances -XFlexibleInstances
    >>> import Data.Proxy
    >>> import Data.Type.Equality
    >>> import Servant.API
    >>> data OK ctx where OK :: ctx => OK ctx
    >>> instance Show (OK ctx) where show _ = "OK"
    >>> let ok :: ctx => Proxy ctx -> OK ctx; ok _ = OK
    >>> type SampleAPI = "hello" :> Get '[JSON] Int :<|> "bye" :> Capture "name" String :> Post '[JSON, PlainText] Bool
    >>> type FailAPI = Fragment Bool :> Fragment Int :> Get '[JSON] NoContent
    >>> let sampleAPI = Proxy :: Proxy SampleAPI

type family Endpoints api :: [Type] where ... #

Flatten API into a list of endpoints.

    >>> Refl :: Endpoints SampleAPI :~: '["hello" :> Verb 'GET 200 '[JSON] Int, "bye" :> (Capture "name" String :> Verb 'POST 200 '[JSON, PlainText] Bool)]
    Refl

Equations

Endpoints (a :<|> b) = AppendList (Endpoints a) (Endpoints b) |
Endpoints (e :> a) = MapSub e (Endpoints a) |
Endpoints a = '[a] |

## Lax inclusion

type family IsElem' a s #

You may use this type family to tell the type checker that your custom type may be skipped as part of a link. This is useful for things like ``QueryParam`` that are optional in a URI and do not affect them if they are omitted.

    >>> data CustomThing
    >>> type instance IsElem' e (CustomThing :> s) = IsElem e s

Note that ``IsElem`` is called, which will mutually recurse back to ``IsElem'`` if it exhausts all other options again.

Once you have written a `HasLink` instance for `CustomThing` you are ready to go.

type family IsElem endpoint api where ... #

Closed type family, check if `endpoint` is within `api`. Uses ``IsElem'`` if it exhausts all other options.

    >>> ok (Proxy :: Proxy (IsElem ("hello" :> Get '[JSON] Int) SampleAPI))
    OK

    >>> ok (Proxy :: Proxy (IsElem ("bye" :> Get '[JSON] Int) SampleAPI))
    ...
    ... Could not ...
    ...

An endpoint is considered within an api even if it is missing combinators that don't affect the URL:

    >>> ok (Proxy :: Proxy (IsElem (Get '[JSON] Int) (Header "h" Bool :> Get '[JSON] Int)))
    OK

    >>> ok (Proxy :: Proxy (IsElem (Get '[JSON] Int) (ReqBody '[JSON] Bool :> Get '[JSON] Int)))
    OK

- N.B.:* `IsElem a b` can be seen as capturing the notion of whether the URL represented by `a` would match the URL represented by `b`, *not* whether a request represented by `a` matches the endpoints serving `b` (for the latter, use `IsIn`).

Equations

IsElem e (sa :<|> sb) = Or (IsElem e sa) (IsElem e sb) |
IsElem (e :> sa) (e :> sb) = IsElem sa sb |
IsElem sa (Header sym x :> sb) = IsElem sa sb |
IsElem sa (Header' mods sym x :> sb) = IsElem sa sb |
IsElem sa (ReqBody y x :> sb) = IsElem sa sb |
IsElem (CaptureAll z y :> sa) (CaptureAll x y :> sb) = IsElem sa sb |
IsElem (Capture z y :> sa) (Capture x y :> sb) = IsElem sa sb |
IsElem sa (QueryParam x y :> sb) = IsElem sa sb |
IsElem sa (QueryParams x y :> sb) = IsElem sa sb |
IsElem sa (QueryFlag x :> sb) = IsElem sa sb |
IsElem sa (Fragment x :> sb) = IsElem sa sb |
IsElem (Verb m s ct typ) (Verb m s ct' typ) = IsSubList ct ct' |
IsElem e e = () |
IsElem e (NamedRoutes rs) = IsElem e (ToServantApi rs) |
IsElem e a = IsElem' e a |

type family IsSubAPI sub api where ... #

Check whether `sub` is a sub-API of `api`.

    >>> ok (Proxy :: Proxy (IsSubAPI SampleAPI (SampleAPI :<|> Get '[JSON] Int)))
    OK

    >>> ok (Proxy :: Proxy (IsSubAPI (SampleAPI :<|> Get '[JSON] Int) SampleAPI))
    ...
    ... Could not ...
    ...

This uses `IsElem` for checking; thus the note there applies here.

Equations

IsSubAPI sub api = AllIsElem (Endpoints sub) api |

type family AllIsElem (xs :: [Type]) api where ... #

Check that every element of `xs` is an endpoint of `api` (using ``IsElem``).

Equations

AllIsElem ('[] :: [Type]) api = () |
AllIsElem (x ': xs) api = (IsElem x api, AllIsElem xs api) |

## Strict inclusion

type family IsIn endpoint api where ... #

Closed type family, check if `endpoint` is exactly within `api`.

    >>> ok (Proxy :: Proxy (IsIn ("hello" :> Get '[JSON] Int) SampleAPI))
    OK

Unlike `IsElem`, this requires an *exact* match.

    >>> ok (Proxy :: Proxy (IsIn (Get '[JSON] Int) (Header "h" Bool :> Get '[JSON] Int)))
    ...
    ... Could not ...
    ...

Equations

IsIn e (sa :<|> sb) = Or (IsIn e sa) (IsIn e sb) |
IsIn (e :> sa) (e :> sb) = IsIn sa sb |
IsIn e e = () |
IsIn e (NamedRoutes record) = IsIn e (ToServantApi record) |

type family IsStrictSubAPI sub api where ... #

Check whether `sub` is a sub API of `api`.

Like `IsSubAPI`, but uses `IsIn` rather than `IsElem`.

Equations

IsStrictSubAPI sub api = AllIsIn (Endpoints sub) api |

type family AllIsIn (xs :: [Type]) api where ... #

Check that every element of `xs` is an endpoint of `api` (using ``IsIn``).

    >>> ok (Proxy :: Proxy (AllIsIn (Endpoints SampleAPI) SampleAPI))
    OK

Equations

AllIsIn ('[] :: [Type]) api = () |
AllIsIn (x ': xs) api = (IsIn x api, AllIsIn xs api) |

# Helpers

## Lists

type family MapSub (e :: k) (xs :: [Type]) :: [Type] where ... #

Apply `(e :>)` to every API in `xs`.

Equations

MapSub (e :: k) ('[] :: [Type]) = '[] :: [Type] |
MapSub (e :: k) (x ': xs) = (e :> x) ': MapSub e xs |

type family AppendList (xs :: [a]) (ys :: [a]) :: [a] where ... #

Append two type-level lists.

Equations

AppendList ('[] :: [a]) (ys :: [a]) = ys |
AppendList (x ': xs :: [a]) (ys :: [a]) = x ': AppendList xs ys |

type family IsSubList (a :: [t]) (b :: [t]) where ... #

Equations

IsSubList ('[] :: [t]) (b :: [t]) = () |
IsSubList (x ': xs :: [t]) (y :: [t]) = And (Elem x y) (IsSubList xs y) |

type Elem (e :: t) (es :: [t]) = ElemGo e es es #

Check that a value is an element of a list:

    >>> ok (Proxy :: Proxy (Elem Bool '[Int, Bool]))
    OK

    >>> ok (Proxy :: Proxy (Elem String '[Int, Bool]))
    ...
    ... [Char]...'[Int, Bool...
    ...

type family ElemGo (e :: t) (es :: [t]) (orig :: t1) where ... #

Equations

ElemGo (x :: a) (x ': xs :: [a]) (orig :: t) = () |
ElemGo (y :: t1) (x ': xs :: [t1]) (orig :: t2) = ElemGo y xs orig |
ElemGo (x :: t1) ('[] :: [t1]) (orig :: t2) = TypeError (('ShowType x ':<>: 'Text " expected in list ") ':<>: 'ShowType orig) :: Constraint |

## Logic

type family Or a b where ... #

If either `a` or `b` produce an empty constraint, produce an empty constraint.

Equations

Or () b = () |
Or a () = () |

type family And a b where ... #

If both `a` or `b` produce an empty constraint, produce an empty constraint.

Equations

And () () = () |

## Fragment

type family FragmentUnique api where ... #

Equations

FragmentUnique (sa :<|> sb) = And (FragmentUnique sa) (FragmentUnique sb) |
FragmentUnique (Fragment a :> sa) = FragmentNotIn sa (Fragment a :> sa) |
FragmentUnique (x :> sa) = FragmentUnique sa |
FragmentUnique (Fragment a) = () |
FragmentUnique x = () |

class FragmentUnique api => AtMostOneFragment api #

If there is more than one fragment in an API endpoint, a compile-time error is raised.

    >>> type FailAPI = Fragment Bool :> Fragment Int :> Get '[JSON] NoContent
    >>> instance AtMostOneFragment FailAPI
    ...
    ...Only one Fragment allowed per endpoint in api...
    ...

#### Instances

Instances details

AtMostOneFragment (Fragment a) # |
Instance details Defined in Servant.API.TypeLevel
AtMostOneFragment (UVerb m cts as) # |
Instance details Defined in Servant.API.TypeLevel
AtMostOneFragment (Verb m s ct typ) # |
Instance details Defined in Servant.API.TypeLevel