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] YWe can factor out the Headers, 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
api = Proxy
getX :<|> getY = client apiLet’s look at the type of the unfactored client API.
It returns two client functions combined with :<|>.
ghci> :t client (Proxy :: Proxy API)
client (Proxy :: Proxy FactoredAPI)
:: (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.
ghci> :t client (Proxy :: Proxy FactoredAPI)
client (Proxy :: Proxy FactoredAPI)
:: 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
api' = Proxy
getX' :<|> getY' = client api' token
where token :: Maybe StringAs expected, client api' token will return two client functions.