git.iko.soy/typeable/servant
on master help sign in
Merge pull request #53 from haskell-servant/servant-examples

Add a servant-examples dir with 3 "classical" examples (and more to come?)

commit6253b8af1b09e830f4efe9a0794f70955f24976a authorAlp Mestanogullari <alpmestan@gmail.com> dateFri 24 Apr 2015, 16:59 (11 years ago) parent6651df54c86c73
8 files changed+308−5
scripts/shell.nix+7−5 servant-examples/LICENSE+30 servant-examples/Setup.hs+2 servant-examples/auth-combinator/auth-combinator.hs+78 servant-examples/hackage/hackage.hs+83 servant-examples/servant-examples.cabal+56 servant-examples/wai-middleware/wai-middleware.hs+51 sources.txt+1
scripts/shell.nix+7−5
@@ -5,16 +5,18 @@ let modifiedHaskellPackages = haskellngPackages.override {
55 overrides = with haskell-ng.lib ; self: super: {
66 servant = appendConfigureFlag ( self.callPackage ../servant {} )
77 "--ghc-options=-Werror";
8− servant-server = appendConfigureFlag (self.callPackage
8+ servant-server = appendConfigureFlag (self.callPackage
99 ../servant-server {}) "--ghc-options=-Werror";
10− servant-client = appendConfigureFlag (self.callPackage
10+ servant-client = appendConfigureFlag (self.callPackage
1111 ../servant-client {}) "--ghc-options=-Werror";
12− servant-jquery = appendConfigureFlag (self.callPackage
12+ servant-jquery = appendConfigureFlag (self.callPackage
1313 ../servant-jquery {}) "--ghc-options=-Werror";
14− servant-docs = appendConfigureFlag (self.callPackage ../servant-docs
14+ servant-docs = appendConfigureFlag (self.callPackage ../servant-docs
15+ {}) "--ghc-options=-Werror";
16+ servant-examples = appendConfigureFlag (self.callPackage ../servant-examples
1517 {}) "--ghc-options=-Werror";
1618 };
1719 };
1820in modifiedHaskellPackages.ghcWithPackages ( p : with p ; [
19− servant servant-server servant-client servant-jquery servant-docs
21+ servant servant-server servant-client servant-jquery servant-docs servant-examples
2022])
servant-examples/LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright (c) 2015, Alp Mestanogullari
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Alp Mestanogullari nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
servant-examples/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
servant-examples/auth-combinator/auth-combinator.hsnew file+78
@@ -0,0 +1,78 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE TypeFamilies #-}
3+{-# LANGUAGE DeriveGeneric #-}
4+{-# LANGUAGE TypeOperators #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE ScopedTypeVariables #-}
8+import Data.Aeson
9+import Data.ByteString (ByteString)
10+import Data.Text (Text)
11+import GHC.Generics
12+import Network.HTTP.Types
13+import Network.Wai
14+import Network.Wai.Handler.Warp
15+import Servant
16+import Servant.Server.Internal
17+
18+-- Pretty much stolen/adapted from
19+-- https://github.com/haskell-servant/HaskellSGMeetup2015/blob/master/examples/authentication-combinator/AuthenticationCombinator.hs
20+
21+type DBLookup = ByteString -> IO Bool
22+
23+isGoodCookie :: DBLookup
24+isGoodCookie = return . (== "good password")
25+
26+data AuthProtected
27+
28+instance HasServer rest => HasServer (AuthProtected :> rest) where
29+ type ServerT' (AuthProtected :> rest) m = ServerT' rest m
30+
31+ route Proxy a request respond =
32+ case lookup "Cookie" (requestHeaders request) of
33+ Nothing -> respond . succeedWith $ responseLBS status401 [] "Missing auth header."
34+ Just v -> do
35+ authGranted <- isGoodCookie v
36+ if authGranted
37+ then route (Proxy :: Proxy rest) a request respond
38+ else respond . succeedWith $ responseLBS status403 [] "Invalid cookie."
39+
40+type PrivateAPI = Get '[JSON] [PrivateData]
41+
42+type PublicAPI = Get '[JSON] [PublicData]
43+
44+type API = "private" :> AuthProtected :> PrivateAPI
45+ :<|> PublicAPI
46+
47+newtype PrivateData = PrivateData { ssshhh :: Text }
48+ deriving (Eq, Show, Generic)
49+
50+instance ToJSON PrivateData
51+
52+newtype PublicData = PublicData { somedata :: Text }
53+ deriving (Eq, Show, Generic)
54+
55+instance ToJSON PublicData
56+
57+api :: Proxy API
58+api = Proxy
59+
60+server :: Server API
61+server = return prvdata :<|> return pubdata
62+
63+ where prvdata = [PrivateData "this is a secret"]
64+ pubdata = [PublicData "this is a public piece of data"]
65+
66+main :: IO ()
67+main = run 8080 (serve api server)
68+
69+{- Sample session:
70+$ curl http://localhost:8080/
71+[{"somedata":"this is a public piece of data"}]
72+$ curl http://localhost:8080/private
73+Missing auth header.
74+$ curl -H "Cookie: good password" http://localhost:8080/private
75+[{"ssshhh":"this is a secret"}]
76+$ curl -H "Cookie: bad password" http://localhost:8080/private
77+Invalid cookie.
78+-}
servant-examples/hackage/hackage.hsnew file+83
@@ -0,0 +1,83 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE TypeOperators #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+import Control.Applicative
6+import Control.Monad
7+import Control.Monad.IO.Class
8+import Control.Monad.Trans.Either
9+import Data.Aeson
10+import Data.Monoid
11+import Data.Proxy
12+import Data.Text (Text)
13+import GHC.Generics
14+import Servant.API
15+import Servant.Client
16+
17+import qualified Data.Text as T
18+import qualified Data.Text.IO as T
19+
20+type HackageAPI =
21+ "users" :> Get '[JSON] [UserSummary]
22+ :<|> "user" :> Capture "username" Username :> Get '[JSON] UserDetailed
23+ :<|> "packages" :> Get '[JSON] [Package]
24+
25+type Username = Text
26+
27+data UserSummary = UserSummary
28+ { summaryUsername :: Username
29+ , summaryUserid :: Int
30+ } deriving (Eq, Show)
31+
32+instance FromJSON UserSummary where
33+ parseJSON (Object o) =
34+ UserSummary <$> o .: "username"
35+ <*> o .: "userid"
36+
37+ parseJSON _ = mzero
38+
39+type Group = Text
40+
41+data UserDetailed = UserDetailed
42+ { username :: Username
43+ , userid :: Int
44+ , groups :: [Group]
45+ } deriving (Eq, Show, Generic)
46+
47+instance FromJSON UserDetailed
48+
49+newtype Package = Package { packageName :: Text }
50+ deriving (Eq, Show, Generic)
51+
52+instance FromJSON Package
53+
54+hackageAPI :: Proxy HackageAPI
55+hackageAPI = Proxy
56+
57+getUsers :: BaseUrl -> EitherT ServantError IO [UserSummary]
58+getUser :: Username -> BaseUrl -> EitherT ServantError IO UserDetailed
59+getPackages :: BaseUrl -> EitherT ServantError IO [Package]
60+getUsers :<|> getUser :<|> getPackages = client hackageAPI
61+
62+run :: (BaseUrl -> r) -> r
63+run f = f (BaseUrl Http "hackage.haskell.org" 80)
64+
65+main :: IO ()
66+main = print =<< uselessNumbers
67+
68+uselessNumbers :: IO (Either ServantError ())
69+uselessNumbers = runEitherT $ do
70+ users <- run getUsers
71+ liftIO . putStrLn $ show (length users) ++ " users"
72+
73+ user <- liftIO $ do
74+ putStrLn "Enter a valid hackage username"
75+ T.getLine
76+ userDetailed <- run (getUser user)
77+ liftIO . T.putStrLn $ user <> " maintains " <> T.pack (show (length $ groups userDetailed)) <> " packages"
78+
79+ packages <- run getPackages
80+ let monadPackages = filter (isMonadPackage . packageName) packages
81+ liftIO . putStrLn $ show (length monadPackages) ++ " monad packages"
82+
83+ where isMonadPackage = T.isInfixOf "monad"
servant-examples/servant-examples.cabalnew file+56
@@ -0,0 +1,56 @@
1+name: servant-examples
2+version: 0.3
3+synopsis: Example programs for servant
4+description: Example programs for servant,
5+ showcasing solutions to common needs.
6+homepage: http://haskell-servant.github.io/
7+license: BSD3
8+license-file: LICENSE
9+author: Alp Mestanogullari
10+maintainer: alpmestan@gmail.com
11+-- copyright:
12+category: Web
13+build-type: Simple
14+cabal-version: >=1.10
15+
16+executable hackage
17+ main-is: hackage.hs
18+ build-depends:
19+ aeson >= 0.8
20+ , base >=4.7
21+ , either
22+ , servant
23+ , servant-client
24+ , text
25+ , transformers
26+ hs-source-dirs: hackage
27+ default-language: Haskell2010
28+
29+executable wai-middleware
30+ main-is: wai-middleware.hs
31+ build-depends:
32+ aeson >= 0.8
33+ , base >= 4.7
34+ , servant
35+ , servant-server
36+ , text
37+ , wai
38+ , wai-extra
39+ , warp
40+ hs-source-dirs: wai-middleware
41+ default-language: Haskell2010
42+
43+executable auth-combinator
44+ main-is: auth-combinator.hs
45+ build-depends:
46+ aeson >= 0.8
47+ , base >= 4.7
48+ , bytestring
49+ , http-types
50+ , servant
51+ , servant-server
52+ , text
53+ , wai
54+ , warp
55+ hs-source-dirs: auth-combinator
56+ default-language: Haskell2010
servant-examples/wai-middleware/wai-middleware.hsnew file+51
@@ -0,0 +1,51 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+import Data.Aeson
5+import Data.Text
6+import GHC.Generics
7+import Network.Wai
8+import Network.Wai.Handler.Warp
9+import Network.Wai.Middleware.RequestLogger
10+import Servant
11+
12+data Product = Product
13+ { name :: Text
14+ , brand :: Text
15+ , current_price_eur :: Double
16+ , available :: Bool
17+ } deriving (Eq, Show, Generic)
18+
19+instance ToJSON Product
20+
21+products :: [Product]
22+products = [p1, p2]
23+
24+ where p1 = Product "Haskell laptop sticker"
25+ "GHC Industries"
26+ 2.50
27+ True
28+
29+ p2 = Product "Foldable USB drive"
30+ "Well-Typed"
31+ 13.99
32+ False
33+
34+type SimpleAPI = Get '[JSON] [Product]
35+
36+simpleAPI :: Proxy SimpleAPI
37+simpleAPI = Proxy
38+
39+server :: Server SimpleAPI
40+server = return products
41+
42+-- logStdout :: Middleware
43+-- i.e, logStdout :: Application -> Application
44+-- serve :: Proxy api -> Server api -> Application
45+-- so applying a middleware is really as simple as
46+-- applying a function to the result of 'serve'
47+app :: Application
48+app = logStdout (serve simpleAPI server)
49+
50+main :: IO ()
51+main = run 8080 app
sources.txt+1
@@ -3,3 +3,4 @@ servant-client
33servant-docs
44servant-jquery
55servant-server
6+servant-examples