Skip to content

Commit 8527a7d

Browse files
authored
Update validate_questions.py
1 parent 298531f commit 8527a7d

File tree

1 file changed

+74
-13
lines changed

1 file changed

+74
-13
lines changed

utils/validate_questions.py

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,89 @@
11
#!/usr/bin/env python
22
"""
3-
Validate build/*.json against schemas/question.schema.json
3+
Validate build/*.json (or files passed on the CLI) against schemas/question.schema.json
44
"""
55

6-
import json, pathlib, sys
7-
from jsonschema import Draft7Validator
6+
import json
7+
import pathlib
8+
import sys
9+
from json.decoder import JSONDecodeError
10+
from jsonschema import Draft7Validator, exceptions as js_exceptions
811

9-
SCHEMA = json.load(open("schemas/question.schema.json"))
12+
HERE = pathlib.Path(__file__).resolve().parent
13+
SCHEMA_PATH = (HERE / ".." / "schemas" / "question.schema.json").resolve()
1014

11-
def validate_file(fp: pathlib.Path):
12-
data = json.load(fp.open())
13-
errors = list(Draft7Validator(SCHEMA).iter_errors(data))
15+
def load_json_strict(path: pathlib.Path):
16+
try:
17+
with path.open("r", encoding="utf-8") as f:
18+
return json.load(f)
19+
except JSONDecodeError as e:
20+
# Helpful context around the error location
21+
text = path.read_text(encoding="utf-8", errors="replace")
22+
start = max(e.pos - 60, 0)
23+
end = min(e.pos + 60, len(text))
24+
snippet = text[start:end]
25+
pointer = " " * (e.pos - start) + "^"
26+
print(f"\n❌ JSON parse error in {path} @ line {e.lineno}, col {e.colno}: {e.msg}")
27+
print(" Context:")
28+
print(snippet)
29+
print(pointer)
30+
raise
31+
32+
def load_schema():
33+
if not SCHEMA_PATH.exists():
34+
print(f"❌ Schema not found at {SCHEMA_PATH}")
35+
sys.exit(1)
36+
schema = load_json_strict(SCHEMA_PATH)
37+
try:
38+
Draft7Validator.check_schema(schema)
39+
except js_exceptions.SchemaError as se:
40+
print("❌ The schema file is not a valid Draft-07 JSON Schema.")
41+
print(" ", se.message)
42+
sys.exit(1)
43+
return schema
44+
45+
def validate_file(validator: Draft7Validator, fp: pathlib.Path) -> bool:
46+
try:
47+
data = load_json_strict(fp)
48+
except JSONDecodeError:
49+
print(f" (Could not parse {fp.name} as JSON.)")
50+
return False
51+
52+
errors = sorted(validator.iter_errors(data), key=lambda e: e.path)
1453
if errors:
1554
print(f"❌ {fp.name}")
1655
for e in errors:
17-
path = "/".join(map(str, e.path))
18-
print(" •", path, e.message)
56+
path = "/".join(map(str, e.path)) or "(root)"
57+
print(" •", path, "-", e.message)
1958
return False
20-
print(f"✓ {fp.name}")
21-
return True
59+
else:
60+
print(f"✓ {fp.name}")
61+
return True
2262

2363
def main():
24-
build_dir = pathlib.Path("build")
25-
ok = all(validate_file(f) for f in build_dir.glob("*.json"))
64+
schema = load_schema()
65+
validator = Draft7Validator(schema)
66+
67+
# Files passed on CLI? Use those. Otherwise default to build/*.json
68+
args = [pathlib.Path(a) for a in sys.argv[1:]]
69+
if args:
70+
files = []
71+
for a in args:
72+
if a.is_dir():
73+
files.extend(sorted(a.glob("*.json")))
74+
else:
75+
files.append(a)
76+
else:
77+
files = sorted((HERE / ".." / "build").resolve().glob("*.json"))
78+
79+
if not files:
80+
print("No JSON files found to validate.")
81+
sys.exit(0)
82+
83+
ok = True
84+
for fp in files:
85+
ok = validate_file(validator, fp) and ok
86+
2687
sys.exit(0 if ok else 1)
2788

2889
if __name__ == "__main__":

0 commit comments

Comments
 (0)