Skip to content

Commit

Permalink
Support passing a schema URI with a fragment as a test target
Browse files Browse the repository at this point in the history
See: #110
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti committed Jul 15, 2024
1 parent 04a906b commit 5df3f26
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 5 deletions.
44 changes: 39 additions & 5 deletions src/command_test.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
#include <sourcemeta/jsontoolkit/json.h>
#include <sourcemeta/jsontoolkit/jsonschema.h>
#include <sourcemeta/jsontoolkit/uri.h>

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

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

static auto
get_schema_object(const sourcemeta::jsontoolkit::URI &identifier,
const sourcemeta::jsontoolkit::SchemaResolver &resolver)
-> std::optional<sourcemeta::jsontoolkit::JSON> {
const auto schema{resolver(identifier.recompose()).get()};
if (schema.has_value()) {
return schema;
}

// Resolving a schema identifier that contains a fragment (i.e. a JSON Pointer
// one) can be tricky, as we might end up re-inventing JSON Schema referencing
// all over again. To make it work without much hassle, we do exactly that:
// create an artificial schema wrapper that uses `$ref`.
if (identifier.fragment().has_value()) {
auto result{sourcemeta::jsontoolkit::JSON::make_object()};
result.assign("$schema", sourcemeta::jsontoolkit::JSON{
"http://json-schema.org/draft-07/schema#"});
result.assign("$ref",
sourcemeta::jsontoolkit::JSON{identifier.recompose()});
return result;
}

return std::nullopt;
}

auto intelligence::jsonschema::cli::test(
const std::span<const std::string> &arguments) -> int {
const auto options{parse_options(arguments, {"h", "http"})};
Expand Down Expand Up @@ -65,12 +91,11 @@ auto intelligence::jsonschema::cli::test(
return EXIT_FAILURE;
}

const auto schema{test_resolver(test.at("target").to_string()).get()};
sourcemeta::jsontoolkit::URI schema_uri{test.at("target").to_string()};
schema_uri.canonicalize();
const auto schema{get_schema_object(schema_uri, test_resolver)};
if (!schema.has_value()) {
if (verbose) {
std::cout << "\n";
}

std::cout << "\n";
throw sourcemeta::jsontoolkit::SchemaResolutionError(
test.at("target").to_string(), "Could not resolve schema under test");
}
Expand All @@ -90,6 +115,15 @@ auto intelligence::jsonschema::cli::test(
schema_template = sourcemeta::jsontoolkit::compile(
schema.value(), sourcemeta::jsontoolkit::default_schema_walker,
test_resolver, sourcemeta::jsontoolkit::default_schema_compiler);
} catch (const sourcemeta::jsontoolkit::SchemaReferenceError &error) {
if (error.location().empty() && error.id() == schema_uri.recompose()) {
std::cout << "\n";
throw sourcemeta::jsontoolkit::SchemaResolutionError(
test.at("target").to_string(),
"Could not resolve schema under test");
}

throw;
} catch (...) {
std::cout << "\n";
throw;
Expand Down
4 changes: 4 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ add_jsonschema_test_unix(test/fail_false_single_resolve_verbose)
add_jsonschema_test_unix(test/fail_multi_resolve)
add_jsonschema_test_unix(test/fail_multi_resolve_verbose)
add_jsonschema_test_unix(test/fail_unresolvable)
add_jsonschema_test_unix(test/fail_unresolvable_fragment)
add_jsonschema_test_unix(test/fail_unresolvable_anchor)
add_jsonschema_test_unix(test/fail_unsupported)
add_jsonschema_test_unix(test/fail_unsupported_verbose)
add_jsonschema_test_unix(test/fail_not_object)
Expand All @@ -87,6 +89,8 @@ add_jsonschema_test_unix(test/pass_empty)
add_jsonschema_test_unix(test/pass_empty_verbose)
add_jsonschema_test_unix(test/pass_single_resolve)
add_jsonschema_test_unix(test/pass_single_resolve_verbose)
add_jsonschema_test_unix(test/pass_single_resolve_fragment)
add_jsonschema_test_unix(test/pass_single_resolve_fragment_verbose)
add_jsonschema_test_unix(test/pass_single_comment_verbose)
add_jsonschema_test_unix(test/pass_single_no_description_verbose)
add_jsonschema_test_unix(test/pass_single_no_test_description_verbose)
Expand Down
48 changes: 48 additions & 0 deletions test/test/fail_unresolvable_anchor.sh
Original file line number Diff line number Diff line change
@@ -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/schema.json"
{
"$id": "https://example.com",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"foo": { "type": "string" },
"bar": { "type": "integer" }
}
}
EOF

cat << 'EOF' > "$TMP/test.json"
{
"target": "https://example.com#foo",
"tests": [
{
"valid": true,
"data": {}
},
{
"valid": true,
"data": { "type": 1 }
}
]
}
EOF

"$1" test "$TMP/test.json" --resolve "$TMP/schema.json" --verbose 1> "$TMP/output.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
Importing schema into the resolution context: $(realpath "$TMP")/schema.json
$(realpath "$TMP")/test.json:
error: Could not resolve schema under test
at https://example.com#foo
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"
48 changes: 48 additions & 0 deletions test/test/fail_unresolvable_fragment.sh
Original file line number Diff line number Diff line change
@@ -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/schema.json"
{
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"foo": { "type": "string" },
"bar": { "type": "integer" }
}
}
EOF

cat << 'EOF' > "$TMP/test.json"
{
"target": "https://example.com#/foo",
"tests": [
{
"valid": true,
"data": {}
},
{
"valid": true,
"data": { "type": 1 }
}
]
}
EOF

"$1" test "$TMP/test.json" --resolve "$TMP/schema.json" --verbose 1> "$TMP/output.txt" 2>&1 \
&& CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

cat << EOF > "$TMP/expected.txt"
Importing schema into the resolution context: $(realpath "$TMP")/schema.json
$(realpath "$TMP")/test.json:
error: Could not resolve schema under test
at https://example.com#/foo
EOF

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

set -o errexit
set -o nounset

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

cat << 'EOF' > "$TMP/schema.json"
{
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"foo": { "type": "string" },
"bar": { "type": "integer" }
}
}
EOF

cat << 'EOF' > "$TMP/test.json"
{
"target": "https://example.com#/definitions/foo",
"tests": [
{
"description": "First test",
"valid": true,
"data": "foo"
},
{
"description": "Invalid type",
"valid": false,
"data": 1
}
]
}
EOF

"$1" test "$TMP/test.json" --resolve "$TMP/schema.json" 1> "$TMP/output.txt" 2>&1

cat << EOF > "$TMP/expected.txt"
$(realpath "$TMP")/test.json: PASS 2/2
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"
48 changes: 48 additions & 0 deletions test/test/pass_single_resolve_fragment_verbose.sh
Original file line number Diff line number Diff line change
@@ -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/schema.json"
{
"id": "https://example.com",
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
"foo": { "type": "string" },
"bar": { "type": "integer" }
}
}
EOF

cat << 'EOF' > "$TMP/test.json"
{
"target": "https://example.com#/definitions/foo",
"tests": [
{
"description": "First test",
"valid": true,
"data": "foo"
},
{
"description": "Invalid type",
"valid": false,
"data": 1
}
]
}
EOF

"$1" test "$TMP/test.json" --resolve "$TMP/schema.json" --verbose 1> "$TMP/output.txt" 2>&1

cat << EOF > "$TMP/expected.txt"
Importing schema into the resolution context: $(realpath "$TMP")/schema.json
$(realpath "$TMP")/test.json:
1/2 PASS First test
2/2 PASS Invalid type
EOF

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

0 comments on commit 5df3f26

Please sign in to comment.