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

Safe Haskell | None
Language | Haskell2010

Servant.Utils.Links

Contents

- Building and using safe links
- Adding custom types
  - Link accessors

Description

Type safe generation of internal links.

Given an API with a few endpoints:

    >>> :set -XDataKinds -XTypeFamilies -XTypeOperators
    >>> import Servant.API
    >>> import Servant.Utils.Links
    >>> import Data.Proxy
    >>>
    >>>
    >>>
    >>> type Hello = "hello" :> Get '[JSON] Int
    >>> type Bye   = "bye"   :> QueryParam "name" String :> Delete '[JSON] NoContent
    >>> type API   = Hello :<|> Bye
    >>> let api = Proxy :: Proxy API

It is possible to generate links that are guaranteed to be within `API` with `safeLink`. The first argument to `safeLink` is a type representing the API you would like to restrict links to. The second argument is the destination endpoint you would like the link to point to, this will need to end with a verb like GET or POST. Further arguments may be required depending on the type of the endpoint. If everything lines up you will get a `Link` out the other end.

You may omit `QueryParam`s and the like should you not want to provide them, but types which form part of the URL path like `Capture` must be included. The reason you may want to omit `QueryParam`s is that safeLink is a bit magical: if parameters are included that could take input it will return a function that accepts that input and generates a link. This is best shown with an example. Here, a link is generated with no parameters:

    >>> let hello = Proxy :: Proxy ("hello" :> Get '[JSON] Int)
    >>> toUrlPiece (safeLink api hello :: Link)
    "hello"

If the API has an endpoint with parameters then we can generate links with or without those:

    >>> let with = Proxy :: Proxy ("bye" :> QueryParam "name" String :> Delete '[JSON] NoContent)
    >>> toUrlPiece $ safeLink api with (Just "Hubert")
    "bye?name=Hubert"

    >>> let without = Proxy :: Proxy ("bye" :> Delete '[JSON] NoContent)
    >>> toUrlPiece $ safeLink api without
    "bye"

If you would like create a helper for generating links only within that API, you can partially apply safeLink if you specify a correct type signature like so:

    >>> :set -XConstraintKinds
    >>> :{
    >>> let apiLink :: (IsElem endpoint API, HasLink endpoint)
    >>> => Proxy endpoint -> MkLink endpoint
    >>> apiLink = safeLink api
    >>> :}

Attempting to construct a link to an endpoint that does not exist in api will result in a type error like this:

    >>> let bad_link = Proxy :: Proxy ("hello" :> Delete '[JSON] NoContent)
    >>> safeLink api bad_link
    ...
    ...Could not deduce...
    ...

This error is essentially saying that the type family couldn't find bad_link under api after trying the open (but empty) type family `IsElem'` as a last resort.

Synopsis

- module Servant.API.TypeLevel
- safeLink :: forall endpoint api. (IsElem endpoint api, HasLink endpoint) => Proxy api -> Proxy endpoint -> MkLink endpoint
- data URI :: * = URI {
  - uriScheme :: String
  - uriAuthority :: Maybe URIAuth
  - uriPath :: String
  - uriQuery :: String
  - uriFragment :: String
  }
- class HasLink endpoint where
  - type MkLink endpoint
- data Link
- linkURI :: Link -> URI
- linkURI' :: LinkArrayElementStyle -> Link -> URI
- data LinkArrayElementStyle
  - = LinkArrayElementBracket
  - | LinkArrayElementPlain
- data Param
  - = SingleParam String Text
  - | ArrayElemParam String Text
  - | FlagParam String
- linkSegments :: Link -> [String]
- linkQueryParams :: Link -> [Param]

# Documentation

module Servant.API.TypeLevel

# Building and using safe links

Note that `URI` is from the Network.URI module in the `network-uri` package.

safeLink #

Arguments

:: (IsElem endpoint api, HasLink endpoint) |
=> Proxy api | The whole API that this endpoint is a part of
-> Proxy endpoint | The API endpoint you would like to point to
-> MkLink endpoint |

Create a valid (by construction) relative URI with query params.

This function will only typecheck if `endpoint` is part of the API `api`

data URI :: * #

Represents a general universal resource identifier using its component parts.

For example, for the URI

      foo://anonymous@www.haskell.org:42/ghc?query#frag

the components are:

Constructors

URI |
- Fields uriScheme :: String
      foo:
- uriAuthority :: Maybe URIAuth
      //anonymous@www.haskell.org:42
- uriPath :: String
      /ghc
- uriQuery :: String
      ?query
- uriFragment :: String
      #frag

Instances

Eq URI |
Methods (==) :: URI -> URI -> Bool # (/=) :: URI -> URI -> Bool #
Data URI |
Methods gfoldl :: (forall d b. Data d => c (d -> b) -> d -> c b) -> (forall g. g -> c g) -> URI -> c URI # gunfold :: (forall b r. Data b => c (b -> r) -> c r) -> (forall r. r -> c r) -> Constr -> c URI # toConstr :: URI -> Constr # dataTypeOf :: URI -> DataType # dataCast1 :: Typeable (* -> *) t => (forall d. Data d => c (t d)) -> Maybe (c URI) # dataCast2 :: Typeable (* -> * -> *) t => (forall d e. (Data d, Data e) => c (t d e)) -> Maybe (c URI) # gmapT :: (forall b. Data b => b -> b) -> URI -> URI # gmapQl :: (r -> r' -> r) -> r -> (forall d. Data d => d -> r') -> URI -> r # gmapQr :: (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> URI -> r # gmapQ :: (forall d. Data d => d -> u) -> URI -> [u] # gmapQi :: Int -> (forall d. Data d => d -> u) -> URI -> u # gmapM :: Monad m => (forall d. Data d => d -> m d) -> URI -> m URI # gmapMp :: MonadPlus m => (forall d. Data d => d -> m d) -> URI -> m URI # gmapMo :: MonadPlus m => (forall d. Data d => d -> m d) -> URI -> m URI #
Ord URI |
Methods compare :: URI -> URI -> Ordering # (<) :: URI -> URI -> Bool # (<=) :: URI -> URI -> Bool # (>) :: URI -> URI -> Bool # (>=) :: URI -> URI -> Bool # max :: URI -> URI -> URI # min :: URI -> URI -> URI #
Show URI |
Methods showsPrec :: Int -> URI -> ShowS # show :: URI -> String # showList :: [URI] -> ShowS #
Generic URI |
Associated Types type Rep URI :: * -> * # Methods from :: URI -> Rep URI x # to :: Rep URI x -> URI #
NFData URI |
Methods rnf :: URI -> () #
type Rep URI |
type Rep URI = D1 (MetaData "URI" "Network.URI" "network-uri-2.6.1.0-GzRvaV3udR3JtWfa3CLYOw" False) (C1 (MetaCons "URI" PrefixI True) ((:*:) ((:*:) (S1 (MetaSel (Just Symbol "uriScheme") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 String)) (S1 (MetaSel (Just Symbol "uriAuthority") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (Maybe URIAuth)))) ((:*:) (S1 (MetaSel (Just Symbol "uriPath") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 String)) ((:*:) (S1 (MetaSel (Just Symbol "uriQuery") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 String)) (S1 (MetaSel (Just Symbol "uriFragment") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 String))))))

# Adding custom types

class HasLink endpoint where #

Construct a toLink for an endpoint.

Minimal complete definition

toLink

Associated Types

type MkLink endpoint #

Methods

toLink :: Proxy endpoint -> Link -> MkLink endpoint #

Instances

HasLink * Raw # |
Associated Types type MkLink Raw (endpoint :: Raw) :: * # Methods toLink :: Proxy Raw endpoint -> Link -> MkLink Raw endpoint #
HasLink k1 sub => HasLink * ((:>) k1 * (AuthProtect k tag) sub) # |
Associated Types type MkLink ((:>) k1 * (AuthProtect k tag) sub) (endpoint :: (:>) k1 * (AuthProtect k tag) sub) :: * # Methods toLink :: Proxy ((k1 :> *) (AuthProtect k tag) sub) endpoint -> Link -> MkLink ((k1 :> *) (AuthProtect k tag) sub) endpoint #
HasLink k sub => HasLink * ((:>) k * (BasicAuth realm a) sub) # |
Associated Types type MkLink ((:>) k * (BasicAuth realm a) sub) (endpoint :: (:>) k * (BasicAuth realm a) sub) :: * # Methods toLink :: Proxy ((k :> *) (BasicAuth realm a) sub) endpoint -> Link -> MkLink ((k :> *) (BasicAuth realm a) sub) endpoint #
HasLink k sub => HasLink * ((:>) k * RemoteHost sub) # |
Associated Types type MkLink ((:>) k * RemoteHost sub) (endpoint :: (:>) k * RemoteHost sub) :: * # Methods toLink :: Proxy ((k :> *) RemoteHost sub) endpoint -> Link -> MkLink ((k :> *) RemoteHost sub) endpoint #
HasLink k sub => HasLink * ((:>) k * (Header sym a) sub) # |
Associated Types type MkLink ((:>) k * (Header sym a) sub) (endpoint :: (:>) k * (Header sym a) sub) :: * # Methods toLink :: Proxy ((k :> *) (Header sym a) sub) endpoint -> Link -> MkLink ((k :> *) (Header sym a) sub) endpoint #
(ToHttpApiData v, HasLink k sub) => HasLink * ((:>) k * (CaptureAll * sym v) sub) # |
Associated Types type MkLink ((:>) k * (CaptureAll * sym v) sub) (endpoint :: (:>) k * (CaptureAll * sym v) sub) :: * # Methods toLink :: Proxy ((k :> *) (CaptureAll * sym v) sub) endpoint -> Link -> MkLink ((k :> *) (CaptureAll * sym v) sub) endpoint #
(ToHttpApiData v, HasLink k sub) => HasLink * ((:>) k * (Capture * sym v) sub) # |
Associated Types type MkLink ((:>) k * (Capture * sym v) sub) (endpoint :: (:>) k * (Capture * sym v) sub) :: * # Methods toLink :: Proxy ((k :> *) (Capture * sym v) sub) endpoint -> Link -> MkLink ((k :> *) (Capture * sym v) sub) endpoint #
HasLink k1 sub => HasLink * ((:>) k1 * (ReqBody k ct a) sub) # |
Associated Types type MkLink ((:>) k1 * (ReqBody k ct a) sub) (endpoint :: (:>) k1 * (ReqBody k ct a) sub) :: * # Methods toLink :: Proxy ((k1 :> *) (ReqBody k ct a) sub) endpoint -> Link -> MkLink ((k1 :> *) (ReqBody k ct a) sub) endpoint #
(KnownSymbol sym, HasLink k sub) => HasLink * ((:>) k * (QueryFlag sym) sub) # |
Associated Types type MkLink ((:>) k * (QueryFlag sym) sub) (endpoint :: (:>) k * (QueryFlag sym) sub) :: * # Methods toLink :: Proxy ((k :> *) (QueryFlag sym) sub) endpoint -> Link -> MkLink ((k :> *) (QueryFlag sym) sub) endpoint #
(KnownSymbol sym, ToHttpApiData v, HasLink k sub) => HasLink * ((:>) k * (QueryParams * sym v) sub) # |
Associated Types type MkLink ((:>) k * (QueryParams * sym v) sub) (endpoint :: (:>) k * (QueryParams * sym v) sub) :: * # Methods toLink :: Proxy ((k :> *) (QueryParams * sym v) sub) endpoint -> Link -> MkLink ((k :> *) (QueryParams * sym v) sub) endpoint #
(KnownSymbol sym, ToHttpApiData v, HasLink k sub) => HasLink * ((:>) k * (QueryParam * sym v) sub) # |
Associated Types type MkLink ((:>) k * (QueryParam * sym v) sub) (endpoint :: (:>) k * (QueryParam * sym v) sub) :: * # Methods toLink :: Proxy ((k :> *) (QueryParam * sym v) sub) endpoint -> Link -> MkLink ((k :> *) (QueryParam * sym v) sub) endpoint #
(KnownSymbol sym, HasLink k sub) => HasLink * ((:>) k Symbol sym sub) # |
Associated Types type MkLink ((:>) k Symbol sym sub) (endpoint :: (:>) k Symbol sym sub) :: * # Methods toLink :: Proxy ((k :> Symbol) sym sub) endpoint -> Link -> MkLink ((k :> Symbol) sym sub) endpoint #
HasLink * (Verb k k1 m s ct a) # |
Associated Types type MkLink (Verb k k1 m s ct a) (endpoint :: Verb k k1 m s ct a) :: * # Methods toLink :: Proxy (Verb k k1 m s ct a) endpoint -> Link -> MkLink (Verb k k1 m s ct a) endpoint #

data Link #

A safe link datatype. The only way of constructing a `Link` is using `safeLink`, which means any `Link` is guaranteed to be part of the mentioned API.

Instances

Show Link # |
Methods showsPrec :: Int -> Link -> ShowS # show :: Link -> String # showList :: [Link] -> ShowS #
ToHttpApiData Link # |
Methods toUrlPiece :: Link -> Text # toEncodedUrlPiece :: Link -> Builder # toHeader :: Link -> ByteString # toQueryParam :: Link -> Text #

linkURI :: Link -> URI #

Transform `Link` into `URI`.

    >>> type API = "something" :> Get '[JSON] Int
    >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
    something

    >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
    >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
    sum?x[]=1&x[]=2&x[]=3

    >>> type API = "foo/bar" :> Get '[JSON] Int
    >>> linkURI $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API)
    foo%2Fbar

linkURI' :: LinkArrayElementStyle -> Link -> URI #

Configurable `linkURI`.

    >>> type API = "sum" :> QueryParams "x" Int :> Get '[JSON] Int
    >>> linkURI' LinkArrayElementBracket $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
    sum?x[]=1&x[]=2&x[]=3

    >>> linkURI' LinkArrayElementPlain $ safeLink (Proxy :: Proxy API) (Proxy :: Proxy API) [1, 2, 3]
    sum?x=1&x=2&x=3

data LinkArrayElementStyle #

How to encode array query elements.

Constructors

LinkArrayElementBracket |
    foo[]=1&foo[]=2
LinkArrayElementPlain |
    foo=1&foo=2

Instances

Bounded LinkArrayElementStyle # |
Methods minBound :: LinkArrayElementStyle # maxBound :: LinkArrayElementStyle #
Enum LinkArrayElementStyle # |
Methods succ :: LinkArrayElementStyle -> LinkArrayElementStyle # pred :: LinkArrayElementStyle -> LinkArrayElementStyle # toEnum :: Int -> LinkArrayElementStyle # fromEnum :: LinkArrayElementStyle -> Int # enumFrom :: LinkArrayElementStyle -> [LinkArrayElementStyle] # enumFromThen :: LinkArrayElementStyle -> LinkArrayElementStyle -> [LinkArrayElementStyle] # enumFromTo :: LinkArrayElementStyle -> LinkArrayElementStyle -> [LinkArrayElementStyle] # enumFromThenTo :: LinkArrayElementStyle -> LinkArrayElementStyle -> LinkArrayElementStyle -> [LinkArrayElementStyle] #
Eq LinkArrayElementStyle # |
Methods (==) :: LinkArrayElementStyle -> LinkArrayElementStyle -> Bool # (/=) :: LinkArrayElementStyle -> LinkArrayElementStyle -> Bool #
Ord LinkArrayElementStyle # |
Methods compare :: LinkArrayElementStyle -> LinkArrayElementStyle -> Ordering # (<) :: LinkArrayElementStyle -> LinkArrayElementStyle -> Bool # (<=) :: LinkArrayElementStyle -> LinkArrayElementStyle -> Bool # (>) :: LinkArrayElementStyle -> LinkArrayElementStyle -> Bool # (>=) :: LinkArrayElementStyle -> LinkArrayElementStyle -> Bool # max :: LinkArrayElementStyle -> LinkArrayElementStyle -> LinkArrayElementStyle # min :: LinkArrayElementStyle -> LinkArrayElementStyle -> LinkArrayElementStyle #
Show LinkArrayElementStyle # |
Methods showsPrec :: Int -> LinkArrayElementStyle -> ShowS # show :: LinkArrayElementStyle -> String # showList :: [LinkArrayElementStyle] -> ShowS #

## Link accessors

data Param #

Query parameter.

Constructors

SingleParam String Text |
ArrayElemParam String Text |
FlagParam String |

Instances

Show Param # |
Methods showsPrec :: Int -> Param -> ShowS # show :: Param -> String # showList :: [Param] -> ShowS #

linkSegments :: Link -> [String] #

linkQueryParams :: Link -> [Param] #