servant-cli-0.1.0.2: example/greet.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
import Control.Concurrent
import Control.Exception
import Control.Monad.IO.Class
import Data.Aeson
import Data.Maybe
import Data.Proxy
import Data.Text (Text)
import Data.Vinyl
import GHC.Generics
import Network.HTTP.Client (newManager, defaultManagerSettings)
import Network.Wai.Handler.Warp (run)
import Options.Applicative (header, progDesc)
import Servant.API
import Servant.CLI
import Servant.Client
import Servant.Server
import System.Random
import qualified Data.ByteString as BS
import qualified Data.Map as M
import qualified Data.Text as T
-- * Example
-- | A greet message data type
newtype Greet = Greet Text
deriving (Generic, Show)
instance ParseBody Greet where
parseBody = Greet <$> parseBody
-- | We can get JSON support automatically. This will be used to parse
-- and encode a Greeting as 'JSON'.
instance FromJSON Greet
instance ToJSON Greet
-- We add some useful annotations to our captures,
-- query parameters and request body to make the docs
-- really helpful.
instance ToCapture (Capture "name" Text) where
toCapture _ = DocCapture "name" "name of the person to greet"
instance ToParam (QueryParam "capital" Bool) where
toParam _ =
DocQueryParam "capital"
["true", "false"]
"Get the greeting message in uppercase (true) or not (false). Default is false."
Normal
instance ToAuthInfo (BasicAuth "login" Int) where
toAuthInfo _ =
DocAuthentication "Login credientials"
"Username and password"
type TestApi =
Summary "Send a greeting"
:> "hello"
:> Capture "name" Text
:> QueryParam "capital" Bool
:> Get '[JSON] Greet
:<|> Summary "Greet utilities"
:> "greet"
:> ReqBody '[JSON] Greet
:> ( Get '[JSON] Int
:<|> BasicAuth "login" Int
:> Post '[JSON] NoContent
)
:<|> Summary "Deep paths test"
:> "dig"
:> "down"
:> "deep"
:> Summary "Almost there"
:> Capture "name" Text
:> "more"
:> Summary "We made it"
:> Get '[JSON] Text
testApi :: Proxy TestApi
testApi = Proxy
server :: Application
server = serveWithContext testApi (authCheck :. EmptyContext) $
(\t b -> pure . Greet $ "Hello, "
<> if fromMaybe False b
then T.toUpper t
else t
)
:<|> (\(Greet g) -> pure (T.length g)
:<|> (\_ -> pure NoContent)
)
:<|> (pure . T.reverse)
where
-- | Map of valid users and passwords
userMap = M.fromList [("alice", "password"), ("bob", "hunter2")]
authCheck = BasicAuthCheck $ \(BasicAuthData u p) ->
case M.lookup u userMap of
Nothing -> pure NoSuchUser
Just p'
| p == p' -> Authorized <$> randomIO @Int
| otherwise -> pure BadPassword
-- | Safely shutdown the server when we're done
withServer :: IO () -> IO ()
withServer action =
bracket (forkIO $ run 8081 server)
killThread
(const action)
main :: IO ()
main = do
c <- parseHandleClientWithContext
testApi
(Proxy :: Proxy ClientM)
(getPwd :& RNil)
cinfo $
(\(Greet g) -> "Greeting: " ++ T.unpack g)
:<|> ( (\i -> show i ++ " letters")
:<|> (\_ -> "posted!")
)
:<|> (\s -> "Reversed: " ++ T.unpack s)
withServer $ do
manager' <- newManager defaultManagerSettings
res <- runClientM c (mkClientEnv manager' (BaseUrl Http "localhost" 8081 ""))
case res of
Left e -> throwIO e
Right rstring -> putStrLn rstring
where
cinfo = header "greet" <> progDesc "Greet API"
getPwd :: ContextFor ClientM (BasicAuth "login" Int)
getPwd = GenBasicAuthData . liftIO $ do
putStrLn "Authentication needed for this action!"
putStrLn "(Hint: try 'bob' and 'hunter2')"
putStrLn "Enter username:"
n <- BS.getLine
putStrLn "Enter password:"
p <- BS.getLine
pure $ BasicAuthData n p