diff --git a/src/seriousdb/wal.py b/src/seriousdb/wal.py index 3acd9df..27a8e94 100644 --- a/src/seriousdb/wal.py +++ b/src/seriousdb/wal.py @@ -31,7 +31,7 @@ def from_dict(data: dict) -> "WalEntry": Raises ------ TypeError - If `data` is not a dictionary. + If `data` is not a dictionary, or if key or value is not a string. ValueError If `data` does not describe a known entry type. """ @@ -41,10 +41,24 @@ def from_dict(data: dict) -> "WalEntry": op = data.get("op") try: if op == "set": - return SetEntry(key=data["key"], value=data["value"]) + key = data["key"] + value = data["value"] + + if not isinstance(key, str): + raise TypeError("key must be a string") + if not isinstance(value, str): + raise TypeError("value must be a string") + + return SetEntry(key=key, value=value) if op == "delete": - return DeleteEntry(key=data["key"]) + key = data["key"] + + if not isinstance(key, str): + raise TypeError("key must be a string") + + return DeleteEntry(key=key) + except KeyError as e: raise ValueError(f"missing required field: {e}") from e diff --git a/tests/test_wal.py b/tests/test_wal.py index 2cbbf10..7c8a680 100644 --- a/tests/test_wal.py +++ b/tests/test_wal.py @@ -69,6 +69,9 @@ def test_replay_drops_and_truncates_a_torn_last_entry(wal, wal_path): [ b'{"op": "set", "key": "b"}\n', # Missing required field b"[]\n", # Unexpected top-level type + b'{"op": "set", "key": null, "value": "b"}\n', # Key is null + b'{"op": "set", "key": 5, "value": "b"}\n', # Key is not a string + b'{"op": "set", "key": "b", "value": [1, 2]}\n', # Value is not a string ], ) def test_replay_recovers_from_malformed_entry(wal, wal_path, malformed):