Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
114 changes: 114 additions & 0 deletions src/iceberg/util/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once

#include <format>
#include <functional>
#include <map>
#include <string>

namespace iceberg {

template <class ConcreteConfig>
class ConfigBase {
public:
template <typename T>
class Entry {
Entry(
std::string key, const T& val,
std::function<std::string(const T&)> toStr =
[](const T& val) {
if constexpr ((std::is_signed_v<T> && std::is_integral_v<T>) ||
std::is_floating_point_v<T>) {
return std::to_string(val);
} else if constexpr (std::is_same_v<T, bool>) {
return val ? "true" : "false";
} else if constexpr (std::is_same_v<T, std::string> ||
std::is_same_v<T, std::string_view>) {
return val;
} else {
throw std::runtime_error(
std::format("Explicit toStr() is required for {}", typeid(T).name()));
}
},
std::function<T(const std::string&)> toT = [](const std::string& val) -> T {
if constexpr (std::is_same_v<T, std::string>) {
return val;
} else if constexpr (std::is_same_v<T, bool>) {
return val == "true";
} else if constexpr (std::is_signed_v<T> && std::is_integral_v<T>) {
return static_cast<T>(std::stoll(val));
} else if constexpr (std::is_floating_point_v<T>) {
return static_cast<T>(std::stod(val));
} else {
throw std::runtime_error(
std::format("Explicit toT() is required for {}", typeid(T).name()));
}
})
: key_{std::move(key)}, default_{val}, toStr_{toStr}, toT_{toT} {}

const std::string key_;
const T default_;
const std::function<std::string(const T&)> toStr_;
const std::function<T(const std::string&)> toT_;

friend ConfigBase;
friend ConcreteConfig;

public:
const std::string& key() const { return key_; }

T value() const { return default_; }
};

template <typename T>
ConfigBase& set(const Entry<T>& entry, const T& val) {
configs_[entry.key_] = entry.toStr_(val);
return *this;
}

template <typename T>
ConfigBase& unset(const Entry<T>& entry) {
configs_.erase(entry.key_);
return *this;
}

ConfigBase& reset() {
configs_.clear();
return *this;
}

template <typename T>
T get(const Entry<T>& entry) const {
try {
auto iter = configs_.find(entry.key_);
return iter != configs_.cend() ? entry.toT_(iter->second) : entry.default_;
} catch (std::exception& e) {
throw std::runtime_error(
std::format("Failed to get config {} with error: {}", entry.key_, e.what()));
}
}

std::map<std::string, std::string> const& configs() const { return configs_; }

protected:
std::map<std::string, std::string> configs_;
};

} // namespace iceberg
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ target_link_libraries(json_serde_test PRIVATE iceberg_static GTest::gtest_main
add_test(NAME json_serde_test COMMAND json_serde_test)

add_executable(util_test)
target_sources(util_test PRIVATE expected_test.cc formatter_test.cc)
target_sources(util_test PRIVATE expected_test.cc formatter_test.cc config_test.cc)
target_link_libraries(util_test PRIVATE iceberg_static GTest::gtest_main GTest::gmock)
add_test(NAME util_test COMMAND util_test)

Expand Down
110 changes: 110 additions & 0 deletions test/config_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

#include <string>

#include <gtest/gtest.h>
#include <iceberg/util/config.h>

namespace iceberg {

enum class TestEnum { VALUE1, VALUE2, VALUE3 };

std::string enumToString(const TestEnum& val) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::string enumToString(const TestEnum& val) {
std::string EnumToString(const TestEnum& val) {

nit

switch (val) {
case TestEnum::VALUE1:
return "VALUE1";
case TestEnum::VALUE2:
return "VALUE2";
case TestEnum::VALUE3:
return "VALUE3";
default:
throw std::runtime_error("Invalid enum value");
}
}

TestEnum stringToEnum(const std::string& val) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
TestEnum stringToEnum(const std::string& val) {
TestEnum StringToEnum(const std::string& val) {

nit

if (val == "VALUE1") {
return TestEnum::VALUE1;
} else if (val == "VALUE2") {
return TestEnum::VALUE2;
} else if (val == "VALUE3") {
return TestEnum::VALUE3;
} else {
throw std::runtime_error("Invalid enum string");
}
}

// Define a concrete config class for testing
class TestConfig : public ConfigBase<TestConfig> {
public:
template <typename T>
using Entry = const ConfigBase<TestConfig>::Entry<T>;

inline static const Entry<std::string> STRING_CONFIG{"string_config", "default_value"};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe we should follow the k-prefix pattern for static const variables.

inline static const Entry<int> INT_CONFIG{"int_config", 25};
inline static const Entry<bool> BOOL_CONFIG{"bool_config", false};
inline static const Entry<TestEnum> ENUM_CONFIG{"enum_config", TestEnum::VALUE1,
enumToString, stringToEnum};
inline static const Entry<double> DOUBLE_CONFIG{"double_config", 3.14};
};

TEST(ConfigTest, BasicOperations) {
TestConfig config;

// Test default values
ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), std::string("default_value"));
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 25);
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), false);
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE1);
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 3.14);

// Test setting values
config.set(TestConfig::STRING_CONFIG, std::string("new_value"));
config.set(TestConfig::INT_CONFIG, 100);
config.set(TestConfig::BOOL_CONFIG, true);
config.set(TestConfig::ENUM_CONFIG, TestEnum::VALUE2);
config.set(TestConfig::DOUBLE_CONFIG, 2.99);

ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), "new_value");
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 100);
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), true);
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE2);
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 2.99);

// Test unsetting a value
config.unset(TestConfig::INT_CONFIG);
config.unset(TestConfig::ENUM_CONFIG);
config.unset(TestConfig::DOUBLE_CONFIG);
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 25);
ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), "new_value");
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), true);
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE1);
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 3.14);

// Test resetting all values
config.reset();
ASSERT_EQ(config.get(TestConfig::STRING_CONFIG), "default_value");
ASSERT_EQ(config.get(TestConfig::INT_CONFIG), 25);
ASSERT_EQ(config.get(TestConfig::BOOL_CONFIG), false);
ASSERT_EQ(config.get(TestConfig::ENUM_CONFIG), TestEnum::VALUE1);
ASSERT_EQ(config.get(TestConfig::DOUBLE_CONFIG), 3.14);
}

} // namespace iceberg
Loading