Skip to content

Commit

Permalink
Support metaschema validation in validate and test (#22)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Jun 1, 2024
1 parent e00c793 commit dec45be
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 10 deletions.
17 changes: 15 additions & 2 deletions src/command_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
#include "command.h"
#include "utils.h"

// TODO: Add a flag to first validate schema against its metaschema
auto intelligence::jsonschema::cli::test(
const std::span<const std::string> &arguments) -> int {
const auto options{parse_options(arguments, {"h", "http"})};
const auto options{
parse_options(arguments, {"h", "http", "m", "metaschema"})};
bool result{true};
const auto test_resolver{
resolver(options, options.contains("h") || options.contains("http"))};
Expand Down Expand Up @@ -41,6 +41,19 @@ auto intelligence::jsonschema::cli::test(
return EXIT_FAILURE;
}

if (options.contains("m") || options.contains("metaschema")) {
const auto metaschema_result{
validate_against_metaschema(schema.value(), test_resolver)};
if (metaschema_result) {
log_verbose(options)
<< "The schema is valid with respect to its metaschema\n";
;
} else {
std::cerr << "The schema is NOT valid with respect to its metaschema\n";
return EXIT_FAILURE;
}
}

const auto schema_template{sourcemeta::jsontoolkit::compile(
schema.value(), sourcemeta::jsontoolkit::default_schema_walker,
test_resolver, sourcemeta::jsontoolkit::default_schema_compiler)};
Expand Down
24 changes: 20 additions & 4 deletions src/command_validate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,37 @@
#include "command.h"
#include "utils.h"

// TODO: Add a flag to first validate schema against its metaschema
// TODO: Add a flag to emit output using the standard JSON Schema output format
auto intelligence::jsonschema::cli::validate(
const std::span<const std::string> &arguments) -> int {
const auto options{parse_options(arguments, {"h", "http"})};
const auto options{
parse_options(arguments, {"h", "http", "m", "metaschema"})};
CLI_ENSURE(options.at("").size() >= 2,
"You must pass a schema followed by an instance")
const auto &schema_path{options.at("").at(0)};
const auto &instance_path{options.at("").at(1)};
const auto custom_resolver{
resolver(options, options.contains("h") || options.contains("http"))};

const auto schema{sourcemeta::jsontoolkit::from_file(schema_path)};

// TODO: If not instance is passed, just validate the schema against its
// metaschema?
if (options.contains("m") || options.contains("metaschema")) {
const auto metaschema_result{
validate_against_metaschema(schema, custom_resolver)};
if (metaschema_result) {
log_verbose(options)
<< "The schema is valid with respect to its metaschema\n";
;
} else {
std::cerr << "The schema is NOT valid with respect to its metaschema\n";
return EXIT_FAILURE;
}
}

const auto schema_template{sourcemeta::jsontoolkit::compile(
schema, sourcemeta::jsontoolkit::default_schema_walker,
resolver(options, options.contains("h") || options.contains("http")),
schema, sourcemeta::jsontoolkit::default_schema_walker, custom_resolver,
sourcemeta::jsontoolkit::default_schema_compiler)};

const auto instance{sourcemeta::jsontoolkit::from_file(instance_path)};
Expand Down
11 changes: 7 additions & 4 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,23 @@ Global Options:
Commands:
validate <schema.json> <instance.json> [--http/-h]
validate <schema.json> <instance.json> [--http/-h] [--metaschema/-m]
Validate an instance against a schema, printing error information, if
any, in a human-readable manner. The `--http/-h` option enables resolving
remote schemas over the HTTP protocol.
remote schemas over the HTTP protocol. The `--metaschema/-m` option
checks that the given schema is valid with respects to its dialect
metaschema.
test [schema.json...] [--http/-h]
test [schema.json...] [--http/-h] [--metaschema/-m]
A schema test runner inspired by the official JSON Schema test suite.
Passing directories as input will run every `.json` file in such
directory (recursively) as a test. If no argument is passed, run every
`.json` file in the current working directory (recursively) as a test.
The `--http/-h` option enables resolving remote schemas over the HTTP
protocol.
protocol. The `--metaschema/-m` option checks that the given schema is
valid with respects to its dialect metaschema.
fmt [schema.json...] [--check/-c]
Expand Down
27 changes: 27 additions & 0 deletions src/utils.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include <sourcemeta/hydra/httpclient.h>
#include <sourcemeta/jsontoolkit/json.h>
#include <sourcemeta/jsontoolkit/jsonschema.h>
#include <sourcemeta/jsontoolkit/uri.h>

#include "utils.h"
Expand Down Expand Up @@ -204,4 +206,29 @@ auto log_verbose(const std::map<std::string, std::vector<std::string>> &options)
return null_stream;
}

auto validate_against_metaschema(
const sourcemeta::jsontoolkit::JSON &schema,
const sourcemeta::jsontoolkit::SchemaResolver &resolver) -> bool {
const std::optional<std::string> dialect{
sourcemeta::jsontoolkit::dialect(schema)};
if (!dialect.has_value()) {
throw std::runtime_error(
"Cannot determine the dialect of the input schema");
}

const auto metaschema{resolver(dialect.value()).get()};
if (!metaschema.has_value()) {
throw sourcemeta::jsontoolkit::SchemaResolutionError(
dialect.value(), "Could not resolve metaschema");
}

const auto metaschema_template{sourcemeta::jsontoolkit::compile(
metaschema.value(), sourcemeta::jsontoolkit::default_schema_walker,
resolver, sourcemeta::jsontoolkit::default_schema_compiler)};
return sourcemeta::jsontoolkit::evaluate(
metaschema_template, schema,
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback);
}

} // namespace intelligence::jsonschema::cli
4 changes: 4 additions & 0 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ auto resolver(const std::map<std::string, std::vector<std::string>> &options,
auto log_verbose(const std::map<std::string, std::vector<std::string>> &options)
-> std::ostream &;

auto validate_against_metaschema(
const sourcemeta::jsontoolkit::JSON &schema,
const sourcemeta::jsontoolkit::SchemaResolver &resolver) -> bool;

} // namespace intelligence::jsonschema::cli

#endif
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ add_jsonschema_test_unix(validate_pass)
add_jsonschema_test_unix(validate_fail)
add_jsonschema_test_unix(validate_fail_remote_no_http)
add_jsonschema_test_unix(validate_non_supported)
add_jsonschema_test_unix(validate_pass_with_metaschema)
add_jsonschema_test_unix(bundle_non_remote)
add_jsonschema_test_unix(bundle_remote_single_schema)
add_jsonschema_test_unix(bundle_remote_no_http)
Expand Down
32 changes: 32 additions & 0 deletions test/validate_fail_at_metaschema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << 'EOF' > "$TMP/schema.json"
{
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"foo": true,
"bar": 1
}
}
EOF

cat << 'EOF' > "$TMP/instance.json"
{ "foo": "bar" }
EOF

"$1" validate "$TMP/schema.json" "$TMP/instance.json" --metaschema && CODE="$?" || CODE="$?"

if [ "$CODE" = "0" ]
then
echo "FAIL" 1>&2
exit 1
else
echo "PASS" 1>&2
fi
25 changes: 25 additions & 0 deletions test/validate_pass_with_metaschema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/sh

set -o errexit
set -o nounset

TMP="$(mktemp -d)"
clean() { rm -rf "$TMP"; }
trap clean EXIT

cat << 'EOF' > "$TMP/schema.json"
{
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"foo": {
"type": "string"
}
}
}
EOF

cat << 'EOF' > "$TMP/instance.json"
{ "foo": "bar" }
EOF

"$1" validate "$TMP/schema.json" "$TMP/instance.json" --metaschema

0 comments on commit dec45be

Please sign in to comment.