Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
162 changes: 162 additions & 0 deletions source/config_schema/schema_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#include "schema_server.hpp"

#include <spdlog/fmt/fmt.h>

#include <regex>
#include <unordered_set>

namespace sim {

SchemaServer::SchemaServer(const std::filesystem::path& a_schemas_dir)
: m_schemas_dir(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<std::string> schema_fields;
for (const auto& it : schema_node) {
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& subnode : config_node) {
const std::string& field = subnode.get_name_or_throw();
if (!schema_fields.contains(field)) {
std::stringstream ss;
ss << "Node has field `'" << field
<< "' that does not described in schema:\n";
ss << schema_node;
throw subnode.create_parsing_error(ss.str());
}
}

// check if config node has all required fields
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 << "Missing required field '" << required_field
<< "' described in schema: \n";
ss << schema_node << "\n";
throw config_node.create_parsing_error(ss.str());
Comment thread
S1mpotyaga marked this conversation as resolved.
}
}

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) {
const ConfigSchema type_node = schema_node["_type"].value();
std::string type = type_node.as_or_throw<std::string>();
auto unsafe_cast_config_node_to = [&]<typename T>() -> T {
auto as_result = config_node.as<T>();
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") {
unsafe_cast_config_node_to.operator()<size_t>();
} else if (type == "int") {
unsafe_cast_config_node_to.operator()<int>();
} else if (type == "double") {
unsafe_cast_config_node_to.operator()<double>();
} else if (type == "bool") {
unsafe_cast_config_node_to.operator()<bool>();
} else if (type == "string") {
unsafe_cast_config_node_to.operator()<std::string>();
} else if (type == "regex") {
std::string pattern =
unsafe_cast_config_node_to.operator()<std::string>();
try {
std::regex r(pattern);
} catch (const std::regex_error&) {
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;
}
return true;
}

[[nodiscard]] bool SchemaServer::try_validate_custom_types(
const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) {
const ConfigSchema type_node = schema_node["_type"].value();
std::string type = type_node.as_or_throw<std::string>();
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(schema_node.get_config_path().value())
.parent_path() /
nested_schema_path;
validate(sub_schema_path, config_node);
return true;
} else {
return false;
}
}

void SchemaServer::validate(const ConfigSchema& schema_node,
const ConfigNodeWithPreset& config_node) {
ConfigNodeExpected exp_type_node = schema_node["_type"];
if (exp_type_node) {
std::string type = exp_type_node.value().as_or_throw<std::string>();
if (try_validate_basic_types(schema_node, config_node)) {
return;
}
if (try_validate_custom_types(schema_node, config_node)) {
return;
}
std::stringstream ss;
throw schema_node.create_parsing_error(
fmt::format("Unknown specified type '{}'", type));
} else {
if (!config_node.IsMap()) {
std::stringstream ss;
ss << "Should be map due to schema\n";
ss << schema_node << '\n';
throw config_node.create_parsing_error(ss.str());
Comment thread
S1mpotyaga marked this conversation as resolved.
}
validate_untyped(schema_node, config_node);
}
}

void SchemaServer::validate(const std::filesystem::path& schema_path,
const ConfigNodeWithPreset& config_node) {
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("Failed to parse corresponding schema file: {}",
exp_sub_schema.error()));
}
Comment thread
S1mpotyaga marked this conversation as resolved.
validate(exp_sub_schema.value(), config_node);
}

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

#include "parser/config_reader/config_node_with_preset.hpp"

namespace sim {

using ConfigSchema = ConfigNode;

class SchemaServer {
public:
explicit SchemaServer(const std::filesystem::path& a_schemas_dir);

void validate(const ConfigSchema& schema_node,
const ConfigNodeWithPreset& config_node);

void validate(const std::filesystem::path& schema_path,
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);
Comment thread
S1mpotyaga marked this conversation as resolved.

void validate_untyped(const ConfigSchema& schema_node,
const ConfigNodeWithPreset& config_node);

static bool is_meta_field(const std::string& field);

private:
const std::filesystem::path m_schemas_dir;
};

} // namespace sim
12 changes: 12 additions & 0 deletions source/parser/config_reader/config_node_expected.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <spdlog/fmt/fmt.h>

#include "config_node.hpp"

namespace sim {
Expand Down Expand Up @@ -29,4 +31,14 @@ ConfigNodeExpected::ConfigNodeExpected(utils::StrExpected<ConfigNode> 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
1 change: 1 addition & 0 deletions source/parser/config_reader/config_node_expected.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ class ConfigNodeExpected : public utils::StrExpected<ConfigNode> {
[[nodiscard]] ConfigNodeExpected operator[](std::string_view key) const;
};

ConfigNodeExpected safe_load_file(std::filesystem::path path) noexcept;
} // namespace sim
43 changes: 42 additions & 1 deletion 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 @@ -114,7 +117,45 @@ const std::optional<ConfigNode> 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,
const ConfigNodeWithPreset& a_parent)
: m_iterator(a_it), m_parent(a_parent) {}

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, m_parent.get_presets_node());
}

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

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

} // namespace sim
25 changes: 25 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,29 @@ class ConfigNodeWithPreset {

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

class Iterator {
public:
Iterator(ConfigNode::Iterator a_it,
const ConfigNodeWithPreset& a_parent);

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;
const ConfigNodeWithPreset& m_parent;
};

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
1 change: 1 addition & 0 deletions test/config_schema/_schemas/absolute_path.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_type: int
13 changes: 13 additions & 0 deletions test/config_schema/_schemas/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
2 changes: 2 additions & 0 deletions test/config_schema/_schemas/custom_type.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_type: int
_doc: Custom type
5 changes: 5 additions & 0 deletions test/config_schema/_schemas/nested_custom_type.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node:
Comment thread
S1mpotyaga marked this conversation as resolved.
field1:
_type: /custom_type.schema
field2:
_type: int
1 change: 1 addition & 0 deletions test/config_schema/_schemas/root_is_type.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_type: bool
4 changes: 4 additions & 0 deletions test/config_schema/_schemas/test_paths/test_paths.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
absolute_path:
_type: /absolute_path.schema
relative_path:
_type: test_relative_path/relative_path.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_type: bool
Loading
Loading