7puns
7puns

Reputation: 1555

How to create route that will trigger on any path in Servant?

I have a type level function (aka type family) in a Haskell Servant App which takes a Symbol and produces a Type (Route) i.e.

type family AppRoute (x :: Symbol) where
    AppRoute x = x :> Get '[HTML] RawHtml

This is expected to be used in an API:

type ServerAPI = 
    Get '[HTML] RawHtml 
    :<|> UserAPI
    :<|> AdminAPI
    :<|> AppRoute "about" 
    :<|> AppRoute "contact" 
    :<|> AppRoute "services"
    :<|> AppRoute "blog"
    :<|> AppRoute "products"

The corresponding server function is

server :: Server ServerAPI
server = 
    html
    :<|> userServer
    :<|> adminServer
    :<|> html
    :<|> html
    :<|> html
    :<|> html
    :<|> html

Essentially all the AppRoutes go to the same endpoint (raw html file). Is there a way to eliminate the duplication (referring to the last five routes) by writing something like (this does not compile)

type family AppRoute where
    AppRoute = String :> Get '[HTML] RawHtml

type ServerAPI =
    Get '[HTML] RawHtml
    :<|> UserAPI
    :<|> AdminAPI
    :<|> AppRoute _  -- * the problem is here. One function is needed here

with a corresponding server

server :: Server ServerAPI
server = 
    html
    :<|> userServer
    :<|> adminServer
    :<|> html

So in effect, AppRoutes is a type level function that takes any string and returns a route.

In summary, instead of writing

:<|> AppRoute "about" :<|> AppRoute "contact" :<|> AppRoute "services" :<|> AppRoute "products"

I want to be able to write just :<|> AppRoute _

Upvotes: 2

Views: 340

Answers (2)

Twizty
Twizty

Reputation: 112

In case someone is still looking for an answere, there is another way to capture any route. Take a look at CaptureAll type

Upvotes: 1

radrow
radrow

Reputation: 7129

You may use Capture to capture any path. However, it will need to be preceded by : char. So for example

type AppRoute = Capture "routePath" String :> Get '[HTML] RawHtml

type ServerAPI =
    Get '[HTML] RawHtml
    :<|> UserAPI
    :<|> AdminAPI
    :<|> AppRoute

Now, AppRoute will trigger on yourserver.com/:thisIsMyPath/ and pass "thisIsMyPath" as an argument for the endpoint. I have currently no idea how to bypass this :. Assuming that html is an endpoint that won't depend on given path at this moment, you may define your whole server as

server :: Server ServerAPI
server = html
  :<|> userServer
  :<|> adminServer
  :<|> const html

You may read about it here.


Btw, why not use type alias instead of taking tanky type families? In my Servant apps I usually do

type AppRoute (x :: Symbol) = x :> Get '[HTML] RawHtml

Which works perfectly.

Upvotes: 2

Related Questions