Search code examples
haskellservant

safeLink in Servant


Servant uses Servant.API.safeLink to generate relative URLs, but I'm running into a problem that makes me think I am misunderstanding something basic either about how to use it or about how to define Servant APIs.

The minimal example I've constructed contains two endpoints. One is intended to be a "front door" endpoint at (relative URL) /foo, and the other at /foo/1:

{-# LANGUAGE DataKinds     #-}
{-# LANGUAGE TypeOperators #-}
import Servant

data HTML
type Foo = "foo" :> (Foo0 :<|> Foo1)
type Foo0 = Get '[HTML] String
type Foo1 = "1" :> Get '[HTML] String

slFoo :: Link
slFoo = safeLink (Proxy :: Proxy Foo) (Proxy :: Proxy Foo1)

The definition of slFoo above gives me the error

Could not deduce: IsElem' ("1" :> Get '[HTML] String) ("foo" :> (Foo0 :<|> Foo1))

...which is exactly the kind of error I get when safeLink is asked to produce a link which is not in the API defined by its first parameter. The error is similar when the second parameter to safeLink is Proxy :: Foo0 instead.

I've tried many, many permutations of this and can't seem to figure it out by myself with the use of the documentation I've found. I'd appreciate some pointers that let me figure out where my misunderstanding(s) lie.


Solution

  • The example doesn't work because Foo1, the type you've defined for the endpoint, does not itself contain the full path to itself relative to the top of the Foo API.

    One way for you to fix this situation is by using a "flattened" API instead:

    safeLink (Proxy :: Proxy (Flat Foo)) (Proxy :: Proxy (Nth 1 (Flat Foo)))

    (requiring import Servant.API.Flatten as well)

    The disadvantage is that you have to know the ordinal position of Foo1 within Foo. There doesn't seem to be a way to get the answer you want by using the types as specified in your question. You could define the flattened API in the first place, at the cost of making the structure as clear (IMO).

    Thanks to Alp Mestanogullari for explaining this to me above, in the comments to the question. He should really get credit for the answer!