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 +30/−0
- Setup.hs +2/−0
- auth-combinator/auth-combinator.hs +78/−0
- hackage/hackage.hs +80/−0
- servant-examples.cabal +95/−0
- tutorial/T1.hs +42/−0
- tutorial/T10.hs +71/−0
- tutorial/T2.hs +49/−0
- tutorial/T3.hs +84/−0
- tutorial/T4.hs +63/−0
- tutorial/T5.hs +37/−0
- tutorial/T6.hs +18/−0
- tutorial/T7.hs +33/−0
- tutorial/T8.hs +43/−0
- tutorial/T9.hs +104/−0
- tutorial/t8-main.hs +4/−0
- tutorial/tutorial.hs +39/−0
- wai-middleware/wai-middleware.hs +51/−0
+ 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