Commit 15c6ee5f3db9bf83ebf738143f85fc8a7e79370d
1 parent
0c1aa2ab
Added Range and ValidationError, Refactor throwing errors to Option instead of App for Validation
Showing
5 changed files
with
79 additions
and
9 deletions
include/CLI/App.hpp
| ... | ... | @@ -550,8 +550,7 @@ protected: |
| 550 | 550 | // Process callbacks |
| 551 | 551 | for(const Option_p& opt : options) { |
| 552 | 552 | if (opt->count() > 0) { |
| 553 | - if(!opt->run_callback()) | |
| 554 | - throw ConversionError(opt->get_name() + "=" + detail::join(opt->flatten_results())); | |
| 553 | + opt->run_callback(); | |
| 555 | 554 | } |
| 556 | 555 | } |
| 557 | 556 | ... | ... |
include/CLI/Error.hpp
| ... | ... | @@ -70,6 +70,11 @@ struct ConversionError : public ParseError { |
| 70 | 70 | ConversionError(std::string name) : ParseError("ConversionError", name, 2) {} |
| 71 | 71 | }; |
| 72 | 72 | |
| 73 | +/// Thrown when validation of results fails | |
| 74 | +struct ValidationError : public ParseError { | |
| 75 | + ValidationError(std::string name) : ParseError("ValidationError", name, 2) {} | |
| 76 | +}; | |
| 77 | + | |
| 73 | 78 | /// Thrown when a required option is missing |
| 74 | 79 | struct RequiredError : public ParseError { |
| 75 | 80 | RequiredError(std::string name) : ParseError("RequiredError", name, 5) {} | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -196,14 +196,15 @@ public: |
| 196 | 196 | |
| 197 | 197 | |
| 198 | 198 | /// Process the callback |
| 199 | - bool run_callback() const { | |
| 199 | + void run_callback() const { | |
| 200 | + if(!callback(results)) | |
| 201 | + throw ConversionError(get_name() + "=" + detail::join(flatten_results())); | |
| 200 | 202 | if(_validators.size()>0) { |
| 201 | 203 | for(const std::string & result : flatten_results()) |
| 202 | 204 | for(const std::function<bool(std::string)> &vali : _validators) |
| 203 | 205 | if(!vali(result)) |
| 204 | - return false; | |
| 206 | + throw ValidationError(get_name() + "=" + result); | |
| 205 | 207 | } |
| 206 | - return callback(results); | |
| 207 | 208 | } |
| 208 | 209 | |
| 209 | 210 | /// If options share any of the same names, they are equal (not counting positional) | ... | ... |
include/CLI/Validators.hpp
| ... | ... | @@ -5,7 +5,8 @@ |
| 5 | 5 | |
| 6 | 6 | #include <string> |
| 7 | 7 | #include <iostream> |
| 8 | - | |
| 8 | +#include <functional> | |
| 9 | +#include "CLI/TypeTools.hpp" | |
| 9 | 10 | |
| 10 | 11 | // C standard library |
| 11 | 12 | // Only needed for existence checking |
| ... | ... | @@ -61,5 +62,20 @@ bool NonexistentPath(std::string filename) { |
| 61 | 62 | } |
| 62 | 63 | } |
| 63 | 64 | |
| 65 | +/// Produce a range validator function | |
| 66 | +template<typename T> | |
| 67 | +std::function<bool(std::string)> Range(T min, T max) { | |
| 68 | + return [min, max](std::string input){ | |
| 69 | + T val; | |
| 70 | + detail::lexical_cast(input, val); | |
| 71 | + return val >= min && val <= max; | |
| 72 | + }; | |
| 73 | +} | |
| 74 | + | |
| 75 | +/// Range of one value is 0 to value | |
| 76 | +template<typename T> | |
| 77 | +std::function<bool(std::string)> Range(T max) { | |
| 78 | + return Range((T) 0, max); | |
| 79 | +} | |
| 64 | 80 | |
| 65 | 81 | } | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -282,7 +282,7 @@ TEST_F(TApp, FileNotExists) { |
| 282 | 282 | |
| 283 | 283 | bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file |
| 284 | 284 | EXPECT_TRUE(ok); |
| 285 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 285 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 286 | 286 | |
| 287 | 287 | std::remove(myfile.c_str()); |
| 288 | 288 | EXPECT_FALSE(CLI::ExistingFile(myfile)); |
| ... | ... | @@ -296,8 +296,7 @@ TEST_F(TApp, FileExists) { |
| 296 | 296 | app.add_option("--file", filename)->check(CLI::ExistingFile); |
| 297 | 297 | args = {"--file", myfile}; |
| 298 | 298 | |
| 299 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 300 | - EXPECT_EQ("Failed", filename); | |
| 299 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 301 | 300 | |
| 302 | 301 | app.reset(); |
| 303 | 302 | |
| ... | ... | @@ -509,3 +508,53 @@ TEST_F(TApp, Env) { |
| 509 | 508 | EXPECT_THROW(run(), CLI::RequiredError); |
| 510 | 509 | } |
| 511 | 510 | |
| 511 | +TEST_F(TApp, RangeInt) { | |
| 512 | + int x=0; | |
| 513 | + app.add_option("--one", x)->check(CLI::Range(3,6)); | |
| 514 | + | |
| 515 | + args = {"--one=1"}; | |
| 516 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 517 | + | |
| 518 | + app.reset(); | |
| 519 | + args = {"--one=7"}; | |
| 520 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 521 | + | |
| 522 | + app.reset(); | |
| 523 | + args = {"--one=3"}; | |
| 524 | + EXPECT_NO_THROW(run()); | |
| 525 | + | |
| 526 | + app.reset(); | |
| 527 | + args = {"--one=5"}; | |
| 528 | + EXPECT_NO_THROW(run()); | |
| 529 | + | |
| 530 | + app.reset(); | |
| 531 | + args = {"--one=6"}; | |
| 532 | + EXPECT_NO_THROW(run()); | |
| 533 | +} | |
| 534 | + | |
| 535 | +TEST_F(TApp, RangeDouble) { | |
| 536 | + | |
| 537 | + double x=0; | |
| 538 | + /// Note that this must be a double in Range, too | |
| 539 | + app.add_option("--one", x)->check(CLI::Range(3.0,6.0)); | |
| 540 | + | |
| 541 | + args = {"--one=1"}; | |
| 542 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 543 | + | |
| 544 | + app.reset(); | |
| 545 | + args = {"--one=7"}; | |
| 546 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 547 | + | |
| 548 | + app.reset(); | |
| 549 | + args = {"--one=3"}; | |
| 550 | + EXPECT_NO_THROW(run()); | |
| 551 | + | |
| 552 | + app.reset(); | |
| 553 | + args = {"--one=5"}; | |
| 554 | + EXPECT_NO_THROW(run()); | |
| 555 | + | |
| 556 | + app.reset(); | |
| 557 | + args = {"--one=6"}; | |
| 558 | + EXPECT_NO_THROW(run()); | |
| 559 | +} | |
| 560 | + | ... | ... |