1 // Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
2 // under NSF AWARD 1414736 and by the respective contributors.
3 // All rights reserved.
4 //
5 // SPDX-License-Identifier: BSD-3-Clause
6 
7 #pragma once
8 
9 // [CLI11:public_includes:set]
10 #include <exception>
11 #include <stdexcept>
12 #include <string>
13 #include <utility>
14 #include <vector>
15 // [CLI11:public_includes:end]
16 
17 // CLI library includes
18 #include "StringTools.hpp"
19 
20 namespace CLI {
21 // [CLI11:error_hpp:verbatim]
22 
23 // Use one of these on all error classes.
24 // These are temporary and are undef'd at the end of this file.
25 #define CLI11_ERROR_DEF(parent, name)                                                                                  \
26   protected:                                                                                                           \
27     name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {}   \
28     name(std::string ename, std::string msg, ExitCodes exit_code)                                                      \
29         : parent(std::move(ename), std::move(msg), exit_code) {}                                                       \
30                                                                                                                        \
31   public:                                                                                                              \
32     name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {}                           \
33     name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}
34 
35 // This is added after the one above if a class is used directly and builds its own message
36 #define CLI11_ERROR_SIMPLE(name)                                                                                       \
37     explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}
38 
39 /// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
40 /// int values from e.get_error_code().
41 enum class ExitCodes {
42     Success = 0,
43     IncorrectConstruction = 100,
44     BadNameString,
45     OptionAlreadyAdded,
46     FileError,
47     ConversionError,
48     ValidationError,
49     RequiredError,
50     RequiresError,
51     ExcludesError,
52     ExtrasError,
53     ConfigError,
54     InvalidError,
55     HorribleError,
56     OptionNotFound,
57     ArgumentMismatch,
58     BaseClass = 127
59 };
60 
61 // Error definitions
62 
63 /// @defgroup error_group Errors
64 /// @brief Errors thrown by CLI11
65 ///
66 /// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors.
67 /// @{
68 
69 /// All errors derive from this one
70 class Error : public std::runtime_error {
71     int actual_exit_code;
72     std::string error_name{"Error"};
73 
74   public:
get_exit_code() const75     int get_exit_code() const { return actual_exit_code; }
76 
get_name() const77     std::string get_name() const { return error_name; }
78 
Error(std::string name,std::string msg,int exit_code=static_cast<int> (ExitCodes::BaseClass))79     Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
80         : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
81 
Error(std::string name,std::string msg,ExitCodes exit_code)82     Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
83 };
84 
85 // Note: Using Error::Error constructors does not work on GCC 4.7
86 
87 /// Construction errors (not in parsing)
88 class ConstructionError : public Error {
89     CLI11_ERROR_DEF(Error, ConstructionError)
90 };
91 
92 /// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
93 class IncorrectConstruction : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError,IncorrectConstruction)94     CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
95     CLI11_ERROR_SIMPLE(IncorrectConstruction)
96     static IncorrectConstruction PositionalFlag(std::string name) {
97         return IncorrectConstruction(name + ": Flags cannot be positional");
98     }
Set0Opt(std::string name)99     static IncorrectConstruction Set0Opt(std::string name) {
100         return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
101     }
SetFlag(std::string name)102     static IncorrectConstruction SetFlag(std::string name) {
103         return IncorrectConstruction(name + ": Cannot set an expected number for flags");
104     }
ChangeNotVector(std::string name)105     static IncorrectConstruction ChangeNotVector(std::string name) {
106         return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
107     }
AfterMultiOpt(std::string name)108     static IncorrectConstruction AfterMultiOpt(std::string name) {
109         return IncorrectConstruction(
110             name + ": You can't change expected arguments after you've changed the multi option policy!");
111     }
MissingOption(std::string name)112     static IncorrectConstruction MissingOption(std::string name) {
113         return IncorrectConstruction("Option " + name + " is not defined");
114     }
MultiOptionPolicy(std::string name)115     static IncorrectConstruction MultiOptionPolicy(std::string name) {
116         return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
117     }
118 };
119 
120 /// Thrown on construction of a bad name
121 class BadNameString : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError,BadNameString)122     CLI11_ERROR_DEF(ConstructionError, BadNameString)
123     CLI11_ERROR_SIMPLE(BadNameString)
124     static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); }
BadLongName(std::string name)125     static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
DashesOnly(std::string name)126     static BadNameString DashesOnly(std::string name) {
127         return BadNameString("Must have a name, not just dashes: " + name);
128     }
MultiPositionalNames(std::string name)129     static BadNameString MultiPositionalNames(std::string name) {
130         return BadNameString("Only one positional name allowed, remove: " + name);
131     }
132 };
133 
134 /// Thrown when an option already exists
135 class OptionAlreadyAdded : public ConstructionError {
CLI11_ERROR_DEF(ConstructionError,OptionAlreadyAdded)136     CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
137     explicit OptionAlreadyAdded(std::string name)
138         : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
Requires(std::string name,std::string other)139     static OptionAlreadyAdded Requires(std::string name, std::string other) {
140         return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded);
141     }
Excludes(std::string name,std::string other)142     static OptionAlreadyAdded Excludes(std::string name, std::string other) {
143         return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
144     }
145 };
146 
147 // Parsing errors
148 
149 /// Anything that can error in Parse
150 class ParseError : public Error {
151     CLI11_ERROR_DEF(Error, ParseError)
152 };
153 
154 // Not really "errors"
155 
156 /// This is a successful completion on parsing, supposed to exit
157 class Success : public ParseError {
CLI11_ERROR_DEF(ParseError,Success)158     CLI11_ERROR_DEF(ParseError, Success)
159     Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
160 };
161 
162 /// -h or --help on command line
163 class CallForHelp : public Success {
CLI11_ERROR_DEF(Success,CallForHelp)164     CLI11_ERROR_DEF(Success, CallForHelp)
165     CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
166 };
167 
168 /// Usually something like --help-all on command line
169 class CallForAllHelp : public Success {
CLI11_ERROR_DEF(Success,CallForAllHelp)170     CLI11_ERROR_DEF(Success, CallForAllHelp)
171     CallForAllHelp()
172         : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
173 };
174 
175 /// -v or --version on command line
176 class CallForVersion : public Success {
CLI11_ERROR_DEF(Success,CallForVersion)177     CLI11_ERROR_DEF(Success, CallForVersion)
178     CallForVersion()
179         : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {}
180 };
181 
182 /// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code.
183 class RuntimeError : public ParseError {
CLI11_ERROR_DEF(ParseError,RuntimeError)184     CLI11_ERROR_DEF(ParseError, RuntimeError)
185     explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
186 };
187 
188 /// Thrown when parsing an INI file and it is missing
189 class FileError : public ParseError {
CLI11_ERROR_DEF(ParseError,FileError)190     CLI11_ERROR_DEF(ParseError, FileError)
191     CLI11_ERROR_SIMPLE(FileError)
192     static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
193 };
194 
195 /// Thrown when conversion call back fails, such as when an int fails to coerce to a string
196 class ConversionError : public ParseError {
CLI11_ERROR_DEF(ParseError,ConversionError)197     CLI11_ERROR_DEF(ParseError, ConversionError)
198     CLI11_ERROR_SIMPLE(ConversionError)
199     ConversionError(std::string member, std::string name)
200         : ConversionError("The value " + member + " is not an allowed value for " + name) {}
ConversionError(std::string name,std::vector<std::string> results)201     ConversionError(std::string name, std::vector<std::string> results)
202         : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {}
TooManyInputsFlag(std::string name)203     static ConversionError TooManyInputsFlag(std::string name) {
204         return ConversionError(name + ": too many inputs for a flag");
205     }
TrueFalse(std::string name)206     static ConversionError TrueFalse(std::string name) {
207         return ConversionError(name + ": Should be true/false or a number");
208     }
209 };
210 
211 /// Thrown when validation of results fails
212 class ValidationError : public ParseError {
CLI11_ERROR_DEF(ParseError,ValidationError)213     CLI11_ERROR_DEF(ParseError, ValidationError)
214     CLI11_ERROR_SIMPLE(ValidationError)
215     explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
216 };
217 
218 /// Thrown when a required option is missing
219 class RequiredError : public ParseError {
CLI11_ERROR_DEF(ParseError,RequiredError)220     CLI11_ERROR_DEF(ParseError, RequiredError)
221     explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {}
Subcommand(std::size_t min_subcom)222     static RequiredError Subcommand(std::size_t min_subcom) {
223         if(min_subcom == 1) {
224             return RequiredError("A subcommand");
225         }
226         return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
227                              ExitCodes::RequiredError);
228     }
229     static RequiredError
Option(std::size_t min_option,std::size_t max_option,std::size_t used,const std::string & option_list)230     Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
231         if((min_option == 1) && (max_option == 1) && (used == 0))
232             return RequiredError("Exactly 1 option from [" + option_list + "]");
233         if((min_option == 1) && (max_option == 1) && (used > 1)) {
234             return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
235                                      " were given",
236                                  ExitCodes::RequiredError);
237         }
238         if((min_option == 1) && (used == 0))
239             return RequiredError("At least 1 option from [" + option_list + "]");
240         if(used < min_option) {
241             return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
242                                      std::to_string(used) + "were given from [" + option_list + "]",
243                                  ExitCodes::RequiredError);
244         }
245         if(max_option == 1)
246             return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
247                                  ExitCodes::RequiredError);
248 
249         return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
250                                  std::to_string(used) + "were given from [" + option_list + "]",
251                              ExitCodes::RequiredError);
252     }
253 };
254 
255 /// Thrown when the wrong number of arguments has been received
256 class ArgumentMismatch : public ParseError {
CLI11_ERROR_DEF(ParseError,ArgumentMismatch)257     CLI11_ERROR_DEF(ParseError, ArgumentMismatch)
258     CLI11_ERROR_SIMPLE(ArgumentMismatch)
259     ArgumentMismatch(std::string name, int expected, std::size_t received)
260         : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
261                                            ", got " + std::to_string(received))
262                                         : ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
263                                            ", got " + std::to_string(received)),
264                            ExitCodes::ArgumentMismatch) {}
265 
AtLeast(std::string name,int num,std::size_t received)266     static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
267         return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
268                                 std::to_string(received));
269     }
AtMost(std::string name,int num,std::size_t received)270     static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
271         return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
272                                 std::to_string(received));
273     }
TypedAtLeast(std::string name,int num,std::string type)274     static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
275         return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
276     }
FlagOverride(std::string name)277     static ArgumentMismatch FlagOverride(std::string name) {
278         return ArgumentMismatch(name + " was given a disallowed flag override");
279     }
280 };
281 
282 /// Thrown when a requires option is missing
283 class RequiresError : public ParseError {
CLI11_ERROR_DEF(ParseError,RequiresError)284     CLI11_ERROR_DEF(ParseError, RequiresError)
285     RequiresError(std::string curname, std::string subname)
286         : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
287 };
288 
289 /// Thrown when an excludes option is present
290 class ExcludesError : public ParseError {
CLI11_ERROR_DEF(ParseError,ExcludesError)291     CLI11_ERROR_DEF(ParseError, ExcludesError)
292     ExcludesError(std::string curname, std::string subname)
293         : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
294 };
295 
296 /// Thrown when too many positionals or options are found
297 class ExtrasError : public ParseError {
CLI11_ERROR_DEF(ParseError,ExtrasError)298     CLI11_ERROR_DEF(ParseError, ExtrasError)
299     explicit ExtrasError(std::vector<std::string> args)
300         : ExtrasError((args.size() > 1 ? "The following arguments were not expected: "
301                                        : "The following argument was not expected: ") +
302                           detail::rjoin(args, " "),
303                       ExitCodes::ExtrasError) {}
ExtrasError(const std::string & name,std::vector<std::string> args)304     ExtrasError(const std::string &name, std::vector<std::string> args)
305         : ExtrasError(name,
306                       (args.size() > 1 ? "The following arguments were not expected: "
307                                        : "The following argument was not expected: ") +
308                           detail::rjoin(args, " "),
309                       ExitCodes::ExtrasError) {}
310 };
311 
312 /// Thrown when extra values are found in an INI file
313 class ConfigError : public ParseError {
CLI11_ERROR_DEF(ParseError,ConfigError)314     CLI11_ERROR_DEF(ParseError, ConfigError)
315     CLI11_ERROR_SIMPLE(ConfigError)
316     static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
NotConfigurable(std::string item)317     static ConfigError NotConfigurable(std::string item) {
318         return ConfigError(item + ": This option is not allowed in a configuration file");
319     }
320 };
321 
322 /// Thrown when validation fails before parsing
323 class InvalidError : public ParseError {
CLI11_ERROR_DEF(ParseError,InvalidError)324     CLI11_ERROR_DEF(ParseError, InvalidError)
325     explicit InvalidError(std::string name)
326         : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
327     }
328 };
329 
330 /// This is just a safety check to verify selection and parsing match - you should not ever see it
331 /// Strings are directly added to this error, but again, it should never be seen.
332 class HorribleError : public ParseError {
333     CLI11_ERROR_DEF(ParseError, HorribleError)
334     CLI11_ERROR_SIMPLE(HorribleError)
335 };
336 
337 // After parsing
338 
339 /// Thrown when counting a non-existent option
340 class OptionNotFound : public Error {
CLI11_ERROR_DEF(Error,OptionNotFound)341     CLI11_ERROR_DEF(Error, OptionNotFound)
342     explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
343 };
344 
345 #undef CLI11_ERROR_DEF
346 #undef CLI11_ERROR_SIMPLE
347 
348 /// @}
349 
350 // [CLI11:error_hpp:end]
351 }  // namespace CLI
352