servant-cli: Command line interface for Servant API clients

[ bsd3, library, program, web ] [ Propose Tags ]

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 an example usage, and the README for a tutorial.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.0.1, 0.1.0.2, 0.1.0.3, 0.1.1.0
Change log CHANGELOG.md
Dependencies aeson, base (>=4.12 && <5), bytestring, case-insensitive, containers, filepath, free, functor-combinators (>=0.2), http-client, http-types, optparse-applicative, profunctors, random, recursion-schemes, servant (>=0.15), servant-cli, servant-client, servant-client-core (>=0.20), servant-docs, servant-server, text, transformers, vinyl, warp [details]
License BSD-3-Clause
Copyright (c) Justin Le 2019
Author Justin Le
Maintainer justin@jle.im
Category Web
Home page https://github.com/mstksg/servant-cli#readme
Bug tracker https://github.com/mstksg/servant-cli/issues
Source repo head: git clone https://github.com/mstksg/servant-cli
Uploaded by jle at 2024-01-12T20:31:51Z
Distributions Stackage:0.1.1.0
Executables greet-cli
Downloads 1300 total (15 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2024-01-12 [all 1 reports]

Readme for servant-cli-0.1.1.0

[back to package description]

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.

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:

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 InfoMods from optparse-applicative, to customize how the top-level --help is displayed.

The result will be a bunch of nested Eithers 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:

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).

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:

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