packages feed

cicero-api-0.1.1.3: cli/Fact.hs

{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
module Fact where

import IOHK.Cicero.API.Fact
import IOHK.Cicero.API.Run hiding (API)
import Servant.Client
import Options.Applicative
import Control.Exception
import Data.Aeson
import Data.Aeson.Parser.Internal hiding (value)
import System.Posix.Types
import System.Posix.IO
import System.IO (stdout)
import Data.ByteString.Lazy as LBS
import Data.ByteString.Lazy.Char8
import Data.Attoparsec.ByteString (feed, eitherResult, parse, IResult(Partial))
import Data.UUID

data Readable
  = File !FilePath
  | Cap !Fd

consume :: Readable -> IO ByteString
consume (File f) = LBS.readFile f
consume (Cap fd) = fdToHandle fd >>= hGetContents

readableOption :: Mod OptionFields FilePath -> Mod OptionFields Fd -> Parser Readable
readableOption fileFields capFields
  =  File <$> strOption fileFields
 <|> Cap <$> option auto capFields

data FactSource
  = LiteralFact !Value
  | ReadableFact !Readable

data CreateFactArgs = CreateFactArgs
  { factSource :: !FactSource
  , artifactSource :: !(Maybe Readable)
  }

valueReader :: ReadM Value
valueReader = do
  bytes <- str
  case eitherDecodeStrict' bytes of
    Left e -> readerError $ "error parsing " ++ show bytes ++ ": " ++ e
    Right v -> pure v

factSourceParser :: Parser FactSource
factSourceParser
  =  LiteralFact <$> option valueReader
       ( long "fact"
      <> metavar "FACT"
      <> help "the fact value"
       )
 <|> ReadableFact <$> readableOption
       ( long "fact-file"
      <> metavar "FACT_FILE"
      <> help "the path to a file containing the fact value"
       )
       ( long "fact-fd"
      <> metavar "FACT_FD"
      <> help "a file descriptor containing the fact value"
       )

createFactArgsParser :: Parser CreateFactArgs
createFactArgsParser = CreateFactArgs
  <$> factSourceParser
  <*> (optional $ readableOption
        ( long "artifact-file"
       <> metavar "ARTIFACT_FILE"
       <> help "the file containing the artifact to attach"
        )
        ( long "artifact-fd"
       <> metavar "ARTIFACT_FD"
       <> help "a file descriptor containing the artifact to attach"
        ))

createFactArgsInfo :: ParserInfo CreateFactArgs
createFactArgsInfo = info createFactArgsParser
  ( fullDesc
 <> header "cicero-cli fact create — Create a new fact"
  )

getFactsParser :: Parser RunID
getFactsParser = option (maybeReader runIdFromString)
  ( long "run-id"
 <> metavar "RUN_ID"
 <> help "the ID of the run which produced the facts"
  )

getFactsInfo :: ParserInfo RunID
getFactsInfo = info getFactsParser
  ( fullDesc
 <> header "cicero-cli fact get-all — Get information about facts"
  )

data FactCommand
  = CmdCreateFact !CreateFactArgs
  | CmdGetFacts !RunID

factCommandParser :: Parser FactCommand
factCommandParser = hsubparser
  ( command "create" (CmdCreateFact <$> createFactArgsInfo)
 <> command "get-all" (CmdGetFacts <$> getFactsInfo)
  )

factCommandInfo :: ParserInfo FactCommand
factCommandInfo = info factCommandParser
  ( fullDesc
 <> header "cicero-cli fact — Manage Cicero facts"
  )

data FactParseException = FactParseException
  { parseError :: !String
  } deriving Show

instance Exception FactParseException

createFactHandler :: CreateFactArgs -> (CreateFactV1 -> ClientM FactV1) -> ClientEnv -> IO ()
createFactHandler createArgs call cEnv = do
  fact <- case createArgs.factSource of
    LiteralFact v -> pure v
    ReadableFact f -> do
      factBytes <- consume f
      let parsedBytes = foldlChunks feed (Partial (parse jsonEOF')) factBytes
          res = feed parsedBytes ""
      case eitherResult res of
        Left e -> throw $ FactParseException e
        Right v -> pure v
  artifact <- case createArgs.artifactSource of
    Nothing -> pure Nothing
    Just f -> Just <$> consume f
  runClientM (call (CreateFact { .. })) cEnv >>= \case
    Left e -> throw e
    Right res -> hPutStrLn stdout $ encode res

handler :: FactCommand -> Client ClientM API -> ClientEnv -> IO ()
handler (CmdCreateFact createArgs) factClient cEnv =
  createFactHandler createArgs factClient.create cEnv
handler (CmdGetFacts rid) factClient cEnv = runClientM (factClient.getAll rid) cEnv >>= \case
  Left e -> throw e
  Right res -> hPutStrLn stdout $ encode res