Factoring APIs in servant-client
I am currently writing a Haskell client
library for the Up Bank API with
servant-client
.
Endpoints require an Authentication
request header with the format
Authorization: TOKEN_TYPE Token
With servant, we can represent this with the Header
combinator and add it to
every endpoint. For example, the type of an API is as follows.
type API =
Header "Authorization" String :> Capture "x" Int :> Get '[JSON] X
:<|> Header "Authorization" String :> Capture "y" Int :> Get '[JSON] Y
We can factor out the Header
s, similar to factoring in algebra f g + f h = f (g + h)
type FactoredAPI = Header "Authorization" String :>
Capture "x" Int :> Get '[JSON] X
( :<|> Capture "y" Int :> Get '[JSON] Y
)
Clients
servant-client
provides the function client
which automagically generates
implementations and allows us to pattern match on the client functions of the API.
An implementation of the unfactored client API is as follows.
getX :: Maybe String -- ^ token
-> Int -- ^ x
-> ClientM X
getY :: Maybe String -- ^ token
-> Int -- ^ y
-> ClientM Y
api :: Proxy API
= Proxy
api
:<|> getY = client api getX
Let’s look at the type of the unfactored client API.
It returns two client functions combined with :<|>
.
> :t client (Proxy :: Proxy API)
ghciProxy :: Proxy FactoredAPI)
client ( :: (Maybe [Char] -> Int -> ClientM X)
:<|> (Maybe [Char] -> Int -> ClientM Y)
Whereas the client of the factored API returns a function that takes a token and returns two client functions.
> :t client (Proxy :: Proxy FactoredAPI)
ghciProxy :: Proxy FactoredAPI)
client ( :: Maybe [Char]
-> (Int -> ClientM X)
:<|> (Int -> ClientM Y)
An implementation of the factored API is then
getX' :: Int -- ^ x
-> ClientM X
getY' :: Int -- ^ y
-> ClientM Y
api' :: Proxy FactoredAPI
= Proxy
api'
:<|> getY' = client api' token
getX' where token :: Maybe String
As expected, client api' token
will return two client functions.