3

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.

Jeremy
  • 1,049
  • 1
  • 15
  • 28
  • 1
    That's because `Foo1` doesn't include the leading `foo` static path fragment, so when you ask servant to produce a link for `Foo1` (within the `Foo` API), it doesn't find an endpoint that matches `Foo1` exactly and complains. – Alp Mestanogullari May 27 '19 at 01:46
  • I think that makes sense, though in that case I don't know how to specify this same sort of configuration -- do you know if it is possible? – Jeremy May 27 '19 at 01:51
  • 1
    Maybe something like [`servant-flatten`](https://hackage.haskell.org/package/servant-flatten-0.2/docs/Servant-API-Flatten.html) is appropriate here? Just like the client/server examples in those docs, you could pass a "flattened" version of your `Foo` API type as `safeLink`'s second argument, and the first argument could perhaps use `Nth` on the said flattened API type to hit the endpoint you want? – Alp Mestanogullari May 30 '19 at 08:49
  • Thank you; I hadn't known about servant-flatten. I'm sure your suggestion makes good sense, but I haven't been able to figure out specifics of how to make it work. (If it involves defining a type instance Nth myself, I'm lost.) – Jeremy Jun 04 '19 at 04:24
  • I should add that I have not been able to find any examples of anyone actually using servant-flatten in publicly-available code indexed by search engine, but I did look! – Jeremy Jun 04 '19 at 04:29
  • 1
    Regarding uses of servant-flatten, surprisingly I'm not the only user :-) See [here](https://github.com/search?q=%22import+Servant.API.Flatten%22&type=Code). – Alp Mestanogullari Jun 05 '19 at 13:11
  • 1
    Regarding your actual question, the idea is that instead of passing `apiProxy :: Proxy YourAPI` to whatever function you're using (`serve`, `client`, `safeLink`, ...), you'd pass `flatten apiProxy` which has type `Proxy (Flat YourAPI)`. You don't have to define an instance of `Nth`, you just have to use it to get a proxy to the right endpoint. `Nth ` will give you an API type for the ` + 1` th endpoint. `Nth 0` for the first, `Nth 1` for the second, etc. Email me if this is not clear enough, I'm not a huge fan of SO for those matters. – Alp Mestanogullari Jun 05 '19 at 13:15
  • I got it now, thank you so much! I had been boneheadedly trying to use Nth as a constructor but finally realized why that wasn't working, and now I know what I need to do. Specifically, expressions of the form `safeLink (Proxy :: Proxy (Flat Foo)) (Proxy :: Proxy (Nth 1 (Flat Foo)))` – Jeremy Jun 06 '19 at 00:55
  • Also thanks for the pointer to the github search. I had been under the mistaken impression that it would have been indexed by the web-wide engine I'd been using. I still couldn't find [anyone using Nth](https://github.com/search?q=%22import+Servant.API.Flatten%22+Nth&type=Code) though! – Jeremy Jun 06 '19 at 00:57
  • 1
    Indeed, `Nth` doesn't seem to be used in public repositories. I'd be delighted to merge a patch that adds an example of using `Nth` to its haddocks. =) Re: github search, I always look there first when I'm looking for examples of using some library/function/..., I definitely recommend it. – Alp Mestanogullari Jun 07 '19 at 08:21

1 Answers1

0

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!

Jeremy
  • 1,049
  • 1
  • 15
  • 28