Skip to content

Commit 2aeeb67

Browse files
committed
fix(zarr-metadata): the store reader checks that it read bytes
`load_store_json` takes a mapping of store keys to `bytes` and never checked the values: `json.loads` decoded a `str` as if it were bytes, and raised `TypeError` for `None` or a `memoryview`, which its docstring promised would never leak. A value that is not `bytes` is now an `invalid_type` problem at its key. Assisted-by: ClaudeCode:claude-opus-5-5
1 parent b3045a2 commit 2aeeb67

3 files changed

Lines changed: 34 additions & 3 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
`from_key_value` checks that each store value it reads is `bytes`, as its
2+
signature says: a `str` was decoded as a JSON document, and `None` or a
3+
`memoryview` raised `TypeError`. Each is now a `MetadataValidationError`
4+
with an `invalid_type` problem at the store key.

‎packages/zarr-metadata/src/zarr_metadata/model/_validation.py‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -904,8 +904,9 @@ def load_store_json(mapping: Mapping[StoreKey, bytes], key: str) -> object:
904904
the floats they spell, as zarr-python writes attributes; where one may
905905
be is the document's validator's to say. Every ingestion failure here
906906
surfaces as `MetadataValidationError`: a missing store key is a
907-
`missing_key` problem and undecodable bytes are an `invalid_json`
908-
problem, rather than leaking `KeyError` / `json.JSONDecodeError` to
907+
`missing_key` problem, a value that is not `bytes` an `invalid_type`
908+
problem, and undecodable bytes an `invalid_json` problem, rather than
909+
leaking `KeyError`, `TypeError` or `json.JSONDecodeError` to
909910
callers.
910911
"""
911912
# Read by a `str` key whatever narrower key type the mapping declares:
@@ -915,8 +916,15 @@ def load_store_json(mapping: Mapping[StoreKey, bytes], key: str) -> object:
915916
raise MetadataValidationError(
916917
[ValidationProblem((key,), "missing store key", "missing_key")]
917918
)
919+
# The runtime half of the annotation: `json.loads` decodes a `str` and
920+
# raises `TypeError` on most else.
921+
raw = cast("object", stored[key])
922+
if not isinstance(raw, bytes):
923+
raise MetadataValidationError(
924+
[ValidationProblem((key,), f"expected bytes, got {type(raw).__name__}", "invalid_type")]
925+
)
918926
try:
919-
return json.loads(stored[key])
927+
return json.loads(raw)
920928
except (UnicodeDecodeError, ValueError) as exc:
921929
raise MetadataValidationError(
922930
[ValidationProblem((key,), f"invalid JSON: {exc}", "invalid_json")]

‎packages/zarr-metadata/tests/model/test_group.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,25 @@ def test_consolidated_v2_metadata_values_must_be_json() -> None:
400400
]
401401

402402

403+
@pytest.mark.parametrize(
404+
"stored",
405+
[
406+
'{"zarr_format": 2}',
407+
None,
408+
memoryview(b'{"zarr_format": 2}'),
409+
bytearray(b'{"zarr_format": 2}'),
410+
],
411+
ids=["str", "none", "memoryview", "bytearray"],
412+
)
413+
def test_error_a_store_value_that_is_not_bytes_is_refused(stored: object) -> None:
414+
"""A store maps keys to bytes, and a reader checks that it read bytes."""
415+
with pytest.raises(MetadataValidationError) as exc_info:
416+
ZarrV2GroupMetadata.from_key_value({".zgroup": stored}) # pyright: ignore[reportArgumentType]
417+
assert [(problem.loc, problem.kind) for problem in exc_info.value.problems] == [
418+
((".zgroup",), "invalid_type")
419+
]
420+
421+
403422
def test_group_v2_from_key_value_scalar_root_raises_metadata_error() -> None:
404423
"""A scalar .zgroup document fails through the unified metadata error channel."""
405424
with pytest.raises(MetadataValidationError) as exc_info:

0 commit comments

Comments
 (0)