2022-04-13 11:28:11 +02:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE PolyKinds #-}
|
|
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
|
|
{-# LANGUAGE TypeApplications #-}
|
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
{-# LANGUAGE TypeOperators #-}
|
2015-08-17 23:56:29 +02:00
|
|
|
|
2018-11-09 18:04:47 +01:00
|
|
|
import Prelude ()
|
|
|
|
import Prelude.Compat
|
|
|
|
|
2015-08-17 23:56:29 +02:00
|
|
|
import Data.Aeson
|
|
|
|
import Data.Proxy
|
|
|
|
import Data.Text
|
|
|
|
import GHC.Generics
|
|
|
|
import Network.Wai
|
|
|
|
import Network.Wai.Handler.Warp
|
|
|
|
|
|
|
|
import Servant
|
Implement `HasServer (NamedRoutes routes)`
We define `ServerT (NamedRoutes api) m` as `api (AsServerT m)`, so that
the server of an record-defined API is a record of handlers.
The implementation piggy backs on the instance for “vanilla” servant
types with `(:<|>)`, using the `GServantProduct` for converting backd
and forth between the record / vanilla servers.
The main difficulty is that GHC needs to know that this operation is
legit, which can be expressed as the fact that:
```
GToServant (Rep (ServerT (NamedRoutes api))) m ~
ServerT (GToServant (Rep (api AsApi))) m
```
plus a few additional constraints.
This is easy enough for `route`, as we know that `m ~ Handler`. But in
the case of `hoistServerWithContext`, the two involved monads are
unknown ; in other words, this constraint needs to hold `forall m.`
Switching `-XQuantifiedConstraints` on is not sufficient, as our
constraints involve type families (`Rep` and `ServerT`). Our trick is to
use an intermediary typeclass, `GServer`, as a provider of evidence (in
the form of a `Dict`) that our constraints are indeed satisfied for a
particular monad.
The only instance of `GServer` is defined along with it, so it is
practically invisible to users.
2021-09-30 18:39:41 +02:00
|
|
|
import Servant.Server.Generic ()
|
2014-12-10 16:10:57 +01:00
|
|
|
|
|
|
|
-- * Example
|
|
|
|
|
|
|
|
-- | A greet message data type
|
2015-01-13 20:40:41 +01:00
|
|
|
newtype Greet = Greet { _msg :: Text }
|
2014-12-10 16:10:57 +01:00
|
|
|
deriving (Generic, Show)
|
|
|
|
|
|
|
|
instance FromJSON Greet
|
|
|
|
instance ToJSON Greet
|
|
|
|
|
|
|
|
-- API specification
|
2022-04-13 11:28:11 +02:00
|
|
|
type TestApi' =
|
2014-12-10 16:10:57 +01:00
|
|
|
-- GET /hello/:name?capital={true, false} returns a Greet as JSON
|
2015-01-13 20:40:41 +01:00
|
|
|
"hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON] Greet
|
2014-12-10 16:10:57 +01:00
|
|
|
|
|
|
|
-- POST /greet with a Greet as JSON in the request body,
|
|
|
|
-- returns a Greet as JSON
|
2015-01-13 20:40:41 +01:00
|
|
|
:<|> "greet" :> ReqBody '[JSON] Greet :> Post '[JSON] Greet
|
2014-12-10 16:10:57 +01:00
|
|
|
|
|
|
|
-- DELETE /greet/:greetid
|
2016-07-08 09:11:34 +02:00
|
|
|
:<|> "greet" :> Capture "greetid" Text :> Delete '[JSON] NoContent
|
2014-12-10 16:10:57 +01:00
|
|
|
|
Implement `HasServer (NamedRoutes routes)`
We define `ServerT (NamedRoutes api) m` as `api (AsServerT m)`, so that
the server of an record-defined API is a record of handlers.
The implementation piggy backs on the instance for “vanilla” servant
types with `(:<|>)`, using the `GServantProduct` for converting backd
and forth between the record / vanilla servers.
The main difficulty is that GHC needs to know that this operation is
legit, which can be expressed as the fact that:
```
GToServant (Rep (ServerT (NamedRoutes api))) m ~
ServerT (GToServant (Rep (api AsApi))) m
```
plus a few additional constraints.
This is easy enough for `route`, as we know that `m ~ Handler`. But in
the case of `hoistServerWithContext`, the two involved monads are
unknown ; in other words, this constraint needs to hold `forall m.`
Switching `-XQuantifiedConstraints` on is not sufficient, as our
constraints involve type families (`Rep` and `ServerT`). Our trick is to
use an intermediary typeclass, `GServer`, as a provider of evidence (in
the form of a `Dict`) that our constraints are indeed satisfied for a
particular monad.
The only instance of `GServer` is defined along with it, so it is
practically invisible to users.
2021-09-30 18:39:41 +02:00
|
|
|
:<|> NamedRoutes OtherRoutes
|
|
|
|
|
2022-04-13 11:28:11 +02:00
|
|
|
type TestApi =
|
|
|
|
TestApi'
|
|
|
|
:<|> "redirect" :> Capture "redirectValue" Int :> RedirectOf TestApi'
|
|
|
|
|
|
|
|
|
Implement `HasServer (NamedRoutes routes)`
We define `ServerT (NamedRoutes api) m` as `api (AsServerT m)`, so that
the server of an record-defined API is a record of handlers.
The implementation piggy backs on the instance for “vanilla” servant
types with `(:<|>)`, using the `GServantProduct` for converting backd
and forth between the record / vanilla servers.
The main difficulty is that GHC needs to know that this operation is
legit, which can be expressed as the fact that:
```
GToServant (Rep (ServerT (NamedRoutes api))) m ~
ServerT (GToServant (Rep (api AsApi))) m
```
plus a few additional constraints.
This is easy enough for `route`, as we know that `m ~ Handler`. But in
the case of `hoistServerWithContext`, the two involved monads are
unknown ; in other words, this constraint needs to hold `forall m.`
Switching `-XQuantifiedConstraints` on is not sufficient, as our
constraints involve type families (`Rep` and `ServerT`). Our trick is to
use an intermediary typeclass, `GServer`, as a provider of evidence (in
the form of a `Dict`) that our constraints are indeed satisfied for a
particular monad.
The only instance of `GServer` is defined along with it, so it is
practically invisible to users.
2021-09-30 18:39:41 +02:00
|
|
|
data OtherRoutes mode = OtherRoutes
|
|
|
|
{ version :: mode :- Get '[JSON] Int
|
|
|
|
, bye :: mode :- "bye" :> Capture "name" Text :> Get '[JSON] Text
|
|
|
|
}
|
|
|
|
deriving Generic
|
|
|
|
|
2014-12-10 16:10:57 +01:00
|
|
|
testApi :: Proxy TestApi
|
|
|
|
testApi = Proxy
|
|
|
|
|
|
|
|
-- Server-side handlers.
|
|
|
|
--
|
|
|
|
-- There's one handler per endpoint, which, just like in the type
|
|
|
|
-- that represents the API, are glued together using :<|>.
|
|
|
|
--
|
2016-04-07 23:34:23 +02:00
|
|
|
-- Each handler runs in the 'Handler' monad.
|
2014-12-10 16:10:57 +01:00
|
|
|
server :: Server TestApi
|
2022-04-13 11:28:11 +02:00
|
|
|
server = (helloH :<|> postGreetH :<|> deleteGreetH :<|> otherRoutes) :<|> redirect
|
Implement `HasServer (NamedRoutes routes)`
We define `ServerT (NamedRoutes api) m` as `api (AsServerT m)`, so that
the server of an record-defined API is a record of handlers.
The implementation piggy backs on the instance for “vanilla” servant
types with `(:<|>)`, using the `GServantProduct` for converting backd
and forth between the record / vanilla servers.
The main difficulty is that GHC needs to know that this operation is
legit, which can be expressed as the fact that:
```
GToServant (Rep (ServerT (NamedRoutes api))) m ~
ServerT (GToServant (Rep (api AsApi))) m
```
plus a few additional constraints.
This is easy enough for `route`, as we know that `m ~ Handler`. But in
the case of `hoistServerWithContext`, the two involved monads are
unknown ; in other words, this constraint needs to hold `forall m.`
Switching `-XQuantifiedConstraints` on is not sufficient, as our
constraints involve type families (`Rep` and `ServerT`). Our trick is to
use an intermediary typeclass, `GServer`, as a provider of evidence (in
the form of a `Dict`) that our constraints are indeed satisfied for a
particular monad.
The only instance of `GServer` is defined along with it, so it is
practically invisible to users.
2021-09-30 18:39:41 +02:00
|
|
|
where otherRoutes = OtherRoutes {..}
|
|
|
|
|
|
|
|
bye name = pure $ "Bye, " <> name <> " !"
|
|
|
|
version = pure 42
|
2014-12-10 16:10:57 +01:00
|
|
|
|
Implement `HasServer (NamedRoutes routes)`
We define `ServerT (NamedRoutes api) m` as `api (AsServerT m)`, so that
the server of an record-defined API is a record of handlers.
The implementation piggy backs on the instance for “vanilla” servant
types with `(:<|>)`, using the `GServantProduct` for converting backd
and forth between the record / vanilla servers.
The main difficulty is that GHC needs to know that this operation is
legit, which can be expressed as the fact that:
```
GToServant (Rep (ServerT (NamedRoutes api))) m ~
ServerT (GToServant (Rep (api AsApi))) m
```
plus a few additional constraints.
This is easy enough for `route`, as we know that `m ~ Handler`. But in
the case of `hoistServerWithContext`, the two involved monads are
unknown ; in other words, this constraint needs to hold `forall m.`
Switching `-XQuantifiedConstraints` on is not sufficient, as our
constraints involve type families (`Rep` and `ServerT`). Our trick is to
use an intermediary typeclass, `GServer`, as a provider of evidence (in
the form of a `Dict`) that our constraints are indeed satisfied for a
particular monad.
The only instance of `GServer` is defined along with it, so it is
practically invisible to users.
2021-09-30 18:39:41 +02:00
|
|
|
helloH name Nothing = helloH name (Just False)
|
2014-12-10 16:10:57 +01:00
|
|
|
helloH name (Just False) = return . Greet $ "Hello, " <> name
|
|
|
|
helloH name (Just True) = return . Greet . toUpper $ "Hello, " <> name
|
|
|
|
|
|
|
|
postGreetH greet = return greet
|
|
|
|
|
2016-07-08 09:11:34 +02:00
|
|
|
deleteGreetH _ = return NoContent
|
2014-12-10 16:10:57 +01:00
|
|
|
|
2022-04-13 11:28:11 +02:00
|
|
|
redirect 42 = pure $
|
|
|
|
RedirectOf (Proxy @("hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON] Greet))
|
|
|
|
(\buildPath -> buildPath "Nicolas" (Just True))
|
|
|
|
|
|
|
|
-- Fail in any other case
|
|
|
|
redirect _ = throwError err500
|
|
|
|
|
2014-12-10 16:10:57 +01:00
|
|
|
-- Turn the server into a WAI app. 'serve' is provided by servant,
|
|
|
|
-- more precisely by the Servant.Server module.
|
|
|
|
test :: Application
|
2016-02-18 16:36:24 +01:00
|
|
|
test = serve testApi server
|
2014-12-10 16:10:57 +01:00
|
|
|
|
|
|
|
-- Run the server.
|
|
|
|
--
|
|
|
|
-- 'run' comes from Network.Wai.Handler.Warp
|
|
|
|
runTestServer :: Port -> IO ()
|
|
|
|
runTestServer port = run port test
|
|
|
|
|
|
|
|
-- Put this all to work!
|
|
|
|
main :: IO ()
|
|
|
|
main = runTestServer 8001
|