Container System 0.1.0
High-performance C++20 type-safe container framework with SIMD-accelerated serialization
Loading...
Searching...
No Matches
container_schema.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
10#include "container/schema.h"
11#include "container.h"
12
13#include <algorithm>
14#include <cmath>
15
16namespace kcenon::container
17{
18 // =========================================================================
19 // Field Definition API
20 // =========================================================================
21
22 container_schema& container_schema::require(std::string_view key, value_types type)
23 {
24 field_def field(key, type, true);
25 fields_.push_back(std::move(field));
26 return *this;
27 }
28
29 container_schema& container_schema::optional(std::string_view key, value_types type)
30 {
31 field_def field(key, type, false);
32 fields_.push_back(std::move(field));
33 return *this;
34 }
35
36 container_schema& container_schema::require(std::string_view key, value_types type,
37 const container_schema& nested_schema)
38 {
39 field_def field(key, type, true);
40 field.nested_schema = std::make_unique<container_schema>(nested_schema);
41 fields_.push_back(std::move(field));
42 return *this;
43 }
44
45 container_schema& container_schema::optional(std::string_view key, value_types type,
46 const container_schema& nested_schema)
47 {
48 field_def field(key, type, false);
49 field.nested_schema = std::make_unique<container_schema>(nested_schema);
50 fields_.push_back(std::move(field));
51 return *this;
52 }
53
54 // =========================================================================
55 // Constraint API
56 // =========================================================================
57
58 container_schema& container_schema::range_int64(std::string_view key, int64_t min, int64_t max)
59 {
60 if (auto* field = find_field(key))
61 {
62 field->min_int = min;
63 field->max_int = max;
64 }
65 return *this;
66 }
67
68 container_schema& container_schema::range_double(std::string_view key, double min, double max)
69 {
70 if (auto* field = find_field(key))
71 {
72 field->min_double = min;
73 field->max_double = max;
74 }
75 return *this;
76 }
77
78 container_schema& container_schema::length(std::string_view key, size_t min, size_t max)
79 {
80 if (auto* field = find_field(key))
81 {
82 field->min_length = min;
83 field->max_length = max;
84 }
85 return *this;
86 }
87
88 container_schema& container_schema::pattern(std::string_view key, std::string_view regex_pattern)
89 {
90 if (auto* field = find_field(key))
91 {
92 field->pattern_str = std::string(regex_pattern);
93 try
94 {
95 field->compiled_pattern = std::regex(std::string(regex_pattern));
96 }
97 catch (const std::regex_error&)
98 {
99 // Invalid regex pattern - will fail validation
100 field->compiled_pattern = std::nullopt;
101 }
102 }
103 return *this;
104 }
105
106 container_schema& container_schema::one_of(std::string_view key, std::vector<std::string> allowed)
107 {
108 if (auto* field = find_field(key))
109 {
110 field->allowed_values = std::move(allowed);
111 }
112 return *this;
113 }
114
115 container_schema& container_schema::custom(std::string_view key, validator_fn validator)
116 {
117 if (auto* field = find_field(key))
118 {
119 field->custom_validators.push_back(std::move(validator));
120 }
121 return *this;
122 }
123
124 // =========================================================================
125 // Validation API
126 // =========================================================================
127
128 std::optional<validation_error> container_schema::validate(
129 const value_container& container) const noexcept
130 {
131 auto errors = validate_all(container);
132 if (errors.empty())
133 {
134 return std::nullopt;
135 }
136 return errors.front();
137 }
138
139 std::vector<validation_error> container_schema::validate_all(
140 const value_container& container) const noexcept
141 {
142 std::vector<validation_error> errors;
143
144 // Check each field definition
145 for (const auto& field : fields_)
146 {
147 auto value_opt = container.get(field.name);
148
149 if (!value_opt.has_value())
150 {
151 // Field not found
152 if (field.required)
153 {
154 errors.push_back(validation_error::missing_required(field.name));
155 }
156 continue;
157 }
158
159 // Field exists - validate it
160 validate_field(field, value_opt.value(), errors);
161 }
162
163 return errors;
164 }
165
166#if SCHEMA_HAS_COMMON_RESULT
167 kcenon::common::VoidResult container_schema::validate_result(
168 const value_container& container) const noexcept
169 {
170 auto error_opt = validate(container);
171 if (!error_opt.has_value())
172 {
173 return kcenon::common::ok();
174 }
175
176 const auto& err = error_opt.value();
177 return kcenon::common::VoidResult(
178 kcenon::common::error_info{
179 err.code,
180 err.message,
181 "container_schema"});
182 }
183#endif
184
185 bool container_schema::has_field(std::string_view key) const noexcept
186 {
187 return find_field(key) != nullptr;
188 }
189
190 bool container_schema::is_required(std::string_view key) const noexcept
191 {
192 const auto* field = find_field(key);
193 return field != nullptr && field->required;
194 }
195
196 // =========================================================================
197 // Private Implementation
198 // =========================================================================
199
200 container_schema::field_def* container_schema::find_field(std::string_view key) noexcept
201 {
202 auto it = std::find_if(fields_.begin(), fields_.end(),
203 [key](const field_def& f) { return f.name == key; });
204 return it != fields_.end() ? &(*it) : nullptr;
205 }
206
207 const container_schema::field_def* container_schema::find_field(std::string_view key) const noexcept
208 {
209 auto it = std::find_if(fields_.begin(), fields_.end(),
210 [key](const field_def& f) { return f.name == key; });
211 return it != fields_.end() ? &(*it) : nullptr;
212 }
213
214 bool container_schema::validate_field(const field_def& field,
215 const optimized_value& value,
216 std::vector<validation_error>& errors) const noexcept
217 {
218 bool valid = true;
219
220 // Type validation
221 if (!validate_type(field, value, errors))
222 {
223 valid = false;
224 }
225
226 // Range validation
227 if (!validate_range(field, value, errors))
228 {
229 valid = false;
230 }
231
232 // Length validation
233 if (!validate_length(field, value, errors))
234 {
235 valid = false;
236 }
237
238 // Pattern validation
239 if (!validate_pattern(field, value, errors))
240 {
241 valid = false;
242 }
243
244 // Allowed values validation
245 if (!validate_allowed(field, value, errors))
246 {
247 valid = false;
248 }
249
250 // Custom validation
251 if (!validate_custom(field, value, errors))
252 {
253 valid = false;
254 }
255
256 // Nested schema validation
257 if (!validate_nested(field, value, errors))
258 {
259 valid = false;
260 }
261
262 return valid;
263 }
264
265 bool container_schema::validate_type(const field_def& field,
266 const optimized_value& value,
267 std::vector<validation_error>& errors) const noexcept
268 {
269 if (value.type != field.type)
270 {
271 errors.push_back(validation_error::type_mismatch(
272 field.name, field.type, value.type));
273 return false;
274 }
275 return true;
276 }
277
278 bool container_schema::validate_range(const field_def& field,
279 const optimized_value& value,
280 std::vector<validation_error>& errors) const noexcept
281 {
282 // Integer range check
283 if (field.min_int.has_value() && field.max_int.has_value())
284 {
285 int64_t val = 0;
286 bool has_value = false;
287
288 switch (value.type)
289 {
290 case value_types::short_value:
291 if (auto* p = std::get_if<short>(&value.data))
292 {
293 val = *p;
294 has_value = true;
295 }
296 break;
297 case value_types::ushort_value:
298 if (auto* p = std::get_if<unsigned short>(&value.data))
299 {
300 val = *p;
301 has_value = true;
302 }
303 break;
304 case value_types::int_value:
305 if (auto* p = std::get_if<int>(&value.data))
306 {
307 val = *p;
308 has_value = true;
309 }
310 break;
311 case value_types::uint_value:
312 if (auto* p = std::get_if<unsigned int>(&value.data))
313 {
314 val = *p;
315 has_value = true;
316 }
317 break;
318 case value_types::long_value:
319 if (auto* p = std::get_if<long>(&value.data))
320 {
321 val = *p;
322 has_value = true;
323 }
324 break;
325 case value_types::ulong_value:
326 if (auto* p = std::get_if<unsigned long>(&value.data))
327 {
328 val = static_cast<int64_t>(*p);
329 has_value = true;
330 }
331 break;
332 case value_types::llong_value:
333 if (auto* p = std::get_if<long long>(&value.data))
334 {
335 val = *p;
336 has_value = true;
337 }
338 break;
339 case value_types::ullong_value:
340 if (auto* p = std::get_if<unsigned long long>(&value.data))
341 {
342 val = static_cast<int64_t>(*p);
343 has_value = true;
344 }
345 break;
346 default:
347 break;
348 }
349
350 if (has_value)
351 {
352 if (val < field.min_int.value() || val > field.max_int.value())
353 {
354 errors.push_back(validation_error::out_of_range(
355 field.name, val, field.min_int.value(), field.max_int.value()));
356 return false;
357 }
358 }
359 }
360
361 // Double range check
362 if (field.min_double.has_value() && field.max_double.has_value())
363 {
364 double val = 0.0;
365 bool has_value = false;
366
367 if (value.type == value_types::float_value)
368 {
369 if (auto* p = std::get_if<float>(&value.data))
370 {
371 val = static_cast<double>(*p);
372 has_value = true;
373 }
374 }
375 else if (value.type == value_types::double_value)
376 {
377 if (auto* p = std::get_if<double>(&value.data))
378 {
379 val = *p;
380 has_value = true;
381 }
382 }
383
384 if (has_value)
385 {
386 if (val < field.min_double.value() || val > field.max_double.value())
387 {
388 errors.push_back(validation_error::out_of_range(
389 field.name, val, field.min_double.value(), field.max_double.value()));
390 return false;
391 }
392 }
393 }
394
395 return true;
396 }
397
398 bool container_schema::validate_length(const field_def& field,
399 const optimized_value& value,
400 std::vector<validation_error>& errors) const noexcept
401 {
402 if (!field.min_length.has_value() || !field.max_length.has_value())
403 {
404 return true;
405 }
406
407 size_t len = 0;
408 bool has_length = false;
409
410 if (value.type == value_types::string_value)
411 {
412 if (auto* p = std::get_if<std::string>(&value.data))
413 {
414 len = p->length();
415 has_length = true;
416 }
417 }
418 else if (value.type == value_types::bytes_value)
419 {
420 if (auto* p = std::get_if<std::vector<uint8_t>>(&value.data))
421 {
422 len = p->size();
423 has_length = true;
424 }
425 }
426
427 if (has_length)
428 {
429 if (len < field.min_length.value() || len > field.max_length.value())
430 {
431 errors.push_back(validation_error::invalid_length(
432 field.name, len, field.min_length.value(), field.max_length.value()));
433 return false;
434 }
435 }
436
437 return true;
438 }
439
440 bool container_schema::validate_pattern(const field_def& field,
441 const optimized_value& value,
442 std::vector<validation_error>& errors) const noexcept
443 {
444 if (!field.compiled_pattern.has_value())
445 {
446 return true;
447 }
448
449 if (value.type != value_types::string_value)
450 {
451 return true;
452 }
453
454 auto* str_ptr = std::get_if<std::string>(&value.data);
455 if (!str_ptr)
456 {
457 return true;
458 }
459
460 try
461 {
462 if (!std::regex_match(*str_ptr, field.compiled_pattern.value()))
463 {
464 errors.push_back(validation_error::pattern_mismatch(
465 field.name, field.pattern_str.value_or("")));
466 return false;
467 }
468 }
469 catch (const std::regex_error&)
470 {
471 // Regex matching failed - treat as pattern mismatch
472 errors.push_back(validation_error::pattern_mismatch(
473 field.name, field.pattern_str.value_or("")));
474 return false;
475 }
476
477 return true;
478 }
479
480 bool container_schema::validate_allowed(const field_def& field,
481 const optimized_value& value,
482 std::vector<validation_error>& errors) const noexcept
483 {
484 if (!field.allowed_values.has_value())
485 {
486 return true;
487 }
488
489 if (value.type != value_types::string_value)
490 {
491 return true;
492 }
493
494 auto* str_ptr = std::get_if<std::string>(&value.data);
495 if (!str_ptr)
496 {
497 return true;
498 }
499
500 const auto& allowed = field.allowed_values.value();
501 auto it = std::find(allowed.begin(), allowed.end(), *str_ptr);
502 if (it == allowed.end())
503 {
504 errors.push_back(validation_error::not_allowed(field.name, *str_ptr));
505 return false;
506 }
507
508 return true;
509 }
510
511 bool container_schema::validate_custom(const field_def& field,
512 const optimized_value& value,
513 std::vector<validation_error>& errors) const noexcept
514 {
515 for (const auto& validator : field.custom_validators)
516 {
517 try
518 {
519 auto result = validator(value);
520 if (result.has_value())
521 {
522 errors.push_back(validation_error::custom_failed(
523 field.name, result.value()));
524 return false;
525 }
526 }
527 catch (...)
528 {
529 errors.push_back(validation_error::custom_failed(
530 field.name, "Validator threw an exception"));
531 return false;
532 }
533 }
534 return true;
535 }
536
537 bool container_schema::validate_nested(const field_def& field,
538 const optimized_value& value,
539 std::vector<validation_error>& errors) const noexcept
540 {
541 if (!field.nested_schema)
542 {
543 return true;
544 }
545
546 if (value.type != value_types::container_value)
547 {
548 return true;
549 }
550
551 const auto* container_ptr = std::get_if<std::shared_ptr<value_container>>(&value.data);
552 if (container_ptr == nullptr || !(*container_ptr))
553 {
554 errors.push_back(validation_error::nested_failed(field.name, {}));
555 return false;
556 }
557
558 auto nested_errors = field.nested_schema->validate_all(**container_ptr);
559 if (!nested_errors.empty())
560 {
561 errors.push_back(validation_error::nested_failed(field.name, nested_errors));
562 // Also append all nested errors with prefixed field names
563 for (auto& err : nested_errors)
564 {
565 err.field = field.name + "." + err.field;
566 errors.push_back(std::move(err));
567 }
568 return false;
569 }
570
571 return true;
572 }
573
574} // namespace kcenon::container