git.iko.soy/typeable/servant
on master help sign in
add a cookbook recipe for sqlite. cabalized cookbook examples.
8543e00Alp Mestanogullari
doc/cookbook/BasicAuth.lhs → doc/cookbook/basic-auth/BasicAuth.lhsrenamed+3−4
@@ -12,11 +12,10 @@ First, some throat clearing.
12{-# LANGUAGE TypeOperators #-}
12{-# LANGUAGE TypeOperators #-}
13import Control.Concurrent
13import Control.Concurrent
14import Control.Exception
14import Control.Exception
15−import Control.Monad.IO.Class
16import qualified Data.Map as Map
15import qualified Data.Map as Map
17import qualified Data.Text as T
16import qualified Data.Text as T
18import Data.Text.Encoding (decodeUtf8)
17import Data.Text.Encoding (decodeUtf8)
19−import Network.HTTP.Client (Manager, newManager, defaultManagerSettings)
18+import Network.HTTP.Client (newManager, defaultManagerSettings)
20import Network.Wai.Handler.Warp
19import Network.Wai.Handler.Warp
21import Servant
20import Servant
22import Servant.Client
21import Servant.Client
@@ -28,7 +27,7 @@ For the sake of simplicity, it will just be read only but the same code could
28be used with mutable references, database connections, files and more in place
27be used with mutable references, database connections, files and more in place
29of our `Map`.
28of our `Map`.
30
29
31−```
30+``` haskell
32type Username = T.Text
31type Username = T.Text
33type Password = T.Text
32type Password = T.Text
34type Website = T.Text
33type Website = T.Text
@@ -176,4 +175,4 @@ code and see what happens when you specify credentials that are not in the
176database.
175database.
177
176
178The entire program covered here is available as a literate Haskell file
177The entire program covered here is available as a literate Haskell file
179−[here](...).
178+[here](...), along with a `cabal` project.
doc/cookbook/basic-auth/basic-auth.cabalnew file+28
@@ -0,0 +1,28 @@
1+name: cookbook-basic-auth
2+version: 0.1
3+synopsis: Basic Authentication cookbook example
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-basic-auth
13+ main-is: BasicAuth.lhs
14+ build-depends: base == 4.*
15+ , text
16+ , aeson
17+ , containers
18+ , servant
19+ , servant-client
20+ , servant-server
21+ , warp
22+ , wai
23+ , http-types
24+ , markdown-unlit >= 0.4
25+ , http-client
26+ default-language: Haskell2010
27+ ghc-options: -Wall -pgmL markdown-unlit
28+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/cabal.projectnew file+9
@@ -0,0 +1,9 @@
1+packages:
2+ basic-auth/
3+ db-sqlite-simple/
4+ ../../servant
5+ ../../servant-server
6+ ../../servant-client-core
7+ ../../servant-client
8+ ../../servant-docs
9+ ../../servant-foreign
doc/cookbook/db-sqlite-simple/DBConnection.lhsnew file+99
@@ -0,0 +1,99 @@
1+# SQLite database
2+
3+Let's see how we can write a simple web application that uses an
4+[SQLite](https://www.sqlite.org/) database to store simple textual
5+messages. As usual, we start with a little bit of throat clearing.
6+
7+``` haskell
8+{-# LANGUAGE DataKinds #-}
9+{-# LANGUAGE OverloadedStrings #-}
10+{-# LANGUAGE TypeOperators #-}
11+import Control.Concurrent
12+import Control.Exception (bracket)
13+import Control.Monad.IO.Class
14+import Database.SQLite.Simple
15+import Network.HTTP.Client (newManager, defaultManagerSettings)
16+import Network.Wai.Handler.Warp
17+import Servant
18+import Servant.Client
19+```
20+
21+We will only care about a single type here, the messages. We want to
22+be able to add a new one and retrieve them all, using two different
23+endpoints.
24+
25+``` haskell
26+type Message = String
27+
28+type API = ReqBody '[PlainText] Message :> Post '[JSON] NoContent
29+ :<|> Get '[JSON] [Message]
30+
31+api :: Proxy API
32+api = Proxy
33+```
34+
35+We proceed with a simple function for creating a table
36+for holding our messages if it doesn't already exist.
37+
38+``` haskell
39+initDB :: FilePath -> IO ()
40+initDB dbfile = withConnection dbfile $ \conn ->
41+ execute_ conn
42+ "CREATE TABLE IF NOT EXISTS messages (msg text not null)"
43+```
44+
45+Next, our server implementation. It will be parametrised (take as
46+argument) by the name of the file that contains our SQLite database.
47+The handlers are straighforward. One takes care of inserting a new
48+value in the database while the other fetches all messages and returns
49+them. We also provide a function for serving our web app given an
50+SQLite database file, which simply calls servant-server's `serve` function.
51+
52+``` haskell
53+server :: FilePath -> Server API
54+server dbfile = postMessage :<|> getMessages
55+
56+ where postMessage :: Message -> Handler NoContent
57+ postMessage msg = do
58+ liftIO . withConnection dbfile $ \conn ->
59+ execute conn
60+ "INSERT INTO messages VALUES (?)"
61+ (Only msg)
62+ return NoContent
63+
64+ getMessages :: Handler [Message]
65+ getMessages = fmap (map fromOnly) . liftIO $
66+ withConnection dbfile $ \conn ->
67+ query_ conn "SELECT msg FROM messages"
68+
69+runApp :: FilePath -> IO ()
70+runApp dbfile = run 8080 (serve api $ server dbfile)
71+```
72+
73+Let's also derive some clients for our API and use them to
74+insert two messages and retrieve them in `main`.
75+
76+``` haskell
77+postMsg :: Message -> ClientM NoContent
78+getMsgs :: ClientM [Message]
79+postMsg :<|> getMsgs = client api
80+
81+main :: IO ()
82+main = do
83+ -- you could read this from some configuration file,
84+ -- environment variable or somewhere else instead.
85+ let dbfile = "test.db"
86+ initDB dbfile
87+ mgr <- newManager defaultManagerSettings
88+ bracket (forkIO $ runApp dbfile) killThread $ \_ -> do
89+ ms <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do
90+ postMsg "hello"
91+ postMsg "world"
92+ getMsgs
93+ print ms
94+```
95+
96+This program prints `Right ["hello","world"]` the first time it is executed,
97+`Right ["hello","world","hello","world"]` the second time and so on.
98+
99+The entire source for this example is available [here](...).
doc/cookbook/db-sqlite-simple/db-sqlite-simple.cabalnew file+28
@@ -0,0 +1,28 @@
1+name: cookbook-db-sqlite-simple
2+version: 0.1
3+synopsis: Simple SQLite DB cookbook example
4+homepage: http://haskell-servant.readthedocs.org/
5+license: BSD3
6+license-file: ../../../servant/LICENSE
7+author: Servant Contributors
8+maintainer: haskell-servant-maintainers@googlegroups.com
9+build-type: Simple
10+cabal-version: >=1.10
11+
12+executable cookbook-db-sqlite-simple
13+ main-is: DBConnection.lhs
14+ build-depends: base == 4.*
15+ , text
16+ , aeson
17+ , servant
18+ , servant-client
19+ , servant-server
20+ , warp
21+ , wai
22+ , http-types
23+ , markdown-unlit >= 0.4
24+ , http-client
25+ , sqlite-simple
26+ default-language: Haskell2010
27+ ghc-options: -Wall -pgmL markdown-unlit
28+ build-tool-depends: markdown-unlit:markdown-unlit
doc/cookbook/index.rst+2−1
@@ -17,4 +17,5 @@ you name it!
17.. toctree::
17.. toctree::
18 :maxdepth: 1
18 :maxdepth: 1
19
19
20− BasicAuth.lhs
20+ basic-auth/BasicAuth.lhs
21+ db-sqlite-simple/DBConnection.lhs