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
25 changes: 23 additions & 2 deletions src/oold/validation/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,9 @@ def _unmapped_properties(run: _Run, name: str, raw, schema, sample) -> set[str]:
Permitted by the specification (`OOLD-SCH-2d05`), so this is not a failure anywhere; it is
reported by `context.coverage` and subtracted from what `roundtrip.generated` calls a loss.
Returns an empty set when the context cannot be resolved - the checks that report resolution
failures do so on their own, and guessing here would double-report it.
failures do so on their own, and guessing here would double-report it. A context that resolves
to nothing is the opposite case and answers the question: no term exists, so every declared
property is unmapped.
"""
if name in run._unmapped:
return run._unmapped[name]
Expand All @@ -418,7 +420,11 @@ def _unmapped_properties(run: _Run, name: str, raw, schema, sample) -> set[str]:
try:
resolved = run.resolver.load(run.directory / name)
context = resolve_context(raw, resolved.base_uri, run.resolver)
if not context.errors and not context.is_empty:
if context.errors:
unmapped = set()
elif context.is_empty:
unmapped = set(collect_composed_properties(schema))
else:
id_key, type_key = find_alias_keys(context.terms())
declared = set(collect_composed_properties(schema)) | {id_key, type_key}
outcome = check_predicates(
Expand Down Expand Up @@ -447,6 +453,21 @@ def _check_predicates(run: _Run, name: str, raw, schema, sample) -> None:
return
if context.is_empty:
run.add("context.predicates", name, SKIP, "schema declares no @context")
# Coverage still has an answer here, and it is the strongest one it ever gives: no term
# exists, so nothing reaches RDF. Staying silent would report a schema mapping one
# property of ten while saying nothing about a schema mapping none of them.
declared = sorted(collect_composed_properties(schema))
if declared and run.options.wants("context.coverage"):
plural = "ies" if declared[1:] else "y"
run.add(
"context.coverage",
name,
FAIL if run.options.strict else WARN,
f"schema declares no @context, so none of its {len(declared)} propert{plural} "
f"will reach RDF: {', '.join(declared)}. Declare @vocab to map them into a "
"default namespace, or add the terms to x-oold-context.",
{"declared": declared},
)
return

_run_rule_checks(run, name, raw, schema, ContextView(terms=context.terms(), entries=list(context.context)))
Expand Down
14 changes: 14 additions & 0 deletions tests/data/oold/broken/no_context.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://oo-ld.org/latest/meta/oold-meta-schema.json",
"$id": "no_context.schema.json",
"title": "NoContext",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"orphan": {
"type": "string"
}
}
}
19 changes: 19 additions & 0 deletions tests/test_validation/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,25 @@ def test_missing_context_term_warns_and_names_the_orphan_property(broken_dir):
assert "context.coverage" not in {c.id for c in report.failures()}


def test_a_schema_with_no_context_warns_instead_of_failing_the_round_trip(broken_dir):
"""No `@context` means no term, so no property is mapped and none can be lost.

`roundtrip.generated` subtracts unmapped properties from what it calls a loss, and an
absent context is the case where that set is every declared property. Reporting them as
lost would fail a schema for the one thing `OOLD-SCH-2d05` says must not fail it.
"""
report = validate_schema(broken_dir / "no_context.schema.json", OFFLINE)
checks = {c.id: c for c in report.checks}

assert checks["roundtrip.generated"].status == OK
assert checks["context.predicates"].status == SKIP
coverage = checks["context.coverage"]
assert coverage.status == WARN
assert coverage.detail["declared"] == ["name", "orphan"]
assert "@vocab" in coverage.message and "x-oold-context" in coverage.message
assert not report.failures()


def test_a_processor_failure_is_not_downgraded_to_a_coverage_warning(broken_dir, monkeypatch):
"""A raised processor must not read as a permitted omission.

Expand Down
Loading