forked from ChelseaKR/ctdl-validate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreferences.py
More file actions
103 lines (95 loc) · 4.15 KB
/
Copy pathreferences.py
File metadata and controls
103 lines (95 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""Check 3: reference resolution across this run's effective data model.
A blank node reference that its own payload does not define is an ERROR:
blank node identifiers have no meaning outside the graph that declares them.
An absolute IRI is judged against everything the run has in hand. Resolving
inside the payload is silent, and check 4 goes on to judge the target's class.
Resolving inside a document supplied with ``--resolve`` is an INFO note naming
the file, because every claim check 4 then makes about that target rests on
that file having been supplied. Resolving nowhere is UNVERIFIABLE, not a
failure: the entity may exist in the Registry or elsewhere, and this tool
fetches nothing at validation time.
"""
from __future__ import annotations
from .. import rules
from ..findings import Finding, Severity
from ..graph import NestedRef
from ..session import Session
def _absolute_iri_finding(entity: str, prop: str, value: str, session: Session) -> Finding:
"""What to say about an IRI the payload itself does not define."""
supplied, schema = session.supplied, session.schema
external = supplied.get(value)
if external is not None:
declared = schema.known_types(external.types)
as_what = (
f"typed [{', '.join(declared)}]"
if declared
else "carrying no class this schema snapshot declares"
)
return Finding(
code="REF_RESOLVED_SUPPLIED",
severity=Severity.INFO,
entity=entity,
prop=prop,
value=value,
message=(
f"Reference resolves in {external.source}, supplied with --resolve, "
f"{as_what}. Anything this report says about the referenced entity "
"rests on that document."
),
rule=rules.RESOLUTION_POLICY,
)
detail = (
f" {supplied.shortfall()}"
if supplied.any_documents
else " Pass it with --resolve to settle this."
)
return Finding(
code="REF_OUTSIDE_PAYLOAD",
severity=Severity.UNVERIFIABLE,
entity=entity,
prop=prop,
value=value,
message=(
"Reference does not resolve inside this payload. It may exist in the "
"Registry or elsewhere; without fetching it, its existence and class "
"cannot be confirmed or denied." + detail
),
rule=rules.NO_NETWORK_POLICY,
)
def check(session: Session) -> list[Finding]:
graph, schema = session.graph, session.schema
findings: list[Finding] = []
for node in graph.nodes:
entity = node.label
for prop, values in sorted(node.props.items()):
prop_def = schema.properties.get(prop)
if prop_def is None or not prop_def.id_coerced or not prop_def.range_has_entities:
continue
for value in values:
if isinstance(value, NestedRef):
continue # inline containment: trivially resolved
if not isinstance(value, str):
continue
if graph.resolve(value) is not None:
continue
if value.startswith("_:"):
findings.append(
Finding(
code="REF_UNRESOLVED_BNODE",
severity=Severity.ERROR,
entity=entity,
prop=prop,
value=value,
message=(
"Blank node reference is not defined anywhere in this "
"payload. A blank node identifier only has meaning inside "
"the graph that declares it, so this reference cannot "
"identify anything."
),
rule=rules.BNODE_SCOPE,
)
)
elif ":" in value:
findings.append(_absolute_iri_finding(entity, prop, value, session))
# Non-IRI strings are already reported by the identifier-kind check.
return findings