Skip to content
Draft
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
44 changes: 36 additions & 8 deletions src/index/generators.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,33 @@ struct GENERATE_MATERIALISED_SCHEMA {
const auto metaschema{resolver(dialect_identifier)};
assert(metaschema.has_value());

// If this schema declares $vocabulary (i.e. it is a metaschema in a
// vocabulary-aware dialect), reject any unknown required vocabularies.
// Pre-vocabulary dialects (draft-4, etc.) may have $vocabulary as a
// regular property, so only check 2020-12 and 2019-09 based schemas.
const auto schema_base_dialect{
sourcemeta::core::to_base_dialect(dialect_identifier)};
if (schema_base_dialect.has_value() &&
(schema_base_dialect.value() ==
sourcemeta::core::SchemaBaseDialect::JSON_Schema_2020_12 ||
schema_base_dialect.value() ==
sourcemeta::core::SchemaBaseDialect::JSON_Schema_2019_09) &&
schema->is_object() && schema->defines("$vocabulary") &&
schema->at("$vocabulary").is_object()) {
sourcemeta::core::Vocabularies declared_vocabularies;
for (const auto &entry : schema->at("$vocabulary").as_object()) {
declared_vocabularies.insert(entry.first, entry.second.to_boolean());
}
try {
declared_vocabularies.throw_if_any_unknown_required(
"The metaschema requires an unrecognised vocabulary");
} catch (const sourcemeta::core::SchemaVocabularyError &error) {
throw sourcemeta::core::FileError<
sourcemeta::core::SchemaVocabularyError>(
resolver.entry(action.data).path, error.uri(), error.what());
}
}

// Validate the schemas against their meta-schemas
sourcemeta::blaze::SimpleOutput output{schema.value()};
sourcemeta::blaze::Evaluator evaluator;
Expand Down Expand Up @@ -531,19 +558,20 @@ struct GENERATE_EDITOR {
static auto generate_blaze_template(
const std::filesystem::path &destination,
const sourcemeta::one::BuildPlan::Action::Dependencies &dependencies,
const sourcemeta::one::Resolver &resolver,
const sourcemeta::blaze::Mode mode) -> void {
const auto timestamp_start{std::chrono::steady_clock::now()};
const auto contents_option{
sourcemeta::one::metapack_read_json(dependencies.front())};
assert(contents_option.has_value());
const auto &contents{contents_option.value()};
const auto resolver_wrapper{
[&resolver](const auto identifier) { return resolver(identifier); }};
sourcemeta::core::SchemaFrame frame{
sourcemeta::core::SchemaFrame::Mode::References};
frame.analyse(contents, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver);
frame.analyse(contents, sourcemeta::core::schema_walker, resolver_wrapper);
const auto schema_template{sourcemeta::blaze::compile(
contents, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver,
contents, sourcemeta::core::schema_walker, resolver_wrapper,
sourcemeta::blaze::default_schema_compiler, frame, frame.root(), mode)};
const auto result{sourcemeta::blaze::to_json(schema_template)};
const auto timestamp_end{std::chrono::steady_clock::now()};
Expand All @@ -558,10 +586,10 @@ struct GENERATE_BLAZE_TEMPLATE_EXHAUSTIVE {
static auto handler(const sourcemeta::one::BuildState &,
const sourcemeta::one::BuildPlan::Action &action,
const sourcemeta::one::BuildDynamicCallback &,
sourcemeta::one::Resolver &,
sourcemeta::one::Resolver &resolver,
const sourcemeta::one::Configuration &,
const sourcemeta::core::JSON &) -> void {
generate_blaze_template(action.destination, action.dependencies,
generate_blaze_template(action.destination, action.dependencies, resolver,
sourcemeta::blaze::Mode::Exhaustive);
}
};
Expand All @@ -570,10 +598,10 @@ struct GENERATE_BLAZE_TEMPLATE_FAST {
static auto handler(const sourcemeta::one::BuildState &,
const sourcemeta::one::BuildPlan::Action &action,
const sourcemeta::one::BuildDynamicCallback &,
sourcemeta::one::Resolver &,
sourcemeta::one::Resolver &resolver,
const sourcemeta::one::Configuration &,
const sourcemeta::core::JSON &) -> void {
generate_blaze_template(action.destination, action.dependencies,
generate_blaze_template(action.destination, action.dependencies, resolver,
sourcemeta::blaze::Mode::FastValidation);
}
};
Expand Down
23 changes: 23 additions & 0 deletions src/index/index.cc
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,19 @@ static auto execute_plan(std::mutex &mutex,
sourcemeta::core::SchemaReferenceObjectResourceError>(
entry->path, error.identifier());
}
throw;
} catch (const sourcemeta::core::FileError<
sourcemeta::core::SchemaVocabularyError> &) {
throw;
} catch (const sourcemeta::core::SchemaVocabularyError &error) {
const auto *entry{
action.data.empty() ? nullptr : &resolver.entry(action.data)};
if (entry) {
throw sourcemeta::core::FileError<
sourcemeta::core::SchemaVocabularyError>(
entry->path, error.uri(), error.what());
}

throw;
}

Expand Down Expand Up @@ -670,6 +683,16 @@ auto main(int argc, char *argv[]) noexcept -> int {
} catch (const sourcemeta::blaze::LinterMissingNameError &error) {
std::cerr << "error: " << error.what() << "\n";
return EXIT_FAILURE;
} catch (
const sourcemeta::core::FileError<sourcemeta::core::SchemaVocabularyError>
&error) {
std::cerr << "error: " << error.what() << "\n at vocabulary "
<< error.uri() << "\n at path " << error.path().string() << "\n";
return EXIT_FAILURE;
} catch (const sourcemeta::core::SchemaVocabularyError &error) {
std::cerr << "error: " << error.what() << "\n at vocabulary "
<< error.uri() << "\n";
return EXIT_FAILURE;
} catch (const sourcemeta::core::FileError<
sourcemeta::core::SchemaUnknownBaseDialectError> &error) {
std::cerr << "error: " << error.what() << "\n at path "
Expand Down
5 changes: 5 additions & 0 deletions test/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ if(ONE_INDEX)
sourcemeta_one_test_cli(common index fail-schema-self-metaschema)
sourcemeta_one_test_cli(common index fail-self-referencing-metaschema)
sourcemeta_one_test_cli(common index fail-unknown-dialect)
sourcemeta_one_test_cli(common index fail-unknown-required-vocabulary)
sourcemeta_one_test_cli(common index fail-unknown-required-vocabulary-standalone)
sourcemeta_one_test_cli(common index fail-no-evaluate-unknown-required-vocabulary)
sourcemeta_one_test_cli(common index fail-unknown-option)
sourcemeta_one_test_cli(common index fail-vocabulary-not-object)
sourcemeta_one_test_cli(common index draft4-ignore-vocabulary)
sourcemeta_one_test_cli(common index extra-files-on-rebuild)
sourcemeta_one_test_cli(common index directory-schema-same-name)
sourcemeta_one_test_cli(common index rebuild-two-to-three)
Expand Down
80 changes: 80 additions & 0 deletions test/cli/index/common/draft4-ignore-vocabulary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/sh

set -o errexit
set -o nounset

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

cat << EOF > "$TMP/one.json"
{
"url": "https://sourcemeta.com/",
"contents": {
"example": {
"contents": {
"schemas": {
"baseUri": "https://example.com/",
"path": "./schemas"
}
}
}
}
}
EOF

mkdir "$TMP/schemas"

cat << 'EOF' > "$TMP/schemas/test.json"
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://example.com/test",
"$vocabulary": {
"https://example.com/vocab/totally-unknown": true
},
"type": "string"
}
EOF

"$1" --skip-banner "$TMP/one.json" "$TMP/output" --concurrency 1 2> "$TMP/output.txt"

# Remove thread information
if [ "$(uname)" = "Darwin" ]
then
sed -i '' 's/ \[.*\]//g' "$TMP/output.txt"
else
sed -i 's/ \[.*\]//g' "$TMP/output.txt"
fi

cat << EOF > "$TMP/expected.txt"
Writing output to: $(realpath "$TMP")/output
Using configuration: $(realpath "$TMP")/one.json
Detecting: $(realpath "$TMP")/schemas/test.json (#1)
(100%) Resolving: test.json
( 4%) Producing: configuration.json
( 8%) Producing: version.json
( 13%) Producing: explorer/%/404.metapack
( 17%) Producing: schemas/example/schemas/test/%/schema.metapack
( 21%) Producing: schemas/example/schemas/test/%/dependencies.metapack
( 26%) Producing: schemas/example/schemas/test/%/locations.metapack
( 30%) Producing: schemas/example/schemas/test/%/positions.metapack
( 34%) Producing: schemas/example/schemas/test/%/stats.metapack
( 39%) Producing: schemas/example/schemas/test/%/bundle.metapack
( 43%) Producing: schemas/example/schemas/test/%/health.metapack
( 47%) Producing: explorer/example/schemas/test/%/schema.metapack
( 52%) Producing: schemas/example/schemas/test/%/blaze-exhaustive.metapack
( 56%) Producing: schemas/example/schemas/test/%/blaze-fast.metapack
( 60%) Producing: schemas/example/schemas/test/%/editor.metapack
( 65%) Producing: explorer/example/schemas/%/directory.metapack
( 69%) Producing: explorer/example/schemas/test/%/schema-html.metapack
( 73%) Producing: explorer/example/%/directory.metapack
( 78%) Producing: explorer/example/schemas/%/directory-html.metapack
( 82%) Producing: explorer/%/directory.metapack
( 86%) Producing: explorer/example/%/directory-html.metapack
( 91%) Producing: explorer/%/directory-html.metapack
( 95%) Producing: explorer/%/search.metapack
(100%) Producing: routes.bin
(100%) Combining: schemas/example/schemas/test/%/dependents.metapack
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/sh

set -o errexit
set -o nounset

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

mkdir "$TMP/schemas"

# A custom metaschema that requires an unknown vocabulary
cat << 'EOF' > "$TMP/schemas/custom-meta.json"
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/custom-meta",
"$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/validation": true,
"https://example.com/vocab/totally-unknown": true
}
}
EOF

# A schema that uses the custom metaschema
cat << 'EOF' > "$TMP/schemas/test.json"
{
"$schema": "https://example.com/custom-meta",
"$id": "https://example.com/test",
"type": "string"
}
EOF

cat << EOF > "$TMP/one.json"
{
"url": "http://localhost:8000",
"html": false,
"contents": {
"example": {
"baseUri": "https://example.com",
"path": "./schemas",
"x-sourcemeta-one:evaluate": false
}
}
}
EOF

"$1" --skip-banner "$TMP/one.json" "$TMP/output" --concurrency 1 2> "$TMP/output.txt" && CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

# Remove thread information
if [ "$(uname)" = "Darwin" ]
then
sed -i '' 's/ \[.*\]//g' "$TMP/output.txt"
else
sed -i 's/ \[.*\]//g' "$TMP/output.txt"
fi

cat << EOF > "$TMP/expected.txt"
Writing output to: $(realpath "$TMP")/output
Using configuration: $(realpath "$TMP")/one.json
Detecting: $(realpath "$TMP")/schemas/custom-meta.json (#1)
Detecting: $(realpath "$TMP")/schemas/test.json (#2)
( 50%) Resolving: custom-meta.json
(100%) Resolving: test.json
( 4%) Producing: configuration.json
( 8%) Producing: version.json
( 12%) Producing: schemas/example/custom-meta/%/schema.metapack
error: The metaschema requires an unrecognised vocabulary
at vocabulary https://example.com/vocab/totally-unknown
at path $(realpath "$TMP")/schemas/custom-meta.json
EOF

diff "$TMP/output.txt" "$TMP/expected.txt"
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/sh

set -o errexit
set -o nounset

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

cat << EOF > "$TMP/one.json"
{
"url": "https://sourcemeta.com/",
"contents": {
"example": {
"contents": {
"schemas": {
"baseUri": "https://example.com/",
"path": "./schemas"
}
}
}
}
}
EOF

mkdir "$TMP/schemas"

# A custom metaschema that requires an unknown vocabulary,
# with no other schema using it
cat << 'EOF' > "$TMP/schemas/custom-meta.json"
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/custom-meta",
"$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/validation": true,
"https://example.com/vocab/totally-unknown": true
}
}
EOF

"$1" --skip-banner "$TMP/one.json" "$TMP/output" --concurrency 1 2> "$TMP/output.txt" && CODE="$?" || CODE="$?"
test "$CODE" = "1" || exit 1

# Remove thread information
if [ "$(uname)" = "Darwin" ]
then
sed -i '' 's/ \[.*\]//g' "$TMP/output.txt"
else
sed -i 's/ \[.*\]//g' "$TMP/output.txt"
fi

cat << EOF > "$TMP/expected.txt"
Writing output to: $(realpath "$TMP")/output
Using configuration: $(realpath "$TMP")/one.json
Detecting: $(realpath "$TMP")/schemas/custom-meta.json (#1)
(100%) Resolving: custom-meta.json
( 4%) Producing: configuration.json
( 8%) Producing: version.json
( 13%) Producing: explorer/%/404.metapack
( 17%) Producing: schemas/example/schemas/custom-meta/%/schema.metapack
error: The metaschema requires an unrecognised vocabulary
at vocabulary https://example.com/vocab/totally-unknown
at path $(realpath "$TMP")/schemas/custom-meta.json
EOF

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