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.
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"