diff --git a/src/command_fmt.cc b/src/command_fmt.cc index f2c2fa42..f5cbc0cd 100644 --- a/src/command_fmt.cc +++ b/src/command_fmt.cc @@ -21,21 +21,26 @@ auto sourcemeta::jsonschema::fmt(const sourcemeta::core::Options &options) std::vector failed_files; const auto indentation{parse_indentation(options)}; for (const auto &entry : for_each_json(options)) { + if (entry.from_stdin) { + throw std::runtime_error{ + "This command does not support reading from standard input"}; + } + if (entry.yaml) { throw YAMLInputError{"This command does not support YAML input files yet", - entry.first}; + entry.resolution_base}; } if (options.contains("check")) { - LOG_VERBOSE(options) << "Checking: " << entry.first.string() << "\n"; + LOG_VERBOSE(options) << "Checking: " << entry.first << "\n"; } else { - LOG_VERBOSE(options) << "Formatting: " << entry.first.string() << "\n"; + LOG_VERBOSE(options) << "Formatting: " << entry.first << "\n"; } try { - const auto configuration_path{find_configuration(entry.first)}; - const auto &configuration{ - read_configuration(options, configuration_path, entry.first)}; + const auto configuration_path{find_configuration(entry.resolution_base)}; + const auto &configuration{read_configuration(options, configuration_path, + entry.resolution_base)}; const auto dialect{default_dialect(options, configuration)}; const auto &custom_resolver{ resolver(options, options.contains("http"), dialect, configuration)}; @@ -51,43 +56,46 @@ auto sourcemeta::jsonschema::fmt(const sourcemeta::core::Options &options) } expected << "\n"; - std::ifstream current_stream{entry.first}; + std::ifstream current_stream{entry.resolution_base}; std::ostringstream current; current << current_stream.rdbuf(); if (options.contains("check")) { if (current.str() == expected.str()) { - LOG_VERBOSE(options) << "ok: " << entry.first.string() << "\n"; + LOG_VERBOSE(options) << "ok: " << entry.first << "\n"; } else if (output_json) { - failed_files.push_back(entry.first.string()); + failed_files.push_back(entry.first); result = false; } else { - std::cerr << "fail: " << entry.first.string() << "\n"; + std::cerr << "fail: " << entry.first << "\n"; result = false; } } else { if (current.str() != expected.str()) { - std::ofstream output{entry.first}; + std::ofstream output{entry.resolution_base}; output << expected.str(); } } } catch (const sourcemeta::core::SchemaKeywordError &error) { - throw FileError(entry.first, error); + throw FileError( + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaFrameError &error) { - throw FileError(entry.first, error); + throw FileError(entry.resolution_base, + error); } catch (const sourcemeta::core::SchemaRelativeMetaschemaResolutionError &error) { throw FileError< sourcemeta::core::SchemaRelativeMetaschemaResolutionError>( - entry.first, error); + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaResolutionError &error) { - throw FileError(entry.first, - error); + throw FileError( + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) { throw FileError( - entry.first); + entry.resolution_base); } catch (const sourcemeta::core::SchemaError &error) { - throw FileError(entry.first, error.what()); + throw FileError(entry.resolution_base, + error.what()); } } diff --git a/src/command_lint.cc b/src/command_lint.cc index 56390a23..33d44ed0 100644 --- a/src/command_lint.cc +++ b/src/command_lint.cc @@ -90,7 +90,7 @@ static auto get_lint_callback(sourcemeta::core::JSON &errors_array, if (output_json) { auto error_obj = sourcemeta::core::JSON::make_object(); - error_obj.assign("path", sourcemeta::core::JSON{entry.first.string()}); + error_obj.assign("path", sourcemeta::core::JSON{entry.first}); error_obj.assign("id", sourcemeta::core::JSON{name}); error_obj.assign("message", sourcemeta::core::JSON{message}); error_obj.assign("description", @@ -106,7 +106,7 @@ static auto get_lint_callback(sourcemeta::core::JSON &errors_array, errors_array.push_back(error_obj); } else { - std::cout << std::filesystem::relative(entry.first).string(); + std::cout << std::filesystem::relative(entry.resolution_base).string(); if (position.has_value()) { std::cout << ":"; std::cout << std::get<0>(position.value()); @@ -215,18 +215,18 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) if (options.contains("fix")) { for (const auto &entry : for_each_json(options)) { - const auto configuration_path{find_configuration(entry.first)}; - const auto &configuration{ - read_configuration(options, configuration_path, entry.first)}; + const auto configuration_path{find_configuration(entry.resolution_base)}; + const auto &configuration{read_configuration(options, configuration_path, + entry.resolution_base)}; const auto dialect{default_dialect(options, configuration)}; const auto &custom_resolver{ resolver(options, options.contains("http"), dialect, configuration)}; - LOG_VERBOSE(options) << "Linting: " << entry.first.string() << "\n"; + LOG_VERBOSE(options) << "Linting: " << entry.first << "\n"; if (entry.yaml) { throw YAMLInputError{ "The --fix option is not supported for YAML input files", - entry.first}; + entry.resolution_base}; } auto copy = entry.second; @@ -239,7 +239,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) copy, sourcemeta::core::schema_walker, custom_resolver, get_lint_callback(errors_array, entry, output_json, true, printed_progress), - dialect, sourcemeta::jsonschema::default_id(entry.first), + dialect, sourcemeta::jsonschema::default_id(entry), EXCLUDE_KEYWORD); if (printed_progress) { std::cerr << "\n"; @@ -257,7 +257,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) std::cerr << "\n"; } - throw LintAutoFixError{error.what(), entry.first, + throw LintAutoFixError{error.what(), entry.resolution_base, error.location()}; } catch ( const sourcemeta::core::SchemaBrokenReferenceError &error) { @@ -268,7 +268,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) throw LintAutoFixError{ "Could not autofix the schema without breaking its internal " "references", - entry.first, error.location()}; + entry.resolution_base, error.location()}; } catch ( const sourcemeta::blaze::CompilerReferenceTargetNotSchemaError &error) { @@ -278,35 +278,35 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) throw FileError< sourcemeta::blaze::CompilerReferenceTargetNotSchemaError>( - entry.first, error); + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaKeywordError &error) { if (printed_progress) { std::cerr << "\n"; } - throw FileError(entry.first, - error); + throw FileError( + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaFrameError &error) { if (printed_progress) { std::cerr << "\n"; } - throw FileError(entry.first, - error); + throw FileError( + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) { if (printed_progress) { std::cerr << "\n"; } throw FileError( - entry.first); + entry.resolution_base); } catch (const sourcemeta::core::SchemaResolutionError &error) { if (printed_progress) { std::cerr << "\n"; } throw FileError( - entry.first, error); + entry.resolution_base, error); } catch (...) { if (printed_progress) { std::cerr << "\n"; @@ -331,16 +331,16 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) sourcemeta::core::prettify(copy, expected, indentation); expected << "\n"; - std::ifstream current_stream{entry.first}; + std::ifstream current_stream{entry.resolution_base}; std::ostringstream current; current << current_stream.rdbuf(); if (current.str() != expected.str()) { - std::ofstream output{entry.first}; + std::ofstream output{entry.resolution_base}; output << expected.str(); } } else if (copy != entry.second) { - std::ofstream output{entry.first}; + std::ofstream output{entry.resolution_base}; sourcemeta::core::prettify(copy, output, indentation); output << "\n"; } @@ -351,13 +351,13 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) } } else { for (const auto &entry : for_each_json(options)) { - const auto configuration_path{find_configuration(entry.first)}; - const auto &configuration{ - read_configuration(options, configuration_path, entry.first)}; + const auto configuration_path{find_configuration(entry.resolution_base)}; + const auto &configuration{read_configuration(options, configuration_path, + entry.resolution_base)}; const auto dialect{default_dialect(options, configuration)}; const auto &custom_resolver{ resolver(options, options.contains("http"), dialect, configuration)}; - LOG_VERBOSE(options) << "Linting: " << entry.first.string() << "\n"; + LOG_VERBOSE(options) << "Linting: " << entry.first << "\n"; bool printed_progress{false}; const auto wrapper_result = @@ -368,7 +368,7 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) custom_resolver, get_lint_callback(errors_array, entry, output_json, false, printed_progress), - dialect, sourcemeta::jsonschema::default_id(entry.first), + dialect, sourcemeta::jsonschema::default_id(entry), EXCLUDE_KEYWORD); scores.emplace_back(subresult.second); if (subresult.first) { @@ -382,19 +382,19 @@ auto sourcemeta::jsonschema::lint(const sourcemeta::core::Options &options) &error) { throw FileError< sourcemeta::blaze::CompilerReferenceTargetNotSchemaError>( - entry.first, error); + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaKeywordError &error) { - throw FileError(entry.first, - error); + throw FileError( + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaFrameError &error) { - throw FileError(entry.first, - error); + throw FileError( + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) { throw FileError( - entry.first); + entry.resolution_base); } catch (const sourcemeta::core::SchemaResolutionError &error) { throw FileError( - entry.first, error); + entry.resolution_base, error); } }); diff --git a/src/command_metaschema.cc b/src/command_metaschema.cc index c7bb78f4..8f8cd9fe 100644 --- a/src/command_metaschema.cc +++ b/src/command_metaschema.cc @@ -32,12 +32,12 @@ auto sourcemeta::jsonschema::metaschema( for (const auto &entry : for_each_json(options)) { if (!sourcemeta::core::is_schema(entry.second)) { - throw NotSchemaError{entry.first}; + throw NotSchemaError{entry.resolution_base}; } - const auto configuration_path{find_configuration(entry.first)}; + const auto configuration_path{find_configuration(entry.resolution_base)}; const auto &configuration{ - read_configuration(options, configuration_path, entry.first)}; + read_configuration(options, configuration_path, entry.resolution_base)}; const auto default_dialect_option{default_dialect(options, configuration)}; const auto &custom_resolver{resolver(options, options.contains("http"), @@ -49,7 +49,7 @@ auto sourcemeta::jsonschema::metaschema( sourcemeta::core::dialect(entry.second, default_dialect_option)}; if (dialect.empty()) { throw FileError( - entry.first); + entry.resolution_base); } const auto metaschema{sourcemeta::core::metaschema( @@ -80,7 +80,7 @@ auto sourcemeta::jsonschema::metaschema( } else if (json_output) { // Otherwise its impossible to correlate the output // when validating i.e. a directory of schemas - std::cerr << entry.first.string() << "\n"; + std::cerr << entry.first << "\n"; const auto output{sourcemeta::blaze::standard( evaluator, cache.at(std::string{dialect}), entry.second, sourcemeta::blaze::StandardOutput::Basic, entry.positions)}; @@ -98,13 +98,9 @@ auto sourcemeta::jsonschema::metaschema( if (evaluator.validate(cache.at(std::string{dialect}), entry.second, std::ref(output))) { LOG_VERBOSE(options) - << "ok: " - << sourcemeta::core::weakly_canonical(entry.first).string() - << "\n matches " << dialect << "\n"; + << "ok: " << entry.first << "\n matches " << dialect << "\n"; } else { - std::cerr << "fail: " - << sourcemeta::core::weakly_canonical(entry.first).string() - << "\n"; + std::cerr << "fail: " << entry.first << "\n"; print(output, entry.positions, std::cerr); result = false; } @@ -112,15 +108,15 @@ auto sourcemeta::jsonschema::metaschema( } catch ( const sourcemeta::blaze::CompilerReferenceTargetNotSchemaError &error) { throw FileError( - entry.first, error); + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaRelativeMetaschemaResolutionError &error) { throw FileError< sourcemeta::core::SchemaRelativeMetaschemaResolutionError>( - entry.first, error); + entry.resolution_base, error); } catch (const sourcemeta::core::SchemaResolutionError &error) { - throw FileError(entry.first, - error); + throw FileError( + entry.resolution_base, error); } } diff --git a/src/command_test.cc b/src/command_test.cc index 04b93faf..4d2624e6 100644 --- a/src/command_test.cc +++ b/src/command_test.cc @@ -29,47 +29,47 @@ auto parse_test_suite(const sourcemeta::jsonschema::InputJSON &entry, -> sourcemeta::blaze::TestSuite { try { return sourcemeta::blaze::TestSuite::parse( - entry.second, entry.positions, entry.first.parent_path(), + entry.second, entry.positions, entry.resolution_base.parent_path(), schema_resolver, sourcemeta::core::schema_walker, sourcemeta::blaze::default_schema_compiler, dialect); } catch (const sourcemeta::blaze::TestParseError &error) { if (!json_output) { - std::cout << entry.first.string() << ":\n"; + std::cout << entry.first << ":\n"; } throw sourcemeta::jsonschema::FileError{ - entry.first, error.what(), error.location(), error.line(), + entry.resolution_base, error.what(), error.location(), error.line(), error.column()}; } catch ( const sourcemeta::blaze::CompilerReferenceTargetNotSchemaError &error) { if (!json_output) { - std::cout << entry.first.string() << ":\n"; + std::cout << entry.first << ":\n"; } throw sourcemeta::jsonschema::FileError< - sourcemeta::blaze::CompilerReferenceTargetNotSchemaError>{entry.first, - error}; + sourcemeta::blaze::CompilerReferenceTargetNotSchemaError>{ + entry.resolution_base, error}; } catch ( const sourcemeta::core::SchemaRelativeMetaschemaResolutionError &error) { if (!json_output) { - std::cout << entry.first.string() << ":\n"; + std::cout << entry.first << ":\n"; } throw sourcemeta::jsonschema::FileError< - sourcemeta::core::SchemaRelativeMetaschemaResolutionError>{entry.first, - error}; + sourcemeta::core::SchemaRelativeMetaschemaResolutionError>{ + entry.resolution_base, error}; } catch (const sourcemeta::core::SchemaResolutionError &error) { if (!json_output) { - std::cout << entry.first.string() << ":\n"; + std::cout << entry.first << ":\n"; } throw sourcemeta::jsonschema::FileError< - sourcemeta::core::SchemaResolutionError>{entry.first, error}; + sourcemeta::core::SchemaResolutionError>{entry.resolution_base, error}; } catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) { if (!json_output) { - std::cout << entry.first.string() << ":\n"; + std::cout << entry.first << ":\n"; } throw sourcemeta::jsonschema::FileError< - sourcemeta::core::SchemaUnknownBaseDialectError>{entry.first}; + sourcemeta::core::SchemaUnknownBaseDialectError>{entry.resolution_base}; } catch (...) { if (!json_output) { - std::cout << entry.first.string() << ":\n"; + std::cout << entry.first << ":\n"; } throw; } @@ -81,7 +81,7 @@ auto report_as_text(const sourcemeta::core::Options &options) -> void { for (const auto &entry : sourcemeta::jsonschema::for_each_json(options)) { const auto configuration_path{ - sourcemeta::jsonschema::find_configuration(entry.first)}; + sourcemeta::jsonschema::find_configuration(entry.resolution_base)}; const auto &configuration{sourcemeta::jsonschema::read_configuration( options, configuration_path)}; const auto dialect{ @@ -91,7 +91,7 @@ auto report_as_text(const sourcemeta::core::Options &options) -> void { auto test_suite{parse_test_suite(entry, schema_resolver, dialect, false)}; - std::cout << entry.first.string() << ":"; + std::cout << entry.first << ":"; const auto suite_result{test_suite.run( [&](const sourcemeta::core::JSON::String &, std::size_t index, @@ -193,7 +193,7 @@ auto report_as_ctrf(const sourcemeta::core::Options &options) -> void { for (const auto &entry : sourcemeta::jsonschema::for_each_json(options)) { const auto configuration_path{ - sourcemeta::jsonschema::find_configuration(entry.first)}; + sourcemeta::jsonschema::find_configuration(entry.resolution_base)}; const auto &configuration{sourcemeta::jsonschema::read_configuration( options, configuration_path)}; const auto dialect{ @@ -203,8 +203,7 @@ auto report_as_ctrf(const sourcemeta::core::Options &options) -> void { auto test_suite{parse_test_suite(entry, schema_resolver, dialect, true)}; - const auto file_path{ - sourcemeta::core::weakly_canonical(entry.first).string()}; + const auto file_path{entry.first}; const auto suite_result{test_suite.run( [&](const sourcemeta::core::JSON::String &target, std::size_t, diff --git a/src/command_validate.cc b/src/command_validate.cc index 580eeba0..d4d5a536 100644 --- a/src/command_validate.cc +++ b/src/command_validate.cc @@ -79,8 +79,8 @@ auto parse_loop(const sourcemeta::core::Options &options) -> std::uint64_t { auto run_loop(sourcemeta::blaze::Evaluator &evaluator, const sourcemeta::blaze::Template &schema_template, const sourcemeta::core::JSON &instance, - const std::filesystem::path &instance_path, - const int64_t instance_index, const uint64_t loop) -> bool { + const std::string &instance_path, const int64_t instance_index, + const uint64_t loop) -> bool { const auto iterations = static_cast(loop); double sum = 0.0, sum2 = 0.0, empty = 0.0; bool result = true; @@ -117,7 +117,7 @@ auto run_loop(sourcemeta::blaze::Evaluator &evaluator, auto avg = sum / iterations; auto stdev = loop == 1 ? 0.0 : std::sqrt(sum2 / iterations - avg * avg); - std::cout << instance_path.string(); + std::cout << instance_path; if (instance_index >= 0) std::cout << "[" << instance_index << "]"; std::cout << std::fixed; @@ -141,6 +141,11 @@ auto sourcemeta::jsonschema::validate(const sourcemeta::core::Options &options) const auto &schema_path{options.positional().at(0)}; + if (schema_path == "-") { + throw std::runtime_error{ + "Reading the schema from standard input is not supported"}; + } + if (std::filesystem::is_directory(schema_path)) { throw std::filesystem::filesystem_error{ "The input was supposed to be a file but it is a directory", @@ -304,158 +309,97 @@ auto sourcemeta::jsonschema::validate(const sourcemeta::core::Options &options) "The `--benchmark/-b` option is only allowed given a single instance"}; } - for (const auto &instance_path_view : instance_arguments) { - const std::filesystem::path instance_path{instance_path_view}; - if (trace && instance_path.extension() == ".jsonl") { - throw std::runtime_error{ - "The `--trace/-t` option is only allowed given a single instance"}; - } - - if (trace && std::filesystem::is_directory(instance_path)) { - throw std::runtime_error{ - "The `--trace/-t` option is only allowed given a single instance"}; + for (const auto &arg : instance_arguments) { + const std::filesystem::path arg_path{arg}; + if (arg != "-") { + if (trace && (std::filesystem::is_directory(arg_path) || + arg_path.extension() == ".jsonl")) { + throw std::runtime_error{ + "The `--trace/-t` option is only allowed given a single instance"}; + } + if (benchmark && std::filesystem::is_directory(arg_path)) { + throw std::runtime_error{"The `--benchmark/-b` option is only allowed " + "given a single instance"}; + } } + } - if (benchmark && std::filesystem::is_directory(instance_path)) { - throw std::runtime_error{"The `--benchmark/-b` option is only allowed " - "given a single instance"}; + const auto entries = for_each_json(instance_arguments, options); + for (const auto &entry : entries) { + std::ostringstream error; + sourcemeta::blaze::SimpleOutput output{entry.second}; + sourcemeta::blaze::TraceOutput trace_output{ + sourcemeta::core::schema_walker, custom_resolver, + sourcemeta::core::empty_weak_pointer, frame}; + bool subresult{true}; + if (benchmark) { + subresult = run_loop( + evaluator, schema_template, entry.second, entry.first, + entry.multidocument ? static_cast(entry.index + 1) + : static_cast(-1), + benchmark_loop); + if (!subresult) { + error << "error: Schema validation failure\n"; + result = false; + } + } else if (trace) { + subresult = evaluator.validate(schema_template, entry.second, + std::ref(trace_output)); + } else if (fast_mode) { + subresult = evaluator.validate(schema_template, entry.second); + } else if (!json_output) { + subresult = + evaluator.validate(schema_template, entry.second, std::ref(output)); } - if (std::filesystem::is_directory(instance_path) || - instance_path.extension() == ".jsonl" || - instance_path.extension() == ".yaml" || - instance_path.extension() == ".yml") { - for (const auto &entry : for_each_json({instance_path_view}, options)) { - std::ostringstream error; - sourcemeta::blaze::SimpleOutput output{entry.second}; - bool subresult{true}; - if (benchmark) { - subresult = run_loop( - evaluator, schema_template, entry.second, entry.first, - entry.multidocument ? static_cast(entry.index + 1) - : static_cast(-1), - benchmark_loop); - if (!subresult) { - error << "error: Schema validation failure\n"; - result = false; - } - } else if (fast_mode) { - subresult = evaluator.validate(schema_template, entry.second); - } else if (!json_output) { - subresult = evaluator.validate(schema_template, entry.second, - std::ref(output)); - } - if (benchmark) { - continue; - } else if (json_output) { - if (!entry.multidocument) { - std::cerr << entry.first.string() << "\n"; - } - const auto suboutput{sourcemeta::blaze::standard( - evaluator, schema_template, entry.second, - fast_mode ? sourcemeta::blaze::StandardOutput::Flag - : sourcemeta::blaze::StandardOutput::Basic, - entry.positions)}; - assert(suboutput.is_object()); - assert(suboutput.defines("valid")); - assert(suboutput.at("valid").is_boolean()); - sourcemeta::core::prettify(suboutput, std::cout); - std::cout << "\n"; - if (!suboutput.at("valid").to_boolean()) { - result = false; - if (entry.multidocument) { - break; - } - } - } else if (subresult) { - LOG_VERBOSE(options) - << "ok: " - << sourcemeta::core::weakly_canonical(entry.first).string(); - if (entry.multidocument) { - LOG_VERBOSE(options) << " (entry #" << entry.index + 1 << ")"; - } - LOG_VERBOSE(options) - << "\n matches " - << sourcemeta::core::weakly_canonical(schema_path).string() - << "\n"; - print_annotations(output, options, entry.positions, std::cerr); - } else { - std::cerr << "fail: " - << sourcemeta::core::weakly_canonical(entry.first).string(); - if (entry.multidocument) { - std::cerr << " (entry #" << entry.index + 1 << ")\n\n"; - sourcemeta::core::prettify(entry.second, std::cerr); - std::cerr << "\n\n"; - } else { - std::cerr << "\n"; - } - std::cerr << error.str(); - print(output, entry.positions, std::cerr); - result = false; - if (entry.multidocument) { - break; - } - } + if (benchmark) { + continue; + } else if (trace) { + print(trace_output, entry.positions, std::cout); + result = subresult; + } else if (json_output) { + if (!entry.multidocument && entries.size() > 1) { + std::cerr << entry.first << "\n"; } - } else { - sourcemeta::core::PointerPositionTracker tracker; - const auto instance{sourcemeta::core::read_yaml_or_json( - instance_path, std::ref(tracker))}; - std::ostringstream error; - sourcemeta::blaze::SimpleOutput output{instance}; - sourcemeta::blaze::TraceOutput trace_output{ - sourcemeta::core::schema_walker, custom_resolver, - sourcemeta::core::empty_weak_pointer, frame}; - bool subresult{true}; - if (benchmark) { - subresult = run_loop(evaluator, schema_template, instance, - instance_path, (int64_t)-1, benchmark_loop); - if (!subresult) { - error << "error: Schema validation failure\n"; - result = false; + const auto suboutput{sourcemeta::blaze::standard( + evaluator, schema_template, entry.second, + fast_mode ? sourcemeta::blaze::StandardOutput::Flag + : sourcemeta::blaze::StandardOutput::Basic, + entry.positions)}; + assert(suboutput.is_object()); + assert(suboutput.defines("valid")); + assert(suboutput.at("valid").is_boolean()); + sourcemeta::core::prettify(suboutput, std::cout); + std::cout << "\n"; + if (!suboutput.at("valid").to_boolean()) { + result = false; + if (entry.multidocument) { + break; } - } else if (trace) { - subresult = evaluator.validate(schema_template, instance, - std::ref(trace_output)); - } else if (fast_mode) { - subresult = evaluator.validate(schema_template, instance); - } else if (!json_output) { - subresult = - evaluator.validate(schema_template, instance, std::ref(output)); } - - if (trace) { - print(trace_output, tracker, std::cout); - result = subresult; - } else if (json_output) { - const auto suboutput{sourcemeta::blaze::standard( - evaluator, schema_template, instance, - fast_mode ? sourcemeta::blaze::StandardOutput::Flag - : sourcemeta::blaze::StandardOutput::Basic, - tracker)}; - assert(suboutput.is_object()); - assert(suboutput.defines("valid")); - assert(suboutput.at("valid").is_boolean()); - if (!suboutput.at("valid").to_boolean()) { - result = false; - } - - sourcemeta::core::prettify(suboutput, std::cout); - std::cout << "\n"; - } else if (subresult) { - LOG_VERBOSE(options) - << "ok: " - << sourcemeta::core::weakly_canonical(instance_path).string() - << "\n matches " - << sourcemeta::core::weakly_canonical(schema_path).string() << "\n"; - print_annotations(output, options, tracker, std::cerr); + } else if (subresult) { + LOG_VERBOSE(options) << "ok: " << entry.first; + if (entry.multidocument) { + LOG_VERBOSE(options) << " (entry #" << entry.index + 1 << ")"; + } + LOG_VERBOSE(options) + << "\n matches " + << sourcemeta::core::weakly_canonical(schema_path).string() << "\n"; + print_annotations(output, options, entry.positions, std::cerr); + } else { + std::cerr << "fail: " << entry.first; + if (entry.multidocument) { + std::cerr << " (entry #" << entry.index + 1 << ")\n\n"; + sourcemeta::core::prettify(entry.second, std::cerr); + std::cerr << "\n\n"; } else { - std::cerr << "fail: " - << sourcemeta::core::weakly_canonical(instance_path).string() - << "\n"; - std::cerr << error.str(); - print(output, tracker, std::cerr); - result = false; + std::cerr << "\n"; + } + std::cerr << error.str(); + print(output, entry.positions, std::cerr); + result = false; + if (entry.multidocument) { + break; } } } diff --git a/src/input.h b/src/input.h index 3896e5ae..bf99d01b 100644 --- a/src/input.h +++ b/src/input.h @@ -17,6 +17,7 @@ #include // std::uintptr_t #include // std::filesystem #include // std::ref +#include // std::cin #include // std::set #include // std::ostringstream #include // std::runtime_error @@ -26,7 +27,9 @@ namespace sourcemeta::jsonschema { struct InputJSON { - std::filesystem::path first; + std::string first; + std::filesystem::path resolution_base; + bool from_stdin{false}; sourcemeta::core::JSON second; sourcemeta::core::PointerPositionTracker positions; std::size_t index{0}; @@ -143,12 +146,26 @@ inline auto read_file(const std::filesystem::path &path) -> ParsedJSON { } } +inline auto read_from_stdin() -> ParsedJSON { + sourcemeta::core::PointerPositionTracker positions; + return {sourcemeta::core::parse_json(std::cin, std::ref(positions)), + std::move(positions), false}; +} + inline auto handle_json_entry(const std::filesystem::path &entry_path, const std::set &blacklist, const std::set &extensions, std::vector &result, const sourcemeta::core::Options &options) -> void { + if (entry_path == "-") { + auto parsed{read_from_stdin()}; + result.emplace_back("", std::filesystem::current_path(), true, + std::move(parsed.document), std::move(parsed.positions), + 0, false, parsed.yaml); + return; + } + if (std::filesystem::is_directory(entry_path)) { for (auto const &entry : std::filesystem::recursive_directory_iterator{entry_path}) { @@ -171,7 +188,8 @@ handle_json_entry(const std::filesystem::path &entry_path, // TODO: Print a verbose message for what is getting parsed auto parsed{read_file(canonical)}; - result.push_back({std::move(canonical), std::move(parsed.document), + result.push_back({canonical.string(), std::move(canonical), false, + std::move(parsed.document), std::move(parsed.positions), 0, false, parsed.yaml}); } } @@ -190,8 +208,8 @@ handle_json_entry(const std::filesystem::path &entry_path, for (const auto &document : sourcemeta::core::JSONL{stream}) { // TODO: Get real positions for JSONL sourcemeta::core::PointerPositionTracker positions; - result.push_back( - {canonical, document, std::move(positions), index, true}); + result.push_back({canonical.string(), canonical, false, document, + std::move(positions), index, true}); index += 1; } } catch (const sourcemeta::core::JSONParseError &error) { @@ -240,22 +258,26 @@ handle_json_entry(const std::filesystem::path &entry_path, << canonical.string() << "\n"; std::size_t index{0}; for (auto &entry : documents) { - result.push_back({canonical, std::move(entry.first), - std::move(entry.second), index, true, true}); + result.push_back({canonical.string(), canonical, false, + std::move(entry.first), std::move(entry.second), + index, true, true}); index += 1; } } else if (documents.size() == 1) { - result.push_back( - {std::move(canonical), std::move(documents.front().first), - std::move(documents.front().second), 0, false, true}); + result.push_back({canonical.string(), std::move(canonical), false, + std::move(documents.front().first), + std::move(documents.front().second), 0, false, + true}); } } else { - if (std::filesystem::is_empty(canonical)) { + if (std::filesystem::is_regular_file(canonical) && + std::filesystem::is_empty(canonical)) { return; } // TODO: Print a verbose message for what is getting parsed auto parsed{read_file(canonical)}; - result.push_back({std::move(canonical), std::move(parsed.document), + result.push_back({canonical.string(), std::move(canonical), false, + std::move(parsed.document), std::move(parsed.positions), 0, false, parsed.yaml}); } } @@ -264,9 +286,26 @@ handle_json_entry(const std::filesystem::path &entry_path, } // namespace +inline auto +check_duplicate_stdin(const std::vector &arguments) -> void { + bool seen_stdin{false}; + for (const auto &arg : arguments) { + if (arg == "-") { + if (seen_stdin) { + throw std::runtime_error{ + "The standard input position argument `-` can only be specified " + "once"}; + } + seen_stdin = true; + } + } +} + inline auto for_each_json(const std::vector &arguments, const sourcemeta::core::Options &options) -> std::vector { + check_duplicate_stdin(arguments); + const auto blacklist{parse_ignore(options)}; std::vector result; diff --git a/src/resolver.h b/src/resolver.h index e14c4460..e945d0b8 100644 --- a/src/resolver.h +++ b/src/resolver.h @@ -106,18 +106,18 @@ class CustomResolver { if (options.contains("resolve")) { for (const auto &entry : for_each_json(options.at("resolve"), options)) { LOG_DEBUG(options) << "Detecting schema resources from file: " - << entry.first.string() << "\n"; + << entry.first << "\n"; if (!sourcemeta::core::is_schema(entry.second)) { throw FileError( - entry.first, + entry.resolution_base, "The file you provided does not represent a valid JSON Schema"); } try { const auto result = this->add( entry.second, default_dialect, - sourcemeta::jsonschema::default_id(entry.first), + sourcemeta::jsonschema::default_id(entry), [&options](const auto &identifier) { LOG_DEBUG(options) << "Importing schema into the resolution context: " @@ -126,15 +126,16 @@ class CustomResolver { if (!result) { LOG_WARNING() << "No schema resources were imported from this file\n" - << " at " << entry.first.string() << "\n" + << " at " << entry.first << "\n" << "Are you sure this schema sets any identifiers?\n"; } } catch (const sourcemeta::core::SchemaFrameError &error) { throw FileError( - entry.first, std::string{error.identifier()}, error.what()); + entry.resolution_base, std::string{error.identifier()}, + error.what()); } catch (const sourcemeta::core::SchemaUnknownBaseDialectError &) { throw FileError( - entry.first); + entry.resolution_base); } } } diff --git a/src/utils.h b/src/utils.h index eec8c652..4d788673 100644 --- a/src/utils.h +++ b/src/utils.h @@ -12,6 +12,8 @@ #include #include +#include "input.h" + #include // assert #include // std::filesystem::path #include // std::next @@ -31,6 +33,12 @@ inline auto default_id(const std::filesystem::path &schema_path) .recompose(); } +inline auto default_id(const InputJSON &entry) -> std::string { + return sourcemeta::core::URI::from_path( + sourcemeta::core::weakly_canonical(entry.resolution_base)) + .recompose(); +} + inline auto resolve_entrypoint(const sourcemeta::core::SchemaFrame &frame, const std::string &entrypoint) -> std::string { if (entrypoint.empty()) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 07106a25..20acda65 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -208,6 +208,9 @@ add_jsonschema_test_unix(validate/fail_entrypoint_invalid_pointer_escape) add_jsonschema_test_unix(validate/fail_entrypoint_invalid_uri_parse) add_jsonschema_test_unix(validate/fail_entrypoint_mismatch) add_jsonschema_test_unix(validate/fail_entrypoint_with_template) +add_jsonschema_test_unix(validate/pass_stdin_instance) +add_jsonschema_test_unix(validate/fail_stdin_schema) +add_jsonschema_test_unix(validate/fail_stdin_multiple) # Metaschema add_jsonschema_test_unix(metaschema/pass_trace) diff --git a/test/validate/fail_stdin_multiple.sh b/test/validate/fail_stdin_multiple.sh new file mode 100755 index 00000000..f58dff7b --- /dev/null +++ b/test/validate/fail_stdin_multiple.sh @@ -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": "https://json-schema.org/draft/2020-12/schema", + "type": "object" +} +EOF + +echo '{}' | "$1" validate "$TMP/schema.json" - - 2>"$TMP/stderr.txt" \ + && EXIT_CODE="$?" || EXIT_CODE="$?" +test "$EXIT_CODE" = "1" || exit 1 + +cat << 'EOF' > "$TMP/expected.txt" +error: The standard input position argument `-` can only be specified once +EOF + +diff "$TMP/stderr.txt" "$TMP/expected.txt" diff --git a/test/validate/fail_stdin_schema.sh b/test/validate/fail_stdin_schema.sh new file mode 100755 index 00000000..c18f7fbd --- /dev/null +++ b/test/validate/fail_stdin_schema.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +set -o errexit +set -o nounset + +TMP="$(mktemp -d)" +clean() { rm -rf "$TMP"; } +trap clean EXIT + +cat << 'EOF' > "$TMP/instance.json" +{ "foo": "bar" } +EOF + +"$1" validate - "$TMP/instance.json" 2>"$TMP/stderr.txt" \ + && EXIT_CODE="$?" || EXIT_CODE="$?" +test "$EXIT_CODE" = "1" || exit 1 + +cat << 'EOF' > "$TMP/expected.txt" +error: Reading the schema from standard input is not supported +EOF + +diff "$TMP/stderr.txt" "$TMP/expected.txt" diff --git a/test/validate/pass_stdin_instance.sh b/test/validate/pass_stdin_instance.sh new file mode 100755 index 00000000..4a1b279e --- /dev/null +++ b/test/validate/pass_stdin_instance.sh @@ -0,0 +1,20 @@ +#!/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", + "type": "object", + "properties": { + "foo": { "type": "string" } + } +} +EOF + +echo '{"foo": "bar"}' | "$1" validate "$TMP/schema.json" -