Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions source/config_schema/validator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "validator.hpp"
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated

#include <regex>
#include <unordered_set>

namespace sim {

bool is_meta_field(const std::string& field) {
return !field.empty() && field[0] == '_';
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
}

void validate_field(const YAML::Node& schema_node,
const ConfigNodeWithPreset& config_node) {
if (schema_node["_type"]) {
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
std::string type = schema_node["_type"].as<std::string>();
if (type == "size_t") {
config_node.as_or_throw<size_t>();
} else if (type == "int") {
config_node.as_or_throw<int>();
} else if (type == "double") {
config_node.as_or_throw<double>();
} else if (type == "bool") {
config_node.as_or_throw<bool>();
} else if (type == "string") {
config_node.as_or_throw<std::string>();
} else if (type == "regex") {
std::string pattern = config_node.as_or_throw<std::string>();
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);
}
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
} 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<std::string> schema_fields;
for (auto it : schema_node) {
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
std::string field = it.first.as<std::string>();
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());
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
}
}

// check if config node has all required fields
for (auto it : schema_node) {
std::string field_schema = it.first.as<std::string>();
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());
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
}
}

for (auto it : schema_node) {
std::string field = it.first.as<std::string>();
if (!is_meta_field(field)) {
validate_field(it.second, config_node[field].value());
}
}
}

} // namespace sim
15 changes: 15 additions & 0 deletions source/config_schema/validator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <yaml-cpp/yaml.h>

#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);
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated

} // namespace sim
37 changes: 37 additions & 0 deletions source/parser/config_reader/config_node_with_preset_.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -80,6 +81,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();
}
Expand Down Expand Up @@ -117,4 +120,38 @@ 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& 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 {
return ConfigNodeWithPreset(*m_iterator);
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
}

ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::begin() const {
return Iterator(m_node.begin());
}

ConfigNodeWithPreset::Iterator ConfigNodeWithPreset::end() const {
return Iterator(m_node.end());
}

} // namespace sim
23 changes: 23 additions & 0 deletions source/parser/config_reader/config_node_with_preset_.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -44,6 +46,27 @@ class ConfigNodeWithPreset {

std::runtime_error create_parsing_error(std::string_view error) const;

class Iterator {
public:
Iterator(ConfigNode::Iterator a_it);

Iterator& operator++();

Iterator operator++(int);

bool operator==(const Iterator& rhs) const;

bool operator!=(const Iterator& rhs) const;

ConfigNodeWithPreset operator*() const;

private:
ConfigNode::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
Expand Down
13 changes: 13 additions & 0 deletions test/config_schema/basic_types.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node:
field1:
_type: size_t
field2:
_type: int
field3:
_type: double
field4:
_type: bool
field5:
_type: string
field6:
_type: regex
7 changes: 7 additions & 0 deletions test/config_schema/basic_types.yml
Original file line number Diff line number Diff line change
@@ -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,}$
7 changes: 7 additions & 0 deletions test/config_schema/basic_types_wrong.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node:
field1: hello
field2: hello
field3: hello
field4: hello
field5: hello
field6: 1.25
30 changes: 30 additions & 0 deletions test/config_schema/check_schemas.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <gtest/gtest.h>
Comment thread
S1mpotyaga marked this conversation as resolved.
Comment thread
S1mpotyaga marked this conversation as resolved.
#include <yaml-cpp/yaml.h>

#include "config_schema/validator.hpp"

namespace sim {
namespace test2 {

TEST(TestBasicTypes, BasicTypes) {
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
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"));
Comment thread
S1mpotyaga marked this conversation as resolved.
Outdated
ASSERT_NO_THROW(validate(schema_node, node));
}

TEST(TestBasicTypes, WrongBasicTypes) {
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));
}

} // namespace test2
} // namespace sim
Loading