1+ #include " schema_server.hpp"
2+
3+ #include < spdlog/fmt/fmt.h>
4+
5+ #include < regex>
6+ #include < unordered_set>
7+
8+ namespace sim {
9+
10+ SchemaServer::SchemaServer (const std::filesystem::path& a_schemas_dir)
11+ : m_schemas_dir(a_schemas_dir) {}
12+
13+ bool SchemaServer::is_meta_field (const std::string& field) {
14+ return field.starts_with (' _' );
15+ }
16+
17+ void SchemaServer::validate_untyped (const ConfigSchema& schema_node,
18+ const ConfigNodeWithPreset& config_node) {
19+ // create set of requried fields
20+ std::unordered_set<std::string> schema_fields;
21+ for (const auto & it : schema_node) {
22+ const std::string& field = it.get_name_or_throw ();
23+ if (!is_meta_field (field)) {
24+ schema_fields.insert (field);
25+ }
26+ }
27+
28+ // check if config node has unknown fields
29+ for (const auto & subnode : config_node) {
30+ const std::string& field = subnode.get_name_or_throw ();
31+ if (!schema_fields.contains (field)) {
32+ std::stringstream ss;
33+ ss << " Node has field `'" << field
34+ << " ' that does not described in schema:\n " ;
35+ ss << schema_node;
36+ throw subnode.create_parsing_error (ss.str ());
37+ }
38+ }
39+
40+ // check if config node has all required fields
41+ for (const auto & required_field : schema_fields) {
42+ ConfigNodeWithPresetExpected exp_field_config =
43+ config_node[required_field];
44+ if (!exp_field_config.has_value ()) {
45+ std::stringstream ss;
46+ ss << " Missing required field '" << required_field
47+ << " ' described in schema: \n " ;
48+ ss << schema_node << " \n " ;
49+ throw config_node.create_parsing_error (ss.str ());
50+ }
51+ }
52+
53+ for (const auto & it : schema_node) {
54+ std::string field = it.get_name ().value ();
55+ if (!is_meta_field (field)) {
56+ validate (schema_node[field].value (), config_node[field].value ());
57+ }
58+ }
59+ }
60+
61+ [[nodiscard]] bool SchemaServer::try_validate_basic_types (
62+ const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) {
63+ const ConfigSchema type_node = schema_node[" _type" ].value ();
64+ std::string type = type_node.as_or_throw <std::string>();
65+ auto unsafe_cast_config_node_to = [&]<typename T>() -> T {
66+ auto as_result = config_node.as <T>();
67+ if (!as_result.has_value ()) {
68+ std::stringstream ss;
69+ ss << " Node should contain basic type `" << type
70+ << " ' due to schema:\n " ;
71+ ss << type_node << " \n " ;
72+ ss << " But its not:\n " ;
73+ ss << as_result.error () << ' \n ' ;
74+ throw config_node.create_parsing_error (ss.str ());
75+ }
76+ return as_result.value ();
77+ };
78+ if (type == " size_t" ) {
79+ unsafe_cast_config_node_to.operator ()<size_t >();
80+ } else if (type == " int" ) {
81+ unsafe_cast_config_node_to.operator ()<int >();
82+ } else if (type == " double" ) {
83+ unsafe_cast_config_node_to.operator ()<double >();
84+ } else if (type == " bool" ) {
85+ unsafe_cast_config_node_to.operator ()<bool >();
86+ } else if (type == " string" ) {
87+ unsafe_cast_config_node_to.operator ()<std::string>();
88+ } else if (type == " regex" ) {
89+ std::string pattern =
90+ unsafe_cast_config_node_to.operator ()<std::string>();
91+ try {
92+ std::regex r (pattern);
93+ } catch (const std::regex_error&) {
94+ std::stringstream ss;
95+ ss << " Field must contain valid regular expression.\n " ;
96+ ss << " Regex pattern: " << pattern << ' \n ' ;
97+ throw config_node.create_parsing_error (ss.str ());
98+ }
99+ } else {
100+ return false ;
101+ }
102+ return true ;
103+ }
104+
105+ [[nodiscard]] bool SchemaServer::try_validate_custom_types (
106+ const ConfigSchema& schema_node, const ConfigNodeWithPreset& config_node) {
107+ const ConfigSchema type_node = schema_node[" _type" ].value ();
108+ std::string type = type_node.as_or_throw <std::string>();
109+ if (type.ends_with (" .schema" )) {
110+ std::filesystem::path nested_schema_path = std::filesystem::path (type);
111+ std::filesystem::path sub_schema_path =
112+ nested_schema_path.is_absolute ()
113+ ? m_schemas_dir / nested_schema_path.relative_path ()
114+ : std::filesystem::path (schema_node.get_config_path ().value ())
115+ .parent_path () /
116+ nested_schema_path;
117+ validate (sub_schema_path, config_node);
118+ return true ;
119+ } else {
120+ return false ;
121+ }
122+ }
123+
124+ void SchemaServer::validate (const ConfigSchema& schema_node,
125+ const ConfigNodeWithPreset& config_node) {
126+ ConfigNodeExpected exp_type_node = schema_node[" _type" ];
127+ if (exp_type_node) {
128+ std::string type = exp_type_node.value ().as_or_throw <std::string>();
129+ if (try_validate_basic_types (schema_node, config_node)) {
130+ return ;
131+ }
132+ if (try_validate_custom_types (schema_node, config_node)) {
133+ return ;
134+ }
135+ std::stringstream ss;
136+ throw schema_node.create_parsing_error (
137+ fmt::format (" Unknown specified type '{}'" , type));
138+ } else {
139+ if (!config_node.IsMap ()) {
140+ std::stringstream ss;
141+ ss << " Should be map due to schema\n " ;
142+ ss << schema_node << ' \n ' ;
143+ throw config_node.create_parsing_error (ss.str ());
144+ }
145+ validate_untyped (schema_node, config_node);
146+ }
147+ }
148+
149+ void SchemaServer::validate (const std::filesystem::path& schema_path,
150+ const ConfigNodeWithPreset& config_node) {
151+ std::filesystem::path full_path =
152+ schema_path.is_absolute () ? schema_path : m_schemas_dir / schema_path;
153+ auto exp_sub_schema = safe_load_file (full_path);
154+ if (!exp_sub_schema) {
155+ throw config_node.create_parsing_error (
156+ fmt::format (" Failed to parse corresponding schema file: {}" ,
157+ exp_sub_schema.error ()));
158+ }
159+ validate (exp_sub_schema.value (), config_node);
160+ }
161+
162+ } // namespace sim
0 commit comments