From 102ecdc13eea9812c81201e7e12a989cf4aee5e2 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Apr 2026 00:21:51 +0300 Subject: [PATCH 01/16] add method validate --- .../config_node_with_preset_.cpp | 2 + .../config_node_with_preset_.hpp | 2 + source/validate/validator.cpp | 93 +++++++++++++++++++ source/validate/validator.hpp | 16 ++++ 4 files changed, 113 insertions(+) create mode 100644 source/validate/validator.cpp create mode 100644 source/validate/validator.hpp diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index 6894aa6605..1df5eec27b 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -80,6 +80,8 @@ ConfigNodeWithPresetExpected ConfigNodeWithPreset::operator[]( std::nullopt); } +bool ConfigNodeWithPreset::IsMap() const noexcept { return m_node.IsMap(); } + std::ostream& operator<<(std::ostream& out, const ConfigNodeWithPreset& node) { return out << node.get_node(); } diff --git a/source/parser/config_reader/config_node_with_preset_.hpp b/source/parser/config_reader/config_node_with_preset_.hpp index 3557d9830c..e733b97709 100644 --- a/source/parser/config_reader/config_node_with_preset_.hpp +++ b/source/parser/config_reader/config_node_with_preset_.hpp @@ -16,6 +16,8 @@ class ConfigNodeWithPreset { ConfigNodeWithPresetExpected operator[](std::string_view key) const; + [[nodiscard]] bool IsMap() const noexcept; + friend std::ostream& operator<<(std::ostream& out, const ConfigNodeWithPreset& node); diff --git a/source/validate/validator.cpp b/source/validate/validator.cpp new file mode 100644 index 0000000000..976a68c456 --- /dev/null +++ b/source/validate/validator.cpp @@ -0,0 +1,93 @@ +#include "validator.hpp" + +#include +#include + +namespace sim{ + +void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node){ + // create set of requried fields + std::unordered_set schema_fields; + for (auto it: schema_node){ + std::string field = it.first.as(); + if (!is_meta_field(field)){ + schema_fields.insert(field); + } + } + + // check if config node has unknown fields + for (auto it: config_node){ + std::string field = it.first.as(); + if (!schema_fields.contains(field)){ + std::stringstream ss; + ss << "Config node: " << config_node.get_name_or_throw(); + ss << "has unknown field: " << field; + throw std::runtime_error(ss.str()); + } + } + + // check if config node has all required fields + for (auto it: schema_node){ + std::string field_schema = it.first.as(); + if (is_meta_field(field_schema)){ + continue; + } + ConfigNodeWithPresetExpected field_config = config_node[field_schema]; + if (!field_config.has_value()){ + std::stringstream ss; + ss << "Config node: " << config_node.get_name_or_throw(); + ss << "hasn't required field: " << field_schema; + throw std::runtime_error(ss.str()); + } + } + + for (auto it: schema_node){ + std::string field = it.first.as(); + if (!is_meta_field(field)){ + validate_field(it.second, config_node[field].value()); + } + } +} + +bool is_meta_field(const std::string& field){ + return !field.empty() && field[0] == '_'; +} + +void validate_field(const YAML::Node schema_node, const ConfigNodeWithPreset& config_node){ + if (schema_node["_type"]){ + std::string type = schema_node["_type"].as(); + if (type == "size_t"){ + config_node.as_or_throw(); + } else if (type == "int"){ + config_node.as_or_throw(); + } else if (type == "double"){ + config_node.as_or_throw(); + } else if (type == "bool"){ + config_node.as_or_throw(); + } else if (type == "string"){ + config_node.as_or_throw(); + } else if (type == "regex"){ + std::string pattern = config_node.as_or_throw(); + try { + std::regex r(pattern); + } catch (const std::regex_error&) { + throw std::runtime_error("Incorrect type specified std::regex"); + } + } else if (type.ends_with(".schema")){ + YAML::Node sub_schema = YAML::LoadFile(type); + if (!config_node.IsMap()){ + throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); + } + validate(sub_schema, config_node); + } else{ + throw std::runtime_error("Unknown type: " + type); + } + } else{ + if (!config_node.IsMap()){ + throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); + } + validate(schema_node, config_node); + } +} + +} //namespace sim \ No newline at end of file diff --git a/source/validate/validator.hpp b/source/validate/validator.hpp new file mode 100644 index 0000000000..e8ce09b910 --- /dev/null +++ b/source/validate/validator.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "parser/config_reader/config_node_with_preset.hpp" + + +namespace sim{ + +void validate(const std::string& path, const ConfigNodeWithPreset& config_node); + +bool is_meta_field(const std::string& field); + +void validate_field(const YAML::Node schema_node, const ConfigNodeWithPreset& config_node); + +} // namespace sim \ No newline at end of file From cc46387872e4303aae72989b408a0491435618ff Mon Sep 17 00:00:00 2001 From: root Date: Mon, 6 Apr 2026 01:06:38 +0300 Subject: [PATCH 02/16] add iterator for ConfigNodeWithPreset --- .../config_node_with_preset_.cpp | 60 +++++++++++++++++++ .../config_node_with_preset_.hpp | 22 +++++++ source/validate/validator.cpp | 4 +- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index 1df5eec27b..1fe9f8f819 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -114,4 +114,64 @@ ConfigNodeWithPreset load_file_with_presets(std::filesystem::path path) { return ConfigNodeWithPreset(node, node["presets"].to_optional()); } +ConfigNodeWithPreset::Iterator::Iterator(YAML::const_iterator a_it) : m_iterator(a_it) {} + +ConfigNodeWithPreset::Iterator& ConfigNodeWithPreset::Iterator::operator++() { + ++m_iterator; + return *this; +} + +ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::Iterator::operator++(int) { + Iterator iterator_copy(*this); + ++(*this); + return iterator_copy; +} + +bool ConfigNodeWithPreset::Iterator::operator==(const Iterator& rhs) const { + return m_iterator == rhs.m_iterator; +} + +bool ConfigNodeWithPreset::Iterator::operator!=(const Iterator& rhs) const { + return m_iterator != rhs.m_iterator; +} + +ConfigNodeWithPreset ConfigNodeWithPreset::Iterator::operator*() const { + // Explanation: iterator_value under *m_iterator inherited from + // YAML::Node and std::pair, but only one value of + // this pair is correct (please, tell them about std::variant...) + if (m_iterator->IsDefined()) { + // iterator goes over "named" nodes like + // list: + // - value_0 + // - value_1 + // ... + YAML::Node node = *m_iterator; + return ConfigNodeWithPreset(ConfigNode(node, std::nullopt)); + } else { + // iterator goes over "named" nodes like + // list: + // node_1: ... + // node_2: ... + // ... + YAML::Node key_node = m_iterator->first; + YAML::Node value_node = m_iterator->second; + if (!key_node || !value_node) { + throw std::runtime_error( + "Can not take value under config node iterator; all possible " + "nodes are invalid"); + } + std::string key = key_node.as(); + return ConfigNodeWithPreset(ConfigNode(value_node, key)); + } +} + +ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::begin() const { + return Iterator(m_node.get_node().begin()); +} + +ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::end() const { + return Iterator(m_node.get_node().end()); +} + + } // namespace sim diff --git a/source/parser/config_reader/config_node_with_preset_.hpp b/source/parser/config_reader/config_node_with_preset_.hpp index e733b97709..068e929848 100644 --- a/source/parser/config_reader/config_node_with_preset_.hpp +++ b/source/parser/config_reader/config_node_with_preset_.hpp @@ -44,6 +44,28 @@ class ConfigNodeWithPreset { std::runtime_error create_parsing_error(std::string_view error) const; + class Iterator { + public: + Iterator(YAML::const_iterator a_it); + + Iterator& operator++(); + + Iterator operator++(int); + + bool operator==(const Iterator& rhs) const; + + bool operator!=(const Iterator& rhs) const; + + ConfigNodeWithPreset operator*() const; + + private: + // Invariant: m_stacktrace_node is not null + YAML::const_iterator m_iterator; + }; + + Iterator begin() const; + Iterator end() const; + private: // m_node contains information about config node and probably preset name // m_preset - preset node which that is used to supplement fields of m_node diff --git a/source/validate/validator.cpp b/source/validate/validator.cpp index 976a68c456..4bd17a5713 100644 --- a/source/validate/validator.cpp +++ b/source/validate/validator.cpp @@ -16,8 +16,8 @@ void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_ } // check if config node has unknown fields - for (auto it: config_node){ - std::string field = it.first.as(); + for (const auto& node: config_node){ + std::string field = node.get_name_or_throw(); if (!schema_fields.contains(field)){ std::stringstream ss; ss << "Config node: " << config_node.get_name_or_throw(); From cf91c821dc5fee2c0d6e29fa9c6defd464da9781 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 7 Apr 2026 00:16:29 +0300 Subject: [PATCH 03/16] resolve conversations without tests --- .../{validate => config_schema}/validator.cpp | 82 +++++++++---------- source/config_schema/validator.hpp | 13 +++ .../config_node_with_preset_.cpp | 34 +------- .../config_node_with_preset_.hpp | 5 +- source/validate/validator.hpp | 16 ---- 5 files changed, 60 insertions(+), 90 deletions(-) rename source/{validate => config_schema}/validator.cpp (97%) create mode 100644 source/config_schema/validator.hpp delete mode 100644 source/validate/validator.hpp diff --git a/source/validate/validator.cpp b/source/config_schema/validator.cpp similarity index 97% rename from source/validate/validator.cpp rename to source/config_schema/validator.cpp index 4bd17a5713..1d4123b7e2 100644 --- a/source/validate/validator.cpp +++ b/source/config_schema/validator.cpp @@ -5,6 +5,47 @@ namespace sim{ +bool is_meta_field(const std::string& field){ + return !field.empty() && field[0] == '_'; +} + +void validate_field(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node){ + if (schema_node["_type"]){ + std::string type = schema_node["_type"].as(); + if (type == "size_t"){ + config_node.as_or_throw(); + } else if (type == "int"){ + config_node.as_or_throw(); + } else if (type == "double"){ + config_node.as_or_throw(); + } else if (type == "bool"){ + config_node.as_or_throw(); + } else if (type == "string"){ + config_node.as_or_throw(); + } else if (type == "regex"){ + std::string pattern = config_node.as_or_throw(); + try { + std::regex r(pattern); + } catch (const std::regex_error&) { + throw std::runtime_error("Incorrect type specified std::regex"); + } + } else if (type.ends_with(".schema")){ + YAML::Node sub_schema = YAML::LoadFile(type); + if (!config_node.IsMap()){ + throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); + } + validate(sub_schema, config_node); + } else{ + throw std::runtime_error("Unknown type: " + type); + } + } else{ + if (!config_node.IsMap()){ + throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); + } + validate(schema_node, config_node); + } +} + void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node){ // create set of requried fields std::unordered_set schema_fields; @@ -49,45 +90,4 @@ void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_ } } -bool is_meta_field(const std::string& field){ - return !field.empty() && field[0] == '_'; -} - -void validate_field(const YAML::Node schema_node, const ConfigNodeWithPreset& config_node){ - if (schema_node["_type"]){ - std::string type = schema_node["_type"].as(); - if (type == "size_t"){ - config_node.as_or_throw(); - } else if (type == "int"){ - config_node.as_or_throw(); - } else if (type == "double"){ - config_node.as_or_throw(); - } else if (type == "bool"){ - config_node.as_or_throw(); - } else if (type == "string"){ - config_node.as_or_throw(); - } else if (type == "regex"){ - std::string pattern = config_node.as_or_throw(); - try { - std::regex r(pattern); - } catch (const std::regex_error&) { - throw std::runtime_error("Incorrect type specified std::regex"); - } - } else if (type.ends_with(".schema")){ - YAML::Node sub_schema = YAML::LoadFile(type); - if (!config_node.IsMap()){ - throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); - } - validate(sub_schema, config_node); - } else{ - throw std::runtime_error("Unknown type: " + type); - } - } else{ - if (!config_node.IsMap()){ - throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); - } - validate(schema_node, config_node); - } -} - } //namespace sim \ No newline at end of file diff --git a/source/config_schema/validator.hpp b/source/config_schema/validator.hpp new file mode 100644 index 0000000000..7f1efb6f27 --- /dev/null +++ b/source/config_schema/validator.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include "parser/config_reader/config_node_with_preset.hpp" + +namespace sim{ + +void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node); + +void validate_field(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node); + +} // namespace sim \ No newline at end of file diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index 1fe9f8f819..8a36f2c6df 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -114,7 +114,7 @@ ConfigNodeWithPreset load_file_with_presets(std::filesystem::path path) { return ConfigNodeWithPreset(node, node["presets"].to_optional()); } -ConfigNodeWithPreset::Iterator::Iterator(YAML::const_iterator a_it) : m_iterator(a_it) {} +ConfigNodeWithPreset::Iterator::Iterator(ConfigNode::Iterator a_it) : m_iterator(a_it) {} ConfigNodeWithPreset::Iterator& ConfigNodeWithPreset::Iterator::operator++() { ++m_iterator; @@ -136,41 +136,15 @@ bool ConfigNodeWithPreset::Iterator::operator!=(const Iterator& rhs) const { } ConfigNodeWithPreset ConfigNodeWithPreset::Iterator::operator*() const { - // Explanation: iterator_value under *m_iterator inherited from - // YAML::Node and std::pair, but only one value of - // this pair is correct (please, tell them about std::variant...) - if (m_iterator->IsDefined()) { - // iterator goes over "named" nodes like - // list: - // - value_0 - // - value_1 - // ... - YAML::Node node = *m_iterator; - return ConfigNodeWithPreset(ConfigNode(node, std::nullopt)); - } else { - // iterator goes over "named" nodes like - // list: - // node_1: ... - // node_2: ... - // ... - YAML::Node key_node = m_iterator->first; - YAML::Node value_node = m_iterator->second; - if (!key_node || !value_node) { - throw std::runtime_error( - "Can not take value under config node iterator; all possible " - "nodes are invalid"); - } - std::string key = key_node.as(); - return ConfigNodeWithPreset(ConfigNode(value_node, key)); - } + return ConfigNodeWithPreset(*m_iterator); } ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::begin() const { - return Iterator(m_node.get_node().begin()); + return Iterator(m_node.begin()); } ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::end() const { - return Iterator(m_node.get_node().end()); + return Iterator(m_node.end()); } diff --git a/source/parser/config_reader/config_node_with_preset_.hpp b/source/parser/config_reader/config_node_with_preset_.hpp index 068e929848..795e2e8859 100644 --- a/source/parser/config_reader/config_node_with_preset_.hpp +++ b/source/parser/config_reader/config_node_with_preset_.hpp @@ -46,7 +46,7 @@ class ConfigNodeWithPreset { class Iterator { public: - Iterator(YAML::const_iterator a_it); + Iterator(ConfigNode::Iterator a_it); Iterator& operator++(); @@ -59,8 +59,7 @@ class ConfigNodeWithPreset { ConfigNodeWithPreset operator*() const; private: - // Invariant: m_stacktrace_node is not null - YAML::const_iterator m_iterator; + ConfigNode::Iterator m_iterator; }; Iterator begin() const; diff --git a/source/validate/validator.hpp b/source/validate/validator.hpp deleted file mode 100644 index e8ce09b910..0000000000 --- a/source/validate/validator.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -#include "parser/config_reader/config_node_with_preset.hpp" - - -namespace sim{ - -void validate(const std::string& path, const ConfigNodeWithPreset& config_node); - -bool is_meta_field(const std::string& field); - -void validate_field(const YAML::Node schema_node, const ConfigNodeWithPreset& config_node); - -} // namespace sim \ No newline at end of file From 5a4a4fa8ca7e877af23b12efab5c77ef731982a6 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 01:35:46 +0300 Subject: [PATCH 04/16] check hook --- source/config_schema/validator.cpp | 60 ++++++++++--------- source/config_schema/validator.hpp | 10 ++-- .../config_node_with_preset_.cpp | 6 +- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/source/config_schema/validator.cpp b/source/config_schema/validator.cpp index 1d4123b7e2..9d1cba696a 100644 --- a/source/config_schema/validator.cpp +++ b/source/config_schema/validator.cpp @@ -3,63 +3,67 @@ #include #include -namespace sim{ +namespace sim { -bool is_meta_field(const std::string& field){ +bool is_meta_field(const std::string& field) { return !field.empty() && field[0] == '_'; } -void validate_field(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node){ - if (schema_node["_type"]){ +void validate_field(const YAML::Node& schema_node, + const ConfigNodeWithPreset& config_node) { + if (schema_node["_type"]) { std::string type = schema_node["_type"].as(); - if (type == "size_t"){ + if (type == "size_t") { config_node.as_or_throw(); - } else if (type == "int"){ + } else if (type == "int") { config_node.as_or_throw(); - } else if (type == "double"){ + } else if (type == "double") { config_node.as_or_throw(); - } else if (type == "bool"){ + } else if (type == "bool") { config_node.as_or_throw(); - } else if (type == "string"){ + } else if (type == "string") { config_node.as_or_throw(); - } else if (type == "regex"){ + } else if (type == "regex") { std::string pattern = config_node.as_or_throw(); try { std::regex r(pattern); } catch (const std::regex_error&) { throw std::runtime_error("Incorrect type specified std::regex"); } - } else if (type.ends_with(".schema")){ + } else if (type.ends_with(".schema")) { YAML::Node sub_schema = YAML::LoadFile(type); - if (!config_node.IsMap()){ - throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); + if (!config_node.IsMap()) { + throw std::runtime_error(config_node.get_name_or_throw() + + " must be object."); } validate(sub_schema, config_node); - } else{ + } else { throw std::runtime_error("Unknown type: " + type); } - } else{ - if (!config_node.IsMap()){ - throw std::runtime_error(config_node.get_name_or_throw() + " must be object."); + } else { + if (!config_node.IsMap()) { + throw std::runtime_error(config_node.get_name_or_throw() + + " must be object."); } validate(schema_node, config_node); } } -void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node){ +void validate(const YAML::Node& schema_node, + const ConfigNodeWithPreset& config_node) { // create set of requried fields std::unordered_set schema_fields; - for (auto it: schema_node){ + for (auto it : schema_node) { std::string field = it.first.as(); - if (!is_meta_field(field)){ + if (!is_meta_field(field)) { schema_fields.insert(field); } } // check if config node has unknown fields - for (const auto& node: config_node){ + for (const auto& node : config_node) { std::string field = node.get_name_or_throw(); - if (!schema_fields.contains(field)){ + if (!schema_fields.contains(field)) { std::stringstream ss; ss << "Config node: " << config_node.get_name_or_throw(); ss << "has unknown field: " << field; @@ -68,13 +72,13 @@ void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_ } // check if config node has all required fields - for (auto it: schema_node){ + for (auto it : schema_node) { std::string field_schema = it.first.as(); - if (is_meta_field(field_schema)){ + if (is_meta_field(field_schema)) { continue; } ConfigNodeWithPresetExpected field_config = config_node[field_schema]; - if (!field_config.has_value()){ + if (!field_config.has_value()) { std::stringstream ss; ss << "Config node: " << config_node.get_name_or_throw(); ss << "hasn't required field: " << field_schema; @@ -82,12 +86,12 @@ void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_ } } - for (auto it: schema_node){ + for (auto it : schema_node) { std::string field = it.first.as(); - if (!is_meta_field(field)){ + if (!is_meta_field(field)) { validate_field(it.second, config_node[field].value()); } } } -} //namespace sim \ No newline at end of file +} // namespace sim \ No newline at end of file diff --git a/source/config_schema/validator.hpp b/source/config_schema/validator.hpp index 7f1efb6f27..e90f37f1b1 100644 --- a/source/config_schema/validator.hpp +++ b/source/config_schema/validator.hpp @@ -4,10 +4,12 @@ #include "parser/config_reader/config_node_with_preset.hpp" -namespace sim{ +namespace sim { -void validate(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node); +void validate(const YAML::Node& schema_node, + const ConfigNodeWithPreset& config_node); -void validate_field(const YAML::Node& schema_node, const ConfigNodeWithPreset& config_node); +void validate_field(const YAML::Node& schema_node, + const ConfigNodeWithPreset& config_node); -} // namespace sim \ No newline at end of file +} // namespace sim \ No newline at end of file diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index e8f33e8e26..d0affe2ca4 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -47,6 +47,8 @@ ConfigNodeWithPresetExpected ConfigNodeWithPreset::operator[]( return std::unexpected(ss.str()); } // conversion is successful. tries to find preset in m_presets_node + + ConfigNodeExpected preset_node = presets_node[preset_name.value()]; if (!preset_node.has_value()) { // m_presets_node hasn't preset with the specified name in @@ -119,7 +121,8 @@ ConfigNodeWithPreset load_file_with_presets(std::filesystem::path path) { return ConfigNodeWithPreset(node, node["presets"].to_optional()); } -ConfigNodeWithPreset::Iterator::Iterator(ConfigNode::Iterator a_it) : m_iterator(a_it) {} +ConfigNodeWithPreset::Iterator::Iterator(ConfigNode::Iterator a_it) + : m_iterator(a_it) {} ConfigNodeWithPreset::Iterator& ConfigNodeWithPreset::Iterator::operator++() { ++m_iterator; @@ -152,5 +155,4 @@ ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::end() const { return Iterator(m_node.end()); } - } // namespace sim From a5e38eef83ef8dfda1c9d622bc0c6b84f0b014ba Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 01:38:39 +0300 Subject: [PATCH 05/16] check hook --- source/parser/config_reader/config_node_with_preset_.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index d0affe2ca4..b4b222a44e 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -47,8 +47,6 @@ ConfigNodeWithPresetExpected ConfigNodeWithPreset::operator[]( return std::unexpected(ss.str()); } // conversion is successful. tries to find preset in m_presets_node - - ConfigNodeExpected preset_node = presets_node[preset_name.value()]; if (!preset_node.has_value()) { // m_presets_node hasn't preset with the specified name in From 6b59ef611380ee7b6c204cf7a8b667708e9fb490 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Apr 2026 01:44:10 +0300 Subject: [PATCH 06/16] check hooks --- source/parser/config_reader/config_node_with_preset_.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index b4b222a44e..6d979df367 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -59,6 +59,7 @@ ConfigNodeWithPresetExpected ConfigNodeWithPreset::operator[]( return std::unexpected(ss.str()); } // preset was found successfull. put the found value in m_preset + m_preset.emplace(preset_node.value()); } // tries to find key in preset From 40e6cf7c5ef7c0079ea7c56d2c8c21ae1718f870 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 11 Apr 2026 18:25:43 +0300 Subject: [PATCH 07/16] need to correct test --- test/config_schema/basic_types.schema | 13 +++++++++++++ test/config_schema/basic_types.yml | 7 +++++++ test/config_schema/basic_types_wrong.yml | 7 +++++++ test/config_schema/check_schemas.cpp | 23 +++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 test/config_schema/basic_types.schema create mode 100644 test/config_schema/basic_types.yml create mode 100644 test/config_schema/basic_types_wrong.yml create mode 100644 test/config_schema/check_schemas.cpp diff --git a/test/config_schema/basic_types.schema b/test/config_schema/basic_types.schema new file mode 100644 index 0000000000..7ee1aa33b7 --- /dev/null +++ b/test/config_schema/basic_types.schema @@ -0,0 +1,13 @@ +node: + field1: + _type: size_t + field2: + _type: int + field3: + _type: double + field4: + _type: bool + field5: + _type: string + field6: + _type: regex \ No newline at end of file diff --git a/test/config_schema/basic_types.yml b/test/config_schema/basic_types.yml new file mode 100644 index 0000000000..1049c47e91 --- /dev/null +++ b/test/config_schema/basic_types.yml @@ -0,0 +1,7 @@ +node: + field1: 12389 + field2: 21039 + field3: 1.35 + field4: false + field5: hello + field6: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ \ No newline at end of file diff --git a/test/config_schema/basic_types_wrong.yml b/test/config_schema/basic_types_wrong.yml new file mode 100644 index 0000000000..5639e631bd --- /dev/null +++ b/test/config_schema/basic_types_wrong.yml @@ -0,0 +1,7 @@ +node: + field1: hello + field2: hello + field3: hello + field4: hello + field5: hello + field6: 1.25 \ No newline at end of file diff --git a/test/config_schema/check_schemas.cpp b/test/config_schema/check_schemas.cpp new file mode 100644 index 0000000000..a4cfafb0d5 --- /dev/null +++ b/test/config_schema/check_schemas.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include "config_schema/validator.hpp" + +namespace sim { +namespace test2 { + +TEST(TestBasicTypes, BasicTypes) { + ConfigNodeWithPreset node = load_file_with_presets("basic_types.yml"); + YAML::Node schema_node = YAML::LoadFile("basic_types.schema"); + validate(schema_node, node); + ASSERT_NO_THROW(validate(schema_node, node)); +} + +TEST(TestBasicTypes, WrongBasicTypes) { + ConfigNodeWithPreset node = load_file_with_presets("basic_types_wrong.yml"); + YAML::Node schema_node = YAML::LoadFile("basic_types.schema"); + ASSERT_ANY_THROW(validate(schema_node, node)); +} + +} // namespace test2 +} // namespace sim \ No newline at end of file From 9d1922abb463294a9d4b19e48fb7f898d9241192 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 17 Apr 2026 11:58:31 +0300 Subject: [PATCH 08/16] done --- test/config_schema/check_schemas.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test/config_schema/check_schemas.cpp b/test/config_schema/check_schemas.cpp index a4cfafb0d5..44b7e467c6 100644 --- a/test/config_schema/check_schemas.cpp +++ b/test/config_schema/check_schemas.cpp @@ -7,15 +7,22 @@ namespace sim { namespace test2 { TEST(TestBasicTypes, BasicTypes) { - ConfigNodeWithPreset node = load_file_with_presets("basic_types.yml"); - YAML::Node schema_node = YAML::LoadFile("basic_types.schema"); - validate(schema_node, node); + std::filesystem::path current_dir = + std::filesystem::path(__FILE__).parent_path(); + ConfigNodeWithPreset node = + load_file_with_presets(current_dir / "basic_types.yml"); + YAML::Node schema_node = + YAML::LoadFile(current_dir.append("basic_types.schema")); ASSERT_NO_THROW(validate(schema_node, node)); } TEST(TestBasicTypes, WrongBasicTypes) { - ConfigNodeWithPreset node = load_file_with_presets("basic_types_wrong.yml"); - YAML::Node schema_node = YAML::LoadFile("basic_types.schema"); + std::filesystem::path current_dir = + std::filesystem::path(__FILE__).parent_path(); + ConfigNodeWithPreset node = + load_file_with_presets(current_dir / "basic_types_wrong.yml"); + YAML::Node schema_node = + YAML::LoadFile(current_dir.append("basic_types.schema")); ASSERT_ANY_THROW(validate(schema_node, node)); } From 9bc87bc7304776e6666d5ab00f7751838717a0cf Mon Sep 17 00:00:00 2001 From: root Date: Wed, 22 Apr 2026 22:27:57 +0300 Subject: [PATCH 09/16] resolve conversations. add SchemaServer --- source/config_schema/schema_server.cpp | 157 ++++++++++++++++++ source/config_schema/schema_server.hpp | 37 +++++ source/config_schema/validator.cpp | 97 ----------- source/config_schema/validator.hpp | 15 -- .../{ => _schemas}/basic_types.schema | 0 .../config_schema/_schemas/custom_type.schema | 2 + .../_schemas/root_is_type.schema | 2 + .../_schemas/test_with_custom_type.schema | 5 + test/config_schema/check_schemas.cpp | 62 ++++++- test/config_schema/custom_type.yml | 3 + test/config_schema/custom_type_wrong.yml | 3 + test/config_schema/root_is_type.yml | 1 + test/config_schema/root_is_type_wrong.yml | 1 + 13 files changed, 266 insertions(+), 119 deletions(-) create mode 100644 source/config_schema/schema_server.cpp create mode 100644 source/config_schema/schema_server.hpp delete mode 100644 source/config_schema/validator.cpp delete mode 100644 source/config_schema/validator.hpp rename test/config_schema/{ => _schemas}/basic_types.schema (100%) create mode 100644 test/config_schema/_schemas/custom_type.schema create mode 100644 test/config_schema/_schemas/root_is_type.schema create mode 100644 test/config_schema/_schemas/test_with_custom_type.schema create mode 100644 test/config_schema/custom_type.yml create mode 100644 test/config_schema/custom_type_wrong.yml create mode 100644 test/config_schema/root_is_type.yml create mode 100644 test/config_schema/root_is_type_wrong.yml diff --git a/source/config_schema/schema_server.cpp b/source/config_schema/schema_server.cpp new file mode 100644 index 0000000000..e0f6f6e842 --- /dev/null +++ b/source/config_schema/schema_server.cpp @@ -0,0 +1,157 @@ +#include "schema_server.hpp" + +#include + +#include +#include + +namespace sim { + +SchemaServer::SchemaServer(const std::filesystem::path& a_schemas_dir) + : m_schemas_dir(std::move(a_schemas_dir)) {} + +bool SchemaServer::is_meta_field(const std::string& field) { + return field.starts_with('_'); +} + +void SchemaServer::validate_untyped(const ConfigSchema& schema_node, + const ConfigNodeWithPreset& config_node) { + // create set of requried fields + std::unordered_set schema_fields; + for (const auto& it : schema_node) { + std::optional tmp_field = it.get_name(); + if (!tmp_field) { + std::stringstream ss; + ss << "Empty schema node:\n"; + ss << it; + throw schema_node.create_parsing_error(ss.str()); + } + std::string field = tmp_field.value(); + if (!is_meta_field(field)) { + schema_fields.insert(field); + } + } + + // check if config node has unknown fields + for (const auto& node : config_node) { + std::optional tmp_field = node.get_name(); + if (!tmp_field) { + std::stringstream ss; + ss << "Empty config node:\n"; + ss << node; + throw schema_node.create_parsing_error(ss.str()); + } + std::string field = tmp_field.value(); + if (!schema_fields.contains(field)) { + std::stringstream ss; + ss << "Field does not correspond to schema:\n"; + ss << schema_node << '\n'; + throw config_node.create_parsing_error(ss.str()); + } + } + + // check if config node has all required fields + for (const auto& it : schema_node) { + std::string field_schema = it.get_name().value(); + if (is_meta_field(field_schema)) { + continue; + } + ConfigNodeWithPresetExpected field_config = config_node[field_schema]; + if (!field_config.has_value()) { + std::stringstream ss; + ss << "Does not have required field '" + << field_config.get_name().value_or("null") + << "' described in schema:\n"; + ss << schema_node; + throw config_node.create_parsing_error(ss.str()); + } + } + + for (const auto& it : schema_node) { + std::string field = it.get_name().value(); + if (!is_meta_field(field)) { + validate(schema_node[field].value(), config_node[field].value()); + } + } +} + +[[nodiscard]] bool SchemaServer::try_validate_basic_types( + const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { + std::string type = schema_node["_type"].value().as().value(); + if (type == "size_t") { + config_node.as_or_throw(); + } else if (type == "int") { + config_node.as_or_throw(); + } else if (type == "double") { + config_node.as_or_throw(); + } else if (type == "bool") { + config_node.as_or_throw(); + } else if (type == "string") { + config_node.as_or_throw(); + } else if (type == "regex") { + std::string pattern = config_node.as_or_throw(); + try { + std::regex r(pattern); + } catch (const std::regex_error&) { + throw std::runtime_error("Incorrect type specified std::regex"); + } + } else { + return false; + } + return true; +} + +[[nodiscard]] bool SchemaServer::try_validate_custom_types( + const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { + std::string type = schema_node["_type"].value().as().value(); + if (type.ends_with(".schema")) { + ConfigSchema sub_schema = load_file(m_schemas_dir / type); + validate(sub_schema, config_node); + return true; + } else { + return false; + } +} + +void SchemaServer::validate(const ConfigSchema& schema_node, + const ConfigNodeWithPreset& config_node) { + if (schema_node["_type"]) { + utils::StrExpected check_type = + schema_node["_type"].value().as(); + if (!check_type.has_value()) { + std::stringstream ss; + ss << "Schema: " << schema_node << '\n'; + ss << "has empty field '_type'"; + throw schema_node.create_parsing_error(ss.str()); + } + std::string type = check_type.value(); + if (try_validate_basic_types(schema_node, config_node)) { + return; + } + if (try_validate_custom_types(schema_node, config_node)) { + return; + } + throw schema_node.create_parsing_error( + fmt::format("Unknown type: {}", type)); + } else { + if (!config_node.IsMap()) { + throw std::runtime_error(config_node.get_name_or_throw() + + " must be object."); + } + validate_untyped(schema_node, config_node); + } +} + +void SchemaServer::validate(const std::filesystem::path& schema_path, + const ConfigNodeWithPreset& config_node) { + std::filesystem::path full_path; + if (schema_path.is_absolute()) { + full_path = schema_path; + } else { + full_path = m_schemas_dir / schema_path; + } + ConfigSchema schema_node = load_file(schema_path); + validate(schema_node, config_node); +} + +} // namespace sim \ No newline at end of file diff --git a/source/config_schema/schema_server.hpp b/source/config_schema/schema_server.hpp new file mode 100644 index 0000000000..4b2a58f1b5 --- /dev/null +++ b/source/config_schema/schema_server.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "parser/config_reader/config_node_with_preset.hpp" + +namespace sim { + +using ConfigSchema = ConfigNode; + +class SchemaServer { +public: + SchemaServer(const std::filesystem::path& a_schema_path); + + void validate(const ConfigSchema& schema_node, + const ConfigNodeWithPreset& config_node); + + void validate(const std::filesystem::path& schema_path, + const ConfigNodeWithPreset& config_node); + +private: + [[nodiscard]] bool try_validate_basic_types( + const ConfigSchema& schema_node, + const ConfigNodeWithPreset& config_node); + + [[nodiscard]] bool try_validate_custom_types( + const ConfigSchema& schema_node, + const ConfigNodeWithPreset& config_node); + + void validate_untyped(const ConfigSchema& schema_node, + const ConfigNodeWithPreset& config_node); + + bool is_meta_field(const std::string& field); + +private: + const std::filesystem::path m_schemas_dir; +}; + +} // namespace sim \ No newline at end of file diff --git a/source/config_schema/validator.cpp b/source/config_schema/validator.cpp deleted file mode 100644 index 9d1cba696a..0000000000 --- a/source/config_schema/validator.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "validator.hpp" - -#include -#include - -namespace sim { - -bool is_meta_field(const std::string& field) { - return !field.empty() && field[0] == '_'; -} - -void validate_field(const YAML::Node& schema_node, - const ConfigNodeWithPreset& config_node) { - if (schema_node["_type"]) { - std::string type = schema_node["_type"].as(); - if (type == "size_t") { - config_node.as_or_throw(); - } else if (type == "int") { - config_node.as_or_throw(); - } else if (type == "double") { - config_node.as_or_throw(); - } else if (type == "bool") { - config_node.as_or_throw(); - } else if (type == "string") { - config_node.as_or_throw(); - } else if (type == "regex") { - std::string pattern = config_node.as_or_throw(); - try { - std::regex r(pattern); - } catch (const std::regex_error&) { - throw std::runtime_error("Incorrect type specified std::regex"); - } - } else if (type.ends_with(".schema")) { - YAML::Node sub_schema = YAML::LoadFile(type); - if (!config_node.IsMap()) { - throw std::runtime_error(config_node.get_name_or_throw() + - " must be object."); - } - validate(sub_schema, config_node); - } else { - throw std::runtime_error("Unknown type: " + type); - } - } else { - if (!config_node.IsMap()) { - throw std::runtime_error(config_node.get_name_or_throw() + - " must be object."); - } - validate(schema_node, config_node); - } -} - -void validate(const YAML::Node& schema_node, - const ConfigNodeWithPreset& config_node) { - // create set of requried fields - std::unordered_set schema_fields; - for (auto it : schema_node) { - std::string field = it.first.as(); - if (!is_meta_field(field)) { - schema_fields.insert(field); - } - } - - // check if config node has unknown fields - for (const auto& node : config_node) { - std::string field = node.get_name_or_throw(); - if (!schema_fields.contains(field)) { - std::stringstream ss; - ss << "Config node: " << config_node.get_name_or_throw(); - ss << "has unknown field: " << field; - throw std::runtime_error(ss.str()); - } - } - - // check if config node has all required fields - for (auto it : schema_node) { - std::string field_schema = it.first.as(); - if (is_meta_field(field_schema)) { - continue; - } - ConfigNodeWithPresetExpected field_config = config_node[field_schema]; - if (!field_config.has_value()) { - std::stringstream ss; - ss << "Config node: " << config_node.get_name_or_throw(); - ss << "hasn't required field: " << field_schema; - throw std::runtime_error(ss.str()); - } - } - - for (auto it : schema_node) { - std::string field = it.first.as(); - if (!is_meta_field(field)) { - validate_field(it.second, config_node[field].value()); - } - } -} - -} // namespace sim \ No newline at end of file diff --git a/source/config_schema/validator.hpp b/source/config_schema/validator.hpp deleted file mode 100644 index e90f37f1b1..0000000000 --- a/source/config_schema/validator.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -#include "parser/config_reader/config_node_with_preset.hpp" - -namespace sim { - -void validate(const YAML::Node& schema_node, - const ConfigNodeWithPreset& config_node); - -void validate_field(const YAML::Node& schema_node, - const ConfigNodeWithPreset& config_node); - -} // namespace sim \ No newline at end of file diff --git a/test/config_schema/basic_types.schema b/test/config_schema/_schemas/basic_types.schema similarity index 100% rename from test/config_schema/basic_types.schema rename to test/config_schema/_schemas/basic_types.schema diff --git a/test/config_schema/_schemas/custom_type.schema b/test/config_schema/_schemas/custom_type.schema new file mode 100644 index 0000000000..8c1105cfb7 --- /dev/null +++ b/test/config_schema/_schemas/custom_type.schema @@ -0,0 +1,2 @@ +_type: int +_doc: Custom type \ No newline at end of file diff --git a/test/config_schema/_schemas/root_is_type.schema b/test/config_schema/_schemas/root_is_type.schema new file mode 100644 index 0000000000..1f15e757a1 --- /dev/null +++ b/test/config_schema/_schemas/root_is_type.schema @@ -0,0 +1,2 @@ +node: + _type: bool \ No newline at end of file diff --git a/test/config_schema/_schemas/test_with_custom_type.schema b/test/config_schema/_schemas/test_with_custom_type.schema new file mode 100644 index 0000000000..b4a875263d --- /dev/null +++ b/test/config_schema/_schemas/test_with_custom_type.schema @@ -0,0 +1,5 @@ +node: + field1: + _type: custom_type.schema + field2: + _type: int \ No newline at end of file diff --git a/test/config_schema/check_schemas.cpp b/test/config_schema/check_schemas.cpp index 44b7e467c6..4598b1a587 100644 --- a/test/config_schema/check_schemas.cpp +++ b/test/config_schema/check_schemas.cpp @@ -1,7 +1,7 @@ #include #include -#include "config_schema/validator.hpp" +#include "config_schema/schema_server.hpp" namespace sim { namespace test2 { @@ -11,9 +11,10 @@ TEST(TestBasicTypes, BasicTypes) { std::filesystem::path(__FILE__).parent_path(); ConfigNodeWithPreset node = load_file_with_presets(current_dir / "basic_types.yml"); - YAML::Node schema_node = - YAML::LoadFile(current_dir.append("basic_types.schema")); - ASSERT_NO_THROW(validate(schema_node, node)); + std::filesystem::path schemas_dir = current_dir / "_schemas"; + ConfigSchema schema_node = load_file(schemas_dir / "basic_types.schema"); + SchemaServer schema_server(schemas_dir); + ASSERT_NO_THROW(schema_server.validate(schema_node, node)); } TEST(TestBasicTypes, WrongBasicTypes) { @@ -21,9 +22,56 @@ TEST(TestBasicTypes, WrongBasicTypes) { std::filesystem::path(__FILE__).parent_path(); ConfigNodeWithPreset node = load_file_with_presets(current_dir / "basic_types_wrong.yml"); - YAML::Node schema_node = - YAML::LoadFile(current_dir.append("basic_types.schema")); - ASSERT_ANY_THROW(validate(schema_node, node)); + std::filesystem::path schemas_dir = current_dir / "_schemas"; + ConfigSchema schema_node = load_file(schemas_dir / "basic_types.schema"); + SchemaServer schema_server(schemas_dir); + ASSERT_ANY_THROW(schema_server.validate(schema_node, node)); +} + +TEST(TestCustomType, CustomType) { + std::filesystem::path current_dir = + std::filesystem::path(__FILE__).parent_path(); + ConfigNodeWithPreset node = + load_file_with_presets(current_dir / "custom_type.yml"); + std::filesystem::path schemas_dir = current_dir / "_schemas"; + ConfigSchema schema_node = + load_file(schemas_dir / "test_with_custom_type.schema"); + SchemaServer schema_server(schemas_dir); + ASSERT_NO_THROW(schema_server.validate(schema_node, node)); +} + +TEST(TestCustomType, WrongCustomType) { + std::filesystem::path current_dir = + std::filesystem::path(__FILE__).parent_path(); + ConfigNodeWithPreset node = + load_file_with_presets(current_dir / "custom_type_wrong.yml"); + std::filesystem::path schemas_dir = current_dir / "_schemas"; + ConfigSchema schema_node = + load_file(schemas_dir / "test_with_custom_type.schema"); + SchemaServer schema_server(schemas_dir); + ASSERT_ANY_THROW(schema_server.validate(schema_node, node)); +} + +TEST(TestRootIsType, RootIsType) { + std::filesystem::path current_dir = + std::filesystem::path(__FILE__).parent_path(); + ConfigNodeWithPreset node = + load_file_with_presets(current_dir / "root_is_type.yml"); + std::filesystem::path schemas_dir = current_dir / "_schemas"; + ConfigSchema schema_node = load_file(schemas_dir / "root_is_type.schema"); + SchemaServer schema_server(schemas_dir); + ASSERT_NO_THROW(schema_server.validate(schema_node, node)); +} + +TEST(TestRootIsType, WrongRootIsType) { + std::filesystem::path current_dir = + std::filesystem::path(__FILE__).parent_path(); + ConfigNodeWithPreset node = + load_file_with_presets(current_dir / "root_is_type_wrong.yml"); + std::filesystem::path schemas_dir = current_dir / "_schemas"; + ConfigSchema schema_node = load_file(schemas_dir / "root_is_type.schema"); + SchemaServer schema_server(schemas_dir); + ASSERT_ANY_THROW(schema_server.validate(schema_node, node)); } } // namespace test2 diff --git a/test/config_schema/custom_type.yml b/test/config_schema/custom_type.yml new file mode 100644 index 0000000000..cae29acba1 --- /dev/null +++ b/test/config_schema/custom_type.yml @@ -0,0 +1,3 @@ +node: + field1: 123 + field2: 456 \ No newline at end of file diff --git a/test/config_schema/custom_type_wrong.yml b/test/config_schema/custom_type_wrong.yml new file mode 100644 index 0000000000..82d3dabf18 --- /dev/null +++ b/test/config_schema/custom_type_wrong.yml @@ -0,0 +1,3 @@ +node: + field1: hello + field2: 456 \ No newline at end of file diff --git a/test/config_schema/root_is_type.yml b/test/config_schema/root_is_type.yml new file mode 100644 index 0000000000..590b4e877e --- /dev/null +++ b/test/config_schema/root_is_type.yml @@ -0,0 +1 @@ +node: true \ No newline at end of file diff --git a/test/config_schema/root_is_type_wrong.yml b/test/config_schema/root_is_type_wrong.yml new file mode 100644 index 0000000000..135c8be78a --- /dev/null +++ b/test/config_schema/root_is_type_wrong.yml @@ -0,0 +1 @@ +node: 123 \ No newline at end of file From 54d46f2f8719b61b3545b3197a808e2a3796c602 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Apr 2026 11:18:09 +0300 Subject: [PATCH 10/16] retell messages about errors --- source/config_schema/schema_server.cpp | 43 +++++++++++++++++--------- source/config_schema/schema_server.hpp | 2 +- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/source/config_schema/schema_server.cpp b/source/config_schema/schema_server.cpp index e0f6f6e842..3efc0528a9 100644 --- a/source/config_schema/schema_server.cpp +++ b/source/config_schema/schema_server.cpp @@ -22,7 +22,7 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, std::optional tmp_field = it.get_name(); if (!tmp_field) { std::stringstream ss; - ss << "Empty schema node:\n"; + ss << "Schema contains a field without name:\n"; ss << it; throw schema_node.create_parsing_error(ss.str()); } @@ -37,15 +37,18 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, std::optional tmp_field = node.get_name(); if (!tmp_field) { std::stringstream ss; - ss << "Empty config node:\n"; + ss << "Configuration contains a field without name:\n"; ss << node; throw schema_node.create_parsing_error(ss.str()); } std::string field = tmp_field.value(); if (!schema_fields.contains(field)) { std::stringstream ss; - ss << "Field does not correspond to schema:\n"; - ss << schema_node << '\n'; + ss << "Unknown field '" << field; + ss << "' found in configuration:\n"; + ss << node; + ss << "This field is not described in schema:\n"; + ss << schema_node; throw config_node.create_parsing_error(ss.str()); } } @@ -59,10 +62,11 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, ConfigNodeWithPresetExpected field_config = config_node[field_schema]; if (!field_config.has_value()) { std::stringstream ss; - ss << "Does not have required field '" - << field_config.get_name().value_or("null") - << "' described in schema:\n"; - ss << schema_node; + ss << "Required field '" << field_schema; + ss << "' is missing in configuration: \n"; + ss << config_node << '\n'; + ss << "Field is required by schema node: \n"; + ss << schema_node << "\n"; throw config_node.create_parsing_error(ss.str()); } } @@ -93,7 +97,10 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, try { std::regex r(pattern); } catch (const std::regex_error&) { - throw std::runtime_error("Incorrect type specified std::regex"); + std::stringstream ss; + ss << "Field must contain valid regular expression.\n"; + ss << "Regex pattern: " << pattern << '\n'; + throw config_node.create_parsing_error(ss.str()); } } else { return false; @@ -120,8 +127,8 @@ void SchemaServer::validate(const ConfigSchema& schema_node, schema_node["_type"].value().as(); if (!check_type.has_value()) { std::stringstream ss; - ss << "Schema: " << schema_node << '\n'; - ss << "has empty field '_type'"; + ss << "Schema contains '_type' field, but it is empty or not a " + "string.\n"; throw schema_node.create_parsing_error(ss.str()); } std::string type = check_type.value(); @@ -131,12 +138,18 @@ void SchemaServer::validate(const ConfigSchema& schema_node, if (try_validate_custom_types(schema_node, config_node)) { return; } - throw schema_node.create_parsing_error( - fmt::format("Unknown type: {}", type)); + std::stringstream ss; + ss << "Unknown specified type '" << type << "' in schema: "; + ss << schema_node; + throw schema_node.create_parsing_error(ss.str()); } else { if (!config_node.IsMap()) { - throw std::runtime_error(config_node.get_name_or_throw() + - " must be object."); + std::stringstream ss; + ss << "Expected object/map for config node:\n"; + ss << config_node << ".\n"; + ss << "Because schema has nested fields:\n"; + ss << schema_node; + throw config_node.create_parsing_error(ss.str()); } validate_untyped(schema_node, config_node); } diff --git a/source/config_schema/schema_server.hpp b/source/config_schema/schema_server.hpp index 4b2a58f1b5..adc7036dab 100644 --- a/source/config_schema/schema_server.hpp +++ b/source/config_schema/schema_server.hpp @@ -28,7 +28,7 @@ class SchemaServer { void validate_untyped(const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node); - bool is_meta_field(const std::string& field); + static bool is_meta_field(const std::string& field); private: const std::filesystem::path m_schemas_dir; From d82f00434439f3df416f74dec214006c4ef57dab Mon Sep 17 00:00:00 2001 From: root Date: Thu, 23 Apr 2026 16:00:11 +0300 Subject: [PATCH 11/16] done --- source/config_schema/schema_server.cpp | 81 ++++++-------- source/config_schema/schema_server.hpp | 4 + .../config_reader/config_node_expected.cpp | 10 ++ .../config_reader/config_node_expected.hpp | 2 + ..._type.schema => nested_custom_type.schema} | 0 test/config_schema/check_schemas.cpp | 103 +++++++++--------- 6 files changed, 102 insertions(+), 98 deletions(-) rename test/config_schema/_schemas/{test_with_custom_type.schema => nested_custom_type.schema} (100%) diff --git a/source/config_schema/schema_server.cpp b/source/config_schema/schema_server.cpp index 3efc0528a9..4d6df23d7e 100644 --- a/source/config_schema/schema_server.cpp +++ b/source/config_schema/schema_server.cpp @@ -8,7 +8,7 @@ namespace sim { SchemaServer::SchemaServer(const std::filesystem::path& a_schemas_dir) - : m_schemas_dir(std::move(a_schemas_dir)) {} + : m_schemas_dir(a_schemas_dir) {} bool SchemaServer::is_meta_field(const std::string& field) { return field.starts_with('_'); @@ -19,34 +19,20 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, // create set of requried fields std::unordered_set schema_fields; for (const auto& it : schema_node) { - std::optional tmp_field = it.get_name(); - if (!tmp_field) { - std::stringstream ss; - ss << "Schema contains a field without name:\n"; - ss << it; - throw schema_node.create_parsing_error(ss.str()); - } - std::string field = tmp_field.value(); + const std::string& field = it.get_name_or_throw(); if (!is_meta_field(field)) { schema_fields.insert(field); } } // check if config node has unknown fields - for (const auto& node : config_node) { - std::optional tmp_field = node.get_name(); - if (!tmp_field) { - std::stringstream ss; - ss << "Configuration contains a field without name:\n"; - ss << node; - throw schema_node.create_parsing_error(ss.str()); - } - std::string field = tmp_field.value(); + for (const auto& subnode : config_node) { + const std::string& field = subnode.get_name_or_throw(); if (!schema_fields.contains(field)) { std::stringstream ss; ss << "Unknown field '" << field; ss << "' found in configuration:\n"; - ss << node; + ss << subnode; ss << "This field is not described in schema:\n"; ss << schema_node; throw config_node.create_parsing_error(ss.str()); @@ -54,15 +40,12 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, } // check if config node has all required fields - for (const auto& it : schema_node) { - std::string field_schema = it.get_name().value(); - if (is_meta_field(field_schema)) { - continue; - } - ConfigNodeWithPresetExpected field_config = config_node[field_schema]; - if (!field_config.has_value()) { + for (const auto& required_field : schema_fields) { + ConfigNodeWithPresetExpected exp_field_config = + config_node[required_field]; + if (!exp_field_config.has_value()) { std::stringstream ss; - ss << "Required field '" << field_schema; + ss << "Required field '" << required_field; ss << "' is missing in configuration: \n"; ss << config_node << '\n'; ss << "Field is required by schema node: \n"; @@ -110,9 +93,21 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, [[nodiscard]] bool SchemaServer::try_validate_custom_types( const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { - std::string type = schema_node["_type"].value().as().value(); + ConfigSchema type_node = schema_node["_type"].value(); + std::string type = type_node.as_or_throw(); if (type.ends_with(".schema")) { - ConfigSchema sub_schema = load_file(m_schemas_dir / type); + std::filesystem::path nested_schema_path = std::filesystem::path(type); + std::filesystem::path sub_schema_path = + nested_schema_path.is_absolute() + ? nested_schema_path + : m_schemas_dir / nested_schema_path; + auto exp_sub_schema = safe_load_file(sub_schema_path); + if (!exp_sub_schema) { + throw schema_node.create_parsing_error( + fmt::format("Could not find custom schema {} due to error {}", + type, exp_sub_schema.error())); + } + ConfigSchema sub_schema = exp_sub_schema.value(); validate(sub_schema, config_node); return true; } else { @@ -122,16 +117,9 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, void SchemaServer::validate(const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { - if (schema_node["_type"]) { - utils::StrExpected check_type = - schema_node["_type"].value().as(); - if (!check_type.has_value()) { - std::stringstream ss; - ss << "Schema contains '_type' field, but it is empty or not a " - "string.\n"; - throw schema_node.create_parsing_error(ss.str()); - } - std::string type = check_type.value(); + ConfigNodeExpected exp_type_node = schema_node["_type"]; + if (exp_type_node) { + std::string type = exp_type_node.value().as_or_throw(); if (try_validate_basic_types(schema_node, config_node)) { return; } @@ -157,14 +145,15 @@ void SchemaServer::validate(const ConfigSchema& schema_node, void SchemaServer::validate(const std::filesystem::path& schema_path, const ConfigNodeWithPreset& config_node) { - std::filesystem::path full_path; - if (schema_path.is_absolute()) { - full_path = schema_path; - } else { - full_path = m_schemas_dir / schema_path; + std::filesystem::path full_path = + schema_path.is_absolute() ? schema_path : m_schemas_dir / schema_path; + auto exp_sub_schema = safe_load_file(full_path); + if (!exp_sub_schema) { + throw config_node.create_parsing_error( + fmt::format("Could not find schema {} due to error {}", + full_path.string(), exp_sub_schema.error())); } - ConfigSchema schema_node = load_file(schema_path); - validate(schema_node, config_node); + validate(exp_sub_schema.value(), config_node); } } // namespace sim \ No newline at end of file diff --git a/source/config_schema/schema_server.hpp b/source/config_schema/schema_server.hpp index adc7036dab..13141762ce 100644 --- a/source/config_schema/schema_server.hpp +++ b/source/config_schema/schema_server.hpp @@ -17,10 +17,14 @@ class SchemaServer { const ConfigNodeWithPreset& config_node); private: + // if schema_node correspond to basic type, validate config_node and returns + // true otherwise, returns false [[nodiscard]] bool try_validate_basic_types( const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node); + // if schema_node correspond to custom type, validate config_node and + // returns true otherwise, returns false [[nodiscard]] bool try_validate_custom_types( const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node); diff --git a/source/parser/config_reader/config_node_expected.cpp b/source/parser/config_reader/config_node_expected.cpp index 6e37c99336..781f8a97ff 100644 --- a/source/parser/config_reader/config_node_expected.cpp +++ b/source/parser/config_reader/config_node_expected.cpp @@ -29,4 +29,14 @@ ConfigNodeExpected::ConfigNodeExpected(utils::StrExpected a_node) return this->value()[key]; } +ConfigNodeExpected safe_load_file(std::filesystem::path path) noexcept { + try { + return ConfigNode(YAML::LoadFile(path.string()), std::nullopt, path); + } catch (const std::exception& ex) { + return std::unexpected( + fmt::format("Failed to parse file at path: {}, due to error: {}", + path.string(), ex.what())); + } +} + } // namespace sim \ No newline at end of file diff --git a/source/parser/config_reader/config_node_expected.hpp b/source/parser/config_reader/config_node_expected.hpp index 6ba1ed4deb..a17a247379 100644 --- a/source/parser/config_reader/config_node_expected.hpp +++ b/source/parser/config_reader/config_node_expected.hpp @@ -1,5 +1,6 @@ #pragma once +#include "spdlog/fmt/fmt.h" #include "utils/str_expected.hpp" namespace sim { @@ -25,4 +26,5 @@ class ConfigNodeExpected : public utils::StrExpected { [[nodiscard]] ConfigNodeExpected operator[](std::string_view key) const; }; +ConfigNodeExpected safe_load_file(std::filesystem::path path) noexcept; } // namespace sim \ No newline at end of file diff --git a/test/config_schema/_schemas/test_with_custom_type.schema b/test/config_schema/_schemas/nested_custom_type.schema similarity index 100% rename from test/config_schema/_schemas/test_with_custom_type.schema rename to test/config_schema/_schemas/nested_custom_type.schema diff --git a/test/config_schema/check_schemas.cpp b/test/config_schema/check_schemas.cpp index 4598b1a587..a2c9c33f10 100644 --- a/test/config_schema/check_schemas.cpp +++ b/test/config_schema/check_schemas.cpp @@ -6,72 +6,71 @@ namespace sim { namespace test2 { -TEST(TestBasicTypes, BasicTypes) { - std::filesystem::path current_dir = - std::filesystem::path(__FILE__).parent_path(); +class SchemaTest : public ::testing::Test { +protected: + void SetUp() override { + current_dir = std::filesystem::path(__FILE__).parent_path(); + schemas_dir = current_dir / "_schemas"; + schema_server = std::make_unique(schemas_dir); + } + + ConfigSchema LoadSchema(const std::string& name) { + return load_file(schemas_dir / name); + } + + ConfigNodeWithPreset LoadConfigNodeWithPreset(const std::string& name) { + return load_file_with_presets(current_dir / name); + } + + std::filesystem::path current_dir; + std::filesystem::path schemas_dir; + std::unique_ptr schema_server; +}; + +TEST_F(SchemaTest, BasicTypes) { ConfigNodeWithPreset node = - load_file_with_presets(current_dir / "basic_types.yml"); - std::filesystem::path schemas_dir = current_dir / "_schemas"; - ConfigSchema schema_node = load_file(schemas_dir / "basic_types.schema"); - SchemaServer schema_server(schemas_dir); - ASSERT_NO_THROW(schema_server.validate(schema_node, node)); + LoadConfigNodeWithPreset(current_dir / "basic_types.yml"); + ConfigSchema schema_node = LoadSchema("basic_types.schema"); + + ASSERT_NO_THROW(schema_server->validate(schema_node, node)); } -TEST(TestBasicTypes, WrongBasicTypes) { - std::filesystem::path current_dir = - std::filesystem::path(__FILE__).parent_path(); +TEST_F(SchemaTest, WrongBasicTypes) { ConfigNodeWithPreset node = - load_file_with_presets(current_dir / "basic_types_wrong.yml"); - std::filesystem::path schemas_dir = current_dir / "_schemas"; - ConfigSchema schema_node = load_file(schemas_dir / "basic_types.schema"); - SchemaServer schema_server(schemas_dir); - ASSERT_ANY_THROW(schema_server.validate(schema_node, node)); + LoadConfigNodeWithPreset(current_dir / "basic_types_wrong.yml"); + ConfigSchema schema_node = LoadSchema("basic_types.schema"); + + ASSERT_ANY_THROW(schema_server->validate(schema_node, node)); } -TEST(TestCustomType, CustomType) { - std::filesystem::path current_dir = - std::filesystem::path(__FILE__).parent_path(); - ConfigNodeWithPreset node = - load_file_with_presets(current_dir / "custom_type.yml"); - std::filesystem::path schemas_dir = current_dir / "_schemas"; - ConfigSchema schema_node = - load_file(schemas_dir / "test_with_custom_type.schema"); - SchemaServer schema_server(schemas_dir); - ASSERT_NO_THROW(schema_server.validate(schema_node, node)); +TEST_F(SchemaTest, CustomType) { + ConfigSchema schema = LoadSchema("nested_custom_type.schema"); + ConfigNodeWithPreset node = LoadConfigNodeWithPreset("custom_type.yml"); + + ASSERT_NO_THROW(schema_server->validate(schema, node)); } -TEST(TestCustomType, WrongCustomType) { - std::filesystem::path current_dir = - std::filesystem::path(__FILE__).parent_path(); +TEST_F(SchemaTest, WrongCustomType) { + ConfigSchema schema = LoadSchema("nested_custom_type.schema"); ConfigNodeWithPreset node = - load_file_with_presets(current_dir / "custom_type_wrong.yml"); - std::filesystem::path schemas_dir = current_dir / "_schemas"; - ConfigSchema schema_node = - load_file(schemas_dir / "test_with_custom_type.schema"); - SchemaServer schema_server(schemas_dir); - ASSERT_ANY_THROW(schema_server.validate(schema_node, node)); + LoadConfigNodeWithPreset("custom_type_wrong.yml"); + + ASSERT_ANY_THROW(schema_server->validate(schema, node)); } -TEST(TestRootIsType, RootIsType) { - std::filesystem::path current_dir = - std::filesystem::path(__FILE__).parent_path(); - ConfigNodeWithPreset node = - load_file_with_presets(current_dir / "root_is_type.yml"); - std::filesystem::path schemas_dir = current_dir / "_schemas"; - ConfigSchema schema_node = load_file(schemas_dir / "root_is_type.schema"); - SchemaServer schema_server(schemas_dir); - ASSERT_NO_THROW(schema_server.validate(schema_node, node)); +TEST_F(SchemaTest, RootIsType) { + ConfigSchema schema = LoadSchema("root_is_type.schema"); + ConfigNodeWithPreset node = LoadConfigNodeWithPreset("root_is_type.yml"); + + ASSERT_NO_THROW(schema_server->validate(schema, node)); } -TEST(TestRootIsType, WrongRootIsType) { - std::filesystem::path current_dir = - std::filesystem::path(__FILE__).parent_path(); +TEST_F(SchemaTest, WrongRootIsType) { + ConfigSchema schema = LoadSchema("root_is_type.schema"); ConfigNodeWithPreset node = - load_file_with_presets(current_dir / "root_is_type_wrong.yml"); - std::filesystem::path schemas_dir = current_dir / "_schemas"; - ConfigSchema schema_node = load_file(schemas_dir / "root_is_type.schema"); - SchemaServer schema_server(schemas_dir); - ASSERT_ANY_THROW(schema_server.validate(schema_node, node)); + LoadConfigNodeWithPreset("root_is_type_wrong.yml"); + + ASSERT_ANY_THROW(schema_server->validate(schema, node)); } } // namespace test2 From 1d2c05f9b2f8cd8c36542f4de402289104addb37 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 4 May 2026 00:22:53 +0300 Subject: [PATCH 12/16] fix --- source/config_schema/schema_server.cpp | 23 ++++++-------- .../config_node_with_preset_.cpp | 11 ++++--- .../config_node_with_preset_.hpp | 4 ++- .../_schemas/nested_custom_type.schema | 2 +- test/config_schema/_schemas/test_paths.schema | 4 +++ test/config_schema/check_schemas.cpp | 31 +++++++++++++++++++ 6 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 test/config_schema/_schemas/test_paths.schema diff --git a/source/config_schema/schema_server.cpp b/source/config_schema/schema_server.cpp index 4d6df23d7e..e0f747393d 100644 --- a/source/config_schema/schema_server.cpp +++ b/source/config_schema/schema_server.cpp @@ -99,16 +99,10 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, std::filesystem::path nested_schema_path = std::filesystem::path(type); std::filesystem::path sub_schema_path = nested_schema_path.is_absolute() - ? nested_schema_path - : m_schemas_dir / nested_schema_path; - auto exp_sub_schema = safe_load_file(sub_schema_path); - if (!exp_sub_schema) { - throw schema_node.create_parsing_error( - fmt::format("Could not find custom schema {} due to error {}", - type, exp_sub_schema.error())); - } - ConfigSchema sub_schema = exp_sub_schema.value(); - validate(sub_schema, config_node); + ? m_schemas_dir / nested_schema_path.relative_path() + : std::filesystem::path(__FILE__).parent_path() / + nested_schema_path; + validate(sub_schema_path, config_node); return true; } else { return false; @@ -135,8 +129,9 @@ void SchemaServer::validate(const ConfigSchema& schema_node, std::stringstream ss; ss << "Expected object/map for config node:\n"; ss << config_node << ".\n"; - ss << "Because schema has nested fields:\n"; - ss << schema_node; + ss << "Because schema\n"; + ss << schema_node << '\n'; + ss << " has nested fields"; throw config_node.create_parsing_error(ss.str()); } validate_untyped(schema_node, config_node); @@ -150,8 +145,8 @@ void SchemaServer::validate(const std::filesystem::path& schema_path, auto exp_sub_schema = safe_load_file(full_path); if (!exp_sub_schema) { throw config_node.create_parsing_error( - fmt::format("Could not find schema {} due to error {}", - full_path.string(), exp_sub_schema.error())); + fmt::format("Failed to parse corresponding schema file: {}", + exp_sub_schema.error())); } validate(exp_sub_schema.value(), config_node); } diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index 6d979df367..6ad1979d20 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -120,8 +120,9 @@ ConfigNodeWithPreset load_file_with_presets(std::filesystem::path path) { return ConfigNodeWithPreset(node, node["presets"].to_optional()); } -ConfigNodeWithPreset::Iterator::Iterator(ConfigNode::Iterator a_it) - : m_iterator(a_it) {} +ConfigNodeWithPreset::Iterator::Iterator(ConfigNode::Iterator a_it, + const ConfigNodeWithPreset& a_parent) + : m_iterator(a_it), m_parent(a_parent) {} ConfigNodeWithPreset::Iterator& ConfigNodeWithPreset::Iterator::operator++() { ++m_iterator; @@ -143,15 +144,15 @@ bool ConfigNodeWithPreset::Iterator::operator!=(const Iterator& rhs) const { } ConfigNodeWithPreset ConfigNodeWithPreset::Iterator::operator*() const { - return ConfigNodeWithPreset(*m_iterator); + return ConfigNodeWithPreset(*m_iterator, m_parent.get_presets_node()); } ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::begin() const { - return Iterator(m_node.begin()); + return Iterator(m_node.begin(), *this); } ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::end() const { - return Iterator(m_node.end()); + return Iterator(m_node.end(), *this); } } // namespace sim diff --git a/source/parser/config_reader/config_node_with_preset_.hpp b/source/parser/config_reader/config_node_with_preset_.hpp index 5aa0100df7..7792371a14 100644 --- a/source/parser/config_reader/config_node_with_preset_.hpp +++ b/source/parser/config_reader/config_node_with_preset_.hpp @@ -48,7 +48,8 @@ class ConfigNodeWithPreset { class Iterator { public: - Iterator(ConfigNode::Iterator a_it); + Iterator(ConfigNode::Iterator a_it, + const ConfigNodeWithPreset& a_parent); Iterator& operator++(); @@ -62,6 +63,7 @@ class ConfigNodeWithPreset { private: ConfigNode::Iterator m_iterator; + const ConfigNodeWithPreset& m_parent; }; Iterator begin() const; diff --git a/test/config_schema/_schemas/nested_custom_type.schema b/test/config_schema/_schemas/nested_custom_type.schema index b4a875263d..5da1db2f8e 100644 --- a/test/config_schema/_schemas/nested_custom_type.schema +++ b/test/config_schema/_schemas/nested_custom_type.schema @@ -1,5 +1,5 @@ node: field1: - _type: custom_type.schema + _type: /custom_type.schema field2: _type: int \ No newline at end of file diff --git a/test/config_schema/_schemas/test_paths.schema b/test/config_schema/_schemas/test_paths.schema new file mode 100644 index 0000000000..b5dfc40d46 --- /dev/null +++ b/test/config_schema/_schemas/test_paths.schema @@ -0,0 +1,4 @@ +absolute_path: + _type: /nested_absolute_path.schema +relative_path: + _type: test_relative_paths/relative_path.schema \ No newline at end of file diff --git a/test/config_schema/check_schemas.cpp b/test/config_schema/check_schemas.cpp index a2c9c33f10..7e0e3ff8db 100644 --- a/test/config_schema/check_schemas.cpp +++ b/test/config_schema/check_schemas.cpp @@ -73,5 +73,36 @@ TEST_F(SchemaTest, WrongRootIsType) { ASSERT_ANY_THROW(schema_server->validate(schema, node)); } +TEST_F(SchemaTest, AbsouletePaths) { + ConfigSchema schema = LoadSchema("test_paths.schema"); + std::string expected_path = schemas_dir / "nested_absolute_path.schema"; + std::string nested_schema = + schema["absolute_path"]["_type"].value().as().value(); + std::filesystem::path nested_schema_path = + std::filesystem::path(nested_schema); + std::filesystem::path sub_schema_path = + nested_schema_path.is_absolute() + ? schemas_dir / nested_schema_path.relative_path() + : std::filesystem::path(__FILE__).parent_path() / + nested_schema_path; + ASSERT_EQ(expected_path, sub_schema_path.string()); +} + +TEST_F(SchemaTest, RelativePaths) { + ConfigSchema schema = LoadSchema("test_paths.schema"); + std::string expected_path = std::filesystem::path(__FILE__).parent_path() / + "test_relative_paths/relative_path.schema"; + std::string nested_schema = + schema["relative_path"]["_type"].value().as().value(); + std::filesystem::path nested_schema_path = + std::filesystem::path(nested_schema); + std::filesystem::path sub_schema_path = + nested_schema_path.is_absolute() + ? schemas_dir / nested_schema_path.relative_path() + : std::filesystem::path(__FILE__).parent_path() / + nested_schema_path; + ASSERT_EQ(expected_path, sub_schema_path.string()); +} + } // namespace test2 } // namespace sim \ No newline at end of file From 7504f48b1b2186edd3098edb8836b7c31b858600 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 17 May 2026 13:51:18 +0300 Subject: [PATCH 13/16] resolved last conversations --- source/config_schema/schema_server.cpp | 58 +++++++++------- source/config_schema/schema_server.hpp | 2 +- .../config_reader/config_node_expected.cpp | 2 + .../config_reader/config_node_expected.hpp | 1 - .../config_node_with_preset_.cpp | 5 +- .../_schemas/root_is_type.schema | 3 +- .../test_nested_paths/absolute_path.schema | 1 + .../test_nested_paths/relative_path.schema | 1 + test/config_schema/_schemas/test_paths.schema | 4 +- test/config_schema/check_schemas.cpp | 68 +++++++------------ .../{ => config_nodes}/basic_types.yml | 0 .../{ => config_nodes}/basic_types_wrong.yml | 0 .../{ => config_nodes}/custom_type.yml | 0 .../{ => config_nodes}/custom_type_wrong.yml | 0 .../config_nodes/root_is_type.yml | 1 + .../config_nodes/root_is_type_wrong.yml | 1 + .../config_schema/config_nodes/test_paths.yml | 2 + test/config_schema/root_is_type.yml | 1 - test/config_schema/root_is_type_wrong.yml | 1 - 19 files changed, 72 insertions(+), 79 deletions(-) create mode 100644 test/config_schema/_schemas/test_nested_paths/absolute_path.schema create mode 100644 test/config_schema/_schemas/test_nested_paths/relative_path.schema rename test/config_schema/{ => config_nodes}/basic_types.yml (100%) rename test/config_schema/{ => config_nodes}/basic_types_wrong.yml (100%) rename test/config_schema/{ => config_nodes}/custom_type.yml (100%) rename test/config_schema/{ => config_nodes}/custom_type_wrong.yml (100%) create mode 100644 test/config_schema/config_nodes/root_is_type.yml create mode 100644 test/config_schema/config_nodes/root_is_type_wrong.yml create mode 100644 test/config_schema/config_nodes/test_paths.yml delete mode 100644 test/config_schema/root_is_type.yml delete mode 100644 test/config_schema/root_is_type_wrong.yml diff --git a/source/config_schema/schema_server.cpp b/source/config_schema/schema_server.cpp index e0f747393d..f3732f4ea8 100644 --- a/source/config_schema/schema_server.cpp +++ b/source/config_schema/schema_server.cpp @@ -30,12 +30,10 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, const std::string& field = subnode.get_name_or_throw(); if (!schema_fields.contains(field)) { std::stringstream ss; - ss << "Unknown field '" << field; - ss << "' found in configuration:\n"; - ss << subnode; - ss << "This field is not described in schema:\n"; + ss << "Node has field `'" << field + << "' that does not described in schema:\n"; ss << schema_node; - throw config_node.create_parsing_error(ss.str()); + throw subnode.create_parsing_error(ss.str()); } } @@ -45,10 +43,8 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, config_node[required_field]; if (!exp_field_config.has_value()) { std::stringstream ss; - ss << "Required field '" << required_field; - ss << "' is missing in configuration: \n"; - ss << config_node << '\n'; - ss << "Field is required by schema node: \n"; + ss << "Missing required field '" << required_field + << "' described in schema: \n"; ss << schema_node << "\n"; throw config_node.create_parsing_error(ss.str()); } @@ -64,19 +60,34 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, [[nodiscard]] bool SchemaServer::try_validate_basic_types( const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { - std::string type = schema_node["_type"].value().as().value(); + const ConfigSchema& type_node = schema_node["_type"].value(); + std::string type = type_node.as_or_throw(); + auto unsafe_cast_config_node_to = [&]() -> T { + auto as_result = config_node.as(); + if (!as_result.has_value()) { + std::stringstream ss; + ss << "Node should contain basic type `" << type + << "' due to schema:\n"; + ss << type_node << "\n"; + ss << "But its not:\n"; + ss << as_result.error() << '\n'; + throw config_node.create_parsing_error(ss.str()); + } + return as_result.value(); + }; if (type == "size_t") { - config_node.as_or_throw(); + unsafe_cast_config_node_to.operator()(); } else if (type == "int") { - config_node.as_or_throw(); + unsafe_cast_config_node_to.operator()(); } else if (type == "double") { - config_node.as_or_throw(); + unsafe_cast_config_node_to.operator()(); } else if (type == "bool") { - config_node.as_or_throw(); + unsafe_cast_config_node_to.operator()(); } else if (type == "string") { - config_node.as_or_throw(); + unsafe_cast_config_node_to.operator()(); } else if (type == "regex") { - std::string pattern = config_node.as_or_throw(); + std::string pattern = + unsafe_cast_config_node_to.operator()(); try { std::regex r(pattern); } catch (const std::regex_error&) { @@ -93,14 +104,15 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, [[nodiscard]] bool SchemaServer::try_validate_custom_types( const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { - ConfigSchema type_node = schema_node["_type"].value(); + const ConfigSchema& type_node = schema_node["_type"].value(); std::string type = type_node.as_or_throw(); if (type.ends_with(".schema")) { std::filesystem::path nested_schema_path = std::filesystem::path(type); std::filesystem::path sub_schema_path = nested_schema_path.is_absolute() ? m_schemas_dir / nested_schema_path.relative_path() - : std::filesystem::path(__FILE__).parent_path() / + : std::filesystem::path(schema_node.get_config_path().value()) + .parent_path() / nested_schema_path; validate(sub_schema_path, config_node); return true; @@ -121,17 +133,13 @@ void SchemaServer::validate(const ConfigSchema& schema_node, return; } std::stringstream ss; - ss << "Unknown specified type '" << type << "' in schema: "; - ss << schema_node; - throw schema_node.create_parsing_error(ss.str()); + throw schema_node.create_parsing_error( + fmt::format("Unknown specified type '{}'", type)); } else { if (!config_node.IsMap()) { std::stringstream ss; - ss << "Expected object/map for config node:\n"; - ss << config_node << ".\n"; - ss << "Because schema\n"; + ss << "Should be map due to schema\n"; ss << schema_node << '\n'; - ss << " has nested fields"; throw config_node.create_parsing_error(ss.str()); } validate_untyped(schema_node, config_node); diff --git a/source/config_schema/schema_server.hpp b/source/config_schema/schema_server.hpp index 13141762ce..bc45c1a9c8 100644 --- a/source/config_schema/schema_server.hpp +++ b/source/config_schema/schema_server.hpp @@ -8,7 +8,7 @@ using ConfigSchema = ConfigNode; class SchemaServer { public: - SchemaServer(const std::filesystem::path& a_schema_path); + explicit SchemaServer(const std::filesystem::path& a_schemas_dir); void validate(const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node); diff --git a/source/parser/config_reader/config_node_expected.cpp b/source/parser/config_reader/config_node_expected.cpp index 781f8a97ff..8b5262c72a 100644 --- a/source/parser/config_reader/config_node_expected.cpp +++ b/source/parser/config_reader/config_node_expected.cpp @@ -1,3 +1,5 @@ +#include + #include "config_node.hpp" namespace sim { diff --git a/source/parser/config_reader/config_node_expected.hpp b/source/parser/config_reader/config_node_expected.hpp index a17a247379..387b255526 100644 --- a/source/parser/config_reader/config_node_expected.hpp +++ b/source/parser/config_reader/config_node_expected.hpp @@ -1,6 +1,5 @@ #pragma once -#include "spdlog/fmt/fmt.h" #include "utils/str_expected.hpp" namespace sim { diff --git a/source/parser/config_reader/config_node_with_preset_.cpp b/source/parser/config_reader/config_node_with_preset_.cpp index 6ad1979d20..45ec292123 100644 --- a/source/parser/config_reader/config_node_with_preset_.cpp +++ b/source/parser/config_reader/config_node_with_preset_.cpp @@ -117,7 +117,10 @@ const std::optional ConfigNodeWithPreset::get_presets_node() ConfigNodeWithPreset load_file_with_presets(std::filesystem::path path) { ConfigNode node = load_file(path); - return ConfigNodeWithPreset(node, node["presets"].to_optional()); + if (node.IsMap()) { + return ConfigNodeWithPreset(node, node["presets"].to_optional()); + } + return ConfigNodeWithPreset(node, std::nullopt); } ConfigNodeWithPreset::Iterator::Iterator(ConfigNode::Iterator a_it, diff --git a/test/config_schema/_schemas/root_is_type.schema b/test/config_schema/_schemas/root_is_type.schema index 1f15e757a1..e3c62f9cb9 100644 --- a/test/config_schema/_schemas/root_is_type.schema +++ b/test/config_schema/_schemas/root_is_type.schema @@ -1,2 +1 @@ -node: - _type: bool \ No newline at end of file +_type: bool \ No newline at end of file diff --git a/test/config_schema/_schemas/test_nested_paths/absolute_path.schema b/test/config_schema/_schemas/test_nested_paths/absolute_path.schema new file mode 100644 index 0000000000..d2e4798693 --- /dev/null +++ b/test/config_schema/_schemas/test_nested_paths/absolute_path.schema @@ -0,0 +1 @@ +_type: int \ No newline at end of file diff --git a/test/config_schema/_schemas/test_nested_paths/relative_path.schema b/test/config_schema/_schemas/test_nested_paths/relative_path.schema new file mode 100644 index 0000000000..e3c62f9cb9 --- /dev/null +++ b/test/config_schema/_schemas/test_nested_paths/relative_path.schema @@ -0,0 +1 @@ +_type: bool \ No newline at end of file diff --git a/test/config_schema/_schemas/test_paths.schema b/test/config_schema/_schemas/test_paths.schema index b5dfc40d46..9c08d02d64 100644 --- a/test/config_schema/_schemas/test_paths.schema +++ b/test/config_schema/_schemas/test_paths.schema @@ -1,4 +1,4 @@ absolute_path: - _type: /nested_absolute_path.schema + _type: /test_nested_paths/absolute_path.schema relative_path: - _type: test_relative_paths/relative_path.schema \ No newline at end of file + _type: test_nested_paths/relative_path.schema \ No newline at end of file diff --git a/test/config_schema/check_schemas.cpp b/test/config_schema/check_schemas.cpp index 7e0e3ff8db..75465575a6 100644 --- a/test/config_schema/check_schemas.cpp +++ b/test/config_schema/check_schemas.cpp @@ -10,98 +10,76 @@ class SchemaTest : public ::testing::Test { protected: void SetUp() override { current_dir = std::filesystem::path(__FILE__).parent_path(); + config_nodes_dir = current_dir / "config_nodes"; schemas_dir = current_dir / "_schemas"; schema_server = std::make_unique(schemas_dir); } - ConfigSchema LoadSchema(const std::string& name) { + ConfigSchema load_schema(const std::string& name) { return load_file(schemas_dir / name); } - ConfigNodeWithPreset LoadConfigNodeWithPreset(const std::string& name) { - return load_file_with_presets(current_dir / name); + ConfigNodeWithPreset load_config_node_with_preset(const std::string& name) { + return load_file_with_presets(config_nodes_dir / name); } std::filesystem::path current_dir; + std::filesystem::path config_nodes_dir; std::filesystem::path schemas_dir; std::unique_ptr schema_server; }; TEST_F(SchemaTest, BasicTypes) { - ConfigNodeWithPreset node = - LoadConfigNodeWithPreset(current_dir / "basic_types.yml"); - ConfigSchema schema_node = LoadSchema("basic_types.schema"); + ConfigNodeWithPreset node = load_config_node_with_preset("basic_types.yml"); + ConfigSchema schema_node = load_schema("basic_types.schema"); ASSERT_NO_THROW(schema_server->validate(schema_node, node)); } TEST_F(SchemaTest, WrongBasicTypes) { ConfigNodeWithPreset node = - LoadConfigNodeWithPreset(current_dir / "basic_types_wrong.yml"); - ConfigSchema schema_node = LoadSchema("basic_types.schema"); + load_config_node_with_preset("basic_types_wrong.yml"); + ConfigSchema schema_node = load_schema("basic_types.schema"); ASSERT_ANY_THROW(schema_server->validate(schema_node, node)); } TEST_F(SchemaTest, CustomType) { - ConfigSchema schema = LoadSchema("nested_custom_type.schema"); - ConfigNodeWithPreset node = LoadConfigNodeWithPreset("custom_type.yml"); + ConfigSchema schema = load_schema("nested_custom_type.schema"); + ConfigNodeWithPreset node = load_config_node_with_preset("custom_type.yml"); ASSERT_NO_THROW(schema_server->validate(schema, node)); } TEST_F(SchemaTest, WrongCustomType) { - ConfigSchema schema = LoadSchema("nested_custom_type.schema"); + ConfigSchema schema = load_schema("nested_custom_type.schema"); ConfigNodeWithPreset node = - LoadConfigNodeWithPreset("custom_type_wrong.yml"); + load_config_node_with_preset("custom_type_wrong.yml"); ASSERT_ANY_THROW(schema_server->validate(schema, node)); } TEST_F(SchemaTest, RootIsType) { - ConfigSchema schema = LoadSchema("root_is_type.schema"); - ConfigNodeWithPreset node = LoadConfigNodeWithPreset("root_is_type.yml"); + ConfigSchema schema = load_schema("root_is_type.schema"); + ConfigNodeWithPreset node = + load_config_node_with_preset("root_is_type.yml"); ASSERT_NO_THROW(schema_server->validate(schema, node)); } TEST_F(SchemaTest, WrongRootIsType) { - ConfigSchema schema = LoadSchema("root_is_type.schema"); + ConfigSchema schema = load_schema("root_is_type.schema"); ConfigNodeWithPreset node = - LoadConfigNodeWithPreset("root_is_type_wrong.yml"); + load_config_node_with_preset("root_is_type_wrong.yml"); ASSERT_ANY_THROW(schema_server->validate(schema, node)); } -TEST_F(SchemaTest, AbsouletePaths) { - ConfigSchema schema = LoadSchema("test_paths.schema"); - std::string expected_path = schemas_dir / "nested_absolute_path.schema"; - std::string nested_schema = - schema["absolute_path"]["_type"].value().as().value(); - std::filesystem::path nested_schema_path = - std::filesystem::path(nested_schema); - std::filesystem::path sub_schema_path = - nested_schema_path.is_absolute() - ? schemas_dir / nested_schema_path.relative_path() - : std::filesystem::path(__FILE__).parent_path() / - nested_schema_path; - ASSERT_EQ(expected_path, sub_schema_path.string()); -} - -TEST_F(SchemaTest, RelativePaths) { - ConfigSchema schema = LoadSchema("test_paths.schema"); - std::string expected_path = std::filesystem::path(__FILE__).parent_path() / - "test_relative_paths/relative_path.schema"; - std::string nested_schema = - schema["relative_path"]["_type"].value().as().value(); - std::filesystem::path nested_schema_path = - std::filesystem::path(nested_schema); - std::filesystem::path sub_schema_path = - nested_schema_path.is_absolute() - ? schemas_dir / nested_schema_path.relative_path() - : std::filesystem::path(__FILE__).parent_path() / - nested_schema_path; - ASSERT_EQ(expected_path, sub_schema_path.string()); +TEST_F(SchemaTest, CheckPaths) { + ConfigSchema schema = load_schema("test_paths.schema"); + ConfigNodeWithPreset config_node = + load_config_node_with_preset("test_paths.yml"); + ASSERT_NO_THROW(schema_server->validate(schema, config_node)); } } // namespace test2 diff --git a/test/config_schema/basic_types.yml b/test/config_schema/config_nodes/basic_types.yml similarity index 100% rename from test/config_schema/basic_types.yml rename to test/config_schema/config_nodes/basic_types.yml diff --git a/test/config_schema/basic_types_wrong.yml b/test/config_schema/config_nodes/basic_types_wrong.yml similarity index 100% rename from test/config_schema/basic_types_wrong.yml rename to test/config_schema/config_nodes/basic_types_wrong.yml diff --git a/test/config_schema/custom_type.yml b/test/config_schema/config_nodes/custom_type.yml similarity index 100% rename from test/config_schema/custom_type.yml rename to test/config_schema/config_nodes/custom_type.yml diff --git a/test/config_schema/custom_type_wrong.yml b/test/config_schema/config_nodes/custom_type_wrong.yml similarity index 100% rename from test/config_schema/custom_type_wrong.yml rename to test/config_schema/config_nodes/custom_type_wrong.yml diff --git a/test/config_schema/config_nodes/root_is_type.yml b/test/config_schema/config_nodes/root_is_type.yml new file mode 100644 index 0000000000..f32a5804e2 --- /dev/null +++ b/test/config_schema/config_nodes/root_is_type.yml @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/test/config_schema/config_nodes/root_is_type_wrong.yml b/test/config_schema/config_nodes/root_is_type_wrong.yml new file mode 100644 index 0000000000..d800886d9c --- /dev/null +++ b/test/config_schema/config_nodes/root_is_type_wrong.yml @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/test/config_schema/config_nodes/test_paths.yml b/test/config_schema/config_nodes/test_paths.yml new file mode 100644 index 0000000000..66eaa48c4c --- /dev/null +++ b/test/config_schema/config_nodes/test_paths.yml @@ -0,0 +1,2 @@ +absolute_path: 123 +relative_path: true \ No newline at end of file diff --git a/test/config_schema/root_is_type.yml b/test/config_schema/root_is_type.yml deleted file mode 100644 index 590b4e877e..0000000000 --- a/test/config_schema/root_is_type.yml +++ /dev/null @@ -1 +0,0 @@ -node: true \ No newline at end of file diff --git a/test/config_schema/root_is_type_wrong.yml b/test/config_schema/root_is_type_wrong.yml deleted file mode 100644 index 135c8be78a..0000000000 --- a/test/config_schema/root_is_type_wrong.yml +++ /dev/null @@ -1 +0,0 @@ -node: 123 \ No newline at end of file From d07bafd90a5bba44276f83f3ceb4d3e51187a46d Mon Sep 17 00:00:00 2001 From: root Date: Sun, 17 May 2026 13:56:54 +0300 Subject: [PATCH 14/16] fix --- source/config_schema/schema_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config_schema/schema_server.cpp b/source/config_schema/schema_server.cpp index f3732f4ea8..dd6d532acd 100644 --- a/source/config_schema/schema_server.cpp +++ b/source/config_schema/schema_server.cpp @@ -60,7 +60,7 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, [[nodiscard]] bool SchemaServer::try_validate_basic_types( const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { - const ConfigSchema& type_node = schema_node["_type"].value(); + const ConfigSchema type_node = schema_node["_type"].value(); std::string type = type_node.as_or_throw(); auto unsafe_cast_config_node_to = [&]() -> T { auto as_result = config_node.as(); From 9ac89286dd25c88debaeb93916a96dab9e194983 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 17 May 2026 13:59:10 +0300 Subject: [PATCH 15/16] fix --- source/config_schema/schema_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config_schema/schema_server.cpp b/source/config_schema/schema_server.cpp index dd6d532acd..f902f5c219 100644 --- a/source/config_schema/schema_server.cpp +++ b/source/config_schema/schema_server.cpp @@ -104,7 +104,7 @@ void SchemaServer::validate_untyped(const ConfigSchema& schema_node, [[nodiscard]] bool SchemaServer::try_validate_custom_types( const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) { - const ConfigSchema& type_node = schema_node["_type"].value(); + const ConfigSchema type_node = schema_node["_type"].value(); std::string type = type_node.as_or_throw(); if (type.ends_with(".schema")) { std::filesystem::path nested_schema_path = std::filesystem::path(type); From b7919d35f20c170bc120f7ea595940bd0aabee71 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 18 May 2026 20:44:41 +0300 Subject: [PATCH 16/16] done --- .../_schemas/{test_nested_paths => }/absolute_path.schema | 0 test/config_schema/_schemas/test_paths.schema | 4 ---- test/config_schema/_schemas/test_paths/test_paths.schema | 4 ++++ .../test_relative_path}/relative_path.schema | 0 test/config_schema/check_schemas.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename test/config_schema/_schemas/{test_nested_paths => }/absolute_path.schema (100%) delete mode 100644 test/config_schema/_schemas/test_paths.schema create mode 100644 test/config_schema/_schemas/test_paths/test_paths.schema rename test/config_schema/_schemas/{test_nested_paths => test_paths/test_relative_path}/relative_path.schema (100%) diff --git a/test/config_schema/_schemas/test_nested_paths/absolute_path.schema b/test/config_schema/_schemas/absolute_path.schema similarity index 100% rename from test/config_schema/_schemas/test_nested_paths/absolute_path.schema rename to test/config_schema/_schemas/absolute_path.schema diff --git a/test/config_schema/_schemas/test_paths.schema b/test/config_schema/_schemas/test_paths.schema deleted file mode 100644 index 9c08d02d64..0000000000 --- a/test/config_schema/_schemas/test_paths.schema +++ /dev/null @@ -1,4 +0,0 @@ -absolute_path: - _type: /test_nested_paths/absolute_path.schema -relative_path: - _type: test_nested_paths/relative_path.schema \ No newline at end of file diff --git a/test/config_schema/_schemas/test_paths/test_paths.schema b/test/config_schema/_schemas/test_paths/test_paths.schema new file mode 100644 index 0000000000..08964b5931 --- /dev/null +++ b/test/config_schema/_schemas/test_paths/test_paths.schema @@ -0,0 +1,4 @@ +absolute_path: + _type: /absolute_path.schema +relative_path: + _type: test_relative_path/relative_path.schema \ No newline at end of file diff --git a/test/config_schema/_schemas/test_nested_paths/relative_path.schema b/test/config_schema/_schemas/test_paths/test_relative_path/relative_path.schema similarity index 100% rename from test/config_schema/_schemas/test_nested_paths/relative_path.schema rename to test/config_schema/_schemas/test_paths/test_relative_path/relative_path.schema diff --git a/test/config_schema/check_schemas.cpp b/test/config_schema/check_schemas.cpp index 75465575a6..7074323571 100644 --- a/test/config_schema/check_schemas.cpp +++ b/test/config_schema/check_schemas.cpp @@ -76,7 +76,7 @@ TEST_F(SchemaTest, WrongRootIsType) { } TEST_F(SchemaTest, CheckPaths) { - ConfigSchema schema = load_schema("test_paths.schema"); + ConfigSchema schema = load_schema("test_paths/test_paths.schema"); ConfigNodeWithPreset config_node = load_config_node_with_preset("test_paths.yml"); ASSERT_NO_THROW(schema_server->validate(schema, config_node));