Skip to content

Commit 7ef9730

Browse files
authored
Merge pull request #1538 from akhesaCaro/reverting
Reverting NamedRoutes cookbook
2 parents 50355d0 + 6da8488 commit 7ef9730

File tree

4 files changed

+152
-386
lines changed

4 files changed

+152
-386
lines changed

cabal.project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ packages:
3434
doc/cookbook/db-postgres-pool
3535
doc/cookbook/db-sqlite-simple
3636
doc/cookbook/file-upload
37+
doc/cookbook/generic
3738
doc/cookbook/hoist-server-with-context
3839
doc/cookbook/https
3940
doc/cookbook/jwt-and-basic-auth
@@ -46,7 +47,6 @@ packages:
4647
doc/cookbook/using-custom-monad
4748
doc/cookbook/using-free-client
4849
-- doc/cookbook/open-id-connect
49-
doc/cookbook/namedRoutes
5050

5151
tests: True
5252
optimization: False

doc/cookbook/generic/Generic.lhs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Using generics
2+
3+
```haskell
4+
{-# LANGUAGE DataKinds #-}
5+
{-# LANGUAGE DeriveGeneric #-}
6+
{-# LANGUAGE RankNTypes #-}
7+
{-# LANGUAGE TypeOperators #-}
8+
module Main (main, api, getLink, routesLinks, cliGet) where
9+
10+
import Control.Exception (throwIO)
11+
import Control.Monad.Trans.Reader (ReaderT, runReaderT)
12+
import Data.Proxy (Proxy (..))
13+
import Network.Wai.Handler.Warp (run)
14+
import System.Environment (getArgs)
15+
16+
import Servant
17+
import Servant.Client
18+
19+
import Servant.API.Generic
20+
import Servant.Client.Generic
21+
import Servant.Server.Generic
22+
```
23+
24+
The usage is simple, if you only need a collection of routes.
25+
First you define a record with field types prefixed by a parameter `route`:
26+
27+
```haskell
28+
data Routes route = Routes
29+
{ _get :: route :- Capture "id" Int :> Get '[JSON] String
30+
, _put :: route :- ReqBody '[JSON] Int :> Put '[JSON] Bool
31+
}
32+
deriving (Generic)
33+
```
34+
35+
Then we'll use this data type to define API, links, server and client.
36+
37+
## API
38+
39+
You can get a `Proxy` of the API using `genericApi`:
40+
41+
```haskell
42+
api :: Proxy (ToServantApi Routes)
43+
api = genericApi (Proxy :: Proxy Routes)
44+
```
45+
46+
It's recommended to use `genericApi` function, as then you'll get
47+
better error message, for example if you forget to `derive Generic`.
48+
49+
## Links
50+
51+
The clear advantage of record-based generics approach, is that
52+
we can get safe links very conveniently. We don't need to define endpoint types,
53+
as field accessors work as proxies:
54+
55+
```haskell
56+
getLink :: Int -> Link
57+
getLink = fieldLink _get
58+
```
59+
60+
We can also get all links at once, as a record:
61+
62+
```haskell
63+
routesLinks :: Routes (AsLink Link)
64+
routesLinks = allFieldLinks
65+
```
66+
67+
## Client
68+
69+
Even more power starts to show when we generate a record of client functions.
70+
Here we use `genericClientHoist` function, which lets us simultaneously
71+
hoist the monad, in this case from `ClientM` to `IO`.
72+
73+
```haskell
74+
cliRoutes :: Routes (AsClientT IO)
75+
cliRoutes = genericClientHoist
76+
(\x -> runClientM x env >>= either throwIO return)
77+
where
78+
env = error "undefined environment"
79+
80+
cliGet :: Int -> IO String
81+
cliGet = _get cliRoutes
82+
```
83+
84+
## Server
85+
86+
Finally, probably the most handy usage: we can convert record of handlers into
87+
the server implementation:
88+
89+
```haskell
90+
record :: Routes AsServer
91+
record = Routes
92+
{ _get = return . show
93+
, _put = return . odd
94+
}
95+
96+
app :: Application
97+
app = genericServe record
98+
99+
main :: IO ()
100+
main = do
101+
args <- getArgs
102+
case args of
103+
("run":_) -> do
104+
putStrLn "Starting cookbook-generic at http://localhost:8000"
105+
run 8000 app
106+
-- see this cookbook below for custom-monad explanation
107+
("run-custom-monad":_) -> do
108+
putStrLn "Starting cookbook-generic with a custom monad at http://localhost:8000"
109+
run 8000 (appMyMonad AppCustomState)
110+
_ -> putStrLn "To run, pass 'run' argument: cabal new-run cookbook-generic run"
111+
```
112+
113+
## Using generics together with a custom monad
114+
115+
If your app uses a custom monad, here's how you can combine it with
116+
generics.
117+
118+
```haskell
119+
data AppCustomState =
120+
AppCustomState
121+
122+
type AppM = ReaderT AppCustomState Handler
123+
124+
apiMyMonad :: Proxy (ToServantApi Routes)
125+
apiMyMonad = genericApi (Proxy :: Proxy Routes)
126+
127+
getRouteMyMonad :: Int -> AppM String
128+
getRouteMyMonad = return . show
129+
130+
putRouteMyMonad :: Int -> AppM Bool
131+
putRouteMyMonad = return . odd
132+
133+
recordMyMonad :: Routes (AsServerT AppM)
134+
recordMyMonad = Routes {_get = getRouteMyMonad, _put = putRouteMyMonad}
135+
136+
-- natural transformation
137+
nt :: AppCustomState -> AppM a -> Handler a
138+
nt s x = runReaderT x s
139+
140+
appMyMonad :: AppCustomState -> Application
141+
appMyMonad state = genericServeT (nt state) recordMyMonad
Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
1-
name: namedRoutes
1+
cabal-version: 2.2
2+
name: cookbook-generic
23
version: 0.1
3-
synopsis: NamedRoutes - Generic servant API implementation cookbook example
4+
synopsis: Using custom monad to pass a state between handlers
45
homepage: http://docs.servant.dev/
5-
license: BSD3
6+
license: BSD-3-Clause
67
license-file: ../../../servant/LICENSE
78
author: Servant Contributors
89
maintainer: [email protected]
910
build-type: Simple
10-
cabal-version: >=1.10
11-
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.1
11+
tested-with: GHC==8.6.5, GHC==8.8.3, GHC ==8.10.7
1212

13-
executable namedRoutes
14-
main-is: NamedRoutes.lhs
13+
executable cookbook-using-custom-monad
14+
main-is: Generic.lhs
1515
build-depends: base == 4.*
16-
, aeson >= 1.2
17-
, text
1816
, servant
1917
, servant-client
2018
, servant-client-core
2119
, servant-server
22-
, wai >= 3.2
20+
, base-compat
2321
, warp >= 3.2
24-
22+
, transformers >= 0.3
2523
default-language: Haskell2010
2624
ghc-options: -Wall -pgmL markdown-unlit
27-
build-tool-depends: markdown-unlit:markdown-unlit
25+
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4

0 commit comments

Comments
 (0)