packages feed

swagger-test-0.1.0: app/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Main
Description : Command line application
Copyright   : (c) Rodrigo Setti, 2017
License     : BSD3
Maintainer  : rodrigosetti@gmail.com
Stability   : experimental
Portability : POSIX
-}
module Main (main) where

import           Control.Lens          ((^.))
import           Control.Monad
import           Data.Aeson
import qualified Data.ByteString.Lazy  as LBS
import           Data.List             (find, intercalate)
import           Data.Semigroup        ((<>))
import           Data.Swagger          hiding (Format, info)
import qualified Data.Text             as T
import qualified Data.Text.IO          as TIO
import           Options.Applicative
import           System.Exit           (die)
import           System.Random
import           Test.Swagger

-- |Program options
data Opts = Opts FilePath -- ^Swagger input file
                 Command

data Command = Generate (Maybe Seed)
                        (Maybe OperationId)
                        Format -- ^request output format
                        Bool -- ^output extra header info with seed and/or operation id
                        Int -- ^size parameter for the generation
             | Validate (Maybe FilePath) -- ^read http response from file or stdin
                        OperationId
             | Request (Maybe Seed)
                        (Maybe OperationId)
                        Format -- ^request output format
                        Format -- ^response output format
                        Bool -- ^output extra header info with seed and/or operation id
                        Int -- ^size parameter for the generation

opts :: Parser Opts
opts  = Opts <$> strOption ( metavar "FILENAME"
                            <> long "schema"
                            <> short 's'
                            <> help "swagger JSON schema file to read from"
                            <> value "swagger.json"
                            <> showDefault)
             <*> subparser ( command "generate" (info (generate <**> helper)
                                                      (progDesc "Generate a random request according to Schema"))
                           <> command "validate" (info (validate <**> helper)
                                                       (progDesc "Validate a response against Schema"))
                           <> command "request" (info (request <**> helper)
                                                      (progDesc "Generate, make the request, and validate response")))
  where
    generate :: Parser Command
    generate = Generate <$> seedP
                        <*> optional operationIdP
                        <*> option (formatReader requestFormats)
                                   ( metavar (intercalate "|" (map show requestFormats))
                                   <> help "output format of the HTTP request"
                                   <> long "request-format"
                                   <> value FormatHttp
                                   <> showDefault )
                        <*> infoP
                        <*> sizeP

    seedP = optional (option auto  ( metavar "N"
                                  <> help "specify the seed for the random generator"
                                  <> long "seed" ))

    infoP = switch (long "info"
                 <> short 'i'
                 <> help "render information about seed and operation id")

    sizeP = option auto ( metavar "N"
                      <> long "size"
                      <> help "control the size of the generated request"
                      <> value 30
                      <> showDefault )

    request :: Parser Command
    request = Request <$> seedP
                        <*> optional operationIdP
                        <*> option (formatReader requestFormats)
                                   ( metavar (intercalate "|" (map show requestFormats))
                                   <> help "output format of the HTTP request"
                                   <> long "request-format"
                                   <> value FormatNone
                                   <> showDefault )
                        <*> option (formatReader responseFormats)
                                   (metavar (intercalate "|" (map show responseFormats))
                                   <> help "output format of the HTTP request"
                                   <> long "response-format"
                                   <> value FormatNone
                                   <> showDefault )
                        <*> infoP
                        <*> sizeP

    validate :: Parser Command
    validate = Validate <$> optional (strArgument ( metavar "FILENAME"
                                                  <> help "http response file to read from (default=stdin)" ))
                        <*> operationIdP

    operationIdP :: Parser OperationId
    operationIdP = T.pack <$> strOption (long "operation"
                                       <> short 'o'
                                       <> metavar "ID"
                                       <> help "specify a operation id to test (default pick randomly)")

    formatReader :: [Format] -> ReadM Format
    formatReader valids = maybeReader (\s -> find (\f -> show f == s) valids)

main :: IO ()
main = do Opts swaggerFile cmd <- execParser optsInfo
          contents <- LBS.readFile swaggerFile
          case eitherDecode contents of
            Left e -> die e
            Right model ->
              case cmd of
                Generate mseed mopid reqFmt renderInfo size ->
                    void $ doGenerate model mseed mopid reqFmt renderInfo size
                Validate respFile opId ->
                    do respContents <- maybe LBS.getContents LBS.readFile respFile
                       case validateResponseBytes respContents model opId of
                         Left e  -> die $ "invalid: " <> e
                         Right _ -> putStrLn "valid"
                Request mseed mopid reqFmt resFmt renderInfo size ->
                    do  (op, req) <- doGenerate model mseed mopid reqFmt renderInfo size
                        res <- doHttpRequest req
                        printResponse resFmt res
                        case validateResponseWithOperation res model op of
                           Left e  -> die $ "invalid: " <> e
                           Right _ -> putStrLn "valid"

  where
    optsInfo = info (opts <**> helper)
                    (fullDesc
                    <> progDesc "Generate Swagger requests and validates responses"
                    <> header "Testing tool for Swagger APIs")

    doGenerate :: Swagger -> Maybe Seed -> Maybe OperationId -> Format -> Bool -> Int -> IO (Operation, HttpRequest)
    doGenerate model mseed mopid reqFmt renderInfo size =
        do seed <- maybe randomIO pure mseed
           let (op, req) = generateRequest seed size model mopid
           when renderInfo $
              TIO.putStrLn $ "# seed=" <> T.pack (show seed) <> maybe "" (\i -> " id=" <> i) (op ^. operationId)
           printRequest reqFmt req
           pure (op, req)