haskelltestingoptionparser

Haskell - generate missing argument error message from either data type


I have the following piece of code;

module Billing.Options
  (
      GlobalOpts(..)
    , globalOptsParser
    , parseDb
    , parseSql
  ) where

import Options.Applicative
import Options.Applicative.Simple
import Options.Applicative.Types
import System.FilePath.Posix
import Text.Regex.PCRE

-- ------------------------------------------------------------

data GlobalOpts = GlobalOpts
  {
    optDb          :: String,
    optSql         :: String
  } deriving Show

-- ------------------------------------------------------------

globalOptsParser :: Parser GlobalOpts
globalOptsParser = GlobalOpts
  <$> option (parseDb =<< readerAsk)
    (   long "db"
    <>  short 'd'
    <>  metavar "<DB name>"
    <>  help "dmt | report"
    )
  <*> option parseSql
    (   long "sql"
    <>  metavar "<SQL SELECT statement>"
    <>  help "sql select statement to use in order to generate JSON config file"
    )
-- ------------------------------------------------------------

matches :: String -> String -> Bool
matches = (=~)

-- ------------------------------------------------------------

parseDb :: String -> ReadM String
parseDb val = do
    if not (elem val ["dmt", "report"])
        then readerError $ "Unknown DB, '" ++ val ++ "'"
        else return val

-- ------------------------------------------------------------

parseSql :: ReadM String
parseSql = do
    val <- readerAsk
    if not (val `matches` "(?i)select .+ from .+")
        then readerError $ "Please provide a valid SQL SELECT statement"
        else return val

-- [EOF]

I'm testing my argument parser with the following code;

error' = let mp = runParser AllowOpts globalOptsParser ["-d", "billing"]
          opts = ParserPrefs "suffix" False False False 80
      in fst $ runP mp opts

The required arguments are,

  -d <DB name>
  --sql <SQL SELECT statement>

I want to test that I get the error message,

Missing: --sql <SQL SELECT statement>

when I only specify "-d billing".

The above test code gives the following output if I print the result,

Left (MissingError (MultNode [MultNode [MultNode [AltNode [Leaf (Chunk {unChunk = Just --sql <SQL SELECT statement>})]]]]))

Is there a way to generate the expected error message (String) from the above result (Either data type)? Does Haskell provide an obvious function to use for this purpose as I cannot find something in the documentation and googling for examples also didn't produce any answers.


Solution

  • If you look at the source code repository for optparse-applicative (link) you'll see that it itself has a test suite in the tests subdirectory.

    A routine which looks like it does a lot like what you want is checkHelpTextWith in the file tests/Test.hs:

    checkHelpTextWith :: Show a => ExitCode -> ParserPrefs -> String
                      -> ParserInfo a -> [String] -> Assertion
    checkHelpTextWith ecode pprefs name p args = do
      let result = execParserPure pprefs p args
      assertError result $ \failure -> do
        expected <- readFile $ name ++ ".err.txt"
        let (msg, code) = renderFailure failure name
        expected @=? msg ++ "\n"
        ecode @=? code
    

    This function expects an option parser to fail and compares the error message with the contents of a file.

    The main functions of interest are:

    execParserPure :: ParserPrefs -> ParserInfo a -> [String] -> ParserResult a
    
    renderFailure :: ParserFailure ParserHelp -> String -> (String, ExitCode)
    

    The String returned by renderFailure is the error message.