diff --git a/src/app/CliArgs.cpp b/src/app/CliArgs.cpp index 0c973dee6..4673c9f90 100644 --- a/src/app/CliArgs.cpp +++ b/src/app/CliArgs.cpp @@ -47,6 +47,7 @@ CliArgs::parse(int argc, char const* argv[]) ("conf,c", po::value()->default_value(kDEFAULT_CONFIG_PATH), "configuration file") ("ng-web-server,w", "Use ng-web-server") ("migrate", po::value(), "start migration helper") + ("verify", "Checks the validity of config values") ; // clang-format on po::positional_options_description positional; @@ -75,6 +76,9 @@ CliArgs::parse(int argc, char const* argv[]) return Action{Action::Migrate{.configPath = std::move(configPath), .subCmd = MigrateSubCmd::migration(opt)}}; } + if (parsed.count("verify") != 0u) + return Action{Action::VerifyConfig{.configPath = std::move(configPath)}}; + return Action{Action::Run{.configPath = std::move(configPath), .useNgWebServer = parsed.count("ng-web-server") != 0} }; } diff --git a/src/app/CliArgs.hpp b/src/app/CliArgs.hpp index 2142ad3f6..8f2a8c359 100644 --- a/src/app/CliArgs.hpp +++ b/src/app/CliArgs.hpp @@ -59,6 +59,11 @@ class CliArgs { MigrateSubCmd subCmd; }; + /** @brief Verify Config action. */ + struct VerifyConfig { + std::string configPath; + }; + /** * @brief Construct an action from a Run. * @@ -66,7 +71,7 @@ class CliArgs { */ template requires std::is_same_v or std::is_same_v or - std::is_same_v + std::is_same_v or std::is_same_v explicit Action(ActionType&& action) : action_(std::forward(action)) { } @@ -86,7 +91,7 @@ class CliArgs { } private: - std::variant action_; + std::variant action_; }; /** diff --git a/src/app/VerifyConfig.hpp b/src/app/VerifyConfig.hpp new file mode 100644 index 000000000..63c806dc9 --- /dev/null +++ b/src/app/VerifyConfig.hpp @@ -0,0 +1,55 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2025, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include "util/newconfig/ConfigDefinition.hpp" +#include "util/newconfig/ConfigFileJson.hpp" + +#include +#include +#include + +namespace app { + +/** + * @brief Verifies user's config values are correct + * + * @param configPath The path to config + * @return true if config values are all correct, false otherwise + */ +inline bool +verifyConfig(std::string_view configPath) +{ + using namespace util::config; + + auto const json = ConfigFileJson::makeConfigFileJson(configPath); + if (!json.has_value()) { + std::cerr << json.error().error << std::endl; + return false; + } + auto const errors = gClioConfig.parse(json.value()); + if (errors.has_value()) { + for (auto const& err : errors.value()) + std::cerr << err.error << std::endl; + return false; + } + return true; +} +} // namespace app diff --git a/src/main/Main.cpp b/src/main/Main.cpp index 6a817f0dc..bd060b12d 100644 --- a/src/main/Main.cpp +++ b/src/main/Main.cpp @@ -19,12 +19,12 @@ #include "app/CliArgs.hpp" #include "app/ClioApplication.hpp" +#include "app/VerifyConfig.hpp" #include "migration/MigrationApplication.hpp" #include "rpc/common/impl/HandlerProvider.hpp" #include "util/TerminationHandler.hpp" #include "util/log/Logger.hpp" #include "util/newconfig/ConfigDefinition.hpp" -#include "util/newconfig/ConfigFileJson.hpp" #include #include @@ -40,34 +40,27 @@ try { auto const action = app::CliArgs::parse(argc, argv); return action.apply( [](app::CliArgs::Action::Exit const& exit) { return exit.exitCode; }, - [](app::CliArgs::Action::Run const& run) { - auto const json = ConfigFileJson::makeConfigFileJson(run.configPath); - if (!json.has_value()) { - std::cerr << json.error().error << std::endl; - return EXIT_FAILURE; + [](app::CliArgs::Action::VerifyConfig const& verify) { + if (app::verifyConfig(verify.configPath)) { + std::cout << "Config is correct" << "\n"; + return EXIT_SUCCESS; } - auto const errors = gClioConfig.parse(json.value()); - if (errors.has_value()) { - for (auto const& err : errors.value()) - std::cerr << err.error << std::endl; + return EXIT_FAILURE; + }, + [](app::CliArgs::Action::Run const& run) { + auto const res = app::verifyConfig(run.configPath); + if (res != EXIT_SUCCESS) return EXIT_FAILURE; - } + util::LogService::init(gClioConfig); app::ClioApplication clio{gClioConfig}; return clio.run(run.useNgWebServer); }, [](app::CliArgs::Action::Migrate const& migrate) { - auto const json = ConfigFileJson::makeConfigFileJson(migrate.configPath); - if (!json.has_value()) { - std::cerr << json.error().error << std::endl; - return EXIT_FAILURE; - } - auto const errors = gClioConfig.parse(json.value()); - if (errors.has_value()) { - for (auto const& err : errors.value()) - std::cerr << err.error << std::endl; + auto const res = app::verifyConfig(migrate.configPath); + if (res != EXIT_SUCCESS) return EXIT_FAILURE; - } + util::LogService::init(gClioConfig); app::MigratorApplication migrator{gClioConfig, migrate.subCmd}; return migrator.run(); diff --git a/src/util/newconfig/ConfigDefinition.hpp b/src/util/newconfig/ConfigDefinition.hpp index 2766e767e..69fb1b6eb 100644 --- a/src/util/newconfig/ConfigDefinition.hpp +++ b/src/util/newconfig/ConfigDefinition.hpp @@ -416,6 +416,7 @@ static ClioConfigDefinition gClioConfig = ClioConfigDefinition{ ConfigValue{ConfigType::Integer}.defaultValue(rpc::kAPI_VERSION_MIN).withConstraint(gValidateApiVersion)}, {"api_version.max", ConfigValue{ConfigType::Integer}.defaultValue(rpc::kAPI_VERSION_MAX).withConstraint(gValidateApiVersion)}, + {"migration.full_scan_threads", ConfigValue{ConfigType::Integer}.defaultValue(2).withConstraint(gValidateUint32)}, {"migration.full_scan_jobs", ConfigValue{ConfigType::Integer}.defaultValue(4).withConstraint(gValidateUint32)}, {"migration.cursors_per_job", ConfigValue{ConfigType::Integer}.defaultValue(100).withConstraint(gValidateUint32)}}, diff --git a/tests/common/util/newconfig/FakeConfigData.hpp b/tests/common/util/newconfig/FakeConfigData.hpp index 4167e2b2e..f7340276c 100644 --- a/tests/common/util/newconfig/FakeConfigData.hpp +++ b/tests/common/util/newconfig/FakeConfigData.hpp @@ -208,3 +208,11 @@ static constexpr auto kINVALID_JSON_DATA = R"JSON({ "withDefault" : "0.0" } })JSON"; + +// used to Verify Config test +static constexpr auto kVALID_JSON_DATA = R"JSON({ + "server": { + "ip": "0.0.0.0", + "port": 51233 + } +})JSON"; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index c1371f594..fe74c1b83 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources( PRIVATE # Common ConfigTests.cpp app/CliArgsTests.cpp + app/VerifyConfigTests.cpp app/WebHandlersTests.cpp data/AmendmentCenterTests.cpp data/BackendCountersTests.cpp diff --git a/tests/unit/app/CliArgsTests.cpp b/tests/unit/app/CliArgsTests.cpp index 437f20157..d6e098420 100644 --- a/tests/unit/app/CliArgsTests.cpp +++ b/tests/unit/app/CliArgsTests.cpp @@ -32,6 +32,7 @@ struct CliArgsTests : testing::Test { testing::StrictMock> onRunMock; testing::StrictMock> onExitMock; testing::StrictMock> onMigrateMock; + testing::StrictMock> onVerifyMock; }; TEST_F(CliArgsTests, Parse_NoArgs) @@ -46,7 +47,13 @@ TEST_F(CliArgsTests, Parse_NoArgs) return returnCode; }); EXPECT_EQ( - action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction(), onMigrateMock.AsStdFunction()), returnCode + action.apply( + onRunMock.AsStdFunction(), + onExitMock.AsStdFunction(), + onMigrateMock.AsStdFunction(), + onVerifyMock.AsStdFunction() + ), + returnCode ); } @@ -62,7 +69,12 @@ TEST_F(CliArgsTests, Parse_NgWebServer) return returnCode; }); EXPECT_EQ( - action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction(), onMigrateMock.AsStdFunction()), + action.apply( + onRunMock.AsStdFunction(), + onExitMock.AsStdFunction(), + onMigrateMock.AsStdFunction(), + onVerifyMock.AsStdFunction() + ), returnCode ); } @@ -79,7 +91,12 @@ TEST_F(CliArgsTests, Parse_VersionHelp) EXPECT_CALL(onExitMock, Call).WillOnce([](CliArgs::Action::Exit const& exit) { return exit.exitCode; }); EXPECT_EQ( - action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction(), onMigrateMock.AsStdFunction()), + action.apply( + onRunMock.AsStdFunction(), + onExitMock.AsStdFunction(), + onMigrateMock.AsStdFunction(), + onVerifyMock.AsStdFunction() + ), EXIT_SUCCESS ); } @@ -97,6 +114,34 @@ TEST_F(CliArgsTests, Parse_Config) return returnCode; }); EXPECT_EQ( - action.apply(onRunMock.AsStdFunction(), onExitMock.AsStdFunction(), onMigrateMock.AsStdFunction()), returnCode + action.apply( + onRunMock.AsStdFunction(), + onExitMock.AsStdFunction(), + onMigrateMock.AsStdFunction(), + onVerifyMock.AsStdFunction() + ), + returnCode + ); +} + +TEST_F(CliArgsTests, Parse_VerifyConfig) +{ + std::string_view configPath = "some_config_path"; + std::array argv{"clio_server", configPath.data(), "--verify"}; // NOLINT(bugprone-suspicious-stringview-data-usage) + auto const action = CliArgs::parse(argv.size(), argv.data()); + + int const returnCode = 123; + EXPECT_CALL(onVerifyMock, Call).WillOnce([&configPath](CliArgs::Action::VerifyConfig const& verify) { + EXPECT_EQ(verify.configPath, configPath); + return returnCode; + }); + EXPECT_EQ( + action.apply( + onRunMock.AsStdFunction(), + onExitMock.AsStdFunction(), + onMigrateMock.AsStdFunction(), + onVerifyMock.AsStdFunction() + ), + returnCode ); } diff --git a/tests/unit/app/VerifyConfigTests.cpp b/tests/unit/app/VerifyConfigTests.cpp new file mode 100644 index 000000000..9dad96286 --- /dev/null +++ b/tests/unit/app/VerifyConfigTests.cpp @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2025, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include "app/VerifyConfig.hpp" +#include "util/TmpFile.hpp" +#include "util/newconfig/FakeConfigData.hpp" + +#include + +using namespace app; +using namespace util::config; + +TEST(VerifyConfigTest, InvalidConfig) +{ + auto const tmpConfigFile = TmpFile(kJSON_DATA); + + // false because json data(kJSON_DATA) is not compatible with current configDefintion + EXPECT_FALSE(verifyConfig(tmpConfigFile.path)); +} + +TEST(VerifyConfigTest, ValidConfig) +{ + auto const tmpConfigFile = TmpFile(kVALID_JSON_DATA); + + // current example config should always be compatible with configDefinition + EXPECT_TRUE(verifyConfig(tmpConfigFile.path)); +} + +TEST(VerifyConfigTest, ConfigFileNotExist) +{ + EXPECT_FALSE(verifyConfig("doesn't exist Config File")); +} + +TEST(VerifyConfigTest, InvalidJsonFile) +{ + // invalid json because extra "," after 51233 + static constexpr auto kINVALID_JSON = R"({ + "server": { + "ip": "0.0.0.0", + "port": 51233, + } + })"; + auto const tmpConfigFile = TmpFile(kINVALID_JSON); + + EXPECT_FALSE(verifyConfig(tmpConfigFile.path)); +}