Skip to content

Commit

Permalink
feat: add config verify flag (#1814)
Browse files Browse the repository at this point in the history
fixes #1806
  • Loading branch information
PeterChen13579 authored Jan 13, 2025
1 parent 91c00e7 commit f1698c5
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 27 deletions.
4 changes: 4 additions & 0 deletions src/app/CliArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ CliArgs::parse(int argc, char const* argv[])
("conf,c", po::value<std::string>()->default_value(kDEFAULT_CONFIG_PATH), "configuration file")
("ng-web-server,w", "Use ng-web-server")
("migrate", po::value<std::string>(), "start migration helper")
("verify", "Checks the validity of config values")
;
// clang-format on
po::positional_options_description positional;
Expand Down Expand Up @@ -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}
};
}
Expand Down
9 changes: 7 additions & 2 deletions src/app/CliArgs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,19 @@ class CliArgs {
MigrateSubCmd subCmd;
};

/** @brief Verify Config action. */
struct VerifyConfig {
std::string configPath;
};

/**
* @brief Construct an action from a Run.
*
* @param action Run action.
*/
template <typename ActionType>
requires std::is_same_v<ActionType, Run> or std::is_same_v<ActionType, Exit> or
std::is_same_v<ActionType, Migrate>
std::is_same_v<ActionType, Migrate> or std::is_same_v<ActionType, VerifyConfig>
explicit Action(ActionType&& action) : action_(std::forward<ActionType>(action))
{
}
Expand All @@ -86,7 +91,7 @@ class CliArgs {
}

private:
std::variant<Run, Exit, Migrate> action_;
std::variant<Run, Exit, Migrate, VerifyConfig> action_;
};

/**
Expand Down
55 changes: 55 additions & 0 deletions src/app/VerifyConfig.hpp
Original file line number Diff line number Diff line change
@@ -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 <cstdlib>
#include <iostream>
#include <string_view>

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
35 changes: 14 additions & 21 deletions src/main/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cstdlib>
#include <exception>
Expand All @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/util/newconfig/ConfigDefinition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)}},
Expand Down
8 changes: 8 additions & 0 deletions tests/common/util/newconfig/FakeConfigData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
1 change: 1 addition & 0 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 49 additions & 4 deletions tests/unit/app/CliArgsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct CliArgsTests : testing::Test {
testing::StrictMock<testing::MockFunction<int(CliArgs::Action::Run)>> onRunMock;
testing::StrictMock<testing::MockFunction<int(CliArgs::Action::Exit)>> onExitMock;
testing::StrictMock<testing::MockFunction<int(CliArgs::Action::Migrate)>> onMigrateMock;
testing::StrictMock<testing::MockFunction<int(CliArgs::Action::VerifyConfig)>> onVerifyMock;
};

TEST_F(CliArgsTests, Parse_NoArgs)
Expand All @@ -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
);
}

Expand All @@ -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
);
}
Expand All @@ -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
);
}
Expand All @@ -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
);
}
62 changes: 62 additions & 0 deletions tests/unit/app/VerifyConfigTests.cpp
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>

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));
}

0 comments on commit f1698c5

Please sign in to comment.