Quandary
Loading...
Searching...
No Matches
config_validators.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <sstream>
4#include <stdexcept>
5#include <string>
6#include <toml++/toml.hpp>
7#include <vector>
8
52namespace validators {
53
57template <typename T>
58std::string getTypeName() {
59 if constexpr (std::is_same_v<T, int> || std::is_same_v<T, int64_t>) {
60 return "integer";
61 } else if constexpr (std::is_same_v<T, size_t>) {
62 return "nonnegative integer";
63 } else if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>) {
64 return "number";
65 } else if constexpr (std::is_same_v<T, std::string>) {
66 return "string";
67 } else if constexpr (std::is_same_v<T, bool>) {
68 return "boolean";
69 } else {
70 return "unknown type";
71 }
72}
73
77class ValidationError : public std::runtime_error {
78 public:
79 ValidationError(const std::string& field, const std::string& message)
80 : std::runtime_error("Validation error for field '" + field + "': " + message) {}
81};
82
106template <typename T>
108 private:
109 const toml::table& config;
110 std::string key;
111 std::optional<T> greater_than;
112 std::optional<T> greater_than_equal;
113 std::optional<T> less_than;
114
115 public:
116 Validator(const toml::table& config_, const std::string& key_) : config(config_), key(key_) {}
117
124 Validator& greaterThan(T greater_than_) {
125 greater_than = greater_than_;
126 return *this;
127 }
128
135 Validator& greaterThanEqual(T greater_than_equal_) {
136 greater_than_equal = greater_than_equal_;
137 return *this;
138 }
139
146 Validator& lessThan(T less_than_) {
147 less_than = less_than_;
148 return *this;
149 }
150
157 greaterThan(T{0});
158 return *this;
159 }
160
161 private:
162 std::optional<T> extractValue() {
163 // If key doesn't exist, return nullopt
164 if (!config.contains(key)) {
165 return std::nullopt;
166 }
167
168 // Key exists, try to extract value with type checking
169 auto val = config[key].template value<T>();
170 if (!val) {
171 // Key exists but wrong type - always an error
172 throw ValidationError(key, "wrong type (expected " + getTypeName<T>() + ")");
173 }
174
175 return val;
176 }
177
178 T validateValue(T result) {
179 if (greater_than && result <= *greater_than) {
180 std::ostringstream oss;
181 oss << "must be > " << *greater_than << ", got " << result;
182 throw ValidationError(key, oss.str());
183 }
184
185 if (greater_than_equal && result < *greater_than_equal) {
186 std::ostringstream oss;
187 oss << "must be >= " << *greater_than_equal << ", got " << result;
188 throw ValidationError(key, oss.str());
189 }
190
191 if (less_than && result >= *less_than) {
192 std::ostringstream oss;
193 oss << "must be < " << *less_than << ", got " << result;
194 throw ValidationError(key, oss.str());
195 }
196
197 return result;
198 }
199
200 public:
210 T value() {
211 auto val = extractValue();
212
213 if (!val) {
214 throw ValidationError(key, "field not found");
215 }
216
217 return validateValue(*val);
218 }
219
230 T valueOr(T default_value_) {
231 auto val = extractValue();
232 if (!val) return default_value_; // Key doesn't exist - use default
233
234 return validateValue(*val); // Key exists - validate it (will throw on wrong type)
235 }
236};
237
263template <typename T>
265 private:
266 const toml::table& config;
267 std::string key;
268 std::optional<size_t> min_length;
269 std::optional<size_t> exact_length;
270 bool is_positive = false;
271
272 public:
273 VectorValidator(const toml::table& config_, const std::string& key_) : config(config_), key(key_) {}
274
281 VectorValidator& minLength(size_t min_len_) {
282 min_length = min_len_;
283 return *this;
284 }
285
292 is_positive = true;
293 return *this;
294 }
295
302 VectorValidator& hasLength(size_t exact_len_) {
303 exact_length = exact_len_;
304 return *this;
305 }
306
307 private:
308 std::optional<std::vector<T>> extractVector() {
309 // If key doesn't exist, return nullopt
310 if (!config.contains(key)) {
311 return std::nullopt;
312 }
313
314 // Key exists, check if it's an array
315 auto* arr = config[key].as_array();
316 if (!arr) {
317 // Key exists but wrong type - always an error
318 throw ValidationError(key, "wrong type (expected array)");
319 }
320
321 // Extract and validate array elements
322 std::vector<T> result;
323 for (size_t i = 0; i < arr->size(); ++i) {
324 auto val = arr->at(i).template value<T>();
325 if (!val) {
326 std::ostringstream oss;
327 oss << "element [" << i << "] wrong type (expected " << getTypeName<T>() << ")";
328 throw ValidationError(key, oss.str());
329 }
330 result.push_back(*val);
331 }
332
333 return result;
334 }
335
336 std::vector<T> validateVector(std::vector<T> result) {
337 if (exact_length && result.size() != *exact_length) {
338 std::ostringstream oss;
339 oss << "must have exactly " << *exact_length << " elements, got " << result.size();
340 throw ValidationError(key, oss.str());
341 }
342
343 if (min_length && result.size() < *min_length) {
344 std::ostringstream oss;
345 oss << "must have at least " << *min_length << " elements, got " << result.size();
346 throw ValidationError(key, oss.str());
347 }
348
349 for (size_t i = 0; i < result.size(); ++i) {
350 T& element = result[i];
351
352 if (is_positive && element <= T{0}) {
353 std::ostringstream oss;
354 oss << "element [" << i << "] must be positive, got " << element;
355 throw ValidationError(key, oss.str());
356 }
357 }
358
359 return result;
360 }
361
362 public:
369 std::vector<T> value() {
370 auto val = extractVector();
371
372 if (!val) {
373 throw ValidationError(key, "field not found");
374 }
375
376 return validateVector(*val);
377 }
378
386 std::vector<T> valueOr(const std::vector<T>& default_value_) {
387 auto val = extractVector();
388 if (!val) return default_value_; // Key doesn't exist - use default
389
390 return validateVector(*val); // Key exists - validate it (will throw on wrong type)
391 }
392};
393
404template <typename T>
405Validator<T> field(const toml::table& config_, const std::string& key_) {
406 return Validator<T>(config_, key_);
407}
408
419template <typename T>
420VectorValidator<T> vectorField(const toml::table& config_, const std::string& key_) {
421 return VectorValidator<T>(config_, key_);
422}
423
436template <typename T, typename NodeType>
437std::optional<T> getOptional(const toml::node_view<NodeType>& node) {
438 return node.template value<T>();
439}
440
453template <typename T, typename NodeType>
454std::optional<std::vector<T>> getOptionalVector(const toml::node_view<NodeType>& node) {
455 auto* arr = node.as_array();
456 if (!arr) return std::nullopt;
457
458 std::vector<T> result;
459 for (size_t i = 0; i < arr->size(); ++i) {
460 auto val = arr->at(i).template value<T>();
461 if (!val) return std::nullopt; // Type mismatch in array element
462 result.push_back(*val);
463 }
464
465 return result;
466}
467
478inline const toml::table& getRequiredTable(const toml::table& config, const std::string& key) {
479 if (!config.contains(key)) {
480 throw ValidationError(key, "table is required");
481 }
482
483 auto* table = config[key].as_table();
484 if (!table) {
485 throw ValidationError(key, "must be a table");
486 }
487
488 return *table;
489}
490
505template <typename T>
506std::vector<T> scalarOrVector(const toml::table& config, const std::string& key, size_t expected_size) {
507 if (!config.contains(key)) {
508 throw ValidationError(key, "field not found");
509 }
510
511 if (auto* arr = config[key].as_array()) {
512 // Array: must have exact expected size
513 if (arr->size() != expected_size) {
514 std::ostringstream oss;
515 oss << "array must have exactly " << expected_size << " elements, got " << arr->size();
516 throw ValidationError(key, oss.str());
517 }
518 std::vector<T> result;
519 for (size_t i = 0; i < arr->size(); ++i) {
520 auto val = arr->at(i).template value<T>();
521 if (!val) {
522 std::ostringstream oss;
523 oss << "element [" << i << "] wrong type (expected " << getTypeName<T>() << ")";
524 throw ValidationError(key, oss.str());
525 }
526 result.push_back(*val);
527 }
528 return result;
529 }
530
531 if (auto val = config[key].template value<T>()) {
532 // Scalar: fill vector with this value
533 return std::vector<T>(expected_size, *val);
534 }
535
536 throw ValidationError(key, "must be either a scalar value or an array of " + getTypeName<T>());
537}
538
552template <typename T>
553std::vector<T> scalarOrVectorOr(const toml::table& config, const std::string& key, size_t expected_size, const std::vector<T>& default_value) {
554 if (!config.contains(key)) {
555 return default_value;
556 }
557 return scalarOrVector<T>(config, key, expected_size);
558}
559
560} // namespace validators
Exception thrown when configuration validation fails.
Definition config_validators.hpp:77
ValidationError(const std::string &field, const std::string &message)
Definition config_validators.hpp:79
Chainable validator for scalar TOML fields.
Definition config_validators.hpp:107
Validator & greaterThan(T greater_than_)
Requires value to be strictly greater than threshold.
Definition config_validators.hpp:124
Validator & lessThan(T less_than_)
Requires value to be strictly less than threshold.
Definition config_validators.hpp:146
Validator & positive()
Requires value to be strictly positive (> 0).
Definition config_validators.hpp:156
T value()
Extracts and validates the field value.
Definition config_validators.hpp:210
Validator(const toml::table &config_, const std::string &key_)
Definition config_validators.hpp:116
Validator & greaterThanEqual(T greater_than_equal_)
Requires value to be greater than or equal to threshold.
Definition config_validators.hpp:135
T valueOr(T default_value_)
Extracts field value or returns default if missing.
Definition config_validators.hpp:230
Chainable validator for vector TOML fields.
Definition config_validators.hpp:264
VectorValidator & positive()
Requires all vector elements to be strictly positive (> 0).
Definition config_validators.hpp:291
std::vector< T > value()
Extracts and validates the vector field.
Definition config_validators.hpp:369
VectorValidator & hasLength(size_t exact_len_)
Requires vector to have exactly the specified length.
Definition config_validators.hpp:302
VectorValidator & minLength(size_t min_len_)
Requires minimum vector length.
Definition config_validators.hpp:281
std::vector< T > valueOr(const std::vector< T > &default_value_)
Extracts vector or returns default if missing.
Definition config_validators.hpp:386
VectorValidator(const toml::table &config_, const std::string &key_)
Definition config_validators.hpp:273
Validation utilities for TOML configuration parsing.
Definition config_validators.hpp:52
std::string getTypeName()
Helper to get readable type names for error messages.
Definition config_validators.hpp:58
VectorValidator< T > vectorField(const toml::table &config_, const std::string &key_)
Creates a vector field validator.
Definition config_validators.hpp:420
std::vector< T > scalarOrVectorOr(const toml::table &config, const std::string &key, size_t expected_size, const std::vector< T > &default_value)
Parses an optional field that can be either a scalar or an exact-size array.
Definition config_validators.hpp:553
const toml::table & getRequiredTable(const toml::table &config, const std::string &key)
Extracts a required table from a TOML configuration.
Definition config_validators.hpp:478
std::vector< T > scalarOrVector(const toml::table &config, const std::string &key, size_t expected_size)
Parses a field that can be either a scalar (applied to all) or an exact-size array.
Definition config_validators.hpp:506
std::optional< std::vector< T > > getOptionalVector(const toml::node_view< NodeType > &node)
Extracts an optional vector from a TOML node.
Definition config_validators.hpp:454
Validator< T > field(const toml::table &config_, const std::string &key_)
Creates a scalar field validator.
Definition config_validators.hpp:405
std::optional< T > getOptional(const toml::node_view< NodeType > &node)
Extracts an optional scalar value from a TOML node.
Definition config_validators.hpp:437