Skip to content

Commit 88f8d3b

Browse files
committed
Merge servant-generic
1 parent 374a7b8 commit 88f8d3b

File tree

14 files changed

+508
-7
lines changed

14 files changed

+508
-7
lines changed

cabal.project

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ packages: servant/
1212
doc/cookbook/db-postgres-pool
1313
doc/cookbook/db-sqlite-simple
1414
doc/cookbook/file-upload
15+
doc/cookbook/generic
1516
doc/cookbook/https
1617
doc/cookbook/jwt-and-basic-auth
1718
doc/cookbook/pagination

doc/cookbook/generic/Generic.lhs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 Data.Proxy (Proxy (..))
12+
import Network.Wai.Handler.Warp (run)
13+
import System.Environment (getArgs)
14+
15+
import Servant
16+
import Servant.Client
17+
18+
import Servant.API.Generic
19+
import Servant.Client.Generic
20+
import Servant.Server.Generic
21+
```
22+
23+
The usage is simple, if you only need a collection of routes.
24+
First you define a record with field types prefixed by a parameter `route`:
25+
26+
```haskell
27+
data Routes route = Routes
28+
{ _get :: route :- Capture "id" Int :> Get '[JSON] String
29+
, _put :: route :- ReqBody '[JSON] Int :> Put '[JSON] Bool
30+
}
31+
deriving (Generic)
32+
```
33+
34+
Then we'll use this data type to define API, links, server and client.
35+
36+
## API
37+
38+
You can get a `Proxy` of the API using `genericApi`:
39+
40+
```haskell
41+
api :: Proxy (ToServantApi Routes)
42+
api = genericApi (Proxy :: Proxy Routes)
43+
```
44+
45+
It's recommented to use `genericApi` function, as then you'll get
46+
better error message, for example if you forget to `derive Generic`.
47+
48+
## Links
49+
50+
The clear advantage of record-based generics approach, is that
51+
we can get safe links very conviently. We don't need to define endpoint types,
52+
as field accessors work as proxies:
53+
54+
```haskell
55+
getLink :: Int -> Link
56+
getLink = fieldLink _get
57+
```
58+
59+
We can also get all links at once, as a record:
60+
61+
```haskell
62+
routesLinks :: Routes (AsLink Link)
63+
routesLinks = allFieldLinks
64+
```
65+
66+
## Client
67+
68+
Even more power starts to show when we generate a record of client functions.
69+
Here we use `genericClientHoist` function, which let us simultaneously
70+
hoist the monad, in this case from `ClientM` to `IO`.
71+
72+
```haskell
73+
cliRoutes :: Routes (AsClientT IO)
74+
cliRoutes = genericClientHoist
75+
(\x -> runClientM x env >>= either throwIO return)
76+
where
77+
env = error "undefined environment"
78+
79+
cliGet :: Int -> IO String
80+
cliGet = _get cliRoutes
81+
```
82+
83+
## Server
84+
85+
Finally, probably the most handy usage: we can convert record of handlers into
86+
the server implementation:
87+
88+
```haskell
89+
record :: Routes AsServer
90+
record = Routes
91+
{ _get = return . show
92+
, _put = return . odd
93+
}
94+
95+
app :: Application
96+
app = genericServe record
97+
98+
main :: IO ()
99+
main = do
100+
args <- getArgs
101+
case args of
102+
("run":_) -> do
103+
putStrLn "Starting cookbook-generic at http://localhost:8000"
104+
run 8000 app
105+
_ -> putStrLn "To run, pass 'run' argument: cabal new-run cookbook-generic run"
106+
```

doc/cookbook/generic/generic.cabal

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: cookbook-generic
2+
version: 0.1
3+
synopsis: Using custom monad to pass a state between handlers
4+
homepage: http://haskell-servant.readthedocs.org/
5+
license: BSD3
6+
license-file: ../../../servant/LICENSE
7+
author: Servant Contributors
8+
maintainer: [email protected]
9+
build-type: Simple
10+
cabal-version: >=1.10
11+
tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.3
12+
13+
executable cookbook-using-custom-monad
14+
main-is: Generic.lhs
15+
build-depends: base == 4.*
16+
, servant
17+
, servant-client
18+
, servant-client-core
19+
, servant-server
20+
, base-compat
21+
, warp >= 3.2
22+
, transformers >= 0.3
23+
default-language: Haskell2010
24+
ghc-options: -Wall -pgmL markdown-unlit
25+
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4

doc/tutorial/tutorial.cabal

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ library
7575
, time >= 1.4.2 && < 1.9
7676

7777
-- For legacy tools, we need to specify build-depends too
78-
build-depends: markdown-unlit >= 0.4.1 && <0.5
79-
build-tool-depends: markdown-unlit:markdown-unlit >= 0.4.1 && <0.5
78+
build-depends: markdown-unlit >= 0.5.0 && <0.6
79+
build-tool-depends: markdown-unlit:markdown-unlit >= 0.5.0 && <0.6
8080

8181
test-suite spec
8282
type: exitcode-stdio-1.0

servant-client-core/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-client-core/CHANGELOG.md)
22
[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
33

4+
0.14.1
5+
------
6+
7+
- Merge in `servant-generic` (by [Patrick Chilton](https://github.com/chpatrick))
8+
into `servant` (`Servant.API.Generic`),
9+
`servant-client-code` (`Servant.Client.Generic`)
10+
and `servant-server` (`Servant.Server.Generic`).
11+
412
0.14
513
----
614

servant-client-core/servant-client-core.cabal

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: servant-client-core
2-
version: 0.14
2+
version: 0.14.1
33
synopsis: Core functionality and class for client function generation for servant APIs
44
description:
55
This library provides backend-agnostic generation of client functions. For
@@ -33,6 +33,7 @@ library
3333
exposed-modules:
3434
Servant.Client.Core
3535
Servant.Client.Free
36+
Servant.Client.Generic
3637
Servant.Client.Core.Reexport
3738
Servant.Client.Core.Internal.Auth
3839
Servant.Client.Core.Internal.BaseUrl
@@ -60,7 +61,7 @@ library
6061

6162
-- Servant dependencies
6263
build-depends:
63-
servant == 0.14.*
64+
servant >= 0.14.1 && <0.15
6465

6566
-- Other dependencies: Lower bound around what is in the latest Stackage LTS.
6667
-- Here can be exceptions if we really need features from the newer versions.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{-# LANGUAGE ConstraintKinds #-}
2+
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE KindSignatures #-}
4+
{-# LANGUAGE RankNTypes #-}
5+
{-# LANGUAGE ScopedTypeVariables #-}
6+
{-# LANGUAGE TypeFamilies #-}
7+
module Servant.Client.Generic (
8+
AsClientT,
9+
genericClient,
10+
genericClientHoist,
11+
) where
12+
13+
import Data.Proxy
14+
(Proxy (..))
15+
16+
import Servant.API.Generic
17+
import Servant.Client.Core
18+
19+
-- | A type that specifies that an API reocrd contains a client implementation.
20+
data AsClientT (m :: * -> *)
21+
instance GenericMode (AsClientT m) where
22+
type AsClientT m :- api = Client m api
23+
24+
-- | Generate a record of client functions.
25+
genericClient
26+
:: forall routes m.
27+
( HasClient m (ToServantApi routes)
28+
, GenericServant routes (AsClientT m)
29+
, Client m (ToServantApi routes) ~ ToServant routes (AsClientT m)
30+
)
31+
=> routes (AsClientT m)
32+
genericClient
33+
= fromServant
34+
$ clientIn (Proxy :: Proxy (ToServantApi routes)) (Proxy :: Proxy m)
35+
36+
-- | 'genericClient' but with 'hoistClientMonad' in between.
37+
genericClientHoist
38+
:: forall routes m n.
39+
( HasClient m (ToServantApi routes)
40+
, GenericServant routes (AsClientT n)
41+
, Client n (ToServantApi routes) ~ ToServant routes (AsClientT n)
42+
)
43+
=> (forall x. m x -> n x) -- ^ natural transformation
44+
-> routes (AsClientT n)
45+
genericClientHoist nt
46+
= fromServant
47+
$ hoistClientMonad m api nt
48+
$ clientIn api m
49+
where
50+
m = Proxy :: Proxy m
51+
api = Proxy :: Proxy (ToServantApi routes)

servant-server/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
[The latest version of this document is on GitHub.](https://github.com/haskell-servant/servant/blob/master/servant-server/CHANGELOG.md)
22
[Changelog for `servant` package contains significant entries for all core packages.](https://github.com/haskell-servant/servant/blob/master/servant/CHANGELOG.md)
33

4+
0.14.1
5+
------
6+
7+
- Merge in `servant-generic` (by [Patrick Chilton](https://github.com/chpatrick))
8+
into `servant` (`Servant.API.Generic`),
9+
`servant-client-code` (`Servant.Client.Generic`)
10+
and `servant-server` (`Servant.Server.Generic`).
11+
12+
- *servant-server* Deprecate `Servant.Utils.StaticUtils`, use `Servant.Server.StaticUtils`.
13+
414
0.14
515
----
616

servant-server/servant-server.cabal

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: servant-server
2-
version: 0.14
2+
version: 0.14.1
33
synopsis: A family of combinators for defining webservices APIs and serving them
44
description:
55
A family of combinators for defining webservices APIs and serving them
@@ -47,6 +47,7 @@ library
4747
Servant
4848
Servant.Server
4949
Servant.Server.Experimental.Auth
50+
Servant.Server.Generic
5051
Servant.Server.Internal
5152
Servant.Server.Internal.BasicAuth
5253
Servant.Server.Internal.Context
@@ -79,7 +80,7 @@ library
7980

8081
-- Servant dependencies
8182
build-depends:
82-
servant == 0.14.*
83+
servant >= 0.14.1 && <0.15
8384

8485
-- Other dependencies: Lower bound around what is in the latest Stackage LTS.
8586
-- Here can be exceptions if we really need features from the newer versions.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{-# LANGUAGE ConstraintKinds #-}
2+
{-# LANGUAGE DataKinds #-}
3+
{-# LANGUAGE FlexibleContexts #-}
4+
{-# LANGUAGE KindSignatures #-}
5+
{-# LANGUAGE ScopedTypeVariables #-}
6+
{-# LANGUAGE TypeFamilies #-}
7+
{-# LANGUAGE TypeOperators #-}
8+
{-# LANGUAGE UndecidableInstances #-}
9+
-- | @since 0.14.1
10+
module Servant.Server.Generic (
11+
AsServerT,
12+
AsServer,
13+
genericServe,
14+
genericServer,
15+
genericServerT,
16+
) where
17+
18+
import Data.Proxy
19+
(Proxy (..))
20+
21+
import Servant.API.Generic
22+
import Servant.Server
23+
24+
-- | A type that specifies that an API record contains a server implementation.
25+
data AsServerT (m :: * -> *)
26+
instance GenericMode (AsServerT m) where
27+
type AsServerT m :- api = ServerT api m
28+
29+
type AsServer = AsServerT Handler
30+
31+
-- | Transform record of routes into a WAI 'Application'.
32+
genericServe
33+
:: forall routes.
34+
( HasServer (ToServantApi routes) '[]
35+
, GenericServant routes AsServer
36+
, Server (ToServantApi routes) ~ ToServant routes AsServer
37+
)
38+
=> routes AsServer -> Application
39+
genericServe = serve (Proxy :: Proxy (ToServantApi routes)) . genericServer
40+
41+
-- | Transform record of endpoints into a 'Server'.
42+
genericServer
43+
:: GenericServant routes AsServer
44+
=> routes AsServer
45+
-> ToServant routes AsServer
46+
genericServer = toServant
47+
48+
genericServerT
49+
:: GenericServant routes (AsServerT m)
50+
=> routes (AsServerT m)
51+
-> ToServant routes (AsServerT m)
52+
genericServerT = toServant

0 commit comments

Comments
 (0)