Skip to content

Commit dd94261

Browse files
added possibility to include spec files
1 parent d7d22e3 commit dd94261

File tree

5 files changed

+171
-5
lines changed

5 files changed

+171
-5
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,23 @@ Optional parameters:
150150

151151
The entry must be a boolean value.
152152

153+
### "type": "include"
154+
155+
Mandatory parameter:
156+
* spec_file: ["spec.json"]
157+
158+
The entry must be valid with respect to the json spec provided in spec_file. spec_file can be a global path, or relative to the paths provided in the advanced option jse.include_directories
159+
153160
## Defaults
154161

155162
Every rule associated with an optional parameter of an object must specify a default value, using the default field.
156163

157164
# Advanced options
158165
```cpp
159-
jse.strict // DEFAULT: false - if strict == false, a json is valid even if it has entries not validated by a rule
160-
jse.skip_file_check // DEFAULT: true - disable checking for existance of file and folders
161-
jse.boxing_primitive // DEFAULT: true - always try to convert any type t to a list of t for the purpose of finding a valid rule
166+
jse.strict // DEFAULT: false - if strict == false, a json is valid even if it has entries not validated by a rule
167+
jse.skip_file_check // DEFAULT: true - disable checking for existance of file and folders
168+
jse.boxing_primitive // DEFAULT: true - always try to convert any type t to a list of t for the purpose of finding a valid rule
169+
jse.include_directories // DEFAULT: empty - list of strings containing additional paths where json spec are possibly located
162170
```
163171

164172
## Authors

data/rules_02.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"pointer": "/",
4+
"type": "object",
5+
"required": ["field1"]
6+
},
7+
{
8+
"pointer": "/field1",
9+
"type": "float",
10+
"min": 45
11+
}
12+
]

src/jse/jse.cpp

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <filesystem> // C++17
55
#include <sstream>
66
#include <algorithm>
7+
#include <fstream>
78
////////////////////////////////////////////////////////////////////////////////
89

910
namespace jse
@@ -46,19 +47,111 @@ namespace jse
4647

4748
namespace
4849
{
50+
// adds key after the provided pointer, correctly handling the json special case that root has an ending /
4951
std::string append_pointer(const std::string &pointer, const std::string &key)
5052
{
5153
if (pointer == "/")
5254
return "/" + key;
5355
else
5456
return pointer + "/" + key;
5557
}
58+
59+
// adds key before the provided pointer, correctly handling the json special case that root has an ending /
60+
std::string prepend_pointer(const std::string &pointer, const std::string &key)
61+
{
62+
if (key == "/")
63+
return pointer;
64+
else
65+
return key + "/" + pointer;
66+
}
67+
5668
} // namespace
5769

70+
71+
// enriches a given json spec with included json specs
72+
json JSE::inject_include(const json &rules)
73+
{
74+
std::vector<string> dirs = include_directories;
75+
dirs.push_back(""); // adding default path
76+
77+
// max 10 levels of nesting to avoid infinite loops
78+
json current = rules;
79+
80+
for (size_t x = 0; x < 10; x++)
81+
{
82+
// check if the rules have any include
83+
bool include_present = false;
84+
for (auto rule : current)
85+
if (rule.at("type") == "include")
86+
include_present = true;
87+
88+
// if there are no includes, return the current ones
89+
if (!include_present)
90+
return current;
91+
92+
json enriched;
93+
// otherwise, do a round of replacement
94+
for (auto rule : current)
95+
{
96+
// copy all rules that are not include
97+
if (rule.at("type") != "include")
98+
{
99+
enriched.push_back(rule);
100+
}
101+
// if the rule is an include, expand the node with a copy of the included file
102+
else
103+
{
104+
bool replaced = false;
105+
// the include file could be in any of the include directories
106+
for (auto dir : dirs)
107+
{
108+
string spec_file = rule.at("spec_file");
109+
string f = dir + spec_file;
110+
// check if the file exists
111+
if (std::filesystem::is_regular_file(f))
112+
{
113+
std::ifstream ifs(f);
114+
json include_rules = json::parse(ifs);
115+
116+
// loop over all rules to add the prefix
117+
for (auto i_rule : include_rules)
118+
{
119+
string prefix = rule.at("pointer");
120+
string pointer = i_rule.at("pointer");
121+
i_rule.at("pointer") = prepend_pointer(pointer,prefix);
122+
}
123+
124+
// save modified rules
125+
for (auto i_rule : include_rules)
126+
enriched.push_back(i_rule);
127+
128+
// one substitution is enough, give up the search over include dirs
129+
replaced = true;
130+
break;
131+
}
132+
133+
}
134+
135+
if (!replaced)
136+
{
137+
string pointer = rule.at("pointer");
138+
throw("Failed to replace the include rule: " + pointer);
139+
assert(replaced == true);
140+
}
141+
}
142+
}
143+
144+
// now that we replaced the include, copy it back to current
145+
current = enriched;
146+
}
147+
148+
throw("Reached maximal 10 levels of include recursion.");
149+
150+
}
151+
152+
58153
bool JSE::verify_json(const string &pointer, json &input, const json &rules)
59154
{
60-
// if (pointer == "/common")
61-
// std::cout << "gotcha" << std::endl;
62155
// Find all rules that apply for the input node
63156
// TODO: accelerate this
64157
std::vector<json> matching_rules = collect_pointer(pointer, rules);
@@ -216,6 +309,8 @@ namespace jse
216309
return verify_rule_object(input, rule);
217310
else if (type == "bool")
218311
return verify_rule_bool(input, rule);
312+
else if (type == "include")
313+
return verify_rule_include(input, rule);
219314
else
220315
{
221316
log.push_back(log_item("error", "Unknown rule type " + type));
@@ -362,6 +457,14 @@ namespace jse
362457
return true;
363458
}
364459

460+
bool JSE::verify_rule_include(const json &input, const json &rule)
461+
{
462+
assert(rule.at("type") == "include");
463+
464+
// An include rule always fails, they should be processed first by the inject_include function
465+
return false;
466+
}
467+
365468
json JSE::collect_default_rules(const json &rules)
366469
{
367470
// Find all rules that apply for the input node

src/jse/jse.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ namespace jse
1818
// enriches a given json with default values
1919
json inject_defaults(const json &input, const json &rules);
2020

21+
// enriches a given json spec with included json specs
22+
json inject_include(const json &rules);
23+
2124
// log to string
2225
std::string log2str();
2326

@@ -26,7 +29,13 @@ namespace jse
2629

2730
// if strict == false, a json is valid even if it has entries not validated by a rule
2831
bool strict = false;
32+
33+
// do not check the existance of the file pointed in file nodes
2934
bool skip_file_check = true;
35+
36+
// additional directories which can be used for relative paths
37+
std::vector<string> include_directories;
38+
3039
// automatic boxing for primitive types
3140
// if all rules fail for a basic type, try boxing it once and try again
3241
bool boxing_primitive = true;
@@ -51,6 +60,7 @@ namespace jse
5160
bool verify_rule_object(const json &input, const json &rule);
5261
bool verify_rule_bool(const json &input, const json &rule);
5362
bool verify_rule_list(const json &input, const json &rule);
63+
bool verify_rule_include(const json &input, const json &rule);
5464

5565
// Collect all rules having a default
5666
json collect_default_rules(const json &rules);

tests/test_validator.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,39 @@ TEST_CASE("type_object", "[validator]")
219219
REQUIRE(!jse.verify_json(input, rules));
220220
}
221221

222+
TEST_CASE("include_rule", "[validator]")
223+
{
224+
json rules = R"(
225+
[
226+
{
227+
"pointer": "/",
228+
"type": "include",
229+
"spec_file": "rules_02.json"
230+
}
231+
]
232+
)"_json;
233+
// ,
234+
// {
235+
// "pointer": "/object1",
236+
// "type": "include",
237+
// "spec_file": "rules_02.json"
238+
// }
239+
240+
241+
242+
243+
JSE jse;
244+
jse.include_directories.push_back(root_path);
245+
json new_rules = jse.inject_include(rules);
246+
247+
std::ifstream ifs2(root_path + "/rules_01.json");
248+
json matching = json::parse(ifs2);
249+
250+
INFO(jse.log2str());
251+
REQUIRE(new_rules == matching);
252+
}
253+
254+
222255
TEST_CASE("file_01", "[validator]")
223256
{
224257
std::ifstream ifs1(root_path + "/input_01.json");

0 commit comments

Comments
 (0)