git.iko.soy/typeable/servant
on master help sign in
add a cookbook recipe for sqlite. cabalized cookbook examples.
commit8543e00aaa06542d92a14d55be924a0c7eb7daf6 authorAlp Mestanogullari <alpmestan@gmail.com> dateSat 2 Dec 2017, 00:00 (9 years ago) parent606a4a6
6 files changed+169−5
doc/cookbook/BasicAuth.lhs → doc/cookbook/basic-auth/BasicAuth.lhs+3−4 doc/cookbook/basic-auth/basic-auth.cabal+28 doc/cookbook/cabal.project+9 doc/cookbook/db-sqlite-simple/DBConnection.lhs+99 doc/cookbook/db-sqlite-simple/db-sqlite-simple.cabal+28 doc/cookbook/index.rst+2−1
doc/cookbook/BasicAuth.lhs → doc/cookbook/basic-auth/BasicAuth.lhsrenamed+3−4
@@ -12,11 +12,10 @@ First, some throat clearing.
1212{-# LANGUAGE TypeOperators #-}
1313import Control.Concurrent
1414import Control.Exception
15−import Control.Monad.IO.Class
1615import qualified Data.Map as Map
1716import qualified Data.Text as T
1817import Data.Text.Encoding (decodeUtf8)
19−import Network.HTTP.Client (Manager, newManager, defaultManagerSettings)
18+import Network.HTTP.Client (newManager, defaultManagerSettings)
2019import Network.Wai.Handler.Warp
2120import Servant
2221import Servant.Client
@@ -28,7 +27,7 @@ For the sake of simplicity, it will just be read only but the same code could
2827be used with mutable references, database connections, files and more in place
2928of our `Map`.
3029
31−```
30+``` haskell
3231type Username = T.Text
3332type Password = T.Text
3433type Website = T.Text
@@ -176,4 +175,4 @@ code and see what happens when you specify credentials that are not in the
176175database.
177176
178177The 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!
1717.. toctree::
1818 :maxdepth: 1
1919
20− BasicAuth.lhs
20+ basic-auth/BasicAuth.lhs
21+ db-sqlite-simple/DBConnection.lhs