{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE EmptyCase #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} module Main where import Data.Monoid ((<>)) import Data.Proxy (Proxy(Proxy)) import Network.HTTP.Client (defaultManagerSettings, newManager) import Options.Applicative (Parser, (<**>), argument, execParser, fullDesc, help, helper, info, long, metavar, progDesc, short, str, switch) import Servant.API ((:<|>)((:<|>))) import Servant.Client (BaseUrl(BaseUrl), ClientEnv, ClientM, Scheme(Http), client, mkClientEnv, runClientM) import Servant.Checked.Exceptions (Envelope, emptyEnvelope, catchesEnvelope) import Api (Api, BadSearchTermErr(BadSearchTermErr), IncorrectCapitalization(IncorrectCapitalization), SearchQuery(SearchQuery), SearchResponse(SearchResponse), port) ----------------------------------------- -- Clients generated by servant-client -- ----------------------------------------- -- We generate the client functions just like normal. Note that when we use -- 'Throws' or 'NoThrow', the client functions get generated with the -- 'Envelope' type. strictSearch :: SearchQuery -> ClientM (Envelope '[BadSearchTermErr, IncorrectCapitalization] SearchResponse) laxSearch :: SearchQuery -> ClientM (Envelope '[BadSearchTermErr] SearchResponse) noErrSearch :: SearchQuery -> ClientM (Envelope '[] SearchResponse) strictSearch :<|> laxSearch :<|> noErrSearch = client (Proxy :: Proxy Api) -------------------------------------- -- Command-line options and parsers -- -------------------------------------- -- The following are needed for using optparse-applicative to parse command -- line arguments. Most people shouldn't need to worry about how this works. data Options = Options { query :: String, useStrict :: Bool, useNoErr :: Bool } queryParser :: Parser String queryParser = argument str (metavar "QUERY") useStrictParser :: Parser Bool useStrictParser = switch $ long "strict" <> short 's' <> help "Whether or not to use the strict api" useNoErrParser :: Parser Bool useNoErrParser = switch $ long "no-err" <> short 'n' <> help "Whether or not to use the api that does not return an error" commandParser :: Parser Options commandParser = Options <$> queryParser <*> useStrictParser <*> useNoErrParser ------------------------------------------------------------------------- -- Command Runners (these use the clients generated by servant-client) -- ------------------------------------------------------------------------- -- | This function uses the 'strictSearch' function to send a 'SearchQuery' to -- the server. -- -- Note how 'catchesEnvelope' is used to handle the two error reponses and the -- success response. runStrict :: ClientEnv -> String -> IO () runStrict clientEnv query = do eitherRes <- runClientM (strictSearch $ SearchQuery query) clientEnv case eitherRes of Left servantErr -> putStrLn $ "Got a ServantErr: " <> show servantErr Right env -> putStrLn $ catchesEnvelope ( \BadSearchTermErr -> "the search term was not \"Hello\"" , \IncorrectCapitalization -> "the search term was not capitalized correctly" ) (\(SearchResponse searchResponse) -> "Success: " <> searchResponse) env -- | This function uses the 'laxSearch' function to send a 'SearchQuery' to -- the server. runLax :: ClientEnv -> String -> IO () runLax clientEnv query = do eitherRes <- runClientM (laxSearch $ SearchQuery query) clientEnv case eitherRes of Left servantErr -> putStrLn $ "Got a ServantErr: " <> show servantErr Right env -> putStrLn $ catchesEnvelope (\BadSearchTermErr -> "the search term was not \"Hello\"") (\(SearchResponse searchResponse) -> "Success: " <> searchResponse) env -- | This function uses the 'noErrSearch' function to send a 'SearchQuery' to -- the server. runNoErr :: ClientEnv -> String -> IO () runNoErr clientEnv query = do eitherRes <- runClientM (noErrSearch $ SearchQuery query) clientEnv case eitherRes of Left servantErr -> putStrLn $ "Got a ServantErr: " <> show servantErr Right env -> do let (SearchResponse res) = emptyEnvelope env putStrLn $ "Success: " <> res -- | Run 'runStrict', 'runLax', or 'runNoErr' depending on the command line options. run :: ClientEnv -> Options -> IO () run clientEnv Options{query, useStrict = True, useNoErr = _} = runStrict clientEnv query run clientEnv Options{query, useStrict = _, useNoErr = True} = runNoErr clientEnv query run clientEnv Options{query, useStrict = _, useNoErr = _} = runLax clientEnv query ---------- -- Main -- ---------- main :: IO () main = do manager <- newManager defaultManagerSettings let clientEnv = mkClientEnv manager baseUrl options <- execParser opts run clientEnv options where opts = info (commandParser <**> helper) $ fullDesc <> progDesc "Send the QUERY to the example server and print the response." baseUrl :: BaseUrl baseUrl = BaseUrl Http "localhost" port ""