packages feed

servant-cli-0.1.0.2: README.md

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