Skip to content

Commit 3313a7b

Browse files
Resolve typed record references during batch validation
1 parent 117be8a commit 3313a7b

1 file changed

Lines changed: 51 additions & 8 deletions

File tree

scripts/validate_repo.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,20 @@
2323
"visitor": "visitor_id",
2424
"tag": "tag_slug",
2525
}
26+
REFERENCE_FIELDS = {
27+
"packet": (("response_packet_id", "response"),),
28+
"response": (("source_packet_id", "packet"),),
29+
"message": (
30+
("reply_to", "message"),
31+
("response_message_id", "message"),
32+
("related_packet", "packet"),
33+
("related_response", "response"),
34+
),
35+
"notification": (("message_id", "message"),),
36+
}
2637

2738

28-
def load_json(path: Path):
39+
def load_json(path: Path) -> dict:
2940
with path.open(encoding="utf-8") as handle:
3041
return json.load(handle)
3142

@@ -75,7 +86,9 @@ def validate_path_fields(record: dict) -> list[str]:
7586
return errors
7687

7788

78-
def validate(path: Path, enforce_filename: bool = False) -> list[str]:
89+
def validate_document(
90+
path: Path, enforce_filename: bool
91+
) -> tuple[dict | None, list[str]]:
7992
try:
8093
record = load_json(path)
8194
schema_path = schema_for(record)
@@ -96,9 +109,33 @@ def validate(path: Path, enforce_filename: bool = False) -> list[str]:
96109
for message in validate_filename(record, path, enforce_filename)
97110
)
98111
messages.extend(f"{path}: {message}" for message in validate_path_fields(record))
99-
return messages
112+
return record, messages
100113
except (OSError, ValueError, json.JSONDecodeError) as error:
101-
return [f"{path}: {error}"]
114+
return None, [f"{path}: {error}"]
115+
116+
117+
def validate_references(records: list[tuple[Path, dict]]) -> list[str]:
118+
index: dict[str, set[str]] = {}
119+
for _, record in records:
120+
record_type = record.get("record_type")
121+
identifier_field = ID_FIELDS.get(record_type)
122+
identifier = record.get(identifier_field) if identifier_field else None
123+
if isinstance(record_type, str) and isinstance(identifier, str):
124+
index.setdefault(record_type, set()).add(identifier)
125+
126+
errors: list[str] = []
127+
for path, record in records:
128+
record_type = record.get("record_type")
129+
for field, target_type in REFERENCE_FIELDS.get(record_type, ()):
130+
target_id = record.get(field)
131+
if target_id is None:
132+
continue
133+
if not isinstance(target_id, str) or target_id not in index.get(target_type, set()):
134+
errors.append(
135+
f"{path}: {field}: no {target_type} record with ID {target_id!r} "
136+
"in the validation set"
137+
)
138+
return errors
102139

103140

104141
def main() -> int:
@@ -107,6 +144,7 @@ def main() -> int:
107144
parser.add_argument("--fixtures", action="store_true")
108145
parser.add_argument("--examples", action="store_true")
109146
parser.add_argument("--enforce-filename", action="store_true")
147+
parser.add_argument("--check-references", action="store_true")
110148
args = parser.parse_args()
111149

112150
paths = list(args.paths)
@@ -117,11 +155,16 @@ def main() -> int:
117155
if not paths:
118156
parser.error("supply JSON paths, --fixtures, or --examples")
119157

120-
errors = [
121-
item
122-
for path in paths
123-
for item in validate(path, enforce_filename=args.enforce_filename)
158+
documents = [
159+
(path, *validate_document(path, args.enforce_filename)) for path in paths
124160
]
161+
errors = [item for _, _, messages in documents for item in messages]
162+
if args.check_references:
163+
valid_records = [
164+
(path, record) for path, record, _ in documents if record is not None
165+
]
166+
errors.extend(validate_references(valid_records))
167+
125168
print("\n".join(errors) if errors else f"validated {len(paths)} record(s)")
126169
return 1 if errors else 0
127170

0 commit comments

Comments
 (0)