Skip to content

Commit 5f300f7

Browse files
committed
Adding early version of unfinished schema for v4
1 parent 6fe43b8 commit 5f300f7

11 files changed

Lines changed: 564 additions & 0 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Check JSON Schema $ref integrity
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
# Optional: only run when schemas or the script change
7+
paths:
8+
- "schema/**"
9+
- ".github/workflows/check-jsonschema.yml"
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
check-refs:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: "3.11"
26+
cache: "pip"
27+
28+
# If you already have a requirements file, replace this step with pip install -r ...
29+
- name: Install dependencies
30+
run: |
31+
pip install -r requirements.txt
32+
33+
- name: Run $ref checker
34+
run: |
35+
python schema/v4/check_refs.py

schema/v4/agent.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://iiif.io/api/presentation/4.0/schema/agent.json",
4+
"title": "Agent",
5+
"description": "W3C Web Annotation Agent (creator, generator)",
6+
"type": "object",
7+
"properties": {
8+
"id": { "$ref": "properties.json#/$defs/id" },
9+
"type": {
10+
"type": "string",
11+
"pattern": "^Agent$",
12+
"default": "Agent"
13+
},
14+
"name": {
15+
"type": "string"
16+
},
17+
"nickname": {
18+
"type": "string"
19+
},
20+
"email": {
21+
"type": "string"
22+
},
23+
"email_sha1": {
24+
"type": "string"
25+
},
26+
"homepage": {
27+
"type": "string",
28+
"format": "uri"
29+
}
30+
},
31+
"required": ["id", "type"]
32+
}

schema/v4/audiance.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://iiif.io/api/presentation/4.0/schema/audiance.json",
4+
"title": "Audience",
5+
"description": "W3C Web Annotation Audience",
6+
"type": "object",
7+
"properties": {
8+
"id": { "$ref": "properties.json#/$defs/id" },
9+
"type": {
10+
"type": "string"
11+
}
12+
},
13+
"required": [
14+
"type"
15+
]
16+
}

schema/v4/check_refs.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from pathlib import Path
5+
from typing import Any, Dict, Iterable, List, Tuple
6+
from urllib.parse import urljoin, urlparse
7+
from pathlib import PurePosixPath
8+
9+
from referencing import Registry, Resource
10+
from referencing.exceptions import Unresolvable
11+
12+
def iter_refs(node: Any, path: str = "#") -> Iterable[Tuple[str, str]]:
13+
"""
14+
Yield ($ref_value, json_pointer_path_in_schema) for every $ref in a schema tree.
15+
"""
16+
#print (f"Travesring {node}")
17+
if isinstance(node, dict):
18+
if "$ref" in node and isinstance(node["$ref"], str):
19+
yield node["$ref"], path + "/$ref"
20+
for k, v in node.items():
21+
yield from iter_refs(v, f"{path}/{k}")
22+
elif isinstance(node, list):
23+
for i, v in enumerate(node):
24+
yield from iter_refs(v, f"{path}/{i}")
25+
26+
27+
def build_registry_from_dir(schema_dir: str | Path) -> Tuple[Registry, Dict[str, Dict[str, Any]]]:
28+
"""
29+
Load all *.json schemas under schema_dir into a referencing.Registry.
30+
31+
Each resource is keyed by:
32+
- its $id, if present, else
33+
- a file:// URI for its absolute path.
34+
"""
35+
schema_dir = Path(schema_dir)
36+
registry = Registry()
37+
by_uri: Dict[str, Dict[str, Any]] = {}
38+
39+
for path in schema_dir.rglob("*.json"):
40+
with path.open("r", encoding="utf-8") as f:
41+
schema = json.load(f)
42+
43+
print (f"Loading {path.name}")
44+
uri = schema.get("$id")
45+
if not uri:
46+
uri = path.resolve().as_uri() # file:///.../schema.json
47+
48+
resource = Resource.from_contents(schema)
49+
registry = registry.with_resource(uri, resource)
50+
if uri in by_uri:
51+
raise Exception(f"Duplicate schema ID {uri} found in {path.name}")
52+
53+
by_uri[uri] = schema
54+
55+
return registry, by_uri
56+
57+
58+
def find_missing_refs_in_dir(schema_dir: str | Path) -> List[Dict[str, str]]:
59+
"""
60+
Returns a list of unresolved $refs across all schemas in schema_dir.
61+
"""
62+
registry, schemas = build_registry_from_dir(schema_dir)
63+
missing: List[Dict[str, str]] = []
64+
for base_uri, schema in schemas.items():
65+
resolver = registry.resolver(base_uri=base_uri)
66+
67+
for ref, where in iter_refs(schema):
68+
# Make relative refs absolute against the schema's base URI
69+
target = urljoin(base_uri, ref)
70+
71+
try:
72+
resolver.lookup(target)
73+
except Unresolvable:
74+
missing.append(
75+
{
76+
"schema": base_uri,
77+
"ref": ref,
78+
"where": where,
79+
"resolved_target": target,
80+
}
81+
)
82+
83+
return missing
84+
85+
86+
if __name__ == "__main__":
87+
problems = find_missing_refs_in_dir(Path(__file__).resolve().parent)
88+
if problems:
89+
print("\nMissing/unresolvable $refs:")
90+
for p in problems:
91+
print(f"- In {p['schema'].split("/")[-1]}:\n at {p['where']}: {p['ref']} (→ {p['resolved_target']})\n")
92+
raise SystemExit(2)
93+
else:
94+
print("All $refs resolved.")

schema/v4/external.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://iiif.io/api/presentation/4.0/schema/external.json",
4+
"title": "LinkedResources",
5+
"type": "array",
6+
"items": {
7+
"title": "linkedResource",
8+
"type": "object",
9+
"properties": {
10+
"id": { "$ref": "properties.json#/$defs/id" },
11+
"type": {
12+
"type": "string"
13+
},
14+
"format": {
15+
"$ref": "properties.json#/$defs/format"
16+
},
17+
"profile": {
18+
"type": "string"
19+
}
20+
},
21+
"required": ["id", "type"]
22+
}
23+
}

schema/v4/main.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://iiif.io/api/presentation/4.0/schema/main.json",
4+
"title": "AbstractIIIFResource",
5+
"type": "object",
6+
"additionalProperties": true,
7+
"properties": {
8+
"@context": {
9+
"oneOf": [
10+
{ "$ref": "properties.json#/$defs/context" },
11+
{
12+
"$comment": "Array form of @context, with at least one entry that is the IIIF context. The IIIF context MUST be the last item but we can't test that with JSON Schema",
13+
"type": "array",
14+
"items": {
15+
"type": "string",
16+
"format": "uri",
17+
"pattern": "^http.*$"
18+
},
19+
"minItems": 1,
20+
"contains": {
21+
"type": "string",
22+
"$ref": "properties.json#/$defs/context"
23+
},
24+
"minContains": 1,
25+
"maxContains": 1
26+
}
27+
]
28+
},
29+
"id": { "$ref": "properties.json#/$defs/id" },
30+
"type": {
31+
"type": "string",
32+
"pattern": "^Manifest$",
33+
"default": "Manifest"
34+
},
35+
"label": {
36+
"$ref": "properties.json#/$defs/lngString"
37+
},
38+
"metadata": {
39+
"$ref": "metadata.json"
40+
},
41+
"summary": {
42+
"$ref": "properties.json#/$defs/lngString"
43+
},
44+
"requiredStatement": {
45+
"$ref": "properties.json#/$defs/keyValueString"
46+
},
47+
"rendering": {
48+
"$ref": "external.json"
49+
},
50+
"service": {
51+
"$ref": "service.json"
52+
},
53+
"services": {
54+
"$ref": "service.json"
55+
},
56+
"viewingDirection": {
57+
"$ref": "properties.json#/$defs/viewingDirection"
58+
}
59+
},
60+
"required": [
61+
"id",
62+
"type",
63+
"label"
64+
]
65+
}

schema/v4/metadata.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://iiif.io/api/presentation/4.0/schema/metadata.json",
4+
"type": "array",
5+
"title": "metadata",
6+
"items": {
7+
"$ref": "properties.json#/$defs/keyValueString"
8+
}
9+
}

0 commit comments

Comments
 (0)