(:<|>) is not Associative
Hello wonderful servant maintainers,
I guess whether this is actually a bug depends on your expected behaviour, but I wasn't easily able to find the expected behaviour written down anywhere (maybe I just missed it);
Given that :<|> is defined in Servant.API.Alternative I expected it to be associative, so I was quite surprised when I learned it isn't!
Here's a small reproduction which demonstrates this:
type FlatAPI =
("a" :> "b" :> Post '[JSON] Int)
:<|> ("a" :> "b" :> Delete '[JSON] String)
:<|> ("c" :> "d" :> Post '[JSON] Int)
:<|> ("c" :> "d" :> Delete '[JSON] String)
type NestedAPI =
("a" :> "b" :> (Post '[JSON] Int :<|> Delete '[JSON] String))
:<|> ("c" :> "d" :> (Post '[JSON] Int :<|> Delete '[JSON] String))
handlePost :: Handler Int
handlePost = pure 1
handleDelete :: Handler String
handleDelete = pure "hello"
server :: Server FlatAPI
server =
handlePost
:<|> handleDelete
:<|> handlePost
:<|> handleDelete
nestedServer :: Server NestedAPI
nestedServer =
(handlePost :<|> handleDelete) -- < these brackets are required.
:<|> handlePost
:<|> handleDelete
I had assumed that FlatAPI and NestedAPI were equivalent, but it turns out that NestedAPI requires you to manually group your server implementations to match the bracketing on your API. Remove the brackets causes it to fail to compile.
If this is expected, that's fine, just letting you know that I got tripped up here 😄
This is sadly a consequence of "just" representing endpoints as an agglomerate of :<|> separated stuffs, instead of having this "reduce" to some internal representation that will "quotient out" the kind of difference you are talking about (e.g generating a [Any] or Vector Any or something underneath).
I wrote a tiny package for "flattening" things in case that's useful: https://hackage.haskell.org/package/servant-flatten-0.2/docs/Servant-API-Flatten.html