Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleaner errors #53

Merged
merged 3 commits into from
Nov 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ class App {
return e.get_exit_code();
}

if(e.exit_code != static_cast<int>(ExitCodes::Success)) {
if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
if(failure_message_)
err << failure_message_(this, e) << std::flush;
}
Expand Down Expand Up @@ -1357,7 +1357,7 @@ class App {
std::string name;
std::string rest;
if(!detail::split_short(current, name, rest))
throw HorribleError("Short");
throw HorribleError("Short parsed but missing! You should not see this");

auto op_ptr = std::find_if(
std::begin(options_), std::end(options_), [name](const Option_p &opt) { return opt->check_sname(name); });
Expand Down Expand Up @@ -1437,7 +1437,7 @@ class App {
std::string name;
std::string value;
if(!detail::split_long(current, name, value))
throw HorribleError("Long:" + args.back());
throw HorribleError("Long parsed but missing (you should not see this):" + args.back());

auto op_ptr = std::find_if(
std::begin(options_), std::end(options_), [name](const Option_p &v) { return v->check_lname(name); });
Expand Down Expand Up @@ -1507,17 +1507,17 @@ class App {
namespace FailureMessage {

inline std::string simple(const App *app, const Error &e) {
std::string header = e.what() + "\n";
std::string header = std::string(e.what()) + "\n";
if(app->get_help_ptr() != nullptr)
header += "Run with " + app->get_help_ptr()->single_name() + " for more information.\n";
return header;
};
}

inline std::string help(const App *app, const Error &e) {
std::string header = std::string("ERROR: ") + e.what() + "\n";
std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n";
header += app->help();
return header;
};
}

} // namespace FailureMessage

Expand Down
159 changes: 95 additions & 64 deletions include/CLI/Error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,25 @@
#include <exception>
#include <stdexcept>
#include <string>
#include <utility>

namespace CLI {

// Use one of these on all error classes
#define CLI11_ERROR_DEF(parent, name) \
protected: \
name(std::string name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \
name(std::string name, std::string msg, ExitCodes exit_code) \
: parent(std::move(name), std::move(msg), exit_code) {} \
\
public: \
name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \
name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}

// This is added after the one above if a class is used directly and builds its own message
#define CLI11_ERROR_SIMPLE(name) \
name(std::string msg) : name(#name, msg, ExitCodes::name) {}

/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
/// int values from e.get_error_code().
enum class ExitCodes {
Expand All @@ -17,15 +33,15 @@ enum class ExitCodes {
BadNameString,
OptionAlreadyAdded,
File,
Conversion,
Validation,
Required,
Requires,
Excludes,
Extras,
ExtrasINI,
Invalid,
Horrible,
ConversionError,
ValidationError,
RequiredError,
RequiresError,
ExcludesError,
ExtrasError,
ExtrasINIError,
InvalidError,
HorribleError,
OptionNotFound,
BaseClass = 127
};
Expand All @@ -39,126 +55,141 @@ enum class ExitCodes {
/// @{

/// All errors derive from this one
struct Error : public std::runtime_error {
class Error : public std::runtime_error {
int exit_code;
std::string name{"Error"};

public:
int get_exit_code() const { return exit_code; }

Error(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass)
: runtime_error(parent + ": " + name), exit_code(static_cast<int>(exit_code)) {}
Error(std::string parent, std::string name, int exit_code = static_cast<int>(ExitCodes::BaseClass))
: runtime_error(parent + ": " + name), exit_code(exit_code) {}
std::string get_name() const { return name; }

Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
: runtime_error(msg), exit_code(exit_code), name(std::move(name)) {}

Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
};

// Note: Using Error::Error constructors does not work on GCC 4.7

/// Construction errors (not in parsing)
struct ConstructionError : public Error {
// Using Error::Error constructors seem to not work on GCC 4.7
ConstructionError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass)
: Error(parent, name, exit_code) {}
class ConstructionError : public Error {
CLI11_ERROR_DEF(Error, ConstructionError)
};

/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
struct IncorrectConstruction : public ConstructionError {
IncorrectConstruction(std::string name)
: ConstructionError("IncorrectConstruction", name, ExitCodes::IncorrectConstruction) {}
class IncorrectConstruction : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
CLI11_ERROR_SIMPLE(IncorrectConstruction)
};

/// Thrown on construction of a bad name
struct BadNameString : public ConstructionError {
BadNameString(std::string name) : ConstructionError("BadNameString", name, ExitCodes::BadNameString) {}
class BadNameString : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, BadNameString)
CLI11_ERROR_SIMPLE(BadNameString)
};

/// Thrown when an option already exists
struct OptionAlreadyAdded : public ConstructionError {
OptionAlreadyAdded(std::string name)
: ConstructionError("OptionAlreadyAdded", name, ExitCodes::OptionAlreadyAdded) {}
class OptionAlreadyAdded : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
CLI11_ERROR_SIMPLE(OptionAlreadyAdded)
};

// Parsing errors

/// Anything that can error in Parse
struct ParseError : public Error {
ParseError(std::string parent, std::string name, ExitCodes exit_code = ExitCodes::BaseClass)
: Error(parent, name, exit_code) {}
ParseError(std::string parent, std::string name, int exit_code = static_cast<int>(ExitCodes::BaseClass))
: Error(parent, name, exit_code) {}
class ParseError : public Error {
CLI11_ERROR_DEF(Error, ParseError)
};

// Not really "errors"

/// This is a successful completion on parsing, supposed to exit
struct Success : public ParseError {
Success() : ParseError("Success", "Successfully completed, should be caught and quit", ExitCodes::Success) {}
class Success : public ParseError {
CLI11_ERROR_DEF(ParseError, Success)
Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
};

/// -h or --help on command line
struct CallForHelp : public ParseError {
CallForHelp()
: ParseError("CallForHelp", "This should be caught in your main function, see examples", ExitCodes::Success) {}
class CallForHelp : public ParseError {
CLI11_ERROR_DEF(ParseError, CallForHelp)
CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
};

/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code.
struct RuntimeError : public ParseError {
RuntimeError(int exit_code = 1) : ParseError("RuntimeError", "runtime error", exit_code) {}
class RuntimeError : public ParseError {
CLI11_ERROR_DEF(ParseError, RuntimeError)
RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
};

/// Thrown when parsing an INI file and it is missing
struct FileError : public ParseError {
FileError(std::string name) : ParseError("FileError", name, ExitCodes::File) {}
class FileError : public ParseError {
CLI11_ERROR_DEF(ParseError, FileError)
FileError(std::string msg) : FileError(msg, ExitCodes::File) {}
};

/// Thrown when conversion call back fails, such as when an int fails to coerce to a string
struct ConversionError : public ParseError {
ConversionError(std::string name) : ParseError("ConversionError", name, ExitCodes::Conversion) {}
class ConversionError : public ParseError {
CLI11_ERROR_DEF(ParseError, ConversionError)
CLI11_ERROR_SIMPLE(ConversionError)
};

/// Thrown when validation of results fails
struct ValidationError : public ParseError {
ValidationError(std::string name) : ParseError("ValidationError", name, ExitCodes::Validation) {}
class ValidationError : public ParseError {
CLI11_ERROR_DEF(ParseError, ValidationError)
CLI11_ERROR_SIMPLE(ValidationError)
};

/// Thrown when a required option is missing
struct RequiredError : public ParseError {
RequiredError(std::string name) : ParseError("RequiredError", name, ExitCodes::Required) {}
class RequiredError : public ParseError {
CLI11_ERROR_DEF(ParseError, RequiredError)
CLI11_ERROR_SIMPLE(RequiredError)
};

/// Thrown when a requires option is missing
struct RequiresError : public ParseError {
RequiresError(std::string name, std::string subname)
: ParseError("RequiresError", name + " requires " + subname, ExitCodes::Requires) {}
class RequiresError : public ParseError {
CLI11_ERROR_DEF(ParseError, RequiresError)
RequiresError(std::string curname, std::string subname)
: RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
};

/// Thrown when a exludes option is present
struct ExcludesError : public ParseError {
ExcludesError(std::string name, std::string subname)
: ParseError("ExcludesError", name + " excludes " + subname, ExitCodes::Excludes) {}
class ExcludesError : public ParseError {
CLI11_ERROR_DEF(ParseError, ExcludesError)
ExcludesError(std::string curname, std::string subname)
: ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
};

/// Thrown when too many positionals or options are found
struct ExtrasError : public ParseError {
ExtrasError(std::string name) : ParseError("ExtrasError", name, ExitCodes::Extras) {}
class ExtrasError : public ParseError {
CLI11_ERROR_DEF(ParseError, ExtrasError)
CLI11_ERROR_SIMPLE(ExtrasError)
};

/// Thrown when extra values are found in an INI file
struct ExtrasINIError : public ParseError {
ExtrasINIError(std::string name) : ParseError("ExtrasINIError", name, ExitCodes::ExtrasINI) {}
class ExtrasINIError : public ParseError {
CLI11_ERROR_DEF(ParseError, ExtrasINIError)
CLI11_ERROR_SIMPLE(ExtrasINIError)
};

/// Thrown when validation fails before parsing
struct InvalidError : public ParseError {
InvalidError(std::string name) : ParseError("InvalidError", name, ExitCodes::Invalid) {}
class InvalidError : public ParseError {
CLI11_ERROR_DEF(ParseError, InvalidError)
CLI11_ERROR_SIMPLE(InvalidError)
};

/// This is just a safety check to verify selection and parsing match
struct HorribleError : public ParseError {
HorribleError(std::string name)
: ParseError("HorribleError", "(You should never see this error) " + name, ExitCodes::Horrible) {}
/// This is just a safety check to verify selection and parsing match - you should not ever see it
class HorribleError : public ParseError {
CLI11_ERROR_DEF(ParseError, HorribleError)
CLI11_ERROR_SIMPLE(HorribleError)
};

// After parsing

/// Thrown when counting a non-existent option
struct OptionNotFound : public Error {
OptionNotFound(std::string name) : Error("OptionNotFound", name, ExitCodes::OptionNotFound) {}
class OptionNotFound : public Error {
CLI11_ERROR_DEF(Error, OptionNotFound)
CLI11_ERROR_SIMPLE(OptionNotFound)
};

/// @}
Expand Down
2 changes: 1 addition & 1 deletion include/CLI/Ini.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ inline std::vector<ini_ret_t> parse_ini(const std::string &name) {

std::ifstream input{name};
if(!input.good())
throw FileError(name);
throw FileError(name + " was not readable (missing?)");

return parse_ini(input);
}
Expand Down
4 changes: 2 additions & 2 deletions include/CLI/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,13 +428,13 @@ class Option : public OptionBase<Option> {
}

if(local_result)
throw ConversionError(get_name() + "=" + detail::join(results_));
throw ConversionError("Could not convert: " + get_name() + "=" + detail::join(results_));

if(!validators_.empty()) {
for(const std::string &result : results_)
for(const std::function<bool(std::string)> &vali : validators_)
if(!vali(result))
throw ValidationError(get_name() + "=" + result);
throw ValidationError("Failed validation: " + get_name() + "=" + result);
}
}

Expand Down
14 changes: 7 additions & 7 deletions tests/HelpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ TEST(THelp, RemoveHelp) {
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
EXPECT_EQ(static_cast<int>(CLI::ExitCodes::Extras), e.get_exit_code());
EXPECT_EQ(static_cast<int>(CLI::ExitCodes::ExtrasError), e.get_exit_code());
}
}

Expand All @@ -328,7 +328,7 @@ TEST(THelp, NoHelp) {
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
EXPECT_EQ(static_cast<int>(CLI::ExitCodes::Extras), e.get_exit_code());
EXPECT_EQ(static_cast<int>(CLI::ExitCodes::ExtrasError), e.get_exit_code());
}
}

Expand Down Expand Up @@ -385,14 +385,14 @@ TEST(Exit, ErrorWithoutHelp) {
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
EXPECT_EQ(static_cast<int>(CLI::ExitCodes::Extras), e.get_exit_code());
EXPECT_EQ(static_cast<int>(CLI::ExitCodes::ExtrasError), e.get_exit_code());
}
}

TEST(Exit, ExitCodes) {
CLI::App app;

auto i = static_cast<int>(CLI::ExitCodes::Extras);
auto i = static_cast<int>(CLI::ExitCodes::ExtrasError);
EXPECT_EQ(0, app.exit(CLI::Success()));
EXPECT_EQ(0, app.exit(CLI::CallForHelp()));
EXPECT_EQ(i, app.exit(CLI::ExtrasError("Thing")));
Expand Down Expand Up @@ -432,18 +432,18 @@ TEST_F(CapturedHelp, CallForHelp) {
}

TEST_F(CapturedHelp, NormalError) {
EXPECT_EQ(run(CLI::ExtrasError("Thing")), static_cast<int>(CLI::ExitCodes::Extras));
EXPECT_EQ(run(CLI::ExtrasError("Thing")), static_cast<int>(CLI::ExitCodes::ExtrasError));
EXPECT_EQ(out.str(), "");
EXPECT_THAT(err.str(), HasSubstr("for more information"));
EXPECT_THAT(err.str(), HasSubstr("ExtrasError"));
EXPECT_THAT(err.str(), Not(HasSubstr("ExtrasError")));
EXPECT_THAT(err.str(), HasSubstr("Thing"));
EXPECT_THAT(err.str(), Not(HasSubstr("Usage")));
}

TEST_F(CapturedHelp, RepacedError) {
app.set_failure_message(CLI::FailureMessage::help);

EXPECT_EQ(run(CLI::ExtrasError("Thing")), static_cast<int>(CLI::ExitCodes::Extras));
EXPECT_EQ(run(CLI::ExtrasError("Thing")), static_cast<int>(CLI::ExitCodes::ExtrasError));
EXPECT_EQ(out.str(), "");
EXPECT_THAT(err.str(), Not(HasSubstr("for more information")));
EXPECT_THAT(err.str(), HasSubstr("ERROR: ExtrasError"));
Expand Down