Monitoring System 0.1.0
System resource monitoring with pluggable collectors and alerting
Loading...
Searching...
No Matches
config_parser.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#pragma once
6
27#include <cctype>
28#include <chrono>
29#include <functional>
30#include <optional>
31#include <regex>
32#include <string>
33#include <type_traits>
34#include <unordered_map>
35#include <unordered_set>
36#include <vector>
37
38namespace kcenon::monitoring {
39
43using config_map = std::unordered_map<std::string, std::string>;
44
53 public:
62 template <typename T>
63 static T get(const config_map& config, const std::string& key, const T& default_value) {
64 auto it = config.find(key);
65 if (it == config.end()) {
66 return default_value;
67 }
68 return parse_value<T>(it->second, default_value);
69 }
70
78 template <typename T>
79 static std::optional<T> get_optional(const config_map& config, const std::string& key) {
80 auto it = config.find(key);
81 if (it == config.end()) {
82 return std::nullopt;
83 }
84 return parse_value_optional<T>(it->second);
85 }
86
93 static bool has_key(const config_map& config, const std::string& key) {
94 return config.find(key) != config.end();
95 }
96
107 template <typename T>
108 static T get_clamped(const config_map& config, const std::string& key, const T& default_value,
109 const T& min_value, const T& max_value) {
110 static_assert(std::is_arithmetic_v<T>, "get_clamped requires arithmetic type");
111 T value = get<T>(config, key, default_value);
112 if (value < min_value) {
113 return min_value;
114 }
115 if (value > max_value) {
116 return max_value;
117 }
118 return value;
119 }
120
130 template <typename T>
131 static T get_enum(const config_map& config, const std::string& key, const T& default_value,
132 const std::unordered_set<T>& allowed_values) {
133 T value = get<T>(config, key, default_value);
134 if (allowed_values.find(value) != allowed_values.end()) {
135 return value;
136 }
137 return default_value;
138 }
139
148 static std::string get_matching(const config_map& config, const std::string& key,
149 const std::string& default_value, const std::string& pattern) {
150 auto it = config.find(key);
151 if (it == config.end()) {
152 return default_value;
153 }
154 try {
155 std::regex regex(pattern);
156 if (std::regex_match(it->second, regex)) {
157 return it->second;
158 }
159 } catch (...) {
160 // Invalid regex or no match
161 }
162 return default_value;
163 }
164
174 template <typename T>
175 static T get_validated(const config_map& config, const std::string& key, const T& default_value,
176 std::function<bool(const T&)> validator) {
177 T value = get<T>(config, key, default_value);
178 if (validator(value)) {
179 return value;
180 }
181 return default_value;
182 }
183
196 template <typename Duration>
197 static Duration get_duration(const config_map& config, const std::string& key,
198 const Duration& default_value) {
199 auto it = config.find(key);
200 if (it == config.end()) {
201 return default_value;
202 }
203 return parse_duration<Duration>(it->second, default_value);
204 }
205
214 template <typename T>
215 static std::vector<T> get_list(const config_map& config, const std::string& key,
216 const std::vector<T>& default_values) {
217 auto it = config.find(key);
218 if (it == config.end()) {
219 return default_values;
220 }
221 return parse_list<T>(it->second, default_values);
222 }
223
224 private:
228 template <typename T>
229 static T parse_value(const std::string& str, const T& default_value) {
230 try {
231 if constexpr (std::is_same_v<T, bool>) {
232 return parse_bool(str);
233 } else if constexpr (std::is_same_v<T, std::string>) {
234 return str;
235 } else if constexpr (std::is_integral_v<T>) {
236 return parse_integral<T>(str);
237 } else if constexpr (std::is_floating_point_v<T>) {
238 return parse_floating<T>(str);
239 } else {
240 return default_value;
241 }
242 } catch (...) {
243 return default_value;
244 }
245 }
246
250 template <typename T>
251 static std::optional<T> parse_value_optional(const std::string& str) {
252 try {
253 if constexpr (std::is_same_v<T, bool>) {
254 return parse_bool(str);
255 } else if constexpr (std::is_same_v<T, std::string>) {
256 return str;
257 } else if constexpr (std::is_integral_v<T>) {
258 return parse_integral<T>(str);
259 } else if constexpr (std::is_floating_point_v<T>) {
260 return parse_floating<T>(str);
261 } else {
262 return std::nullopt;
263 }
264 } catch (...) {
265 return std::nullopt;
266 }
267 }
268
274 static bool parse_bool(const std::string& str) {
275 if (str.empty()) {
276 return false;
277 }
278 // Case-insensitive comparison
279 std::string lower = str;
280 for (auto& c : lower) {
281 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
282 }
283 return lower == "true" || lower == "1" || lower == "yes" || lower == "on";
284 }
285
289 template <typename T>
290 static T parse_integral(const std::string& str) {
291 static_assert(std::is_integral_v<T>, "parse_integral requires integral type");
292 if constexpr (std::is_signed_v<T>) {
293 long long value = std::stoll(str);
294 return static_cast<T>(value);
295 } else {
296 unsigned long long value = std::stoull(str);
297 return static_cast<T>(value);
298 }
299 }
300
304 template <typename T>
305 static T parse_floating(const std::string& str) {
306 static_assert(std::is_floating_point_v<T>, "parse_floating requires floating point type");
307 if constexpr (std::is_same_v<T, float>) {
308 return std::stof(str);
309 } else if constexpr (std::is_same_v<T, double>) {
310 return std::stod(str);
311 } else {
312 return static_cast<T>(std::stold(str));
313 }
314 }
315
320 template <typename Duration>
321 static Duration parse_duration(const std::string& str, const Duration& default_value) {
322 try {
323 if (str.empty()) {
324 return default_value;
325 }
326
327 // Find where the number ends and suffix begins
328 size_t suffix_start = str.find_first_not_of("0123456789.-");
329
330 if (suffix_start == std::string::npos) {
331 // Plain number, interpret as Duration's unit
332 long long value = std::stoll(str);
333 return Duration(value);
334 }
335
336 // Parse the numeric part
337 long long value = std::stoll(str.substr(0, suffix_start));
338 std::string suffix = str.substr(suffix_start);
339
340 // Trim whitespace from suffix
341 while (!suffix.empty() && std::isspace(static_cast<unsigned char>(suffix.front()))) {
342 suffix.erase(0, 1);
343 }
344
345 // Convert to lowercase
346 for (auto& c : suffix) {
347 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
348 }
349
350 // Parse suffix and convert to Duration
351 if (suffix == "ms" || suffix == "millisecond" || suffix == "milliseconds") {
352 return std::chrono::duration_cast<Duration>(std::chrono::milliseconds(value));
353 } else if (suffix == "s" || suffix == "sec" || suffix == "second" || suffix == "seconds") {
354 return std::chrono::duration_cast<Duration>(std::chrono::seconds(value));
355 } else if (suffix == "m" || suffix == "min" || suffix == "minute" || suffix == "minutes") {
356 return std::chrono::duration_cast<Duration>(std::chrono::minutes(value));
357 } else if (suffix == "h" || suffix == "hr" || suffix == "hour" || suffix == "hours") {
358 return std::chrono::duration_cast<Duration>(std::chrono::hours(value));
359 } else {
360 // Unknown suffix, treat as plain number
361 return Duration(value);
362 }
363 } catch (...) {
364 return default_value;
365 }
366 }
367
371 template <typename T>
372 static std::vector<T> parse_list(const std::string& str, const std::vector<T>& default_values) {
373 try {
374 if (str.empty()) {
375 return default_values;
376 }
377
378 std::vector<T> result;
379 std::string current;
380
381 for (char c : str) {
382 if (c == ',') {
383 // Trim whitespace
384 size_t start = current.find_first_not_of(" \t");
385 size_t end = current.find_last_not_of(" \t");
386 if (start != std::string::npos) {
387 std::string trimmed = current.substr(start, end - start + 1);
388 auto parsed = parse_value_optional<T>(trimmed);
389 if (parsed) {
390 result.push_back(*parsed);
391 }
392 }
393 current.clear();
394 } else {
395 current += c;
396 }
397 }
398
399 // Handle last element
400 if (!current.empty()) {
401 size_t start = current.find_first_not_of(" \t");
402 size_t end = current.find_last_not_of(" \t");
403 if (start != std::string::npos) {
404 std::string trimmed = current.substr(start, end - start + 1);
405 auto parsed = parse_value_optional<T>(trimmed);
406 if (parsed) {
407 result.push_back(*parsed);
408 }
409 }
410 }
411
412 return result.empty() ? default_values : result;
413 } catch (...) {
414 return default_values;
415 }
416 }
417};
418
419} // namespace kcenon::monitoring
Unified configuration parsing utility.
static T parse_integral(const std::string &str)
Parse integral value from string.
static T get_validated(const config_map &config, const std::string &key, const T &default_value, std::function< bool(const T &)> validator)
Get a configuration value with custom validation.
static std::optional< T > parse_value_optional(const std::string &str)
Parse a string value to target type as optional.
static Duration get_duration(const config_map &config, const std::string &key, const Duration &default_value)
Get a duration value from configuration.
static bool parse_bool(const std::string &str)
Parse boolean value from string.
static bool has_key(const config_map &config, const std::string &key)
Check if a configuration key exists.
static Duration parse_duration(const std::string &str, const Duration &default_value)
Parse duration string with optional suffix Supported suffixes: ms (milliseconds), s (seconds),...
static T parse_value(const std::string &str, const T &default_value)
Parse a string value to target type with default fallback.
static std::optional< T > get_optional(const config_map &config, const std::string &key)
Get a configuration value as optional.
static std::vector< T > get_list(const config_map &config, const std::string &key, const std::vector< T > &default_values)
Get a list of values from a comma-separated string.
static T get_enum(const config_map &config, const std::string &key, const T &default_value, const std::unordered_set< T > &allowed_values)
Get a configuration value from a set of allowed values.
static T parse_floating(const std::string &str)
Parse floating-point value from string.
static std::vector< T > parse_list(const std::string &str, const std::vector< T > &default_values)
Parse comma-separated list of values.
static T get(const config_map &config, const std::string &key, const T &default_value)
Get a configuration value with type conversion.
static std::string get_matching(const config_map &config, const std::string &key, const std::string &default_value, const std::string &pattern)
Get a string configuration value matching a regex pattern.
static T get_clamped(const config_map &config, const std::string &key, const T &default_value, const T &min_value, const T &max_value)
Get a configuration value with validation.
std::unordered_map< std::string, std::string > config_map
Type alias for configuration map.