From 2ddb60607b9410f594b41bea6844011fdeb69798 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 31 May 2024 13:44:23 -0300 Subject: [PATCH] Add an `--http` option to `validate`, `test`, and `bundle` Signed-off-by: Juan Cruz Viotti --- src/command_bundle.cc | 5 +++-- src/command_test.cc | 6 +++--- src/command_validate.cc | 6 +++--- src/main.cc | 19 +++++++++++++----- src/utils.cc | 6 +++--- src/utils.h | 3 ++- test/CMakeLists.txt | 5 +++++ test/bundle_remote_no_http.sh | 26 ++++++++++++++++++++++++ test/validate_fail_remote_no_http.sh | 30 ++++++++++++++++++++++++++++ 9 files changed, 89 insertions(+), 17 deletions(-) create mode 100755 test/bundle_remote_no_http.sh create mode 100755 test/validate_fail_remote_no_http.sh diff --git a/src/command_bundle.cc b/src/command_bundle.cc index d5d2fb1..581065c 100644 --- a/src/command_bundle.cc +++ b/src/command_bundle.cc @@ -9,11 +9,12 @@ auto intelligence::jsonschema::cli::bundle( const std::span &arguments) -> int { - const auto options{parse_options(arguments, {})}; + const auto options{parse_options(arguments, {"h", "http"})}; CLI_ENSURE(!options.at("").empty(), "You must pass a JSON Schema as input"); auto schema{sourcemeta::jsontoolkit::from_file(options.at("").front())}; sourcemeta::jsontoolkit::bundle( - schema, sourcemeta::jsontoolkit::default_schema_walker, resolver(options)) + schema, sourcemeta::jsontoolkit::default_schema_walker, + resolver(options, options.contains("h") || options.contains("http"))) .wait(); sourcemeta::jsontoolkit::prettify( schema, std::cout, sourcemeta::jsontoolkit::schema_format_compare); diff --git a/src/command_test.cc b/src/command_test.cc index 8dd6075..6bdce4e 100644 --- a/src/command_test.cc +++ b/src/command_test.cc @@ -8,12 +8,12 @@ #include "utils.h" // TODO: Add a flag to first validate schema against its metaschema -// TODO: Add a flag to prevent the use of HTTP resolution auto intelligence::jsonschema::cli::test( const std::span &arguments) -> int { - const auto options{parse_options(arguments, {})}; + const auto options{parse_options(arguments, {"h", "http"})}; bool result{true}; - const auto test_resolver{resolver(options)}; + const auto test_resolver{ + resolver(options, options.contains("h") || options.contains("http"))}; for (const auto &entry : for_each_json(options.at(""))) { const sourcemeta::jsontoolkit::JSON test{ diff --git a/src/command_validate.cc b/src/command_validate.cc index 0ac7a01..f87a9ef 100644 --- a/src/command_validate.cc +++ b/src/command_validate.cc @@ -11,10 +11,9 @@ // 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 -// TODO: Add a flag to prevent the use of HTTP resolution auto intelligence::jsonschema::cli::validate( const std::span &arguments) -> int { - const auto options{parse_options(arguments, {})}; + const auto options{parse_options(arguments, {"h", "http"})}; CLI_ENSURE(options.at("").size() >= 2, "You must pass a schema followed by an instance") const auto &schema_path{options.at("").at(0)}; @@ -23,7 +22,8 @@ auto intelligence::jsonschema::cli::validate( const auto schema{sourcemeta::jsontoolkit::from_file(schema_path)}; const auto schema_template{sourcemeta::jsontoolkit::compile( - schema, sourcemeta::jsontoolkit::default_schema_walker, resolver(options), + schema, sourcemeta::jsontoolkit::default_schema_walker, + resolver(options, options.contains("h") || options.contains("http")), sourcemeta::jsontoolkit::default_schema_compiler)}; const auto instance{sourcemeta::jsontoolkit::from_file(instance_path)}; diff --git a/src/main.cc b/src/main.cc index f36b0fb..809f9dc 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,3 +1,5 @@ +#include + #include // std::min #include // EXIT_FAILURE, EXIT_SUCCESS #include // std::filesystem @@ -18,17 +20,20 @@ Global Options: Commands: - validate + validate [--http/-h] Validate an instance against a schema, printing error information, if - any, in a human-readable manner. + any, in a human-readable manner. The `--http/-h` option enables resolving + remote schemas over the HTTP protocol. - test [schema.json...] + test [schema.json...] [--http/-h] 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. fmt [schema.json...] [--check/-c] @@ -46,12 +51,13 @@ Global Options: (recursively). The `--fix/-f` option will attempt to automatically fix the linter errors. - bundle + bundle [--http/-h] Perform JSON Schema Bundling on a schema to inline remote references, printing the result to standard output. Read https://json-schema.org/blog/posts/bundling-json-schema-compound-documents - to learn more. + to learn more. The `--http/-h` option enables resolving remote schemas + over the HTTP protocol. frame @@ -91,6 +97,9 @@ auto main(int argc, char *argv[]) noexcept -> int { const std::vector arguments{argv + std::min(2, argc), argv + argc}; return jsonschema_main(program, command, arguments); + } catch (const sourcemeta::jsontoolkit::SchemaResolutionError &error) { + std::cerr << error.what() << ": " << error.id() << "\n"; + return EXIT_FAILURE; } catch (const std::exception &error) { std::cerr << "Error: " << error.what() << "\n"; return EXIT_FAILURE; diff --git a/src/utils.cc b/src/utils.cc index 11f3aad..7305aba 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -165,10 +165,10 @@ static auto fallback_resolver(std::string_view identifier) return promise.get_future(); } -auto resolver(const std::map> &options) - -> sourcemeta::jsontoolkit::SchemaResolver { +auto resolver(const std::map> &options, + const bool remote) -> sourcemeta::jsontoolkit::SchemaResolver { sourcemeta::jsontoolkit::MapSchemaResolver dynamic_resolver{ - fallback_resolver}; + remote ? fallback_resolver : sourcemeta::jsontoolkit::official_resolver}; if (options.contains("resolve")) { for (const auto &schema_path : options.at("resolve")) { diff --git a/src/utils.h b/src/utils.h index 5c4f09e..91430d1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -41,7 +41,8 @@ auto pretty_evaluate_callback( const sourcemeta::jsontoolkit::JSON &, const sourcemeta::jsontoolkit::JSON &) -> void; -auto resolver(const std::map> &options) +auto resolver(const std::map> &options, + const bool remote = false) -> sourcemeta::jsontoolkit::SchemaResolver; auto log_verbose(const std::map> &options) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cc06d67..ce987f9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,12 +14,17 @@ add_jsonschema_test_unix(format_cwd) add_jsonschema_test_unix(frame) 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(bundle_non_remote) add_jsonschema_test_unix(bundle_remote_single_schema) +add_jsonschema_test_unix(bundle_remote_no_http) 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(lint_pass) add_jsonschema_test_unix(lint_fail) add_jsonschema_test_unix(lint_fix) + +# TODO: Enable certain tests (if a given CMake option +# is set) that perform outbound HTTP requests diff --git a/test/bundle_remote_no_http.sh b/test/bundle_remote_no_http.sh new file mode 100755 index 0000000..d96b8e9 --- /dev/null +++ b/test/bundle_remote_no_http.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/schema.json" +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com", + "$ref": "nested" +} +EOF + +"$1" bundle "$TMP/schema.json" && CODE="$?" || CODE="$?" + +if [ "$CODE" = "0" ] +then + echo "FAIL" 1>&2 + exit 1 +else + echo "PASS" 1>&2 +fi diff --git a/test/validate_fail_remote_no_http.sh b/test/validate_fail_remote_no_http.sh new file mode 100755 index 0000000..195c67e --- /dev/null +++ b/test/validate_fail_remote_no_http.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/schema.json" +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com", + "$ref": "nested" +} +EOF + +cat << 'EOF' > "$TMP/instance.json" +{ "foo": 1 } +EOF + +"$1" validate "$TMP/schema.json" "$TMP/instance.json" && CODE="$?" || CODE="$?" + +if [ "$CODE" = "0" ] +then + echo "FAIL" 1>&2 + exit 1 +else + echo "PASS" 1>&2 +fi