Skip to content

Commit c7122dc

Browse files
refactor(validation): DRY field checks, clearer root path, null test
1 parent 131194f commit c7122dc

2 files changed

Lines changed: 43 additions & 53 deletions

File tree

tests/test_jsonl_validation.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ def test_wrong_type_session_id(self):
3939
validate_session_dict(_valid_payload(session_id=123))
4040
assert exc_info.value.path == "session_id"
4141

42+
def test_null_session_id(self):
43+
with pytest.raises(SessionValidationError) as exc_info:
44+
validate_session_dict(_valid_payload(session_id=None))
45+
assert exc_info.value.path == "session_id"
46+
assert exc_info.value.detail == "must not be null"
47+
4248
def test_null_role_in_message(self):
4349
with pytest.raises(SessionValidationError) as exc_info:
4450
validate_session_dict(

utils/validation.py

Lines changed: 37 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,68 +5,52 @@
55
from models.errors import SessionValidationError
66
from models.session import SessionDict
77

8-
_REQUIRED_SESSION_KEYS = ("session_id", "title", "messages", "metadata")
8+
_ROOT_PATH = "(root)"
9+
10+
11+
def _require_field(
12+
container: dict[str, Any],
13+
key: str,
14+
expected_type: type[Any],
15+
type_label: str,
16+
*,
17+
path: str | None = None,
18+
) -> Any:
19+
field_path = path or key
20+
if key not in container:
21+
raise SessionValidationError(field_path, "missing required field")
22+
return _require_value(field_path, container[key], expected_type, type_label)
23+
24+
25+
def _require_value(
26+
path: str,
27+
val: Any,
28+
expected_type: type[Any],
29+
type_label: str,
30+
) -> Any:
31+
if val is None:
32+
raise SessionValidationError(path, "must not be null")
33+
if not isinstance(val, expected_type):
34+
raise SessionValidationError(
35+
path, f"expected {type_label}, got {type(val).__name__}"
36+
)
37+
return val
938

1039

1140
def validate_session_dict(data: dict[str, Any]) -> SessionDict:
1241
"""Validate a plain dict matches SessionDict before returning it."""
1342
# Runtime guard for dynamic callers; mypy already types the parameter as dict.
1443
if not isinstance(data, dict):
15-
raise SessionValidationError("$", "expected dict")
16-
17-
for key in _REQUIRED_SESSION_KEYS:
18-
if key not in data:
19-
raise SessionValidationError(key, "missing required field")
20-
21-
session_id = data["session_id"]
22-
# Explicit null check before isinstance so errors say "must not be null".
23-
if session_id is None:
24-
raise SessionValidationError("session_id", "must not be null")
25-
if not isinstance(session_id, str):
26-
raise SessionValidationError(
27-
"session_id", f"expected str, got {type(session_id).__name__}"
28-
)
29-
30-
title = data["title"]
31-
if title is None:
32-
raise SessionValidationError("title", "must not be null")
33-
if not isinstance(title, str):
34-
raise SessionValidationError(
35-
"title", f"expected str, got {type(title).__name__}"
36-
)
44+
raise SessionValidationError(_ROOT_PATH, "expected dict")
3745

38-
messages = data["messages"]
39-
if messages is None:
40-
raise SessionValidationError("messages", "must not be null")
41-
if not isinstance(messages, list):
42-
raise SessionValidationError(
43-
"messages", f"expected list, got {type(messages).__name__}"
44-
)
46+
_require_field(data, "session_id", str, "str")
47+
_require_field(data, "title", str, "str")
48+
messages = _require_field(data, "messages", list, "list")
49+
_require_field(data, "metadata", dict, "dict")
4550

4651
for index, message in enumerate(messages):
4752
path = f"messages[{index}]"
48-
if message is None:
49-
raise SessionValidationError(path, "must not be null")
50-
if not isinstance(message, dict):
51-
raise SessionValidationError(
52-
path, f"expected dict, got {type(message).__name__}"
53-
)
54-
if "role" not in message:
55-
raise SessionValidationError(f"{path}.role", "missing required field")
56-
role = message["role"]
57-
if role is None:
58-
raise SessionValidationError(f"{path}.role", "must not be null")
59-
if not isinstance(role, str):
60-
raise SessionValidationError(
61-
f"{path}.role", f"expected str, got {type(role).__name__}"
62-
)
63-
64-
metadata = data["metadata"]
65-
if metadata is None:
66-
raise SessionValidationError("metadata", "must not be null")
67-
if not isinstance(metadata, dict):
68-
raise SessionValidationError(
69-
"metadata", f"expected dict, got {type(metadata).__name__}"
70-
)
53+
msg_dict = _require_value(path, message, dict, "dict")
54+
_require_field(msg_dict, "role", str, "str", path=f"{path}.role")
7155

7256
return cast(SessionDict, data)

0 commit comments

Comments
 (0)