Skip to content

Commit cab2775

Browse files
authored
Support decoding schema templates from JSON (#463)
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 27f16e9 commit cab2775

File tree

6 files changed

+160
-23
lines changed

6 files changed

+160
-23
lines changed

src/compiler/compile_json.cc

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,95 @@
11
#include <sourcemeta/blaze/compiler.h>
22

3+
#include <cassert> // assert
34
#include <variant> // std::visit
45

5-
namespace sourcemeta::blaze {
6+
namespace {
7+
auto value_from_json(const sourcemeta::core::JSON &wrapper)
8+
-> sourcemeta::blaze::Value {
9+
assert(wrapper.is_object());
10+
assert(wrapper.defines("t"));
11+
assert(wrapper.defines("v"));
12+
const auto &type{wrapper.at("t")};
13+
const auto &value{wrapper.at("v")};
14+
assert(type.is_integer() && type.is_positive());
15+
using namespace sourcemeta::blaze;
16+
switch (type.to_integer()) {
17+
// clang-format off
18+
case 0: return sourcemeta::core::from_json<ValueNone>(value);
19+
case 1: return sourcemeta::core::from_json<ValueJSON>(value);
20+
case 2: return sourcemeta::core::from_json<ValueSet>(value);
21+
case 3: return sourcemeta::core::from_json<ValueString>(value);
22+
case 4: return sourcemeta::core::from_json<ValueProperty>(value);
23+
case 5: return sourcemeta::core::from_json<ValueStrings>(value);
24+
case 6: return sourcemeta::core::from_json<ValueStringSet>(value);
25+
case 7: return sourcemeta::core::from_json<ValueTypes>(value);
26+
case 8: return sourcemeta::core::from_json<ValueType>(value);
27+
case 9: return sourcemeta::core::from_json<ValueRegex>(value);
28+
case 10: return sourcemeta::core::from_json<ValueUnsignedInteger>(value);
29+
case 11: return sourcemeta::core::from_json<ValueRange>(value);
30+
case 12: return sourcemeta::core::from_json<ValueBoolean>(value);
31+
case 13: return sourcemeta::core::from_json<ValueNamedIndexes>(value);
32+
case 14: return sourcemeta::core::from_json<ValueStringType>(value);
33+
case 15: return sourcemeta::core::from_json<ValueStringMap>(value);
34+
case 16: return sourcemeta::core::from_json<ValuePropertyFilter>(value);
35+
case 17: return sourcemeta::core::from_json<ValueIndexPair>(value);
36+
case 18: return sourcemeta::core::from_json<ValuePointer>(value);
37+
case 19: return sourcemeta::core::from_json<ValueTypedProperties>(value);
38+
case 20: return sourcemeta::core::from_json<ValueStringHashes>(value);
39+
case 21: return sourcemeta::core::from_json<ValueTypedHashes>(value);
40+
// clang-format on
41+
default:
42+
assert(false);
43+
return ValueNone{};
44+
}
45+
}
46+
47+
auto instructions_from_json(const sourcemeta::core::JSON &instructions)
48+
-> sourcemeta::blaze::Instructions {
49+
assert(instructions.is_array());
50+
sourcemeta::blaze::Instructions result;
51+
result.reserve(instructions.size());
52+
for (const auto &instruction : instructions.as_array()) {
53+
assert(instruction.is_object());
54+
assert(instruction.defines("t"));
55+
assert(instruction.defines("s"));
56+
assert(instruction.defines("i"));
57+
assert(instruction.defines("k"));
58+
assert(instruction.defines("r"));
59+
assert(instruction.defines("v"));
60+
assert(instruction.defines("c"));
61+
const auto &type{instruction.at("t")};
62+
const auto &relative_schema_location{instruction.at("s")};
63+
const auto &relative_instance_location{instruction.at("i")};
64+
const auto &keyword_location{instruction.at("k")};
65+
const auto &schema_resource{instruction.at("r")};
66+
const auto &value{instruction.at("v")};
67+
const auto &children{instruction.at("c")};
68+
assert(type.is_integer() && type.is_positive());
69+
assert(relative_schema_location.is_string());
70+
assert(relative_instance_location.is_string());
71+
assert(keyword_location.is_string());
72+
assert(schema_resource.is_integer() && schema_resource.is_positive());
73+
assert(value.is_object());
74+
assert(children.is_array());
675

7-
auto to_json(const Instruction &instruction) -> sourcemeta::core::JSON {
76+
// TODO: Maybe we should emplace here?
77+
result.push_back(
78+
{sourcemeta::core::from_json<sourcemeta::blaze::InstructionIndex>(type),
79+
sourcemeta::core::from_json<sourcemeta::core::Pointer>(
80+
relative_schema_location),
81+
sourcemeta::core::from_json<sourcemeta::core::Pointer>(
82+
relative_instance_location),
83+
sourcemeta::core::from_json<std::string>(keyword_location),
84+
sourcemeta::core::from_json<std::size_t>(schema_resource),
85+
value_from_json(value), instructions_from_json(children)});
86+
}
87+
88+
return result;
89+
}
90+
91+
auto to_json(const sourcemeta::blaze::Instruction &instruction)
92+
-> sourcemeta::core::JSON {
893
auto result{sourcemeta::core::JSON::make_object()};
994
// We use single characters to save space, as this serialised format
1095
// is not meant to be human-readable anyway
@@ -38,6 +123,9 @@ auto to_json(const Instruction &instruction) -> sourcemeta::core::JSON {
38123
}));
39124
return result;
40125
}
126+
} // namespace
127+
128+
namespace sourcemeta::blaze {
41129

42130
auto to_json(const Template &schema_template) -> sourcemeta::core::JSON {
43131
auto result{sourcemeta::core::JSON::make_object()};
@@ -46,9 +134,24 @@ auto to_json(const Template &schema_template) -> sourcemeta::core::JSON {
46134
result.assign("instructions",
47135
sourcemeta::core::to_json(schema_template.instructions,
48136
[](const auto &instruction) {
49-
return to_json(instruction);
137+
return ::to_json(instruction);
50138
}));
51139
return result;
52140
}
53141

142+
auto from_json(const sourcemeta::core::JSON &json) -> Template {
143+
assert(json.is_object());
144+
assert(json.defines("instructions"));
145+
assert(json.defines("dynamic"));
146+
assert(json.defines("track"));
147+
const auto &instructions{json.at("instructions")};
148+
const auto &dynamic{json.at("dynamic")};
149+
const auto &track{json.at("track")};
150+
assert(instructions.is_array());
151+
assert(dynamic.is_boolean());
152+
assert(track.is_boolean());
153+
return {instructions_from_json(instructions), dynamic.to_boolean(),
154+
track.to_boolean()};
155+
}
156+
54157
} // namespace sourcemeta::blaze

src/compiler/include/sourcemeta/blaze/compiler.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,17 @@ compile(const Context &context, const SchemaContext &schema_context,
185185
sourcemeta::core::empty_pointer,
186186
const std::optional<std::string> &uri = std::nullopt) -> Instructions;
187187

188-
/// @ingroup compiler
189-
/// Serialise an instruction as JSON
190-
auto SOURCEMETA_BLAZE_COMPILER_EXPORT to_json(const Instruction &instruction)
191-
-> sourcemeta::core::JSON;
192-
193188
/// @ingroup compiler
194189
/// Serialise a template as JSON
195190
auto SOURCEMETA_BLAZE_COMPILER_EXPORT to_json(const Template &schema_template)
196191
-> sourcemeta::core::JSON;
197192

193+
/// @ingroup compiler
194+
/// Parse a template from JSON. Note that this function assumes that the JSON
195+
/// document represents a valid template and minimal error checking is performed
196+
auto SOURCEMETA_BLAZE_COMPILER_EXPORT
197+
from_json(const sourcemeta::core::JSON &json) -> Template;
198+
198199
} // namespace sourcemeta::blaze
199200

200201
#endif

src/evaluator/include/sourcemeta/blaze/evaluator_string_set.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ class SOURCEMETA_BLAZE_EVALUATOR_EXPORT StringSet {
5151
});
5252
}
5353

54+
static auto from_json(const sourcemeta::core::JSON &value) -> StringSet {
55+
assert(value.is_array());
56+
StringSet result;
57+
for (const auto &item : value.as_array()) {
58+
assert(item.is_string());
59+
result.insert(
60+
sourcemeta::core::from_json<sourcemeta::core::JSON::String>(item));
61+
}
62+
63+
return result;
64+
}
65+
5466
private:
5567
// Exporting symbols that depends on the standard C++ library is considered
5668
// safe.

src/evaluator/include/sourcemeta/blaze/evaluator_value.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ struct ValueNone {
2323
auto to_json() const -> sourcemeta::core::JSON {
2424
return sourcemeta::core::JSON{nullptr};
2525
}
26+
27+
static auto from_json(const sourcemeta::core::JSON &) -> ValueNone {
28+
return {};
29+
}
2630
};
2731

2832
/// @ingroup evaluator
@@ -75,6 +79,14 @@ struct ValueRegex {
7579
auto to_json() const -> sourcemeta::core::JSON {
7680
return sourcemeta::core::to_json(this->second);
7781
}
82+
83+
static auto from_json(const sourcemeta::core::JSON &value) -> ValueRegex {
84+
assert(value.is_string());
85+
auto string{value.to_string()};
86+
auto regex{sourcemeta::core::to_regex(string)};
87+
assert(regex.has_value());
88+
return {std::move(regex).value(), std::move(string)};
89+
}
7890
};
7991

8092
/// @ingroup evaluator

test/compiler/compiler_json_test.cc

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
#include <sourcemeta/blaze/compiler.h>
44

5-
// TODO: Eventually test parsing the JSON back and asserting that it equals the
6-
// original template, using a helper macro to define each test
5+
#define EXPECT_BIDIRECTIONAL_JSON(schema_template, expected_json) \
6+
{ \
7+
const auto result{sourcemeta::blaze::to_json(schema_template)}; \
8+
EXPECT_EQ(result, (expected)); \
9+
EXPECT_EQ( \
10+
sourcemeta::blaze::to_json(sourcemeta::blaze::from_json(result)), \
11+
(expected)); \
12+
}
713

814
TEST(Compiler_JSON, example_1) {
915
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
@@ -16,8 +22,6 @@ TEST(Compiler_JSON, example_1) {
1622
sourcemeta::core::schema_official_resolver,
1723
sourcemeta::blaze::default_schema_compiler)};
1824

19-
const auto result{sourcemeta::blaze::to_json(schema_template)};
20-
2125
const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON({
2226
"dynamic": false,
2327
"track": false,
@@ -37,7 +41,7 @@ TEST(Compiler_JSON, example_1) {
3741
]
3842
})JSON")};
3943

40-
EXPECT_EQ(result, expected);
44+
EXPECT_BIDIRECTIONAL_JSON(schema_template, expected);
4145
}
4246

4347
TEST(Compiler_JSON, example_2) {
@@ -54,8 +58,6 @@ TEST(Compiler_JSON, example_2) {
5458
sourcemeta::core::schema_official_resolver,
5559
sourcemeta::blaze::default_schema_compiler)};
5660

57-
const auto result{sourcemeta::blaze::to_json(schema_template)};
58-
5961
const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON({
6062
"dynamic": false,
6163
"track": false,
@@ -100,7 +102,7 @@ TEST(Compiler_JSON, example_2) {
100102
]
101103
})JSON")};
102104

103-
EXPECT_EQ(result, expected);
105+
EXPECT_BIDIRECTIONAL_JSON(schema_template, expected);
104106
}
105107

106108
TEST(Compiler_JSON, example_3) {
@@ -114,8 +116,6 @@ TEST(Compiler_JSON, example_3) {
114116
sourcemeta::core::schema_official_resolver,
115117
sourcemeta::blaze::default_schema_compiler)};
116118

117-
const auto result{sourcemeta::blaze::to_json(schema_template)};
118-
119119
const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON({
120120
"dynamic": false,
121121
"track": false,
@@ -135,5 +135,5 @@ TEST(Compiler_JSON, example_3) {
135135
]
136136
})JSON")};
137137

138-
EXPECT_EQ(result, expected);
138+
EXPECT_BIDIRECTIONAL_JSON(schema_template, expected);
139139
}

test/evaluator/evaluator_utils.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,22 @@ inline auto FIRST_PROPERTY_IS(const sourcemeta::core::JSON &document,
4040
EXPECT_EQ(trace_pre.size(), count); \
4141
EXPECT_EQ(trace_post.size(), count);
4242

43+
#define __ASSERT_TEMPLATE_JSON_SERIALISATION(compiled_schema) \
44+
{ \
45+
const auto template_json{sourcemeta::blaze::to_json(compiled_schema)}; \
46+
EXPECT_TRUE(template_json.is_object()); \
47+
const auto template_json_back{sourcemeta::blaze::to_json( \
48+
sourcemeta::blaze::from_json(template_json))}; \
49+
EXPECT_EQ(template_json, template_json_back); \
50+
}
51+
4352
#define EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, count) \
4453
const auto compiled_schema{sourcemeta::blaze::compile( \
4554
schema, sourcemeta::core::schema_official_walker, \
4655
sourcemeta::core::schema_official_resolver, \
4756
sourcemeta::blaze::default_schema_compiler, \
4857
sourcemeta::blaze::Mode::FastValidation)}; \
49-
EXPECT_TRUE(sourcemeta::blaze::to_json(compiled_schema).is_object()); \
58+
__ASSERT_TEMPLATE_JSON_SERIALISATION(compiled_schema); \
5059
EVALUATE_WITH_TRACE(compiled_schema, instance, count) \
5160
EXPECT_TRUE(result);
5261

@@ -56,7 +65,7 @@ inline auto FIRST_PROPERTY_IS(const sourcemeta::core::JSON &document,
5665
sourcemeta::core::schema_official_resolver, \
5766
sourcemeta::blaze::default_schema_compiler, \
5867
sourcemeta::blaze::Mode::FastValidation)}; \
59-
EXPECT_TRUE(sourcemeta::blaze::to_json(compiled_schema).is_object()); \
68+
__ASSERT_TEMPLATE_JSON_SERIALISATION(compiled_schema); \
6069
EVALUATE_WITH_TRACE(compiled_schema, instance, count) \
6170
EXPECT_FALSE(result);
6271

@@ -66,7 +75,7 @@ inline auto FIRST_PROPERTY_IS(const sourcemeta::core::JSON &document,
6675
sourcemeta::core::schema_official_resolver, \
6776
sourcemeta::blaze::default_schema_compiler, \
6877
sourcemeta::blaze::Mode::Exhaustive)}; \
69-
EXPECT_TRUE(sourcemeta::blaze::to_json(compiled_schema).is_object()); \
78+
__ASSERT_TEMPLATE_JSON_SERIALISATION(compiled_schema); \
7079
EVALUATE_WITH_TRACE(compiled_schema, instance, count) \
7180
EXPECT_TRUE(result);
7281

@@ -76,7 +85,7 @@ inline auto FIRST_PROPERTY_IS(const sourcemeta::core::JSON &document,
7685
sourcemeta::core::schema_official_resolver, \
7786
sourcemeta::blaze::default_schema_compiler, \
7887
sourcemeta::blaze::Mode::Exhaustive)}; \
79-
EXPECT_TRUE(sourcemeta::blaze::to_json(compiled_schema).is_object()); \
88+
__ASSERT_TEMPLATE_JSON_SERIALISATION(compiled_schema); \
8089
EVALUATE_WITH_TRACE(compiled_schema, instance, count) \
8190
EXPECT_FALSE(result);
8291

0 commit comments

Comments
 (0)