Home > OS >  Haskell Servant: Construct URL from API
Haskell Servant: Construct URL from API

Time:12-25

Suppose we have this simple API:

type FooAPI
   = "foo" 
  :> QueryParam "age" Int 
  :> Get '[PlainText] Text

Is there a way to link type-level servant's API with a function that will generate URL for it? Like

someMagicFunction :: Proxy api -> SomeTypeFamily api
someMagicFunction = ?

generateURL :: Maybe Int -> Text
generateURL = someMagicFunction (Proxy @FooAPI)

>>> generateURL (Just 42)
"https://somehost/foo?age=42"
>>> generateURL Nothing
"https://somehost/foo"

I want to increase type-safety of URL generation so if I'll add some new parameter to FooAPI it will immediately appear in the type of generateURL and my code will not compile without editing the calls of generateURL.

I'm aware of servant-client library and I know that client function does somewhat similar but I couldn't figure out how to use this library in my case.

CodePudding user response:

I would say that Servant.Links is exactly what you are looking for.

In particular, in your case I would use allLinks to generate a Link corresponding to the URL piece containing the path and query parameters:

>>> linkURI $ allLinks (Proxy @FooAPI) Nothing
foo
>>> linkURI $ allLinks (Proxy @FooAPI) (Just 18)
foo?age=18

Then I would transform the generated Link into an URI where I would specify required scheme and hostname. Here's a fully working module:

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators    #-}

import           Data.Proxy
import           Data.Text     (Text)
import           Network.URI
import           Servant.API
import           Servant.Links

type FooAPI
   = "foo"
  :> QueryParam "age" Int
  :> Get '[PlainText] Text

generateURL :: Maybe Int -> String
generateURL age = show uri
  { uriScheme = "https:"
  , uriAuthority = Just nullURIAuth
    { uriRegName = "somehost" }
  , uriPath = "/" <> uriPath uri
  }
  where
    uri = linkURI link
    link = allLinks (Proxy @FooAPI) age

And demonstration:

>>> generateURL (Just 42)
"https://somehost/foo?age=42"
>>> generateURL Nothing
"https://somehost/foo"
  • Related