# servant-cli Parse command line arguments into a servant client, from a servant API, using *optparse-applicative* for parsing, displaying help, and auto-completion. Hooks into the annotation system used by *servant-docs* to provide descriptions for parameters and captures. See `example/greet.hs` for a sample program. Getting started --------------- We're going to break down the example program in `example/greet.hs`. Here's a sample API revolving around greeting and some deep paths, with authentication. ```haskell type TestApi = Summary "Send a greeting" :> "hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON] Text :<|> Summary "Greet utilities" :> "greet" :> ( Get '[JSON] 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 ``` To parse this, we can use `parseClient`, which generates a client action that we can run: ```haskell main :: IO () main = do c <- parseClient testApi (Proxy :: Proxy ClientM) $ header "greet" <> progDesc "Greet API" manager' <- newManager defaultManagerSettings res <- runClientM c $ mkClientEnv manager' (BaseUrl Http "localhost" 8081 "") case res of Left e -> throwIO e Right r -> putStrLn $ case r of Left g -> "Greeting: " ++ T.unpack g Right (Left (Left i)) -> show i ++ " returned" Right (Left (Right _)) -> "Posted!" Right (Right s) -> s ``` Note that `parseClient` and other functions all take `InfoMod`s from *optparse-applicative*, to customize how the top-level `--help` is displayed. The result will be a bunch of nested `Either`s for each `:<|>` branch and endpoint. However, this can be somewhat tedious to handle. With Handlers ------------- The library also offers `parseHandleClient`, which accepts nested `:<|>`s with handlers for each endpoint, mirroring the structure of the API: ```haskell main :: IO () main = do c <- parseHandleClient testApi (Proxy :: Proxy ClientM) (header "greet" <> progDesc "Greet API") $ (\g -> "Greeting: " ++ T.unpack g) :<|> ( (\i -> show i ++ " returned") :<|> (\_ -> "Posted!") ) :<|> id manager' <- newManager defaultManagerSettings res <- runClientM c $ mkClientEnv manager' (BaseUrl Http "localhost" 8081 "") case res of Left e -> throwIO e Right r -> putStrLn r ``` The handlers essentially let you specify how to sort each potential endpoint's response into a single output value. Clients that need context ------------------------- Things get slightly more complicated when your client requires something that can't be passed in through the command line, such as authentication information (username, password). ```haskell type TestApi = Summary "Send a greeting" :> "hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON] Text :<|> Summary "Greet utilities" :> "greet" :> ( Get '[JSON] Int :<|> BasicAuth "login" Int -- ^ Adding 'BasicAuth' :> Post '[JSON] NoContent ) :<|> Summary "Deep paths test" :> "dig" :> "down" :> "deep" :> Summary "Almost there" :> Capture "name" Text :> "more" :> Summary "We made it" :> Get '[JSON] Text ``` For this, you can pass in a context, using `parseClientWithContext` or `parseHandleClientWithContext`: ```haskell main :: IO () main = do c <- parseHandleClientWithContext testApi (Proxy :: Proxy ClientM) (getPwd :& RNil) (header "greet" <> progDesc "Greet API") $ (\g -> "Greeting: " ++ T.unpack g) :<|> ( (\i -> show i ++ " returned") :<|> (\_ -> "Posted!") ) :<|> id manager' <- newManager defaultManagerSettings res <- runClientM c $ mkClientEnv manager' (BaseUrl Http "localhost" 8081 "") case res of Left e -> throwIO e Right r -> putStrLn r where getPwd :: ContextFor ClientM (BasicAuth "login" Int) getPwd = GenBasicAuthData . liftIO $ do putStrLn "Authentication needed for this action!" putStrLn "Enter username:" n <- BS.getLine putStrLn "Enter password:" p <- BS.getLine pure $ BasicAuthData n p ```