packages feed

servant-cli-0.1.0.3: src/Servant/CLI.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
-- Module      : Servant.CLI
-- Copyright   : (c) Justin Le 2019
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- Parse command line arguments into a servant client, from a servant API.
--
-- Mainly used through 'parseClient' and 'parseHandleClient'.
-- 'parseClient' returns a servant client action that returns nested
-- 'Either's for every endpoint, but 'parseHandleClient' allows you to
-- conveniently specify how you want to sort each endpoint entry into
-- a single result.
--
-- See <https://hackage.haskell.org/package/servant-cli README> for
-- a tutorial.
module Servant.CLI
  ( -- * Parse Client
    parseClient,
    parseHandleClient,

    -- ** With context
    parseClientWithContext,
    parseHandleClientWithContext,

    -- * Typeclasses
    HasCLI (CLIResult, CLIHandler, cliHandler),

    -- * Context
    ContextFor (..),
    NamedContext (..),
    descendIntoNamedContext,

    -- * Lower-level
    cliPStruct,
    cliPStructWithContext,
    structParser,

    -- ** With context
    cliHandlePStruct,
    cliHandlePStructWithContext,

    -- * Re-export
    ParseBody (..),
    defaultParseBody,
    ToCapture (..),
    DocCapture (..),
    ToParam (..),
    DocQueryParam (..),
    ParamKind (..),
    ToAuthInfo (..),
    DocAuthentication (..),
  )
where

import Data.Proxy
import Data.Vinyl
import Options.Applicative
import Servant.CLI.HasCLI
import Servant.CLI.Internal.PStruct
import Servant.CLI.ParseBody
import Servant.Client.Core
import Servant.Docs.Internal

-- | A version of 'cliPStruct' that can be used if the API requires
-- any external context to generate runtime data.
cliPStructWithContext ::
  (HasCLI m api context) =>
  -- | Client monad
  Proxy m ->
  -- | API
  Proxy api ->
  -- | Extra context
  Rec (ContextFor m) context ->
  PStruct (m (CLIResult m api))
cliPStructWithContext pm pa =
  fmap ($ defaultRequest)
    . cliPStructWithContext_ pm pa

-- | A version of 'cliHandlePStruct' that can be used if the API requires
-- any external context to generate runtime data.
cliHandlePStructWithContext ::
  forall m api context r.
  (HasCLI m api context, Functor m) =>
  -- | Client monad
  Proxy m ->
  -- | API
  Proxy api ->
  -- | Extra context
  Rec (ContextFor m) context ->
  -- | Handler
  CLIHandler m api r ->
  PStruct (m r)
cliHandlePStructWithContext pm pa p h =
  fmap (cliHandler pm pa (Proxy @context) h)
    <$> cliPStructWithContext pm pa p

-- | A version of 'parseClient' that can be used if the API requires
-- any external context to generate runtime data.
parseClientWithContext ::
  (HasCLI m api context) =>
  -- | API
  Proxy api ->
  -- | Client monad
  Proxy m ->
  -- | Extra context
  Rec (ContextFor m) context ->
  -- | Options for top-level display
  InfoMod (m (CLIResult m api)) ->
  IO (m (CLIResult m api))
parseClientWithContext pa pm p im =
  execParser . flip structParser im $
    cliPStructWithContext pm pa p

-- | A version of 'parseHandleClient' that can be used if the API requires
-- any external context to generate runtime data.
parseHandleClientWithContext ::
  forall m api context r.
  (HasCLI m api context, Functor m) =>
  -- | API
  Proxy api ->
  -- | Client monad
  Proxy m ->
  -- | Extra context
  Rec (ContextFor m) context ->
  -- | Options for top-level display
  InfoMod (m (CLIResult m api)) ->
  -- | Handler
  CLIHandler m api r ->
  IO (m r)
parseHandleClientWithContext pa pm p im h =
  fmap (cliHandler pm pa (Proxy @context) h)
    <$> parseClientWithContext pa pm p im

-- | Create a structure for a command line parser.
--
-- This can be useful if you are combining functionality with existing
-- /optparse-applicative/ parsers.  You can convert a 'PStruct' to
-- a 'Parser' using 'structParser'.
cliPStruct ::
  (HasCLI m api '[]) =>
  -- | Client monad
  Proxy m ->
  -- | API
  Proxy api ->
  PStruct (m (CLIResult m api))
cliPStruct pm pa = cliPStructWithContext pm pa RNil

-- | Create a structure for a command line parser, producing results
-- according to a 'CLIHandler'.  See 'parseHandleClient' for more
-- information.
--
-- This can be useful if you are combining functionality with existing
-- /optparse-applicative/ parsers.  You can convert a 'PStruct' to
-- a 'Parser' using 'structParser'.
cliHandlePStruct ::
  (HasCLI m api '[], Functor m) =>
  -- | Client monad
  Proxy m ->
  -- | API
  Proxy api ->
  -- | Handler
  CLIHandler m api r ->
  PStruct (m r)
cliHandlePStruct pm pa = cliHandlePStructWithContext pm pa RNil

-- | Parse a servant client; the result can be run.  The choice of @m@
-- gives the backend you are using; for example, the default GHC
-- /servant-client/ backend is 'Servant.Client.ClientM'.
--
-- Returns the request response, which is usually a layer of 'Either' for
-- every endpoint branch.  You can find the response type directly by using
-- typed holes or asking ghci with @:t@ or @:kind! forall m. CLIResult
-- m MyAPI@.  Because it might be tedious handling nested 'Either's, see
-- 'parseHandleClient' for a way to handle each potential branch in
-- a convenient way.
--
-- Takes options on how the top-level prompt is displayed when given
-- @"--help"@; it can be useful for adding a header or program description.
-- Otherwise, just use 'mempty'.
parseClient ::
  (HasCLI m api '[]) =>
  -- | API
  Proxy api ->
  -- | Client monad
  Proxy m ->
  -- | Options for top-level display
  InfoMod (m (CLIResult m api)) ->
  IO (m (CLIResult m api))
parseClient pa pm = parseClientWithContext pa pm RNil

-- | Parse a server client, like 'parseClient'.  However, instead of that
-- client action returning the request response, instead use a 'CLIHandler'
-- to handle every potential request response.  It essentially lets you
-- specify how to sort each potential endpoint's response into a single
-- output value.
--
-- The handler is usually a 'Servant.API.:<|>' for every endpoint branch.
-- You can find it by using typed holes or asking ghci with @:t@ or @:kind!
-- forall m r.  CLIHandler m MyAPI r@.
--
-- Takes options on how the top-level prompt is displayed when given
-- @"--help"@; it can be useful for adding a header or program description.
-- Otherwise, just use 'mempty'.
parseHandleClient ::
  (HasCLI m api '[], Functor m) =>
  -- | API
  Proxy api ->
  -- | Client monad
  Proxy m ->
  -- | Options for top-level display
  InfoMod (m (CLIResult m api)) ->
  -- | Handler
  CLIHandler m api r ->
  IO (m r)
parseHandleClient pa pm = parseHandleClientWithContext pa pm RNil