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
13 changes: 13 additions & 0 deletions src/oold/validation/compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,11 @@ def record(description: str, kind: str, passed: bool, detail: str = "") -> None:
return
try:
feature_schema = dereference(schema_ref)
# This is the compliance suite itself, walking fixture files rather than one committed
# document: a bad `schemaRef` in one group must record a "setup" failure for that group
# and let `run_suite` move on to the next, not abort the whole suite. `dereference` can
# also raise something other than SchemaResolutionError - a fixture resolving to a
# non-dict schema fails differently downstream - so the catch stays broad to match.
except Exception as exc:
record(str(schema_ref), "setup", False, f"could not dereference: {exc}")
return
Expand Down Expand Up @@ -333,6 +338,9 @@ def record(description: str, kind: str, passed: bool, detail: str = "") -> None:
got_rdf == want_rdf,
"" if got_rdf == want_rdf else f"not isomorphic\n got: {got_rdf}\n want: {want_rdf}",
)
# One bad fixture case must record an "rdf" failure and let the loop continue to the
# next test in the group, not abort the whole suite; fixture `data` is arbitrary, so
# this is not limited to JsonLdError.
except Exception as exc:
record(description, "rdf", False, describe_jsonld_error(exc))

Expand All @@ -359,6 +367,8 @@ def record(description: str, kind: str, passed: bool, detail: str = "") -> None:
else f"instance != reconstruction\n in: {json.dumps(canonical(document))}"
f"\n out: {json.dumps(canonical(restored))}",
)
# Same reasoning as the "rdf" case above: one bad fixture case records a "roundtrip"
# failure and the loop continues, rather than the whole suite aborting.
except Exception as exc:
record(description, "roundtrip", False, describe_jsonld_error(exc))

Expand All @@ -367,6 +377,9 @@ def record(description: str, kind: str, passed: bool, detail: str = "") -> None:
raised: BaseException | None = None
try:
jsonld.to_rdf(data, loader.options(base=rdf_base, format="application/n-quads"))
# An `expectErrorCode` fixture asserts that processing this data *does* raise, so
# catching broadly is the point here, not a fallback: whatever comes out is exactly
# what `_error_code` and the assertion below need to inspect.
except Exception as exc:
raised = exc
if raised is None:
Expand Down
7 changes: 7 additions & 0 deletions src/oold/validation/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,13 @@ def generate(schema: dict[str, Any], unique_ids: bool = True) -> GenerationResul
except RecursionError:
result.error = "generation recursed too deeply; the schema may not be fully bounded"
return result
# `schema` reaches here even when `schema.meta` already failed: only unresolved `$ref`s stop
# the pipeline before this call, not meta-schema invalidity. `_generate` walks keyword values
# such as `properties` assuming they are shaped as the meta-schema requires, so a malformed
# one (say a string instead of an object) raises a plain AttributeError/TypeError from this
# module's own traversal, not from anything with a narrower, catchable type. Letting it
# propagate would abort the whole directory run over one bad schema instead of reporting a
# `generate.satisfiable` FAIL and moving on to the next file.
except Exception as exc:
result.error = f"generation failed: {type(exc).__name__}: {exc}"
return result
Expand Down
12 changes: 12 additions & 0 deletions src/oold/validation/instance_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ def validate_instance(instance: Any, schema: dict[str, Any]) -> InstanceCheckRes
try:
validator = Draft202012Validator(schema, format_checker=OOLD_FORMAT_CHECKER)
errors = sorted(validator.iter_errors(instance), key=lambda e: list(e.absolute_path))
# `schema` is whatever the instance's `$schema` dereferences to; nothing here has checked it
# is a well-formed OO-LD schema first, so it can carry a malformed keyword value (say
# `properties` typed as something other than an object) that makes jsonschema's internals
# raise a plain AttributeError/TypeError while walking it as a validator, not a
# jsonschema-specific type. Reporting that as an `instance.schema` FAIL, rather than raising,
# is what lets the run continue to the next instance file.
except Exception as exc:
result.valid = False
result.errors = [f"could not validate against the schema: {type(exc).__name__}: {exc}"]
Expand Down Expand Up @@ -118,6 +124,12 @@ def roundtrip_instance(
else:
result.method = "compacted"
result.restored = jsonld.compact(back, schema_url, loader.options(base=instance_url))
# Unlike roundtrip.py's `roundtrip()`, `schema` here has not already survived a successful
# `generate()` call - no such gate exists on the instance-check path - so a malformed keyword
# value can still reach `embedded_properties`/`schema_to_frame` and raise a plain
# AttributeError/TypeError from their traversal, on top of the JsonLdError the jsonld.* calls
# themselves can raise. Reporting that as a `roundtrip.instance` FAIL, rather than raising,
# is what lets the run continue to the next instance file.
except Exception as exc:
result.error = describe_jsonld_error(exc)
return result
Expand Down
4 changes: 4 additions & 0 deletions src/oold/validation/pattern_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ def lint(schema: dict[str, Any], bundle: MetaBundle) -> PatternLintResult:
key=lambda e: list(e.absolute_path),
)
result.schema_errors = [format_error(error) for error in errors]
# `schema` here is the same untrusted, not-yet-vetted document `validate_against_meta` sees
# (see the comment there), walked the same way by iter_errors, so the exception surface is
# the same: not just jsonschema-specific types, but plain AttributeError/TypeError from
# malformed keyword values and referencing.exceptions.Unresolvable from a bad `$ref`.
except Exception as exc:
result.schema_errors = [f"pattern lint could not run: {type(exc).__name__}: {exc}"]

Expand Down
5 changes: 5 additions & 0 deletions src/oold/validation/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,11 @@ def _check_schema_jsonld(run: _Run, name: str, raw: dict[str, Any]) -> None:
run.loader.options(base=run.loader.base_url),
)
run.add("context.remote", name, OK)
# Kept broad deliberately. This calls jsonld.expand on the same shared pyld.jsonld
# module predicates.py calls it through, so an unexpected exception here has the same
# nowhere-to-go problem as the one documented there: no attribution path of its own, and
# no per-file guard in validate_directory to land in if it propagates. Narrowing this
# waits on #145 for the same reason predicates.py's catch does.
except Exception as exc:
from .loader import describe_jsonld_error

Expand Down
7 changes: 7 additions & 0 deletions src/oold/validation/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,13 @@ def classify_property(name: str, value: Any, context: Any, options: dict[str, An
"""
try:
expanded = jsonld.expand({"@context": context, name: value, ANCHOR: "anchor"}, options)
# Kept broad deliberately. Routing to ERRORED already distinguishes a genuine processor
# failure from a permitted missing term (2a03e2f) - a narrower catch would only change which
# exceptions get that attribution, not add any. And `validate_directory` has no per-file
# guard (pipeline.py:649-652), so anything this does not catch would discard the verdicts for
# every other file in the run: there is currently nowhere for "this is my bug, not your
# document's" to go without either misattributing it or aborting the run. Narrowing this
# waits on that gap being closed; see #145.
except Exception as exc:
return PropertyOutcome(name=name, status=ERRORED, detail=f"expansion failed: {describe_jsonld_error(exc)}")

Expand Down
8 changes: 7 additions & 1 deletion src/oold/validation/roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from .frame import embedded_properties, instance_rdf_types, schema_to_frame
from .loader import DocumentLoader, describe_jsonld_error
from .resolve import SchemaResolutionError

#: Keys that are metadata rather than data, and are excluded from both comparisons.
_METADATA_KEYS = frozenset({"@context", "$schema"})
Expand Down Expand Up @@ -210,7 +211,12 @@ def roundtrip(
else:
result.method = "compacted"
result.restored = jsonld.compact(back, context_ref, loader.options(base=rdf_base))
except Exception as exc:
# jsonld.to_rdf/from_rdf/frame/compact raise JsonLdError, and the document loader they call
# through converts every resolver failure to one (see loader._loader_error), so those two
# types cover this block. Anything else is a bug in this package's own code - `frame.py`'s
# `embedded_properties`/`schema_to_frame`, most likely - and must not be misreported as a
# `roundtrip.generated` finding against the schema under test.
except (jsonld.JsonLdError, SchemaResolutionError) as exc:
result.ok = False
result.error = describe_jsonld_error(exc)
return result
Expand Down
6 changes: 6 additions & 0 deletions src/oold/validation/schema_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ def validate_against_meta(schema: Any, bundle: MetaBundle) -> MetaValidationResu
bundle.meta_validator().iter_errors(schema),
key=lambda e: list(e.absolute_path),
)
# `schema` here is a candidate document straight off disk, not yet known to be well-formed
# in any way, and iter_errors walks it as jsonschema instance data: a malformed keyword
# value (say `"properties": "x"`) surfaces as a plain AttributeError/TypeError from
# jsonschema's internals, and an unresolvable `$ref` as a referencing.exceptions.Unresolvable
# subclass, not a ValidationError - so no jsonschema-specific type covers this. The docstring
# above commits this function to never raising, which is what a broken-schema caller needs.
except Exception as exc:
return MetaValidationResult(
valid=False,
Expand Down
Loading