Safe Haskell | None |
---|---|
Language | Haskell2010 |
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.
- module Servant.API.TypeLevel
- safeLink :: forall endpoint api. (IsElem endpoint api, HasLink endpoint) => Proxy api -> Proxy endpoint -> MkLink endpoint
- allLinks :: forall api. HasLink api => Proxy api -> MkLink api
- data URI :: * = URI {}
- class HasLink endpoint where
- type MkLink endpoint
- data Link
- linkURI :: Link -> URI
- linkURI' :: LinkArrayElementStyle -> Link -> URI
- data LinkArrayElementStyle
- data Param
- 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.
:: (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
allLinks :: forall api. HasLink api => Proxy api -> MkLink api Source #
Create all links in an API.
Note that the api
type must be restricted to the endpoints that have
valid links to them.
>>>
type API = "foo" :> Capture "name" Text :> Get '[JSON] Text :<|> "bar" :> Capture "name" Int :> Get '[JSON] Double
>>>
let fooLink :<|> barLink = allLinks (Proxy :: Proxy API)
>>>
:t fooLink
fooLink :: Text -> Link>>>
:t barLink
barLink :: Int -> Link
Note: nested APIs don't work well with this approach
>>>
:kind! MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double))
MkLink (Capture "nest" Char :> (Capture "x" Int :> Get '[JSON] Int :<|> Capture "y" Double :> Get '[JSON] Double)) :: * = Char -> (Int -> Link) :<|> (Double -> Link)
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:
Adding custom types
class HasLink endpoint where Source #
Construct a toLink for an endpoint.
linkURI :: Link -> URI Source #
>>>
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
>>>
type SomeRoute = "abc" :> Capture "email" String :> Put '[JSON] ()
>>>
let someRoute = Proxy :: Proxy SomeRoute
>>>
safeLink someRoute someRoute "test@example.com"
Link {_segments = ["abc","test%40example.com"], _queryParams = []}
>>>
linkURI $ safeLink someRoute someRoute "test@example.com"
abc/test%40example.com
linkURI' :: LinkArrayElementStyle -> Link -> URI Source #
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 Source #
How to encode array query elements.
LinkArrayElementBracket | foo[]=1&foo[]=2 |
LinkArrayElementPlain | foo=1&foo=2 |
Link accessors
Query parameter.
linkSegments :: Link -> [String] Source #
linkQueryParams :: Link -> [Param] Source #