Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions src/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@

#include <sourcemeta/blaze/configuration.h>
#include <sourcemeta/core/io.h>
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/jsonpointer.h>
#include <sourcemeta/core/options.h>
#include <sourcemeta/core/uri.h>

#include "error.h"
#include "logger.h"

#include <cassert> // assert
#include <cstddef> // std::size_t
#include <cstdint> // std::uint64_t
#include <deque> // std::deque
#include <filesystem> // std::filesystem
#include <map> // std::map
#include <memory> // std::shared_ptr, std::make_shared
#include <optional> // std::optional
#include <sstream> // std::ostringstream
#include <string> // std::string
Expand Down Expand Up @@ -53,16 +61,52 @@ inline auto read_configuration(
configuration_path.value())
.string()
<< "\n";
sourcemeta::core::PointerPositionTracker positions;
auto property_storage = std::make_shared<std::deque<std::string>>();
try {
result = sourcemeta::blaze::Configuration::read_json(
configuration_path.value(), configuration_reader);
auto stream{sourcemeta::core::read_file(configuration_path.value())};
std::ostringstream buffer;
buffer << stream.rdbuf();
sourcemeta::core::JSON config_json{nullptr};
sourcemeta::core::parse_json(
Comment thread
jviotti marked this conversation as resolved.
buffer.str(), config_json,
[&positions, &property_storage](
const sourcemeta::core::JSON::ParsePhase phase,
const sourcemeta::core::JSON::Type type, const std::uint64_t line,
const std::uint64_t column,
const sourcemeta::core::JSON::ParseContext context,
const std::size_t index,
const sourcemeta::core::JSON::String &property) {
property_storage->emplace_back(property);
positions(phase, type, line, column, context, index,
property_storage->back());
});
result = sourcemeta::blaze::Configuration::from_json(
config_json, configuration_path.value().parent_path());
} catch (const sourcemeta::blaze::ConfigurationParseError &error) {
throw sourcemeta::core::FileError<
sourcemeta::blaze::ConfigurationParseError>(
configuration_path.value(), error);
}

assert(result.has_value());
for (const auto &[resolve_uri, resolve_value] : result.value().resolve) {
const sourcemeta::core::URI value_uri{resolve_value};
if (value_uri.is_relative()) {
const auto resolved_path{std::filesystem::weakly_canonical(
result.value().base_path / value_uri.to_path())};
if (!std::filesystem::exists(resolved_path)) {
const sourcemeta::core::Pointer resolve_pointer{"resolve",
resolve_uri};
const auto position{positions.get(resolve_pointer)};
assert(position.has_value());
throw ConfigurationResolveFileNotFoundError(
configuration_path.value(), resolve_pointer, resolved_path,
std::get<0>(position.value()), std::get<1>(position.value()));
}
}
}

if (schema_path.has_value() &&
!result.value().applies_to(schema_path.value())) {
LOG_DEBUG(options)
Expand Down
60 changes: 60 additions & 0 deletions src/error.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,48 @@ class InstallError : public std::runtime_error {
std::string uri_;
};

class ConfigurationResolveFileNotFoundError : public std::runtime_error {
public:
ConfigurationResolveFileNotFoundError(
std::filesystem::path configuration_path,
sourcemeta::core::Pointer location, std::filesystem::path resolve_path,
std::uint64_t line, std::uint64_t column)
: std::runtime_error{"The resolve target does not exist on the "
"filesystem"},
configuration_path_{std::move(configuration_path)},
location_{std::move(location)}, resolve_path_{std::move(resolve_path)},
line_{line}, column_{column} {}

[[nodiscard]] auto path() const noexcept -> const std::filesystem::path & {
return this->configuration_path_;
}

[[nodiscard]] auto location() const noexcept
-> const sourcemeta::core::Pointer & {
return this->location_;
}

[[nodiscard]] auto resolve_path() const noexcept
-> const std::filesystem::path & {
return this->resolve_path_;
}

[[nodiscard]] auto line() const noexcept -> std::uint64_t {
return this->line_;
}

[[nodiscard]] auto column() const noexcept -> std::uint64_t {
return this->column_;
}

private:
std::filesystem::path configuration_path_;
sourcemeta::core::Pointer location_;
std::filesystem::path resolve_path_;
std::uint64_t line_;
std::uint64_t column_;
};

class Fail : public std::runtime_error {
public:
Fail(int exit_code) : std::runtime_error{"Fail"}, exit_code_{exit_code} {}
Expand Down Expand Up @@ -333,6 +375,20 @@ inline auto print_exception(const bool is_json, const Exception &exception)
}
}

if constexpr (requires(const Exception &current) {
{
current.resolve_path()
} -> std::convertible_to<std::filesystem::path>;
}) {
const auto &resolve_path_value{exception.resolve_path()};
if (is_json) {
error_json.assign("resolvePath",
sourcemeta::core::JSON{resolve_path_value.string()});
} else {
std::cerr << " at resolve path " << resolve_path_value.string() << "\n";
}
}

if constexpr (requires(const Exception &current) { current.line(); }) {
if (is_json) {
error_json.assign("line", sourcemeta::core::JSON{static_cast<std::size_t>(
Expand Down Expand Up @@ -614,6 +670,10 @@ inline auto try_catch(const sourcemeta::core::Options &options,
const auto is_json{options.contains("json")};
print_exception(is_json, error);
return EXIT_SCHEMA_INPUT_ERROR;
} catch (const ConfigurationResolveFileNotFoundError &error) {
const auto is_json{options.contains("json")};
print_exception(is_json, error);
return EXIT_OTHER_INPUT_ERROR;
} catch (const sourcemeta::core::FileError<
sourcemeta::blaze::ConfigurationParseError> &error) {
const auto is_json{options.contains("json")};
Expand Down
2 changes: 1 addition & 1 deletion src/resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ resolve_map_uri(const sourcemeta::blaze::Configuration &configuration,

const sourcemeta::core::URI new_uri{match->second};
if (new_uri.is_relative()) {
return sourcemeta::core::URI::from_path(configuration.absolute_path /
return sourcemeta::core::URI::from_path(configuration.base_path /
new_uri.to_path())
.recompose();
}
Expand Down
3 changes: 3 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,9 @@ add_jsonschema_test_unix(lint/pass_lint_config_rule_other_directory)
add_jsonschema_test_unix(lint/fail_lint_config_rule_other_directory)
add_jsonschema_test_unix(lint/fail_lint_config_rule_extension_mismatch)
add_jsonschema_test_unix(lint/pass_lint_config_ignore)
add_jsonschema_test_unix(lint/pass_lint_config_resolve_custom_dialect)
add_jsonschema_test_unix(lint/pass_lint_config_resolve_custom_dialect_with_path)
add_jsonschema_test_unix(lint/fail_lint_config_resolve_missing_file)
add_jsonschema_test_unix(lint/pass_stdin_lint)
add_jsonschema_test_unix(lint/pass_stdin_lint_verbose)
add_jsonschema_test_unix(lint/pass_stdin_fix)
Expand Down
2 changes: 1 addition & 1 deletion test/bundle/pass_resolve_config_match.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cat << 'EOF' > "$TMP/jsonschema.json"
{
"path": "./schemas",
"resolve": {
"https://example.com/my-external-schema.json": "../dependency.json"
"https://example.com/my-external-schema.json": "./dependency.json"
}
}
EOF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cat << 'EOF' > "$TMP/jsonschema.json"
{
"path": "./schemas",
"resolve": {
"https://example.com/my-external-schema": "../dependency.json"
"https://example.com/my-external-schema": "./dependency.json"
}
}
EOF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cat << 'EOF' > "$TMP/jsonschema.json"
{
"path": "./schemas",
"resolve": {
"https://example.com/my-external-schema.json": "../dependency.json"
"https://example.com/my-external-schema.json": "./dependency.json"
}
}
EOF
Expand Down
60 changes: 60 additions & 0 deletions test/lint/fail_lint_config_resolve_missing_file.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/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",
"title": "Test",
"description": "Test schema",
"examples": [ { "name": "John" } ],
"$ref": "https://example.com/my-defs"
}
EOF

cat << 'EOF' > "$TMP/jsonschema.json"
{
"resolve": {
"https://example.com/my-defs": "./does-not-exist.json"
}
}
EOF

BIN="$(realpath "$1")"
cd "$TMP"
"$BIN" lint schema.json > "$TMP/output.txt" 2>&1 && EXIT_CODE="$?" || EXIT_CODE="$?"
# Other input error
test "$EXIT_CODE" = "6"

cat << EOF > "$TMP/expected.txt"
error: The resolve target does not exist on the filesystem
at resolve path $(realpath "$TMP")/does-not-exist.json
at line 3
at column 5
at file path $(realpath "$TMP")/jsonschema.json
at location "/resolve/https:~1~1example.com~1my-defs"
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"

"$BIN" lint schema.json --json > "$TMP/output_json.txt" 2>&1 && EXIT_CODE="$?" || EXIT_CODE="$?"
# Other input error
test "$EXIT_CODE" = "6"

cat << EOF > "$TMP/expected_json.txt"
{
"error": "The resolve target does not exist on the filesystem",
"resolvePath": "$(realpath "$TMP")/does-not-exist.json",
"line": 3,
"column": 5,
"filePath": "$(realpath "$TMP")/jsonschema.json",
"location": "/resolve/https:~1~1example.com~1my-defs"
}
EOF

diff "$TMP/output_json.txt" "$TMP/expected_json.txt"
55 changes: 55 additions & 0 deletions test/lint/pass_lint_config_resolve_custom_dialect.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/sh

set -o errexit
set -o nounset

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

mkdir "$TMP/v2"

cat << 'EOF' > "$TMP/v2/dialect.json"
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/my-dialect",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
"https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
"https://json-schema.org/draft/2020-12/vocab/validation": true,
"https://json-schema.org/draft/2020-12/vocab/meta-data": true,
"https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
"https://json-schema.org/draft/2020-12/vocab/content": true
},
"type": "object"
}
EOF

cat << 'EOF' > "$TMP/v2/person.json"
{
"$schema": "https://example.com/my-dialect",
"title": "Person",
"description": "A person schema",
"type": "object",
"examples": [ { "name": "John" } ]
}
EOF

cat << 'EOF' > "$TMP/jsonschema.json"
{
"resolve": {
"https://example.com/my-dialect": "./v2/dialect.json"
}
}
EOF

BIN="$(realpath "$1")"
cd "$TMP"
"$BIN" lint v2/person.json --verbose > "$TMP/output.txt" 2>&1

cat << EOF > "$TMP/expected.txt"
Linting: $(realpath "$TMP")/v2/person.json
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"
56 changes: 56 additions & 0 deletions test/lint/pass_lint_config_resolve_custom_dialect_with_path.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/sh

set -o errexit
set -o nounset

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

mkdir "$TMP/v2"

cat << 'EOF' > "$TMP/v2/dialect.json"
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/my-dialect",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
"https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
"https://json-schema.org/draft/2020-12/vocab/validation": true,
"https://json-schema.org/draft/2020-12/vocab/meta-data": true,
"https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
"https://json-schema.org/draft/2020-12/vocab/content": true
},
"type": "object"
}
EOF

cat << 'EOF' > "$TMP/v2/person.json"
{
"$schema": "https://example.com/my-dialect",
"title": "Person",
"description": "A person schema",
"type": "object",
"examples": [ { "name": "John" } ]
}
EOF

cat << 'EOF' > "$TMP/jsonschema.json"
{
"path": "v2",
"resolve": {
"https://example.com/my-dialect": "./v2/dialect.json"
}
}
EOF

BIN="$(realpath "$1")"
cd "$TMP"
"$BIN" lint v2/person.json --verbose > "$TMP/output.txt" 2>&1

cat << EOF > "$TMP/expected.txt"
Linting: $(realpath "$TMP")/v2/person.json
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"
Loading