diff --git a/DEPENDENCIES b/DEPENDENCIES index e62d9f47..53c1cdc6 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,5 +1,5 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 -core https://github.com/sourcemeta/core 626949ede75fb77ac4a919a24af0ade6f776751d +core https://github.com/sourcemeta/core 81cf10ad7243e799991e5c692cd3d4a6a6e5cc9f jsonbinpack https://github.com/sourcemeta/jsonbinpack abd40e41050d14d74af1fddb5c397de5cca3b13c blaze https://github.com/sourcemeta/blaze 53d6ba77c26e613b2803f044c2852d4441bfb0a1 hydra https://github.com/sourcemeta/hydra af9f2c54709d620872ead0c3f8f683c15a0fa702 diff --git a/src/command_bundle.cc b/src/command_bundle.cc index e583725f..330d72b2 100644 --- a/src/command_bundle.cc +++ b/src/command_bundle.cc @@ -23,7 +23,8 @@ auto sourcemeta::jsonschema::bundle(const sourcemeta::core::Options &options) const std::filesystem::path schema_path{options.positional().front()}; const auto configuration_path{find_configuration(schema_path)}; - const auto &configuration{read_configuration(options, configuration_path)}; + const auto &configuration{ + read_configuration(options, configuration_path, schema_path)}; const auto dialect{default_dialect(options, configuration)}; auto schema{sourcemeta::core::read_yaml_or_json(schema_path)}; diff --git a/src/command_compile.cc b/src/command_compile.cc index ad926cde..b9786c51 100644 --- a/src/command_compile.cc +++ b/src/command_compile.cc @@ -22,7 +22,8 @@ auto sourcemeta::jsonschema::compile(const sourcemeta::core::Options &options) const auto &schema_path{options.positional().at(0)}; const auto configuration_path{find_configuration(schema_path)}; - const auto &configuration{read_configuration(options, configuration_path)}; + const auto &configuration{ + read_configuration(options, configuration_path, schema_path)}; const auto dialect{default_dialect(options, configuration)}; const auto schema{sourcemeta::core::read_yaml_or_json(schema_path)}; diff --git a/src/command_fmt.cc b/src/command_fmt.cc index 2e505d21..447c1230 100644 --- a/src/command_fmt.cc +++ b/src/command_fmt.cc @@ -36,7 +36,7 @@ auto sourcemeta::jsonschema::fmt(const sourcemeta::core::Options &options) try { const auto configuration_path{find_configuration(entry.first)}; const auto &configuration{ - read_configuration(options, configuration_path)}; + read_configuration(options, configuration_path, entry.first)}; const auto dialect{default_dialect(options, configuration)}; const auto &custom_resolver{ resolver(options, options.contains("http"), dialect, configuration)}; diff --git a/src/command_inspect.cc b/src/command_inspect.cc index 085532d3..c69f806d 100644 --- a/src/command_inspect.cc +++ b/src/command_inspect.cc @@ -157,7 +157,8 @@ auto sourcemeta::jsonschema::inspect(const sourcemeta::core::Options &options) sourcemeta::core::read_yaml_or_json(schema_path, std::ref(positions))}; const auto configuration_path{find_configuration(schema_path)}; - const auto &configuration{read_configuration(options, configuration_path)}; + const auto &configuration{ + read_configuration(options, configuration_path, schema_path)}; const auto dialect{default_dialect(options, configuration)}; sourcemeta::core::SchemaFrame frame{ diff --git a/src/command_lint.cc b/src/command_lint.cc index 4bfc313b..15ed7546 100644 --- a/src/command_lint.cc +++ b/src/command_lint.cc @@ -196,7 +196,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) for (const auto &entry : for_each_json(options)) { const auto configuration_path{find_configuration(entry.first)}; const auto &configuration{ - read_configuration(options, configuration_path)}; + read_configuration(options, configuration_path, entry.first)}; const auto dialect{default_dialect(options, configuration)}; const auto &custom_resolver{ @@ -255,7 +255,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) for (const auto &entry : for_each_json(options)) { const auto configuration_path{find_configuration(entry.first)}; const auto &configuration{ - read_configuration(options, configuration_path)}; + read_configuration(options, configuration_path, entry.first)}; const auto dialect{default_dialect(options, configuration)}; const auto &custom_resolver{ resolver(options, options.contains("http"), dialect, configuration)}; diff --git a/src/command_metaschema.cc b/src/command_metaschema.cc index 7defa8d0..e45e8869 100644 --- a/src/command_metaschema.cc +++ b/src/command_metaschema.cc @@ -36,7 +36,8 @@ auto sourcemeta::jsonschema::metaschema( } const auto configuration_path{find_configuration(entry.first)}; - const auto &configuration{read_configuration(options, configuration_path)}; + const auto &configuration{ + read_configuration(options, configuration_path, entry.first)}; const auto default_dialect_option{default_dialect(options, configuration)}; const auto &custom_resolver{resolver(options, options.contains("http"), diff --git a/src/command_validate.cc b/src/command_validate.cc index 812254e2..54ee82bd 100644 --- a/src/command_validate.cc +++ b/src/command_validate.cc @@ -149,7 +149,8 @@ auto sourcemeta::jsonschema::validate(const sourcemeta::core::Options &options) } const auto configuration_path{find_configuration(schema_path)}; - const auto &configuration{read_configuration(options, configuration_path)}; + const auto &configuration{ + read_configuration(options, configuration_path, schema_path)}; const auto dialect{default_dialect(options, configuration)}; const auto schema{sourcemeta::core::read_yaml_or_json(schema_path)}; diff --git a/src/configuration.h b/src/configuration.h index 09062ee5..36542995 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -21,7 +21,8 @@ inline auto find_configuration(const std::filesystem::path &path) inline auto read_configuration( const sourcemeta::core::Options &options, - const std::optional &configuration_path) + const std::optional &configuration_path, + const std::optional &schema_path = std::nullopt) -> const std::optional & { using CacheKey = std::optional; static std::map> @@ -48,6 +49,17 @@ inline auto read_configuration( throw FileError( configuration_path.value(), error); } + + assert(result.has_value()); + if (schema_path.has_value() && + !result.value().applies_to(schema_path.value())) { + LOG_VERBOSE(options) + << "Ignoring configuration file given extensions mismatch: " + << sourcemeta::core::weakly_canonical(configuration_path.value()) + .string() + << "\n"; + result = std::nullopt; + } } auto [inserted_iterator, inserted] = diff --git a/src/input.h b/src/input.h index 2e4882fe..0ef76d1b 100644 --- a/src/input.h +++ b/src/input.h @@ -156,6 +156,7 @@ inline auto for_each_json(const std::vector &arguments, const auto configuration_path{find_configuration(current_path)}; const auto &configuration{read_configuration(options, configuration_path)}; const auto extensions{parse_extensions(options, configuration)}; + handle_json_entry(configuration.has_value() ? configuration.value().absolute_path : current_path, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2f767185..8e6705f9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -167,6 +167,7 @@ add_jsonschema_test_unix(validate/fail_benchmark_directory) add_jsonschema_test_unix(validate/pass_benchmark_loop) add_jsonschema_test_unix(validate/pass_benchmark_loop_jsonl) add_jsonschema_test_unix(validate/fail_benchmark_zero) +add_jsonschema_test_unix(validate/fail_default_dialect_config_extension_mismatch) add_jsonschema_test_unix(validate/pass_resolve_remap) add_jsonschema_test_unix(validate/pass_resolve_remap_relative) @@ -178,6 +179,7 @@ add_jsonschema_test_unix(metaschema/fail_single) add_jsonschema_test_unix(metaschema/fail_yaml) add_jsonschema_test_unix(metaschema/fail_non_schema) add_jsonschema_test_unix(metaschema/fail_no_dialect) +add_jsonschema_test_unix(metaschema/fail_default_dialect_config_extension_mismatch) add_jsonschema_test_unix(metaschema/pass_cwd) add_jsonschema_test_unix(metaschema/pass_single) add_jsonschema_test_unix(metaschema/pass_2020_12) @@ -267,6 +269,7 @@ add_jsonschema_test_unix(bundle/fail_resolve_duplicate) add_jsonschema_test_unix(bundle/fail_resolve_invalid_json) add_jsonschema_test_unix(bundle/fail_schema_invalid_json) add_jsonschema_test_unix(bundle/fail_unknown_metaschema) +add_jsonschema_test_unix(bundle/fail_default_dialect_config_extension_mismatch) add_jsonschema_test_unix(bundle/pass_bigint) add_jsonschema_test_unix(bundle/pass_resolve_default_dialect_config) add_jsonschema_test_unix(bundle/pass_resolve_default_dialect_config_relative) @@ -285,6 +288,7 @@ add_jsonschema_test_unix(inspect/pass_json_output) add_jsonschema_test_unix(inspect/fail_no_schema) add_jsonschema_test_unix(inspect/fail_schema_invalid_json) add_jsonschema_test_unix(inspect/fail_unknown_metaschema) +add_jsonschema_test_unix(inspect/fail_default_dialect_config_extension_mismatch) add_jsonschema_test_unix(inspect/fail_relative_file_metaschema_ref) # Compile @@ -300,6 +304,7 @@ add_jsonschema_test_unix(compile/pass_yaml) add_jsonschema_test_unix(compile/fail_no_schema) add_jsonschema_test_unix(compile/fail_schema_invalid_json) add_jsonschema_test_unix(compile/fail_unknown_metaschema) +add_jsonschema_test_unix(compile/fail_default_dialect_config_extension_mismatch) add_jsonschema_test_unix(compile/pass_resolve_remap) add_jsonschema_test_unix(compile/pass_resolve_remap_relative) diff --git a/test/bundle/fail_default_dialect_config_extension_mismatch.sh b/test/bundle/fail_default_dialect_config_extension_mismatch.sh new file mode 100755 index 00000000..44f4369e --- /dev/null +++ b/test/bundle/fail_default_dialect_config_extension_mismatch.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/document.json" +{ "foo": 1 } +EOF + +cat << 'EOF' > "$TMP/jsonschema.json" +{ + "extension": [".schema.json"], + "defaultDialect": "https://json-schema.org/draft/2020-12/schema" +} +EOF + +"$1" bundle "$TMP/document.json" --verbose 2>"$TMP/stderr.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +Using configuration file: $(realpath "$TMP")/jsonschema.json +Ignoring configuration file given extensions mismatch: $(realpath "$TMP")/jsonschema.json +error: Could not determine the base dialect of the schema + at file path $(realpath "$TMP")/document.json + +Are you sure the input is a valid JSON Schema and its base dialect is known? +If the input does not declare the \`\$schema\` keyword, you might want to +explicitly declare a default dialect using \`--default-dialect/-d\` +EOF + +diff "$TMP/stderr.txt" "$TMP/expected.txt" + +# JSON error +"$1" bundle "$TMP/document.json" --json >"$TMP/stdout.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +{ + "error": "Could not determine the base dialect of the schema", + "filePath": "$(realpath "$TMP")/document.json" +} +EOF + +diff "$TMP/stdout.txt" "$TMP/expected.txt" diff --git a/test/compile/fail_default_dialect_config_extension_mismatch.sh b/test/compile/fail_default_dialect_config_extension_mismatch.sh new file mode 100755 index 00000000..114d23dd --- /dev/null +++ b/test/compile/fail_default_dialect_config_extension_mismatch.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/document.json" +{ "foo": 1 } +EOF + +cat << 'EOF' > "$TMP/jsonschema.json" +{ + "extension": [".schema.json"], + "defaultDialect": "https://json-schema.org/draft/2020-12/schema" +} +EOF + +"$1" compile "$TMP/document.json" --verbose 2>"$TMP/stderr.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +Using configuration file: $(realpath "$TMP")/jsonschema.json +Ignoring configuration file given extensions mismatch: $(realpath "$TMP")/jsonschema.json +error: Could not determine the base dialect of the schema + at file path $(realpath "$TMP")/document.json + +Are you sure the input is a valid JSON Schema and its base dialect is known? +If the input does not declare the \`\$schema\` keyword, you might want to +explicitly declare a default dialect using \`--default-dialect/-d\` +EOF + +diff "$TMP/stderr.txt" "$TMP/expected.txt" + +# JSON error +"$1" compile "$TMP/document.json" --json >"$TMP/stdout.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +{ + "error": "Could not determine the base dialect of the schema", + "filePath": "$(realpath "$TMP")/document.json" +} +EOF + +diff "$TMP/stdout.txt" "$TMP/expected.txt" diff --git a/test/inspect/fail_default_dialect_config_extension_mismatch.sh b/test/inspect/fail_default_dialect_config_extension_mismatch.sh new file mode 100755 index 00000000..ccd5eb73 --- /dev/null +++ b/test/inspect/fail_default_dialect_config_extension_mismatch.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/document.json" +{ "foo": 1 } +EOF + +cat << 'EOF' > "$TMP/jsonschema.json" +{ + "extension": [".schema.json"], + "defaultDialect": "https://json-schema.org/draft/2020-12/schema" +} +EOF + +"$1" inspect "$TMP/document.json" --verbose 2>"$TMP/stderr.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +Using configuration file: $(realpath "$TMP")/jsonschema.json +Ignoring configuration file given extensions mismatch: $(realpath "$TMP")/jsonschema.json +error: Could not determine the base dialect of the schema + at file path $(realpath "$TMP")/document.json + +Are you sure the input is a valid JSON Schema and its base dialect is known? +If the input does not declare the \`\$schema\` keyword, you might want to +explicitly declare a default dialect using \`--default-dialect/-d\` +EOF + +diff "$TMP/stderr.txt" "$TMP/expected.txt" + +# JSON error +"$1" inspect "$TMP/document.json" --json >"$TMP/stdout.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +{ + "error": "Could not determine the base dialect of the schema", + "filePath": "$(realpath "$TMP")/document.json" +} +EOF + +diff "$TMP/stdout.txt" "$TMP/expected.txt" diff --git a/test/metaschema/fail_default_dialect_config_extension_mismatch.sh b/test/metaschema/fail_default_dialect_config_extension_mismatch.sh new file mode 100755 index 00000000..1f92d7bb --- /dev/null +++ b/test/metaschema/fail_default_dialect_config_extension_mismatch.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/document.json" +{ "foo": 1 } +EOF + +cat << 'EOF' > "$TMP/jsonschema.json" +{ + "extension": [".schema.json"], + "defaultDialect": "https://json-schema.org/draft/2020-12/schema" +} +EOF + +"$1" metaschema "$TMP/document.json" --verbose 2>"$TMP/stderr.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +Using configuration file: $(realpath "$TMP")/jsonschema.json +Ignoring configuration file given extensions mismatch: $(realpath "$TMP")/jsonschema.json +error: Could not determine the base dialect of the schema + at file path $(realpath "$TMP/document.json") + +Are you sure the input is a valid JSON Schema and its base dialect is known? +If the input does not declare the \`\$schema\` keyword, you might want to +explicitly declare a default dialect using \`--default-dialect/-d\` +EOF + +diff "$TMP/stderr.txt" "$TMP/expected.txt" + +# JSON error +"$1" metaschema "$TMP/document.json" --json >"$TMP/stdout.txt" && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +{ + "error": "Could not determine the base dialect of the schema", + "filePath": "$(realpath "$TMP/document.json")" +} +EOF + +diff "$TMP/stdout.txt" "$TMP/expected.txt" diff --git a/test/validate/fail_default_dialect_config_extension_mismatch.sh b/test/validate/fail_default_dialect_config_extension_mismatch.sh new file mode 100755 index 00000000..3e36973f --- /dev/null +++ b/test/validate/fail_default_dialect_config_extension_mismatch.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/document.json" +{ "foo": 1 } +EOF + +cat << 'EOF' > "$TMP/instance.json" +{ "bar": 2 } +EOF + +cat << 'EOF' > "$TMP/jsonschema.json" +{ + "extension": [".schema.json"], + "defaultDialect": "https://json-schema.org/draft/2020-12/schema" +} +EOF + +"$1" validate "$TMP/document.json" "$TMP/instance.json" --verbose 2>"$TMP/stderr.txt" \ + && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +Using configuration file: $(realpath "$TMP")/jsonschema.json +Ignoring configuration file given extensions mismatch: $(realpath "$TMP")/jsonschema.json +error: Could not determine the base dialect of the schema + at file path $(realpath "$TMP")/document.json + +Are you sure the input is a valid JSON Schema and its base dialect is known? +If the input does not declare the \`\$schema\` keyword, you might want to +explicitly declare a default dialect using \`--default-dialect/-d\` +EOF + +diff "$TMP/stderr.txt" "$TMP/expected.txt" + +# JSON error +"$1" validate "$TMP/document.json" "$TMP/instance.json" --json >"$TMP/stdout.txt" \ + && CODE="$?" || CODE="$?" +test "$CODE" = "1" || exit 1 + +cat << EOF > "$TMP/expected.txt" +{ + "error": "Could not determine the base dialect of the schema", + "filePath": "$(realpath "$TMP")/document.json" +} +EOF + +diff "$TMP/stdout.txt" "$TMP/expected.txt" diff --git a/vendor/core/src/extension/schemaconfig/CMakeLists.txt b/vendor/core/src/extension/schemaconfig/CMakeLists.txt index 44423676..d27f4190 100644 --- a/vendor/core/src/extension/schemaconfig/CMakeLists.txt +++ b/vendor/core/src/extension/schemaconfig/CMakeLists.txt @@ -1,5 +1,5 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME schemaconfig - PRIVATE_HEADERS error.h SOURCES parse.cc find.cc) + PRIVATE_HEADERS error.h SOURCES parse.cc schemaconfig.cc) if(SOURCEMETA_CORE_INSTALL) sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME schemaconfig) diff --git a/vendor/core/src/extension/schemaconfig/include/sourcemeta/core/schemaconfig.h b/vendor/core/src/extension/schemaconfig/include/sourcemeta/core/schemaconfig.h index 8ae6bda6..ade4e89a 100644 --- a/vendor/core/src/extension/schemaconfig/include/sourcemeta/core/schemaconfig.h +++ b/vendor/core/src/extension/schemaconfig/include/sourcemeta/core/schemaconfig.h @@ -49,6 +49,11 @@ struct SOURCEMETA_CORE_SCHEMACONFIG_EXPORT SchemaConfig { std::unordered_map resolve; JSON extra = JSON::make_object(); + /// Check if the given path represents a schema described by this + /// configuration + [[nodiscard]] + auto applies_to(const std::filesystem::path &path) const -> bool; + /// Parse a configuration file from its contents [[nodiscard]] static auto from_json(const JSON &value, diff --git a/vendor/core/src/extension/schemaconfig/find.cc b/vendor/core/src/extension/schemaconfig/schemaconfig.cc similarity index 69% rename from vendor/core/src/extension/schemaconfig/find.cc rename to vendor/core/src/extension/schemaconfig/schemaconfig.cc index 717227f2..4a428d2c 100644 --- a/vendor/core/src/extension/schemaconfig/find.cc +++ b/vendor/core/src/extension/schemaconfig/schemaconfig.cc @@ -1,7 +1,9 @@ #include #include -#include // assert +#include // std::ranges::any_of +#include // assert +#include // std::string namespace sourcemeta::core { @@ -31,4 +33,11 @@ auto SchemaConfig::find(const std::filesystem::path &path) return std::nullopt; } +auto SchemaConfig::applies_to(const std::filesystem::path &path) const -> bool { + const std::string filename{path.filename().string()}; + return std::ranges::any_of(this->extension, [&filename](const auto &suffix) { + return filename.ends_with(suffix); + }); +} + } // namespace sourcemeta::core