Skip to content

Commit d0b382c

Browse files
feat: Check type on compile time
1 parent c8b9954 commit d0b382c

6 files changed

Lines changed: 434 additions & 108 deletions

File tree

examples/ExampleConfiguration.h

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ enum class AppConfigName : uint8_t {
2222
LogLevel
2323
};
2424

25+
} // namespace example
26+
27+
namespace example {
28+
2529
// Implement the toString and fromString functions for the enum
2630
// (These are required by JsonSerializer)
2731
inline std::string ToString(AppConfigName name)
@@ -62,7 +66,7 @@ inline AppConfigName FromString(const std::string& str)
6266
throw std::runtime_error("Invalid configuration name: " + str);
6367
}
6468

65-
} // namespace example
69+
} // namespace example
6670

6771
// Provide template specializations for the JsonSerializer
6872
namespace config {
@@ -77,57 +81,56 @@ inline example::AppConfigName JsonSerializer<example::AppConfigName>::FromString
7781
{
7882
return example::FromString(str);
7983
}
80-
} // namespace config
84+
} // namespace config
8185

8286
namespace example {
8387

8488
// Define the configuration type
85-
using AppConfig = config::GenericConfiguration<AppConfigName, config::JsonSerializer<AppConfigName>>;
86-
89+
using AppConfig = ::config::GenericConfiguration<AppConfigName, ::config::JsonSerializer<AppConfigName>>;
8790
// Define the variant type for AppConfig settings
8891
using AppSettingVariant = std::variant<
89-
config::Setting<AppConfigName, int>,
90-
config::Setting<AppConfigName, float>,
91-
config::Setting<AppConfigName, double>,
92-
config::Setting<AppConfigName, std::string>,
93-
config::Setting<AppConfigName, bool>>;
92+
::config::Setting<AppConfigName, int>,
93+
::config::Setting<AppConfigName, float>,
94+
::config::Setting<AppConfigName, double>,
95+
::config::Setting<AppConfigName, std::string>,
96+
::config::Setting<AppConfigName, bool>>;
9497

9598
// Define default configuration values
9699
inline const AppConfig::DefaultConfigMap DefaultAppConfig = {
97100
{ AppConfigName::DatabaseUrl,
98-
config::Setting<AppConfigName, std::string>(
101+
::config::Setting<AppConfigName, std::string>(
99102
AppConfigName::DatabaseUrl,
100103
"mongodb://localhost:27017",
101104
std::nullopt,
102105
std::nullopt,
103106
std::nullopt,
104107
"URL for database connection") },
105108
{ AppConfigName::MaxConnections,
106-
config::Setting<AppConfigName, int>(
109+
::config::Setting<AppConfigName, int>(
107110
AppConfigName::MaxConnections,
108111
100,
109112
1000,
110113
1,
111114
"connections",
112115
"Maximum number of database connections") },
113116
{ AppConfigName::EnableLogging,
114-
config::Setting<AppConfigName, bool>(
117+
::config::Setting<AppConfigName, bool>(
115118
AppConfigName::EnableLogging,
116119
true,
117120
std::nullopt,
118121
std::nullopt,
119122
std::nullopt,
120123
"Enable application logging") },
121124
{ AppConfigName::RetryCount,
122-
config::Setting<AppConfigName, int>(
125+
::config::Setting<AppConfigName, int>(
123126
AppConfigName::RetryCount,
124127
3,
125128
10,
126129
0,
127130
"retries",
128131
"Number of retry attempts for operations") },
129132
{ AppConfigName::LogLevel,
130-
config::Setting<AppConfigName, std::string>(
133+
::config::Setting<AppConfigName, std::string>(
131134
AppConfigName::LogLevel,
132135
"info",
133136
std::nullopt,
@@ -142,13 +145,13 @@ class Application {
142145
explicit Application(const std::filesystem::path& config_path)
143146
: config_(config_path, DefaultAppConfig)
144147
{
145-
// Access configuration values in a type-safe way with the new API
148+
// Access configuration values in a type-safe way with natural syntax
146149
auto db_url = config_.GetSetting(AppConfigName::DatabaseUrl).Value<std::string>();
147150
auto max_conn = config_.GetSetting(AppConfigName::MaxConnections).Value<int>();
148151
auto logging_enabled = config_.GetSetting(AppConfigName::EnableLogging).Value<bool>();
149152

150-
// Example of updating a setting
151-
config_.UpdateSetting(AppConfigName::RetryCount, 5);
153+
// Example of updating a setting with type checking
154+
config_.UpdateSettingValue<int>(AppConfigName::RetryCount, 5);
152155
config_.Save();
153156
}
154157

examples/example.cpp

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,43 +9,37 @@ int main()
99
std::cout << "Configuration Library Example" << '\n';
1010
std::cout << "----------------------------" << '\n';
1111

12-
// Initialize the configuration with a file path
12+
// Initialize configuration
1313
std::filesystem::path config_path = "config.json";
1414
example::Application app(config_path);
1515

16-
// Access the configuration object
16+
// Get config instance
1717
auto& config = app.GetConfig();
1818

19-
// Demonstrate retrieving values with the new, more natural syntax
20-
std::cout << "Database URL: "
21-
<< config.GetSetting(example::AppConfigName::DatabaseUrl).Value<std::string>()
22-
<< '\n';
19+
// Type-safe value retrieval
20+
std::string db_url = config.GetSetting(example::AppConfigName::DatabaseUrl).Value<std::string>();
21+
std::cout << "Database URL: " << db_url << '\n';
2322

24-
std::cout << "Max Connections: "
25-
<< config.GetSetting(example::AppConfigName::MaxConnections).Value<int>()
26-
<< '\n';
23+
int max_conn = config.GetSetting(example::AppConfigName::MaxConnections).Value<int>();
24+
std::cout << "Max Connections: " << max_conn << '\n';
2725

28-
std::cout << "Logging Enabled: "
29-
<< (config.GetSetting(example::AppConfigName::EnableLogging).Value<bool>() ? "Yes" : "No")
30-
<< '\n';
26+
bool logging_enabled = config.GetSetting(example::AppConfigName::EnableLogging).Value<bool>();
27+
std::cout << "Logging Enabled: " << (logging_enabled ? "Yes" : "No") << '\n';
3128

32-
std::cout << "Retry Count: "
33-
<< config.GetSetting(example::AppConfigName::RetryCount).Value<int>()
34-
<< '\n';
29+
int retry_count = config.GetSetting(example::AppConfigName::RetryCount).Value<int>();
30+
std::cout << "Retry Count: " << retry_count << '\n';
3531

36-
std::cout << "Log Level: "
37-
<< config.GetSetting(example::AppConfigName::LogLevel).Value<std::string>()
38-
<< '\n';
32+
std::string log_level = config.GetSetting(example::AppConfigName::LogLevel).Value<std::string>();
33+
std::cout << "Log Level: " << log_level << '\n';
3934

40-
// Demonstrate updating a setting
35+
// Update a setting
4136
std::cout << "\nUpdating max connections to 200..." << '\n';
42-
config.UpdateSetting(example::AppConfigName::MaxConnections, 200);
37+
config.UpdateSettingValue<int>(example::AppConfigName::MaxConnections, 200);
4338

44-
std::cout << "New Max Connections: "
45-
<< config.GetSetting(example::AppConfigName::MaxConnections).Value<int>()
46-
<< '\n';
39+
int new_max_conn = config.GetSetting(example::AppConfigName::MaxConnections).Value<int>();
40+
std::cout << "New Max Connections: " << new_max_conn << '\n';
4741

48-
// Save changes
42+
// Save to file
4943
if (config.Save()) {
5044
std::cout << "\nChanges saved successfully to \"" << config_path.string() << "\"" << '\n';
5145
}

src/GenericConfiguration.h

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,107 @@ class GenericConfiguration : public IConfiguration<E> {
3737
using SettingVariant = typename IConfiguration<E>::SettingVariant;
3838
using DefaultConfigMap = std::unordered_map<E, SettingVariant>;
3939

40+
/**
41+
* @brief Get the default type for a specific enum value
42+
*
43+
* @param enum_value The enum value to get the type for
44+
* @return std::string A string representation of the type
45+
*/
46+
std::string GetDefaultValueType(E enum_value) const
47+
{
48+
auto it = defaults_.find(enum_value);
49+
if (it == defaults_.end()) {
50+
throw std::runtime_error("No default setting found for this enum value");
51+
}
52+
53+
return std::visit([](const auto& setting) -> std::string {
54+
using ValueType = std::decay_t<decltype(setting.Value())>;
55+
56+
if constexpr (std::is_same_v<ValueType, int>) {
57+
return "int";
58+
}
59+
else if constexpr (std::is_same_v<ValueType, float>) {
60+
return "float";
61+
}
62+
else if constexpr (std::is_same_v<ValueType, double>) {
63+
return "double";
64+
}
65+
else if constexpr (std::is_same_v<ValueType, std::string>) {
66+
return "string";
67+
}
68+
else if constexpr (std::is_same_v<ValueType, bool>) {
69+
return "bool";
70+
}
71+
else {
72+
return "unknown";
73+
}
74+
},
75+
it->second);
76+
}
77+
78+
/**
79+
* @brief Function to check if a type matches the default type
80+
*
81+
* @param enum_value The enum value to check
82+
* @return The expected type as a string
83+
*/
84+
std::function<std::string(E)> GetTypeChecker() const
85+
{
86+
return [this](E enum_value) -> std::string {
87+
return this->GetDefaultValueType(enum_value);
88+
};
89+
}
90+
91+
/**
92+
* @brief Check if the given type matches the default type for the enum value
93+
*
94+
* @tparam T The type to check
95+
* @param enum_value The enum value to check against
96+
* @return true if the types match
97+
* @return false if the types don't match
98+
*/
99+
template <typename T>
100+
bool IsCorrectType(E enum_value) const
101+
{
102+
std::string type_name;
103+
104+
if constexpr (std::is_same_v<T, int>) {
105+
type_name = "int";
106+
}
107+
else if constexpr (std::is_same_v<T, float>) {
108+
type_name = "float";
109+
}
110+
else if constexpr (std::is_same_v<T, double>) {
111+
type_name = "double";
112+
}
113+
else if constexpr (std::is_same_v<T, std::string>) {
114+
type_name = "string";
115+
}
116+
else if constexpr (std::is_same_v<T, bool>) {
117+
type_name = "bool";
118+
}
119+
else {
120+
type_name = "unknown";
121+
}
122+
123+
return type_name == GetDefaultValueType(enum_value);
124+
}
125+
126+
/**
127+
* @brief Type-safe getter with runtime type checking against defaults
128+
* This method is provided for backward compatibility or advanced use cases
129+
*
130+
* @tparam EnumValue The enum value to retrieve
131+
* @tparam T The requested type
132+
* @return The setting value with the correct type
133+
* @throws std::runtime_error if the type doesn't match the default type
134+
*/
135+
template <E EnumValue, typename T>
136+
const T& GetTypedValue() const
137+
{
138+
return GetSetting(EnumValue).template Value<T>();
139+
}
140+
40141
/**
41142
* @brief Constructor with filepath and default configurations
42143
*
@@ -82,6 +183,26 @@ class GenericConfiguration : public IConfiguration<E> {
82183
throw std::runtime_error("Configuration not found");
83184
}
84185

186+
/**
187+
* @brief Const version of GetSettingVariant
188+
*/
189+
SettingVariant GetSettingVariant(E config_name) const
190+
{
191+
// Check if setting exists in active settings
192+
auto it = settings_.find(config_name);
193+
if (it != settings_.end()) {
194+
return it->second;
195+
}
196+
197+
// Fall back to defaults if not in active settings
198+
auto default_it = defaults_.find(config_name);
199+
if (default_it != defaults_.end()) {
200+
return default_it->second;
201+
}
202+
203+
throw std::runtime_error("Configuration not found");
204+
}
205+
85206
/**
86207
* @brief Implementation of UpdateSettingInt from IConfiguration
87208
*/
@@ -122,6 +243,17 @@ class GenericConfiguration : public IConfiguration<E> {
122243
return UpdateSettingImpl<bool>(config_name, value);
123244
}
124245

246+
/**
247+
* @brief Get a setting with type checking based on defaults
248+
*
249+
* @param config_name The configuration name
250+
* @return GenericSetting<E> A type-safe wrapper for the setting
251+
*/
252+
GenericSetting<E> GetSetting(E config_name) override
253+
{
254+
return GenericSetting<E>(GetSettingVariant(config_name), config_name, GetTypeChecker());
255+
}
256+
125257
/**
126258
* @brief Save the configuration to file
127259
*
@@ -152,6 +284,16 @@ class GenericConfiguration : public IConfiguration<E> {
152284
return settings_;
153285
}
154286

287+
/**
288+
* @brief Get the default settings map
289+
*
290+
* @return const DefaultConfigMap& The default settings
291+
*/
292+
const DefaultConfigMap& GetDefaults() const
293+
{
294+
return defaults_;
295+
}
296+
155297
/**
156298
* @brief Update settings directly (useful for serializers)
157299
*
@@ -162,6 +304,43 @@ class GenericConfiguration : public IConfiguration<E> {
162304
settings_ = new_settings;
163305
}
164306

307+
/**
308+
* @brief Type-safe update method that checks at runtime if the type matches the default
309+
*
310+
* @tparam EnumValue Enum value to update
311+
* @tparam T Type of the value
312+
* @param value New value
313+
* @return true if update was successful
314+
* @throws std::runtime_error if the type doesn't match the default type
315+
*/
316+
template <E EnumValue, typename T>
317+
bool UpdateTypedSetting(T value)
318+
{
319+
if (!IsCorrectType<T>(EnumValue)) {
320+
throw std::runtime_error("Type mismatch: The provided type doesn't match the type in default configuration");
321+
}
322+
323+
return UpdateSettingImpl<T>(EnumValue, value);
324+
}
325+
326+
/**
327+
* @brief Update a setting with a more natural syntax
328+
*
329+
* @tparam T Type of the value
330+
* @param config_name The configuration name
331+
* @param value The new value
332+
* @return true if successful
333+
*/
334+
template <typename T>
335+
bool UpdateSettingValue(E config_name, T value)
336+
{
337+
if (!IsCorrectType<T>(config_name)) {
338+
throw std::runtime_error("Type mismatch: The provided type doesn't match the type in default configuration");
339+
}
340+
341+
return UpdateSettingImpl<T>(config_name, value);
342+
}
343+
165344
private:
166345
/**
167346
* @brief Implementation of setting update logic

0 commit comments

Comments
 (0)