{-# 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(ClientEnv), ClientM, Scheme(Http), client, runClientM) import Servant.Checked.Exceptions (Envelope, 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', the client functions get generated with the 'Envelope' type. strictSearch :: SearchQuery -> ClientM (Envelope '[BadSearchTermErr, IncorrectCapitalization] SearchResponse) laxSearch :: SearchQuery -> ClientM (Envelope '[BadSearchTermErr] SearchResponse) strictSearch :<|> laxSearch = 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 } queryParser :: Parser String queryParser = argument str (metavar "QUERY") useStrictParser :: Parser Bool useStrictParser = switch $ long "strict" <> short 's' <> help "Whether to be use the strict api" commandParser :: Parser Options commandParser = Options <$> queryParser <*> useStrictParser ------------------------------------------------------------------------- -- 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 -- | Run either 'runStrict' or 'runLax' depending on the command line options. run :: ClientEnv -> Options -> IO () run clientEnv Options{query, useStrict = True} = runStrict clientEnv query run clientEnv Options{query, useStrict = False} = runLax clientEnv query ---------- -- Main -- ---------- main :: IO () main = do manager <- newManager defaultManagerSettings let clientEnv = ClientEnv 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 ""