packages feed

servant-examples (empty) → 0.4.2

raw patch · 18 files changed

+923/−0 lines, 18 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, directory, either, http-types, js-jquery, lucid, random, servant, servant-client, servant-docs, servant-jquery, servant-lucid, servant-server, text, time, transformers, wai, wai-extra, warp

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Alp Mestanogullari++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Alp Mestanogullari nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ auth-combinator/auth-combinator.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+import Data.Aeson+import Data.ByteString (ByteString)+import Data.Text (Text)+import GHC.Generics+import Network.HTTP.Types+import Network.Wai+import Network.Wai.Handler.Warp+import Servant+import Servant.Server.Internal++-- Pretty much stolen/adapted from+-- https://github.com/haskell-servant/HaskellSGMeetup2015/blob/master/examples/authentication-combinator/AuthenticationCombinator.hs++type DBLookup = ByteString -> IO Bool++isGoodCookie :: DBLookup+isGoodCookie = return . (== "good password")++data AuthProtected++instance HasServer rest => HasServer (AuthProtected :> rest) where+  type ServerT (AuthProtected :> rest) m = ServerT rest m++  route Proxy a request respond =+    case lookup "Cookie" (requestHeaders request) of+      Nothing -> respond . succeedWith $ responseLBS status401 [] "Missing auth header."+      Just v  -> do+        authGranted <- isGoodCookie v+        if authGranted+          then route (Proxy :: Proxy rest) a request respond+          else respond . succeedWith $ responseLBS status403 [] "Invalid cookie."++type PrivateAPI = Get '[JSON] [PrivateData]++type PublicAPI = Get '[JSON] [PublicData]++type API = "private" :> AuthProtected :> PrivateAPI+      :<|> PublicAPI++newtype PrivateData = PrivateData { ssshhh :: Text }+  deriving (Eq, Show, Generic)++instance ToJSON PrivateData++newtype PublicData = PublicData { somedata :: Text }+  deriving (Eq, Show, Generic)++instance ToJSON PublicData++api :: Proxy API+api = Proxy++server :: Server API+server = return prvdata :<|> return pubdata++  where prvdata = [PrivateData "this is a secret"]+        pubdata = [PublicData "this is a public piece of data"]++main :: IO ()+main = run 8080 (serve api server)++{- Sample session:+$ curl http://localhost:8080/+[{"somedata":"this is a public piece of data"}]+$ curl http://localhost:8080/private+Missing auth header.+$ curl -H "Cookie: good password" http://localhost:8080/private+[{"ssshhh":"this is a secret"}]+$ curl -H "Cookie: bad password" http://localhost:8080/private+Invalid cookie.+-}
+ hackage/hackage.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE OverloadedStrings #-}+import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Trans.Either+import Data.Aeson+import Data.Monoid+import Data.Proxy+import Data.Text (Text)+import GHC.Generics+import Servant.API+import Servant.Client++import qualified Data.Text    as T+import qualified Data.Text.IO as T++type HackageAPI =+       "users" :> Get '[JSON] [UserSummary]+  :<|> "user" :> Capture "username" Username :> Get '[JSON] UserDetailed+  :<|> "packages" :> Get '[JSON] [Package]++type Username = Text++data UserSummary = UserSummary+  { summaryUsername :: Username+  , summaryUserid   :: Int+  } deriving (Eq, Show)++instance FromJSON UserSummary where+  parseJSON (Object o) =+    UserSummary <$> o .: "username"+                <*> o .: "userid"++  parseJSON _ = mzero++type Group = Text++data UserDetailed = UserDetailed+  { username :: Username+  , userid   :: Int+  , groups   :: [Group]+  } deriving (Eq, Show, Generic)++instance FromJSON UserDetailed++newtype Package = Package { packageName :: Text }+  deriving (Eq, Show, Generic)++instance FromJSON Package++hackageAPI :: Proxy HackageAPI+hackageAPI = Proxy++getUsers :: EitherT ServantError IO [UserSummary]+getUser :: Username -> EitherT ServantError IO UserDetailed+getPackages :: EitherT ServantError IO [Package]+getUsers :<|> getUser :<|> getPackages = client hackageAPI $ BaseUrl Http "hackage.haskell.org" 80++main :: IO ()+main = print =<< uselessNumbers++uselessNumbers :: IO (Either ServantError ())+uselessNumbers = runEitherT $ do+  users <- getUsers+  liftIO . putStrLn $ show (length users) ++ " users"++  user <- liftIO $ do+    putStrLn "Enter a valid hackage username"+    T.getLine+  userDetailed <- (getUser user)+  liftIO . T.putStrLn $ user <> " maintains " <> T.pack (show (length $ groups userDetailed)) <> " packages"++  packages <- getPackages+  let monadPackages = filter (isMonadPackage . packageName) packages+  liftIO . putStrLn $ show (length monadPackages) ++ " monad packages"++  where isMonadPackage = T.isInfixOf "monad"
+ servant-examples.cabal view
@@ -0,0 +1,95 @@+name:                servant-examples+version:             0.4.2+synopsis:            Example programs for servant+description:         Example programs for servant,+                     showcasing solutions to common needs.+homepage:            http://haskell-servant.github.io/+license:             BSD3+license-file:        LICENSE+author:              Alp Mestanogullari+maintainer:          alpmestan@gmail.com+-- copyright:+category:            Web+build-type:          Simple+cabal-version:       >=1.10++executable tutorial+  main-is: tutorial.hs+  other-modules: T1, T2, T3, T4, T5, T6, T7, T8, T9, T10+  build-depends:+      aeson >= 0.8+    , base >= 4.7 && < 5+    , bytestring+    , directory+    , either+    , http-types+    , js-jquery+    , lucid+    , random+    , servant == 0.4.*+    , servant-docs == 0.4.*+    , servant-jquery == 0.4.*+    , servant-lucid == 0.4.*+    , servant-server == 0.4.*+    , text+    , time+    , transformers+    , wai+    , warp+  hs-source-dirs: tutorial+  default-language: Haskell2010++executable t8-main+  main-is: t8-main.hs+  hs-source-dirs: tutorial+  default-language: Haskell2010+  build-depends:+      aeson+    , base >= 4.7 && < 5+    , either+    , servant == 0.4.*+    , servant-client == 0.4.*+    , servant-server == 0.4.*+    , wai++executable hackage+  main-is:             hackage.hs+  build-depends:+      aeson >= 0.8+    , base >=4.7 && < 5+    , either+    , servant == 0.4.*+    , servant-client == 0.4.*+    , text+    , transformers+  hs-source-dirs:      hackage+  default-language:    Haskell2010++executable wai-middleware+  main-is: wai-middleware.hs+  build-depends:+      aeson >= 0.8+    , base >= 4.7 && < 5+    , servant == 0.4.*+    , servant-server == 0.4.*+    , text+    , wai+    , wai-extra+    , warp+  hs-source-dirs: wai-middleware+  default-language: Haskell2010++executable auth-combinator+  main-is: auth-combinator.hs+  build-depends:+      aeson >= 0.8+    , base >= 4.7 && < 5+    , bytestring+    , http-types+    , servant == 0.4.*+    , servant-server == 0.4.*+    , text+    , wai+    , warp+  hs-source-dirs: auth-combinator+  default-language: Haskell2010
+ tutorial/T1.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+module T1 where++import Data.Aeson+import Data.Time.Calendar+import GHC.Generics+import Network.Wai+import Servant++data User = User+  { name :: String+  , age :: Int+  , email :: String+  , registration_date :: Day+  } deriving (Eq, Show, Generic)++-- orphan ToJSON instance for Day. necessary to derive one for User+instance ToJSON Day where+  -- display a day in YYYY-mm-dd format+  toJSON d = toJSON (showGregorian d)++instance ToJSON User++type UserAPI = "users" :> Get '[JSON] [User]++users :: [User]+users = +  [ User "Isaac Newton"    372 "isaac@newton.co.uk" (fromGregorian 1683  3 1)+  , User "Albert Einstein" 136 "ae@mc2.org"         (fromGregorian 1905 12 1)+  ]++userAPI :: Proxy UserAPI+userAPI = Proxy++server :: Server UserAPI+server = return users++app :: Application+app = serve userAPI server
+ tutorial/T10.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module T10 where++import Data.ByteString.Lazy (ByteString)+import Data.Text.Lazy (pack)+import Data.Text.Lazy.Encoding (encodeUtf8)+import Network.HTTP.Types+import Network.Wai+import Servant+import Servant.Docs+import qualified T3++type DocsAPI = T3.API :<|> Raw++instance ToCapture (Capture "x" Int) where+  toCapture _ = DocCapture "x" "(integer) position on the x axis"++instance ToCapture (Capture "y" Int) where+  toCapture _ = DocCapture "y" "(integer) position on the y axis"++instance ToSample T3.Position T3.Position where+  toSample _ = Just (T3.Position 3 14)++instance ToParam (QueryParam "name" String) where+  toParam _ =+    DocQueryParam "name"+                  ["Alp", "John Doe", "..."]+                  "Name of the person to say hello to."+                  Normal++instance ToSample T3.HelloMessage T3.HelloMessage where+  toSamples _ =+    [ ("When a value is provided for 'name'", T3.HelloMessage "Hello, Alp")+    , ("When 'name' is not specified", T3.HelloMessage "Hello, anonymous coward")+    ]++ci :: T3.ClientInfo+ci = T3.ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"]++instance ToSample T3.ClientInfo T3.ClientInfo where+  toSample _ = Just ci++instance ToSample T3.Email T3.Email where+  toSample _ = Just (T3.emailForClient ci)++api :: Proxy DocsAPI+api = Proxy++docsBS :: ByteString+docsBS = encodeUtf8+       . pack+       . markdown+       $ docsWithIntros [intro] T3.api++  where intro = DocIntro "Welcome" ["This is our super webservice's API.", "Enjoy!"]++server :: Server DocsAPI+server = T3.server :<|> serveDocs++  where serveDocs _ respond =+          respond $ responseLBS ok200 [plain] docsBS++        plain = ("Content-Type", "text/plain")++app :: Application+app = serve api server
+ tutorial/T2.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+module T2 where++import Data.Aeson+import Data.Time.Calendar+import GHC.Generics+import Network.Wai+import Servant++data User = User+  { name :: String+  , age :: Int+  , email :: String+  , registration_date :: Day+  } deriving (Eq, Show, Generic)++-- orphan ToJSON instance for Day. necessary to derive one for User+instance ToJSON Day where+  -- display a day in YYYY-mm-dd format+  toJSON d = toJSON (showGregorian d)++instance ToJSON User++type UserAPI = "users" :> Get '[JSON] [User]+          :<|> "albert" :> Get '[JSON] User+          :<|> "isaac" :> Get '[JSON] User++isaac :: User+isaac = User "Isaac Newton" 372 "isaac@newton.co.uk" (fromGregorian 1683 3 1)++albert :: User+albert = User "Albert Einstein" 136 "ae@mc2.org" (fromGregorian 1905 12 1)++users :: [User]+users = [isaac, albert]++userAPI :: Proxy UserAPI+userAPI = Proxy++server :: Server UserAPI+server = return users+    :<|> return albert+    :<|> return isaac++app :: Application+app = serve userAPI server
+ tutorial/T3.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+module T3 where++import Control.Monad.Trans.Either+import Data.Aeson+import Data.List+import GHC.Generics+import Network.Wai+import Servant++data Position = Position+  { x :: Int+  , y :: Int+  } deriving (Show, Generic)++instance FromJSON Position+instance ToJSON Position++newtype HelloMessage = HelloMessage { msg :: String }+  deriving (Show, Generic)++instance FromJSON HelloMessage+instance ToJSON HelloMessage++data ClientInfo = ClientInfo+  { name :: String+  , email :: String+  , age :: Int+  , interested_in :: [String]+  } deriving (Show, Generic)++instance FromJSON ClientInfo+instance ToJSON ClientInfo++data Email = Email+  { from :: String+  , to :: String+  , subject :: String+  , body :: String+  } deriving (Show, Generic)++instance FromJSON Email+instance ToJSON Email++emailForClient :: ClientInfo -> Email+emailForClient c = Email from' to' subject' body'++  where from'    = "great@company.com"+        to'      = email c+        subject' = "Hey " ++ name c ++ ", we miss you!"+        body'    = "Hi " ++ name c ++ ",\n\n"+                ++ "Since you've recently turned " ++ show (age c)+                ++ ", have you checked out our latest "+                ++ intercalate ", " (interested_in c)+                ++ " products? Give us a visit!"++type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position+      :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage+      :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email++api :: Proxy API+api = Proxy++server :: Server API+server = position+    :<|> hello+    :<|> marketing++  where position :: Int -> Int -> EitherT ServantErr IO Position+        position x y = return (Position x y)++        hello :: Maybe String -> EitherT ServantErr IO HelloMessage+        hello mname = return . HelloMessage $ case mname of+          Nothing -> "Hello, anonymous coward"+          Just n  -> "Hello, " ++ n++        marketing :: ClientInfo -> EitherT ServantErr IO Email+        marketing clientinfo = return (emailForClient clientinfo)++app :: Application+app = serve api server
+ tutorial/T4.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+module T4 where++import Data.Aeson+import Data.Foldable (foldMap)+import GHC.Generics+import Lucid+import Network.Wai+import Servant+import Servant.HTML.Lucid++data Person = Person+  { firstName :: String+  , lastName  :: String+  , age       :: Int+  } deriving Generic -- for the JSON instance++-- JSON serialization+instance ToJSON Person++-- HTML serialization of a single person+instance ToHtml Person where+  toHtml p =+    tr_ $ do+      td_ (toHtml $ firstName p)+      td_ (toHtml $ lastName p)+      td_ (toHtml . show $ age p)++  toHtmlRaw = toHtml++-- HTML serialization of a list of persons+instance ToHtml [Person] where+  toHtml persons = table_ $ do+    tr_ $ do+      td_ "first name"+      td_ "last name"+      td_ "age"++    foldMap toHtml persons++  toHtmlRaw = toHtml++persons :: [Person]+persons =+  [ Person "Isaac"  "Newton"   372+  , Person "Albert" "Einstein" 136+  ]++type PersonAPI = "persons" :> Get '[JSON, HTML] [Person]++personAPI :: Proxy PersonAPI+personAPI = Proxy++server :: Server PersonAPI+server = return persons++app :: Application+app = serve personAPI server
+ tutorial/T5.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE OverloadedStrings #-}+module T5 where++import Control.Monad.IO.Class+import Control.Monad.Trans.Either+import Data.Aeson+import GHC.Generics+import Network.Wai+import Servant+import System.Directory++type IOAPI = "myfile.txt" :> Get '[JSON] FileContent++ioAPI :: Proxy IOAPI+ioAPI = Proxy++newtype FileContent = FileContent+  { content :: String }+  deriving Generic++instance ToJSON FileContent++server :: Server IOAPI+server = do+  exists <- liftIO (doesFileExist "myfile.txt")+  if exists+    then liftIO (readFile "myfile.txt") >>= return . FileContent+    else left custom404Err++  where custom404Err = err404 { errBody = "myfile.txt just isn't there, please leave this server alone." }++app :: Application+app = serve ioAPI server
+ tutorial/T6.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+module T6 where++import Network.Wai+import Servant++type API = "code" :> Raw++api :: Proxy API+api = Proxy++server :: Server API+server = serveDirectory "tutorial"++app :: Application+app = serve api server
+ tutorial/T7.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+module T7 where++import Control.Monad.Trans.Either+import Control.Monad.Trans.Reader+import Network.Wai+import Servant++type ReaderAPI = "a" :> Get '[JSON] Int+            :<|> "b" :> Get '[JSON] String++readerAPI :: Proxy ReaderAPI+readerAPI = Proxy++readerServerT :: ServerT ReaderAPI (Reader String)+readerServerT = a :<|> b++  where a :: Reader String Int+        a = return 1797++        b :: Reader String String+        b = ask++readerServer :: Server ReaderAPI+readerServer = enter readerToEither readerServerT++  where readerToEither :: Reader String :~> EitherT ServantErr IO+        readerToEither = Nat $ \r -> return (runReader r "hi")++app :: Application+app = serve readerAPI readerServer
+ tutorial/T8.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+module T8 where++import Control.Monad.Trans.Either+import Data.Aeson+import Servant+import Servant.Client++import T3++position :: Int -- ^ value for "x"+         -> Int -- ^ value for "y"+         -> EitherT ServantError IO Position++hello :: Maybe String -- ^ an optional value for "name"+      -> EitherT ServantError IO HelloMessage++marketing :: ClientInfo -- ^ value for the request body+          -> EitherT ServantError IO Email++position :<|> hello :<|> marketing = client api baseUrl++baseUrl :: BaseUrl+baseUrl = BaseUrl Http "localhost" 8081++queries :: EitherT ServantError IO (Position, HelloMessage, Email)+queries = do+  pos <- position 10 10+  msg <- hello (Just "servant")+  em  <- marketing (ClientInfo "Alp" "alp@foo.com" 26 ["haskell", "mathematics"])+  return (pos, msg, em)++run :: IO ()+run = do+  res <- runEitherT queries+  case res of+    Left err -> putStrLn $ "Error: " ++ show err+    Right (pos, msg, em) -> do+      print pos+      print msg+      print em
+ tutorial/T9.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE OverloadedStrings #-}+module T9 where++import Control.Applicative+import Control.Monad.IO.Class+import Data.Aeson+import Data.Text (Text)+import GHC.Generics+import Network.Wai+import Servant+import Servant.JQuery+import System.Random++import qualified Data.Text                  as T+import qualified Language.Javascript.JQuery as JQ++data Point = Point+  { x :: Double+  , y :: Double+  } deriving Generic++instance ToJSON Point++randomPoint :: MonadIO m => m Point+randomPoint = liftIO . getStdRandom $ \g ->+  let (rx, g')  = randomR (-1, 1) g+      (ry, g'') = randomR (-1, 1) g'+  in (Point rx ry, g'')++data Search a = Search+  { query   :: Text+  , results :: [a]+  } deriving Generic++mkSearch :: Text -> [a] -> Search a+mkSearch = Search++instance ToJSON a => ToJSON (Search a)++data Book = Book+  { author :: Text+  , title  :: Text+  , year   :: Int+  } deriving Generic++instance ToJSON Book++book :: Text -> Text -> Int -> Book+book = Book++books :: [Book]+books =+  [ book "Paul Hudak" "The Haskell School of Expression: Learning Functional Programming through Multimedia" 2000+  , book "Bryan O'Sullivan, Don Stewart, and John Goerzen" "Real World Haskell" 2008+  , book "Miran Lipovača" "Learn You a Haskell for Great Good!" 2011+  , book "Graham Hutton" "Programming in Haskell" 2007+  , book "Simon Marlow" "Parallel and Concurrent Programming in Haskell" 2013+  , book "Richard Bird" "Introduction to Functional Programming using Haskell" 1998+  ]++searchBook :: Monad m => Maybe Text -> m (Search Book)+searchBook Nothing  = return (mkSearch "" books)+searchBook (Just q) = return (mkSearch q books')++  where books' = filter (\b -> q' `T.isInfixOf` T.toLower (author b)+                            || q' `T.isInfixOf` T.toLower (title b)+                        )+                        books+        q' = T.toLower q++type API = "point" :> Get '[JSON] Point+      :<|> "books" :> QueryParam "q" Text :> Get '[JSON] (Search Book)++type API' = API :<|> Raw++api :: Proxy API+api = Proxy++api' :: Proxy API'+api' = Proxy++server :: Server API+server = randomPoint+    :<|> searchBook++server' :: Server API'+server' = server+     :<|> serveDirectory "tutorial/t9"++apiJS :: String+apiJS = jsForAPI api++writeJSFiles :: IO ()+writeJSFiles = do+  writeFile "tutorial/t9/api.js" apiJS+  jq <- readFile =<< JQ.file+  writeFile "tutorial/t9/jq.js" jq++app :: Application+app = serve api' server'
+ tutorial/t8-main.hs view
@@ -0,0 +1,4 @@+import T8++main :: IO ()+main = run
+ tutorial/tutorial.hs view
@@ -0,0 +1,39 @@+import Network.Wai+import Network.Wai.Handler.Warp+import System.Environment++import qualified T1+import qualified T2+import qualified T3+import qualified T4+import qualified T5+import qualified T6+import qualified T7+import qualified T9+import qualified T10++app :: String -> (Application -> IO ()) -> IO ()+app n f = case n of+  "1" -> f T1.app+  "2" -> f T2.app+  "3" -> f T3.app+  "4" -> f T4.app+  "5" -> f T5.app+  "6" -> f T6.app+  "7" -> f T7.app+  "8" -> f T3.app+  "9" -> T9.writeJSFiles >> f T9.app+  "10" -> f T10.app+  _   -> usage++main :: IO ()+main = do+  args <- getArgs+  case args of+    [n] -> app n (run 8081)+    _   -> usage++usage :: IO ()+usage = do+  putStrLn "Usage:\t tutorial N"+  putStrLn "\t\twhere N is the number of the example you want to run."
+ wai-middleware/wai-middleware.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+import Data.Aeson+import Data.Text+import GHC.Generics+import Network.Wai+import Network.Wai.Handler.Warp+import Network.Wai.Middleware.RequestLogger+import Servant++data Product = Product+  { name              :: Text+  , brand             :: Text+  , current_price_eur :: Double+  , available         :: Bool+  } deriving (Eq, Show, Generic)++instance ToJSON Product++products :: [Product]+products = [p1, p2]++  where p1 = Product "Haskell laptop sticker"+                     "GHC Industries"+                     2.50+                     True++        p2 = Product "Foldable USB drive"+                     "Well-Typed"+                     13.99+                     False++type SimpleAPI = Get '[JSON] [Product]++simpleAPI :: Proxy SimpleAPI+simpleAPI = Proxy++server :: Server SimpleAPI+server = return products++-- logStdout :: Middleware+-- i.e, logStdout :: Application -> Application+-- serve :: Proxy api -> Server api -> Application+-- so applying a middleware is really as simple as+-- applying a function to the result of 'serve'+app :: Application+app = logStdout (serve simpleAPI server)++main :: IO ()+main = run 8080 app