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
4 changes: 4 additions & 0 deletions ports/javascript/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# @sourcemeta/blaze

[![NPM Version](https://img.shields.io/npm/v/@sourcemeta/blaze)](https://www.npmjs.com/package/@sourcemeta/blaze)
[![NPM Downloads](https://img.shields.io/npm/dm/%40sourcemeta%2Fblaze)](https://www.npmjs.com/package/@sourcemeta/blaze)
[![GitHub contributors](https://img.shields.io/github/contributors/sourcemeta/blaze.svg)](https://github.com/sourcemeta/blaze/graphs/contributors/)

A pure JavaScript port of the evaluator from
[Blaze](https://github.com/sourcemeta/blaze), a high-performance
C++ JSON Schema validator. Zero dependencies. Supports Draft 4,
Expand Down
21 changes: 13 additions & 8 deletions ports/javascript/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const JSON_VERSION = 1;
const DEPTH_LIMIT = 300;
const ANNOTATION_EMIT = 44;
const ANNOTATION_TO_PARENT = 45;
Expand Down Expand Up @@ -194,7 +195,7 @@ function collectAnchorNamesFromInstructions(instructions, result) {
}

function compile(template) {
const targets = template[2];
const targets = template[3];
for (let targetIndex = 0; targetIndex < targets.length; targetIndex++) {
const target = targets[targetIndex];
for (let index = 0; index < target.length; index++) {
Expand All @@ -207,14 +208,14 @@ function compile(template) {
}

const labels = new Map();
const rawLabels = template[3];
const rawLabels = template[4];
for (let index = 0; index < rawLabels.length; index++) {
const pair = rawLabels[index];
labels.set(pair[0], pair[1]);
}

const anchors = new Map();
if (template[0]) {
if (template[1]) {
const anchorNames = new Set();
collectAnchorNames(targets, anchorNames);
const resourceCount = targets.length;
Expand Down Expand Up @@ -333,8 +334,8 @@ function compileInstructionToCode(instruction, captures, visited, budget) {
}

function generateNativeValidator(template) {
if (template[0] || template[1]) return null;
const targets = template[2];
if (template[1] || template[2]) return null;
const targets = template[3];
if (targets.length === 0) return () => true;
const instructions = targets[0];
if (instructions.length === 0) return () => true;
Expand Down Expand Up @@ -367,6 +368,10 @@ function generateNativeValidator(template) {

class Blaze {
constructor(template) {
if (!Array.isArray(template) || template[0] !== JSON_VERSION) {
throw new Error(
`Only version ${JSON_VERSION} of the compiled template is supported by this version of the evaluator`);
}
compile(template);
this.template = template;
this.callbackMode = false;
Expand All @@ -391,11 +396,11 @@ class Blaze {
}

const template = this.template;
const targets = template[2];
const targets = template[3];
if (targets.length === 0) return true;

const track = template[1];
const dynamic = template[0];
const track = template[2];
const dynamic = template[1];
this.trackMode = track;
this.dynamicMode = dynamic;
this.callbackMode = callback !== undefined;
Expand Down
15 changes: 15 additions & 0 deletions ports/javascript/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,18 @@ for (const [subdirectory, blacklist] of Object.entries(BLACKLISTS)) {
const defaultDialect = DIALECTS[draftKey];
registerTests(subdirectory, defaultDialect, blacklist);
}

describe('version', () => {
it('rejects a template with an unsupported version', () => {
const template = [2, false, false, [[]], []];
assert.throws(() => new Blaze(template), {
message: 'Only version 1 of the compiled template is supported by this version of the evaluator'
});
});

it('rejects a template that is not an array', () => {
assert.throws(() => new Blaze({}), {
message: 'Only version 1 of the compiled template is supported by this version of the evaluator'
});
});
});
2 changes: 2 additions & 0 deletions src/compiler/compile_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ auto to_json(const Template &schema_template) -> sourcemeta::core::JSON {
// Note that we purposely avoid objects to help consumers avoid potentially
// expensive hash-map or flat-map lookups when parsing back
auto result{sourcemeta::core::JSON::make_array()};
result.push_back(sourcemeta::core::JSON{
static_cast<std::int64_t>(sourcemeta::blaze::JSON_VERSION)});
result.push_back(sourcemeta::core::JSON{schema_template.dynamic});
result.push_back(sourcemeta::core::JSON{schema_template.track});

Expand Down
16 changes: 11 additions & 5 deletions src/evaluator/evaluator_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -119,18 +119,24 @@ auto instructions_from_json(
namespace sourcemeta::blaze {

auto from_json(const sourcemeta::core::JSON &json) -> std::optional<Template> {
if (!json.is_array() || json.array_size() != 4) {
if (!json.is_array() || json.array_size() != 5) {
return std::nullopt;
}

const auto &dynamic{json.at(0)};
const auto &track{json.at(1)};
const auto &version{json.at(0)};
if (!version.is_integer() ||
version.to_integer() != static_cast<std::int64_t>(JSON_VERSION)) {
return std::nullopt;
}

const auto &dynamic{json.at(1)};
const auto &track{json.at(2)};

if (!dynamic.is_boolean() || !track.is_boolean()) {
return std::nullopt;
}

const auto &targets{json.at(2)};
const auto &targets{json.at(3)};
if (!targets.is_array()) {
return std::nullopt;
}
Expand All @@ -146,7 +152,7 @@ auto from_json(const sourcemeta::core::JSON &json) -> std::optional<Template> {
targets_result.push_back(std::move(target_result).value());
}

const auto &labels{json.at(3)};
const auto &labels{json.at(4)};
if (!labels.is_array()) {
return std::nullopt;
}
Expand Down
3 changes: 3 additions & 0 deletions src/evaluator/include/sourcemeta/blaze/evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ struct Template {
std::vector<InstructionExtra> extra;
};

/// @ingroup evaluator
constexpr std::size_t JSON_VERSION{1};

/// @ingroup evaluator
/// Parse a template from JSON
auto SOURCEMETA_BLAZE_EVALUATOR_EXPORT
Expand Down
34 changes: 32 additions & 2 deletions test/compiler/compiler_json_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ TEST(Compiler_JSON, example_1) {
sourcemeta::blaze::default_schema_compiler)};

const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([
1,
false,
false,
[
Expand Down Expand Up @@ -66,6 +67,7 @@ TEST(Compiler_JSON, example_2) {
sourcemeta::blaze::default_schema_compiler)};

const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([
1,
false,
false,
[
Expand Down Expand Up @@ -116,6 +118,7 @@ TEST(Compiler_JSON, example_3) {
sourcemeta::blaze::default_schema_compiler)};

const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([
1,
false,
false,
[
Expand Down Expand Up @@ -153,6 +156,7 @@ TEST(Compiler_JSON, example_4) {
sourcemeta::blaze::default_schema_compiler)};

const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([
1,
false,
false,
[
Expand Down Expand Up @@ -229,6 +233,7 @@ TEST(Compiler_JSON, example_5) {
sourcemeta::blaze::Mode::Exhaustive)};

const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([
1,
false,
true,
[
Expand Down Expand Up @@ -273,6 +278,7 @@ TEST(Compiler_JSON, example_6) {
sourcemeta::blaze::Mode::Exhaustive)};

const sourcemeta::core::JSON expected{sourcemeta::core::parse_json(R"JSON([
1,
false,
true,
[
Expand Down Expand Up @@ -400,6 +406,30 @@ TEST(Compiler_JSON, invalid_1) {
EXPECT_FALSE(result.has_value());
}

TEST(Compiler_JSON, invalid_version) {
const auto input{sourcemeta::core::parse_json(R"JSON([
2,
false,
false,
[
[
[
11,
[ "type" ],
[],
"#/type",
0,
[ 8, 4 ]
]
]
],
[]
])JSON")};

const auto result{sourcemeta::blaze::from_json(input)};
EXPECT_FALSE(result.has_value());
}

TEST(Compiler_JSON, unreachable_refs_are_pruned) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
Expand Down Expand Up @@ -427,8 +457,8 @@ TEST(Compiler_JSON, unreachable_refs_are_pruned) {
const auto json_output{sourcemeta::blaze::to_json(schema_template)};

EXPECT_TRUE(json_output.is_array());
EXPECT_EQ(json_output.size(), 4);
const auto &targets{json_output.at(2)};
EXPECT_EQ(json_output.size(), 5);
const auto &targets{json_output.at(3)};

// NOTE: The targets must have exactly 1 entry (the root)
// as all definitions are unreachable from the root
Expand Down
Loading