unilink  0.4.3
A simple C++ library for unified async communication
config_manager.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2025 Jinwoo Sung
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "config_manager.hpp"
18 
19 #include <algorithm>
20 #include <fstream>
21 #include <iostream>
22 #include <mutex>
23 #include <sstream>
24 #include <stdexcept>
25 #include <unordered_map>
26 
28 
29 namespace unilink {
30 namespace config {
31 
33  mutable std::mutex mutex_;
34  std::unordered_map<std::string, ConfigItem> config_items_;
35  std::unordered_map<std::string, ConfigChangeCallback> change_callbacks_;
36 
37  ValidationResult validate_value(const std::string& key, const std::any& value) const {
38  auto it = config_items_.find(key);
39  if (it != config_items_.end()) {
40  if (it->second.validator) {
41  return it->second.validator(value);
42  }
43 
44  ConfigType expected_type = it->second.type;
45  ConfigType actual_type = ConfigType::String;
46 
47  if (value.type() == typeid(std::string)) {
48  actual_type = ConfigType::String;
49  } else if (value.type() == typeid(int)) {
50  actual_type = ConfigType::Integer;
51  } else if (value.type() == typeid(bool)) {
52  actual_type = ConfigType::Boolean;
53  } else if (value.type() == typeid(double)) {
54  actual_type = ConfigType::Double;
55  }
56 
57  if (expected_type != actual_type) {
58  return ValidationResult::error("Type mismatch for key '" + key + "'");
59  }
60  }
62  }
63 
64  void notify_change(const std::string& key, const std::any& old_value, const std::any& new_value) {
65  auto it = change_callbacks_.find(key);
66  if (it != change_callbacks_.end()) {
67  try {
68  it->second(key, old_value, new_value);
69  } catch (const std::exception& e) {
70  UNILINK_LOG_ERROR("config_manager", "callback",
71  "Error in change callback for key '" + key + "': " + std::string(e.what()));
72  }
73  }
74  }
75 
76  std::string serialize_value(const std::any& value, ConfigType type) const {
77  try {
78  switch (type) {
79  case ConfigType::String:
80  return std::any_cast<std::string>(value);
82  return std::to_string(std::any_cast<int>(value));
84  return std::any_cast<bool>(value) ? "true" : "false";
85  case ConfigType::Double:
86  return std::to_string(std::any_cast<double>(value));
87  default:
88  return "unknown";
89  }
90  } catch (const std::bad_any_cast&) {
91  return "unknown";
92  }
93  }
94 
95  std::any deserialize_value(const std::string& value_str, ConfigType type) const {
96  try {
97  switch (type) {
98  case ConfigType::String:
99  return std::any(value_str);
100  case ConfigType::Integer:
101  return std::any(std::stoi(value_str));
102  case ConfigType::Boolean:
103  return std::any(value_str == "true");
104  case ConfigType::Double:
105  return std::any(std::stod(value_str));
106  default:
107  return std::any(value_str);
108  }
109  } catch (const std::exception&) {
110  return std::any(value_str);
111  }
112  }
113 };
114 
115 ConfigManager::ConfigManager() : impl_(std::make_unique<Impl>()) {}
117 
118 ConfigManager::ConfigManager(ConfigManager&&) noexcept = default;
119 ConfigManager& ConfigManager::operator=(ConfigManager&&) noexcept = default;
120 
121 std::any ConfigManager::get(const std::string& key) const {
122  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
123  auto it = get_impl()->config_items_.find(key);
124  if (it != get_impl()->config_items_.end()) {
125  return it->second.value;
126  }
127  throw std::runtime_error("Configuration key not found: " + key);
128 }
129 
130 std::any ConfigManager::get(const std::string& key, const std::any& default_value) const {
131  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
132  auto it = get_impl()->config_items_.find(key);
133  if (it != get_impl()->config_items_.end()) {
134  return it->second.value;
135  }
136  return default_value;
137 }
138 
139 bool ConfigManager::has(const std::string& key) const {
140  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
141  return get_impl()->config_items_.find(key) != get_impl()->config_items_.end();
142 }
143 
144 ValidationResult ConfigManager::set(const std::string& key, const std::any& value) {
145  std::lock_guard<std::mutex> lock(impl_->mutex_);
146 
147  auto validation_result = impl_->validate_value(key, value);
148  if (!validation_result.is_valid) {
149  return validation_result;
150  }
151 
152  std::any old_value;
153  bool had_key = false;
154  auto it = impl_->config_items_.find(key);
155  if (it != impl_->config_items_.end()) {
156  old_value = it->second.value;
157  had_key = true;
158  it->second.value = value;
159  } else {
160  ConfigItem item(key, value, ConfigType::String, false);
161  impl_->config_items_[key] = item;
162  }
163 
164  if (had_key) {
165  impl_->notify_change(key, old_value, value);
166  }
167 
168  return ValidationResult::success();
169 }
170 
171 bool ConfigManager::remove(const std::string& key) {
172  std::lock_guard<std::mutex> lock(impl_->mutex_);
173  auto it = impl_->config_items_.find(key);
174  if (it != impl_->config_items_.end()) {
175  impl_->config_items_.erase(it);
176  return true;
177  }
178  return false;
179 }
180 
182  std::lock_guard<std::mutex> lock(impl_->mutex_);
183  impl_->config_items_.clear();
184 }
185 
187  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
188 
189  for (const auto& [key, item] : get_impl()->config_items_) {
190  auto result = get_impl()->validate_value(key, item.value);
191  if (!result.is_valid) {
192  return result;
193  }
194  }
195 
196  return ValidationResult::success();
197 }
198 
199 ValidationResult ConfigManager::validate(const std::string& key) const {
200  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
201  auto it = get_impl()->config_items_.find(key);
202  if (it == get_impl()->config_items_.end()) {
203  return ValidationResult::error("Configuration key not found: " + key);
204  }
205 
206  return get_impl()->validate_value(key, it->second.value);
207 }
208 
210  std::lock_guard<std::mutex> lock(impl_->mutex_);
211  impl_->config_items_[item.key] = item;
212 }
213 
214 void ConfigManager::register_validator(const std::string& key,
215  std::function<ValidationResult(const std::any&)> validator) {
216  std::lock_guard<std::mutex> lock(impl_->mutex_);
217  auto it = impl_->config_items_.find(key);
218  if (it != impl_->config_items_.end()) {
219  it->second.validator = validator;
220  }
221 }
222 
223 void ConfigManager::on_change(const std::string& key, ConfigChangeCallback callback) {
224  std::lock_guard<std::mutex> lock(impl_->mutex_);
225  impl_->change_callbacks_[key] = callback;
226 }
227 
228 void ConfigManager::remove_change_callback(const std::string& key) {
229  std::lock_guard<std::mutex> lock(impl_->mutex_);
230  impl_->change_callbacks_.erase(key);
231 }
232 
233 bool ConfigManager::save_to_file(const std::string& filepath) const {
234  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
235 
236  try {
237  std::ofstream file(filepath);
238  if (!file.is_open()) {
239  return false;
240  }
241 
242  file << "# unilink configuration file\n";
243  file << "# Generated automatically\n\n";
244 
245  for (const auto& [key, item] : get_impl()->config_items_) {
246  file << "# " << item.description << "\n";
247  file << key << "=" << get_impl()->serialize_value(item.value, item.type) << "\n\n";
248  }
249 
250  return true;
251  } catch (const std::exception& e) {
252  UNILINK_LOG_ERROR("config_manager", "save", "Error saving configuration: " + std::string(e.what()));
253  return false;
254  }
255 }
256 
257 bool ConfigManager::load_from_file(const std::string& filepath) {
258  std::lock_guard<std::mutex> lock(impl_->mutex_);
259 
260  try {
261  std::ifstream file(filepath);
262  if (!file.is_open()) {
263  return false;
264  }
265 
266  std::string line;
267  while (std::getline(file, line)) {
268  if (line.empty() || line[0] == '#') {
269  continue;
270  }
271 
272  size_t pos = line.find('=');
273  if (pos != std::string::npos) {
274  std::string key = line.substr(0, pos);
275  std::string value_str = line.substr(pos + 1);
276 
277  key.erase(0, key.find_first_not_of(" \t"));
278  key.erase(key.find_last_not_of(" \t") + 1);
279  value_str.erase(0, value_str.find_first_not_of(" \t"));
280  value_str.erase(value_str.find_last_not_of(" \t") + 1);
281 
283  auto it = impl_->config_items_.find(key);
284  bool exists = (it != impl_->config_items_.end());
285 
286  if (exists) {
287  type = it->second.type;
288  } else {
289  if (value_str == "true" || value_str == "false") {
290  type = ConfigType::Boolean;
291  } else if (std::all_of(value_str.begin(), value_str.end(),
292  [](char c) { return std::isdigit(c) || c == '-'; })) {
293  type = ConfigType::Integer;
294  } else if (std::count(value_str.begin(), value_str.end(), '.') == 1 &&
295  std::all_of(value_str.begin(), value_str.end(),
296  [](char c) { return std::isdigit(c) || c == '.' || c == '-'; })) {
297  type = ConfigType::Double;
298  }
299  }
300 
301  std::any value = impl_->deserialize_value(value_str, type);
302 
303  if (exists) {
304  auto result = impl_->validate_value(key, value);
305  if (!result.is_valid) {
306  UNILINK_LOG_ERROR("config_manager", "load",
307  "Validation failed for key '" + key + "': " + result.error_message);
308  continue;
309  }
310 
311  std::any old_value = it->second.value;
312  it->second.value = value;
313  impl_->notify_change(key, old_value, value);
314  } else {
315  ConfigItem item(key, value, type, false);
316  impl_->config_items_[key] = item;
317  }
318  }
319  }
320 
321  return true;
322  } catch (const std::exception& e) {
323  UNILINK_LOG_ERROR("config_manager", "load", "Error loading configuration: " + std::string(e.what()));
324  return false;
325  }
326 }
327 
328 std::vector<std::string> ConfigManager::get_keys() const {
329  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
330  std::vector<std::string> keys;
331  keys.reserve(get_impl()->config_items_.size());
332 
333  for (const auto& [key, item] : get_impl()->config_items_) {
334  keys.push_back(key);
335  }
336 
337  return keys;
338 }
339 
340 ConfigType ConfigManager::get_type(const std::string& key) const {
341  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
342  auto it = get_impl()->config_items_.find(key);
343  if (it != get_impl()->config_items_.end()) {
344  return it->second.type;
345  }
346  throw std::runtime_error("Configuration key not found: " + key);
347 }
348 
349 std::string ConfigManager::get_description(const std::string& key) const {
350  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
351  auto it = get_impl()->config_items_.find(key);
352  if (it != get_impl()->config_items_.end()) {
353  return it->second.description;
354  }
355  return "";
356 }
357 
358 bool ConfigManager::is_required(const std::string& key) const {
359  std::lock_guard<std::mutex> lock(get_impl()->mutex_);
360  auto it = get_impl()->config_items_.find(key);
361  if (it != get_impl()->config_items_.end()) {
362  return it->second.required;
363  }
364  return false;
365 }
366 
367 } // namespace config
368 } // namespace unilink
#define UNILINK_LOG_ERROR(component, operation, message)
Definition: logger.hpp:279