Skip to content

Commit

Permalink
Replace --metaschema from lint/test with a new metaschema com…
Browse files Browse the repository at this point in the history
…mand

Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
  • Loading branch information
jviotti committed Jun 13, 2024
1 parent ea89853 commit bc8f0e5
Show file tree
Hide file tree
Showing 18 changed files with 202 additions and 194 deletions.
1 change: 1 addition & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Schema CLI without passing a command will print convenient reference
documentation:

- [Validating](./docs/validate.markdown)
- [Metaschema](./docs/metaschema.markdown) (ensure a schema is valid)
- [Testing](./docs/test.markdown)
- [Formatting](./docs/format.markdown)
- [Linting](./docs/lint.markdown)
Expand Down
75 changes: 75 additions & 0 deletions docs/metaschema.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
Formatting
==========

```sh
jsonschema metaschema [schemas-or-directories...]
[--verbose/-v] [--http/-h] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
```

Ensure that a schema or a set of schemas are considered valid with regards to
their metaschemas.

Examples
--------

For example, consider this fictitious JSON Schema that follows the Draft 4
dialect but sets the `type` property to an invalid value:

```json
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": 1
}
```

Running the `metaschema` command on it will surface an error:

```sh
$ jsonschema metaschema schema.json
error: The target document is expected to be one of the given values
at instance location "/type"
at evaluate path "/properties/type/anyOf/0/$ref/enum"
error: Mark the current position of the evaluation process for future jumps
at instance location "/type"
at evaluate path "/properties/type/anyOf/0/$ref"
error: The target document is expected to be of the given type
at instance location "/type"
at evaluate path "/properties/type/anyOf/1/type"
error: The target is expected to match at least one of the given assertions
at instance location "/type"
at evaluate path "/properties/type/anyOf"
error: The target is expected to match all of the given assertions
at instance location ""
at evaluate path "/properties"
```

### Validate the metaschema of JSON Schemas in-place

```sh
jsonschema metaschema path/to/my/schema_1.json path/to/my/schema_2.json
```

### Validate the metaschema of every `.json` file in a given directory (recursively)

```sh
jsonschema metaschema path/to/schemas/
```

### Validate the metaschema of every `.json` file in the current directory (recursively)

```sh
jsonschema metaschema
```

### Validate the metaschema of every `.json` file in a given directory while ignoring another

```sh
jsonschema metaschema path/to/schemas/ --ignore path/to/schemas/nested
```

### Validate the metaschema of every `.schema.json` file in the current directory (recursively)

```sh
jsonschema metaschema --extension .schema.json
```
14 changes: 5 additions & 9 deletions docs/test.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ Testing
```sh
jsonschema test [schemas-or-directories...]
[--http/-h] [--metaschema/-m] [--verbose/-v]
[--resolve/-r <schemas-or-directories> ...] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
[--http/-h] [--verbose/-v] [--resolve/-r <schemas-or-directories> ...]
[--extension/-e <extension>] [--ignore/-i <schemas-or-directories>]
```

Schemas are code. As such, you should run an automated unit testing suite
Expand All @@ -20,6 +19,9 @@ against them. Just like popular test frameworks like [Jest](https://jestjs.io),
schema-oriented test runner inspired by the [official JSON Schema test
suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite).

**If you want to validate that a schema adheres to its metaschema, use the
[`metaschema`](./metaschema.markdown) command instead.**

Examples
--------

Expand Down Expand Up @@ -82,12 +84,6 @@ jsonschema test --ignore dist
jsonschema test --extension .test.json
```

### Run a single test definition validating the schemas against their metaschemas

```sh
jsonschema test path/to/test.json --metaschema
```

### Run a single test definition enabling HTTP resolution

```sh
Expand Down
12 changes: 4 additions & 8 deletions docs/validate.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ Validating
> Draft 2020-12 soon.
```sh
jsonschema validate <schema.json>
[instance.json] [--http/-h] [--metaschema/-m] [--verbose/-v]
jsonschema validate <schema.json> [instance.json] [--http/-h] [--verbose/-v]
[--resolve/-r <schemas-or-directories> ...]
```

Expand All @@ -17,6 +16,9 @@ JSON Schema CLI offers a `validate` command to evaluate a JSON instance against
a JSON Schema or a JSON Schema against its meta-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.**

Examples
--------

Expand Down Expand Up @@ -59,12 +61,6 @@ jsonschema validate path/to/my/schema.json path/to/my/instance.json
jsonschema validate path/to/my/schema.json
```

### Validate a JSON instance against a schema plus the schema against its meta-schema

```sh
jsonschema validate path/to/my/schema.json path/to/my/instance.json --metaschema
```

### Validate a JSON instance enabling HTTP resolution

```sh
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_executable(jsonschema_cli
command_bundle.cc
command_test.cc
command_lint.cc
command_metaschema.cc
command_validate.cc)

noa_add_default_options(PRIVATE jsonschema_cli)
Expand Down
1 change: 1 addition & 0 deletions src/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ auto bundle(const std::span<const std::string> &arguments) -> int;
auto test(const std::span<const std::string> &arguments) -> int;
auto lint(const std::span<const std::string> &arguments) -> int;
auto validate(const std::span<const std::string> &arguments) -> int;
auto metaschema(const std::span<const std::string> &arguments) -> int;
} // namespace intelligence::jsonschema::cli

#endif
45 changes: 45 additions & 0 deletions src/command_metaschema.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <sourcemeta/jsontoolkit/json.h>
#include <sourcemeta/jsontoolkit/jsonschema.h>

#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE
#include <iostream> // std::cerr
#include <set> // std::set
#include <string> // std::string

#include "command.h"
#include "utils.h"

// TODO: Add a flag to emit output using the standard JSON Schema output format
auto intelligence::jsonschema::cli::metaschema(
const std::span<const std::string> &arguments) -> int {
const auto options{parse_options(arguments, {"h", "http"})};
const auto custom_resolver{
resolver(options, options.contains("h") || options.contains("http"))};
bool result{true};

for (const auto &entry : for_each_json(options.at(""), parse_ignore(options),
parse_extensions(options))) {
// TODO: Cache this somehow for performance reasons?
const auto metaschema_template{sourcemeta::jsontoolkit::compile(
sourcemeta::jsontoolkit::metaschema(entry.second, custom_resolver),
sourcemeta::jsontoolkit::default_schema_walker, custom_resolver,
sourcemeta::jsontoolkit::default_schema_compiler)};

std::ostringstream error;
if (sourcemeta::jsontoolkit::evaluate(
metaschema_template, entry.second,
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback(error))) {
log_verbose(options)
<< entry.first.string()
<< ": The schema is valid with respect to its metaschema\n";
} else {
std::cerr << error.str();
std::cerr << entry.first.string()
<< ": The schema is NOT valid with respect to its metaschema\n";
result = false;
}
}

return result ? EXIT_SUCCESS : EXIT_FAILURE;
}
24 changes: 1 addition & 23 deletions src/command_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@

auto intelligence::jsonschema::cli::test(
const std::span<const std::string> &arguments) -> int {
const auto options{
parse_options(arguments, {"h", "http", "m", "metaschema"})};
const auto options{parse_options(arguments, {"h", "http"})};
bool result{true};
const auto test_resolver{
resolver(options, options.contains("h") || options.contains("http"))};
Expand Down Expand Up @@ -42,27 +41,6 @@ auto intelligence::jsonschema::cli::test(
return EXIT_FAILURE;
}

if (options.contains("m") || options.contains("metaschema")) {
std::cout << " Metaschema - "
<< sourcemeta::jsontoolkit::dialect(schema.value())
.value_or("<unknown>")
<< "\n";
const auto metaschema_template{sourcemeta::jsontoolkit::compile(
sourcemeta::jsontoolkit::metaschema(schema.value(), test_resolver),
sourcemeta::jsontoolkit::default_schema_walker, test_resolver,
sourcemeta::jsontoolkit::default_schema_compiler)};
std::ostringstream error;
if (sourcemeta::jsontoolkit::evaluate(
metaschema_template, schema.value(),
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback(error))) {
std::cout << " PASS\n";
} else {
std::cout << " FAIL\n";
result = false;
}
}

const auto schema_template{sourcemeta::jsontoolkit::compile(
schema.value(), sourcemeta::jsontoolkit::default_schema_walker,
test_resolver, sourcemeta::jsontoolkit::default_schema_compiler)};
Expand Down
26 changes: 2 additions & 24 deletions src/command_validate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,15 @@
// TODO: Add a flag to collect annotations
auto intelligence::jsonschema::cli::validate(
const std::span<const std::string> &arguments) -> int {
const auto options{
parse_options(arguments, {"h", "http", "m", "metaschema"})};
const auto options{parse_options(arguments, {"h", "http"})};
CLI_ENSURE(options.at("").size() >= 1, "You must pass a schema")
CLI_ENSURE(options.at("").size() >= 2, "You must pass an instance")
const auto &schema_path{options.at("").at(0)};
const auto custom_resolver{
resolver(options, options.contains("h") || options.contains("http"))};

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

if (options.contains("m") || options.contains("metaschema") ||
options.at("").size() < 2) {
const auto metaschema_template{sourcemeta::jsontoolkit::compile(
sourcemeta::jsontoolkit::metaschema(schema, custom_resolver),
sourcemeta::jsontoolkit::default_schema_walker, custom_resolver,
sourcemeta::jsontoolkit::default_schema_compiler)};
std::ostringstream error;
if (sourcemeta::jsontoolkit::evaluate(
metaschema_template, schema,
sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast,
pretty_evaluate_callback(error))) {
log_verbose(options)
<< schema_path
<< ": The schema is valid with respect to its metaschema\n";
} else {
std::cerr << error.str();
std::cerr << schema_path
<< ": The schema is NOT valid with respect to its metaschema\n";
return EXIT_FAILURE;
}
}

bool result{true};
if (options.at("").size() >= 2) {
const auto &instance_path{options.at("").at(1)};
Expand Down
34 changes: 24 additions & 10 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "command.h"
#include "configure.h"

// TODO: Stop duplicating most of the docs here. Provide very brief
// info here and link to the docs on GitHub instead
constexpr std::string_view USAGE_DETAILS{R"EOF(
Global Options:
Expand All @@ -21,17 +23,28 @@ Global Options:
Commands:
validate <schema.json> [instance.json] [--http/-h] [--metaschema/-m]
validate <schema.json> [instance.json] [--http/-h]
If an instance is passed, validate it against the given schema.
Otherwise, validate the schema against its dialect metaschema. The
`--http/-h` option enables resolving remote schemas over the HTTP
protocol. The `--metaschema/-m` option checks that the given schema
is valid with respects to its dialect metaschema even if an instance
was passed.
protocol.
test [schemas-or-directories...] [--http/-h] [--metaschema/-m]
[--extension/-e <extension>] [--ignore/-i <schemas-or-directories>]
metaschema [schemas-or-directories...] [--http/-h]
[--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
Validate that a schema or a set of schemas are valid with respect
to their metaschemas. If no argument is passed, validate every
`.json` file in the current working directory (recursively). The
`--ignore/-i` option can be set to files or directories to ignore.
The `--http/-h` option enables resolving remote schemas over the HTTP
protocol. When scanning directories, the `--extension/-e` option is
used to prefer a file extension other than `.json`. This option can
be set multiple times.
test [schemas-or-directories...] [--http/-h] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
A schema test runner inspired by the official JSON Schema test suite.
Passing directories as input will run every `.json` file in such
Expand All @@ -40,10 +53,9 @@ Global Options:
The `--ignore/-i` option can be set to files or directories to ignore.
The `--http/-h` option enables resolving remote schemas over the HTTP
protocol. The `--metaschema/-m` option checks that the given schema is
valid with respects to its dialect metaschema. When scanning
directories, the `--extension/-e` option is used to prefer a file
extension other than `.json`. This option can be set multiple times.
protocol. When scanning directories, the `--extension/-e` option is
used to prefer a file extension other than `.json`. This option can be
set multiple times.
fmt [schemas-or-directories...] [--check/-c] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
Expand Down Expand Up @@ -101,6 +113,8 @@ auto jsonschema_main(const std::string &program, const std::string &command,
return intelligence::jsonschema::cli::lint(arguments);
} else if (command == "validate") {
return intelligence::jsonschema::cli::validate(arguments);
} else if (command == "metaschema") {
return intelligence::jsonschema::cli::metaschema(arguments);
} else if (command == "test") {
return intelligence::jsonschema::cli::test(arguments);
} else {
Expand Down
9 changes: 4 additions & 5 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,20 @@ add_jsonschema_test_unix(validate_fail_draft7)
add_jsonschema_test_unix(validate_fail_remote_no_http)
add_jsonschema_test_unix(validate_fail_invalid_ref)
add_jsonschema_test_unix(validate_non_supported)
add_jsonschema_test_unix(validate_pass_with_metaschema)
add_jsonschema_test_unix(validate_pass_only_metaschema)
add_jsonschema_test_unix(validate_fail_only_metaschema)
add_jsonschema_test_unix(validate_fail_only_metaschema_without_option)
add_jsonschema_test_unix(bundle_non_remote)
add_jsonschema_test_unix(bundle_remote_single_schema)
add_jsonschema_test_unix(bundle_remote_no_http)
add_jsonschema_test_unix(bundle_remote_directory)
add_jsonschema_test_unix(test_single_pass)
add_jsonschema_test_unix(test_single_fail)
add_jsonschema_test_unix(test_single_unsupported)
add_jsonschema_test_unix(test_single_pass_with_metaschema)
add_jsonschema_test_unix(lint_pass)
add_jsonschema_test_unix(lint_fail)
add_jsonschema_test_unix(lint_fix)
add_jsonschema_test_unix(metaschema_fail_directory)
add_jsonschema_test_unix(metaschema_fail_single)
add_jsonschema_test_unix(metaschema_pass_cwd)
add_jsonschema_test_unix(metaschema_pass_single)

# CI specific tests
add_jsonschema_test_unix_ci(bundle_remote_http)
Loading

0 comments on commit bc8f0e5

Please sign in to comment.