Skip to content

Commit

Permalink
[WIP] Extend validate to take multiple instances
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti committed Jul 24, 2024
1 parent 7139df4 commit 8c19f0a
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 87 deletions.
17 changes: 13 additions & 4 deletions docs/validate.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ Validating
> Draft 2020-12 soon.
```sh
jsonschema validate <schema.json> <instance.json|.jsonl> [--http/-h]
jsonschema validate <schema.json> <instance.json|.jsonl...> [--http/-h]
[--verbose/-v] [--resolve/-r <schemas-or-directories> ...] [--benchmark/-b]
```

The most popular use case of JSON Schema is to validate JSON documents. The
JSON Schema CLI offers a `validate` command to evaluate either a JSON instance
or a JSONL dataset against a JSON Schema, presenting human-friendly information
on unsuccessful validation.
JSON Schema CLI offers a `validate` command to evaluate one or many JSON
instances or JSONL datasets against a JSON Schema, presenting human-friendly
information on unsuccessful validation.

**If you want to validate that a schema adheres to its metaschema, use the
[`metaschema`](./metaschema.markdown) command instead.**
Expand Down Expand Up @@ -55,6 +55,15 @@ error: The target document is expected to be of the given type
jsonschema validate path/to/my/schema.json path/to/my/instance.json
```

### Validate a multiple JSON instances against a schema

```sh
jsonschema validate path/to/my/schema.json \
path/to/my/instance_1.json \
path/to/my/instance_2.json \
path/to/my/instance_3.json
```

### Validate a JSONL dataset against a schema

```sh
Expand Down
165 changes: 87 additions & 78 deletions src/command_validate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,99 +47,108 @@ auto intelligence::jsonschema::cli::validate(
return EXIT_FAILURE;
}

bool result{true};
const std::filesystem::path instance_path{options.at("").at(1)};
const auto benchmark{options.contains("b") || options.contains("benchmark")};
const auto schema_template{sourcemeta::jsontoolkit::compile(
schema, sourcemeta::jsontoolkit::default_schema_walker, custom_resolver,
sourcemeta::jsontoolkit::default_schema_compiler)};

const auto benchmark{options.contains("b") || options.contains("benchmark")};
bool result{true};

auto iterator{options.at("").cbegin()};
std::advance(iterator, 1);
for (; iterator != options.at("").cend(); ++iterator) {
const std::filesystem::path instance_path{*iterator};
if (instance_path.extension() == ".jsonl") {
log_verbose(options)
<< "Interpreting input as JSONL: "
<< std::filesystem::weakly_canonical(instance_path).string() << "\n";
std::size_t index{0};

auto stream{sourcemeta::jsontoolkit::read_file(instance_path)};
try {
for (const auto &instance : sourcemeta::jsontoolkit::JSONL{stream}) {
std::ostringstream error;
bool subresult = true;
if (benchmark) {
const auto timestamp_start{
std::chrono::high_resolution_clock::now()};
subresult =
sourcemeta::jsontoolkit::evaluate(schema_template, instance);
const auto timestamp_end{std::chrono::high_resolution_clock::now()};
const auto duration_us{
std::chrono::duration_cast<std::chrono::microseconds>(
timestamp_end - timestamp_start)};
if (subresult) {
std::cout << "took: " << duration_us.count() << "us\n";
} else {
error << "error: Schema validation failure\n";
}
} else {
subresult = sourcemeta::jsontoolkit::evaluate(
schema_template, instance,
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback(
error, sourcemeta::jsontoolkit::empty_pointer));
}

if (instance_path.extension() == ".jsonl") {
log_verbose(options) << "Interpreting input as JSONL\n";
std::size_t index{0};

auto stream{sourcemeta::jsontoolkit::read_file(instance_path)};
try {
for (const auto &instance : sourcemeta::jsontoolkit::JSONL{stream}) {
std::ostringstream error;
bool subresult = true;
if (benchmark) {
const auto timestamp_start{std::chrono::high_resolution_clock::now()};
subresult =
sourcemeta::jsontoolkit::evaluate(schema_template, instance);
const auto timestamp_end{std::chrono::high_resolution_clock::now()};
const auto duration_us{
std::chrono::duration_cast<std::chrono::microseconds>(
timestamp_end - timestamp_start)};
if (subresult) {
std::cout << "took: " << duration_us.count() << "us\n";
log_verbose(options)
<< "ok: "
<< std::filesystem::weakly_canonical(instance_path).string()
<< " (entry #" << index << ")"
<< "\n matches "
<< std::filesystem::weakly_canonical(schema_path).string()
<< "\n";
} else {
error << "error: Schema validation failure\n";
std::cerr
<< "fail: "
<< std::filesystem::weakly_canonical(instance_path).string()
<< " (entry #" << index << ")\n\n";
sourcemeta::jsontoolkit::prettify(instance, std::cerr);
std::cerr << "\n\n";
std::cerr << error.str();
result = false;
break;
}
} else {
subresult = sourcemeta::jsontoolkit::evaluate(
schema_template, instance,
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback(error,
sourcemeta::jsontoolkit::empty_pointer));
}

if (subresult) {
log_verbose(options)
<< "ok: "
<< std::filesystem::weakly_canonical(instance_path).string()
<< " (entry #" << index << ")"
<< "\n matches "
<< std::filesystem::weakly_canonical(schema_path).string()
<< "\n";
index += 1;
}
} catch (const sourcemeta::jsontoolkit::ParseError &error) {
// For producing better error messages
throw sourcemeta::jsontoolkit::FileParseError(instance_path, error);
}
} else {
const auto instance{sourcemeta::jsontoolkit::from_file(instance_path)};
std::ostringstream error;
if (benchmark) {
const auto timestamp_start{std::chrono::high_resolution_clock::now()};
result = sourcemeta::jsontoolkit::evaluate(schema_template, instance);
const auto timestamp_end{std::chrono::high_resolution_clock::now()};
const auto duration_us{
std::chrono::duration_cast<std::chrono::microseconds>(
timestamp_end - timestamp_start)};
if (result) {
std::cout << "took: " << duration_us.count() << "us\n";
} else {
std::cerr << "fail: "
<< std::filesystem::weakly_canonical(instance_path).string()
<< " (entry #" << index << ")\n\n";
sourcemeta::jsontoolkit::prettify(instance, std::cerr);
std::cerr << "\n\n";
std::cerr << error.str();
result = false;
break;
error << "error: Schema validation failure\n";
}

index += 1;
} else {
result = sourcemeta::jsontoolkit::evaluate(
schema_template, instance,
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback(error,
sourcemeta::jsontoolkit::empty_pointer));
}
} catch (const sourcemeta::jsontoolkit::ParseError &error) {
// For producing better error messages
throw sourcemeta::jsontoolkit::FileParseError(instance_path, error);
}
} else {
const auto instance{sourcemeta::jsontoolkit::from_file(instance_path)};
std::ostringstream error;
if (benchmark) {
const auto timestamp_start{std::chrono::high_resolution_clock::now()};
result = sourcemeta::jsontoolkit::evaluate(schema_template, instance);
const auto timestamp_end{std::chrono::high_resolution_clock::now()};
const auto duration_us{
std::chrono::duration_cast<std::chrono::microseconds>(
timestamp_end - timestamp_start)};

if (result) {
std::cout << "took: " << duration_us.count() << "us\n";
log_verbose(options)
<< "ok: "
<< std::filesystem::weakly_canonical(instance_path).string()
<< "\n matches "
<< std::filesystem::weakly_canonical(schema_path).string() << "\n";
} else {
error << "error: Schema validation failure\n";
std::cerr << error.str();
}
} else {
result = sourcemeta::jsontoolkit::evaluate(
schema_template, instance,
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback(error,
sourcemeta::jsontoolkit::empty_pointer));
}

if (result) {
log_verbose(options)
<< "ok: " << std::filesystem::weakly_canonical(instance_path).string()
<< "\n matches "
<< std::filesystem::weakly_canonical(schema_path).string() << "\n";
} else {
std::cerr << error.str();
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ Global Options:
Commands:
validate <schema.json> <instance.json|.jsonl> [--http/-h] [--benchmark/-b]
validate <schema.json> <instance.json|.jsonl...> [--http/-h]
[--benchmark/-b]
Validate an instance against the given schema.
Validate one of more instances against the given schema.
metaschema [schemas-or-directories...] [--http/-h]
[--extension/-e <extension>]
Expand Down
2 changes: 1 addition & 1 deletion test/validate/fail_jsonl_all_verbose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ EOF
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
Interpreting input as JSONL
Interpreting input as JSONL: $(realpath "$TMP")/instance.jsonl
fail: $(realpath "$TMP")/instance.jsonl (entry #0)
{
Expand Down
2 changes: 1 addition & 1 deletion test/validate/fail_jsonl_one_verbose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ EOF
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
Interpreting input as JSONL
Interpreting input as JSONL: $(realpath "$TMP")/instance.jsonl
ok: $(realpath "$TMP")/instance.jsonl (entry #0)
matches $(realpath "$TMP")/schema.json
fail: $(realpath "$TMP")/instance.jsonl (entry #1)
Expand Down
2 changes: 1 addition & 1 deletion test/validate/pass_jsonl_verbose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ EOF
"$1" validate "$TMP/schema.json" "$TMP/instance.jsonl" --verbose 2> "$TMP/output.txt" 1>&2

cat << EOF > "$TMP/expected.txt"
Interpreting input as JSONL
Interpreting input as JSONL: $(realpath "$TMP")/instance.jsonl
ok: $(realpath "$TMP")/instance.jsonl (entry #0)
matches $(realpath "$TMP")/schema.json
ok: $(realpath "$TMP")/instance.jsonl (entry #1)
Expand Down

0 comments on commit 8c19f0a

Please sign in to comment.