# 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
```