From 3b023198f69e9abf5969c2889fbdb56e937c1468 Mon Sep 17 00:00:00 2001 From: sumanjeet0012 Date: Thu, 4 Dec 2025 20:40:41 +0530 Subject: [PATCH] Implemented serization methods in py-multicodec --- multicodec/__init__.py | 49 +++- multicodec/exceptions.py | 45 ++++ multicodec/serialization.py | 364 ++++++++++++++++++++++++++++++ newsfragments/25.feature.rst | 1 + tests/test_serialization.py | 418 +++++++++++++++++++++++++++++++++++ 5 files changed, 875 insertions(+), 2 deletions(-) create mode 100644 multicodec/exceptions.py create mode 100644 multicodec/serialization.py create mode 100644 newsfragments/25.feature.rst create mode 100644 tests/test_serialization.py diff --git a/multicodec/__init__.py b/multicodec/__init__.py index 78dd065..bf47a72 100644 --- a/multicodec/__init__.py +++ b/multicodec/__init__.py @@ -13,23 +13,68 @@ known_codes, ) +# Exceptions +from .exceptions import ( + CodecError, + DecodeError, + EncodeError, + MulticodecError, + UnknownCodecError, +) + # Original multicodec functions from .multicodec import add_prefix, extract_prefix, get_codec, get_prefix, is_codec, remove_prefix +# Serialization support +from .serialization import ( + Codec, + JSONCodec, + RawCodec, + decode, + encode, + get_registered_codec, + is_codec_registered, + json_codec, + list_registered_codecs, + raw_codec, + register_codec, + unregister_codec, +) + __all__ = [ - "RESERVED_END", # Constants + "RESERVED_END", "RESERVED_START", # Code type "Code", + # Serialization base classes + "Codec", + "CodecError", + "DecodeError", + "EncodeError", + # Built-in codecs + "JSONCodec", + # Exceptions + "MulticodecError", + "RawCodec", + "UnknownCodecError", # Original functions "add_prefix", + "decode", + # Serialization functions + "encode", "extract_prefix", "get_codec", "get_prefix", + "get_registered_codec", "is_codec", + "is_codec_registered", "is_reserved", - # Functions + "json_codec", "known_codes", + "list_registered_codecs", + "raw_codec", + "register_codec", "remove_prefix", + "unregister_codec", ] diff --git a/multicodec/exceptions.py b/multicodec/exceptions.py new file mode 100644 index 0000000..d5e991f --- /dev/null +++ b/multicodec/exceptions.py @@ -0,0 +1,45 @@ +""" +Exception classes for multicodec. + +This module defines the exception hierarchy for multicodec operations, +providing specific error types for different failure modes. +""" + + +class MulticodecError(Exception): + """Base exception for all multicodec-related errors.""" + + pass + + +class CodecError(MulticodecError): + """Base exception for codec-related errors.""" + + pass + + +class EncodeError(CodecError): + """Raised when encoding fails.""" + + pass + + +class DecodeError(CodecError): + """Raised when decoding fails.""" + + pass + + +class UnknownCodecError(CodecError): + """Raised when an unknown codec is requested.""" + + pass + + +__all__ = [ + "CodecError", + "DecodeError", + "EncodeError", + "MulticodecError", + "UnknownCodecError", +] diff --git a/multicodec/serialization.py b/multicodec/serialization.py new file mode 100644 index 0000000..19cbf86 --- /dev/null +++ b/multicodec/serialization.py @@ -0,0 +1,364 @@ +""" +Serialization module for multicodec. + +This module provides a codec interface for serializing and deserializing data +with multicodec prefixes. It includes built-in codecs for common formats: +- JSON: Structured data serialization +- Raw: Pass-through codec for binary data + +The design follows a similar pattern to js-multiformats and rust-multicodec, +providing a clean interface for encoding/decoding operations. + +Example usage: + >>> from multicodec.serialization import json_codec, raw_codec, encode, decode + >>> # Using JSON codec + >>> data = {"hello": "world"} + >>> encoded = json_codec.encode(data) + >>> decoded = json_codec.decode(encoded) + >>> assert decoded == data + >>> + >>> # Using the generic encode/decode with codec name + >>> encoded = encode("json", {"key": "value"}) + >>> decoded = decode(encoded) +""" + +from __future__ import annotations + +import json +from abc import ABC, abstractmethod +from typing import Any, Generic, TypeVar + +import varint + +from .constants import CODE_TABLE +from .exceptions import CodecError, DecodeError, EncodeError, UnknownCodecError + +# Type variable for codec data types +T = TypeVar("T") + + +class Codec(ABC, Generic[T]): + """ + Abstract base class for multicodec serialization codecs. + + A codec provides methods to encode data to bytes and decode bytes back + to data. Each codec is identified by its multicodec name and code. + + Subclasses must implement: + - name: The multicodec name (e.g., 'json', 'raw') + - code: The multicodec code (e.g., 0x0200 for json) + - _encode: Transform data to bytes (without prefix) + - _decode: Transform bytes to data (without prefix) + """ + + @property + @abstractmethod + def name(self) -> str: + """Return the multicodec name for this codec.""" + ... + + @property + @abstractmethod + def code(self) -> int: + """Return the multicodec code for this codec.""" + ... + + @abstractmethod + def _encode(self, data: T) -> bytes: + """ + Encode data to bytes without the multicodec prefix. + + :param data: Data to encode + :return: Encoded bytes without prefix + :raises EncodeError: If encoding fails + """ + ... + + @abstractmethod + def _decode(self, data: bytes) -> T: + """ + Decode bytes to data, assuming no multicodec prefix. + + :param data: Bytes to decode (without prefix) + :return: Decoded data + :raises DecodeError: If decoding fails + """ + ... + + def encode(self, data: T) -> bytes: + """ + Encode data to bytes with multicodec prefix. + + :param data: Data to encode + :return: Multicodec-prefixed encoded bytes + :raises EncodeError: If encoding fails + """ + try: + encoded = self._encode(data) + prefix = varint.encode(self.code) + return prefix + encoded + except EncodeError: + raise + except Exception as e: + raise EncodeError(f"Failed to encode with {self.name}: {e}") from e + + def decode(self, data: bytes) -> T: + """ + Decode multicodec-prefixed bytes to data. + + :param data: Multicodec-prefixed bytes to decode + :return: Decoded data + :raises DecodeError: If decoding fails or codec mismatch + """ + try: + # Extract and verify the prefix + prefix_int = varint.decode_bytes(data) + if prefix_int != self.code: + expected_name = CODE_TABLE.get(prefix_int, f"0x{prefix_int:x}") + raise DecodeError( + f"Codec mismatch: expected {self.name} (0x{self.code:x}), got {expected_name} (0x{prefix_int:x})" + ) + + # Remove prefix and decode + prefix_bytes = varint.encode(prefix_int) + payload = data[len(prefix_bytes) :] + return self._decode(payload) + except DecodeError: + raise + except Exception as e: + raise DecodeError(f"Failed to decode with {self.name}: {e}") from e + + def decode_raw(self, data: bytes) -> T: + """ + Decode bytes without expecting a multicodec prefix. + + :param data: Raw bytes to decode (no prefix) + :return: Decoded data + :raises DecodeError: If decoding fails + """ + try: + return self._decode(data) + except DecodeError: + raise + except Exception as e: + raise DecodeError(f"Failed to decode raw data with {self.name}: {e}") from e + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}(name={self.name!r}, code=0x{self.code:x})>" + + +class JSONCodec(Codec[Any]): + """ + JSON codec for encoding/decoding JSON-serializable data. + + Uses the standard library json module with UTF-8 encoding. + The multicodec code for JSON is 0x0200. + + Example: + >>> codec = JSONCodec() + >>> encoded = codec.encode({"hello": "world"}) + >>> decoded = codec.decode(encoded) + >>> assert decoded == {"hello": "world"} + """ + + @property + def name(self) -> str: + return "json" + + @property + def code(self) -> int: + return 0x0200 # json multicodec code + + def _encode(self, data: Any) -> bytes: + """Encode data as JSON bytes.""" + try: + return json.dumps(data, separators=(",", ":"), ensure_ascii=False).encode("utf-8") + except (TypeError, ValueError) as e: + raise EncodeError(f"Data is not JSON serializable: {e}") from e + + def _decode(self, data: bytes) -> Any: + """Decode JSON bytes to Python object.""" + try: + return json.loads(data.decode("utf-8")) + except (json.JSONDecodeError, UnicodeDecodeError) as e: + raise DecodeError(f"Invalid JSON data: {e}") from e + + +class RawCodec(Codec[bytes]): + """ + Raw codec for pass-through binary data. + + This codec performs no transformation on the data, useful for + binary data that should be stored as-is with a multicodec prefix. + The multicodec code for raw is 0x55. + + Example: + >>> codec = RawCodec() + >>> data = b"binary data" + >>> encoded = codec.encode(data) + >>> decoded = codec.decode(encoded) + >>> assert decoded == data + """ + + @property + def name(self) -> str: + return "raw" + + @property + def code(self) -> int: + return 0x55 # raw multicodec code + + def _encode(self, data: bytes) -> bytes: + """Pass through bytes unchanged.""" + if not isinstance(data, bytes): + raise EncodeError(f"RawCodec expects bytes, got {type(data).__name__}") + return data + + def _decode(self, data: bytes) -> bytes: + """Pass through bytes unchanged.""" + return data + + +# Singleton codec instances for convenience +json_codec = JSONCodec() +raw_codec = RawCodec() + + +# Codec registry for dynamic codec lookup +_codec_registry: dict[str, Codec[Any]] = { + "json": json_codec, + "raw": raw_codec, +} + + +def register_codec(codec: Codec[Any]) -> None: + """ + Register a custom codec in the global registry. + + :param codec: The codec instance to register + :raises ValueError: If codec name is already registered + """ + if codec.name in _codec_registry: + raise ValueError(f"Codec '{codec.name}' is already registered") + _codec_registry[codec.name] = codec + + +def unregister_codec(name: str) -> None: + """ + Unregister a codec from the global registry. + + :param name: The codec name to unregister + :raises KeyError: If codec is not registered + """ + if name not in _codec_registry: + raise KeyError(f"Codec '{name}' is not registered") + del _codec_registry[name] + + +def get_registered_codec(name: str) -> Codec[Any]: + """ + Get a registered codec by name. + + :param name: The codec name + :return: The codec instance + :raises UnknownCodecError: If codec is not registered + """ + try: + return _codec_registry[name] + except KeyError: + raise UnknownCodecError(f"Codec '{name}' is not registered") from None + + +def list_registered_codecs() -> list[str]: + """ + List all registered codec names. + + :return: List of registered codec names + """ + return list(_codec_registry.keys()) + + +def encode(codec_name: str, data: Any) -> bytes: + """ + Encode data using a registered codec by name. + + :param codec_name: Name of the codec to use (e.g., 'json', 'raw') + :param data: Data to encode + :return: Multicodec-prefixed encoded bytes + :raises UnknownCodecError: If codec is not registered + :raises EncodeError: If encoding fails + """ + codec = get_registered_codec(codec_name) + return codec.encode(data) + + +def decode(data: bytes, codec_name: str | None = None) -> Any: + """ + Decode multicodec-prefixed data. + + If codec_name is provided, uses that specific codec (and verifies prefix matches). + If codec_name is None, auto-detects codec from the prefix. + + :param data: Multicodec-prefixed bytes to decode + :param codec_name: Optional codec name to use for decoding + :return: Decoded data + :raises UnknownCodecError: If codec is not registered + :raises DecodeError: If decoding fails or codec mismatch + """ + if codec_name is not None: + codec = get_registered_codec(codec_name) + return codec.decode(data) + + # Auto-detect codec from prefix + try: + prefix_int = varint.decode_bytes(data) + except TypeError as e: + raise DecodeError(f"Invalid varint prefix: {e}") from e + + codec_name_detected = CODE_TABLE.get(prefix_int) + if codec_name_detected is None: + raise DecodeError(f"Unknown codec prefix: 0x{prefix_int:x}") + + if codec_name_detected not in _codec_registry: + raise UnknownCodecError( + f"Codec '{codec_name_detected}' (0x{prefix_int:x}) is not registered. " + f"Available codecs: {list_registered_codecs()}" + ) + + return _codec_registry[codec_name_detected].decode(data) + + +def is_codec_registered(name: str) -> bool: + """ + Check if a codec is registered. + + :param name: The codec name to check + :return: True if codec is registered, False otherwise + """ + return name in _codec_registry + + +__all__ = [ + # Base classes + "Codec", + # Exceptions + "CodecError", + "DecodeError", + "EncodeError", + "JSONCodec", + # Built-in codecs + "RawCodec", + "UnknownCodecError", + # Generic functions + "decode", + "encode", + "get_registered_codec", + "is_codec_registered", + # Codec instances + "json_codec", + "list_registered_codecs", + "raw_codec", + # Registry functions + "register_codec", + "unregister_codec", +] diff --git a/newsfragments/25.feature.rst b/newsfragments/25.feature.rst new file mode 100644 index 0000000..78b3d38 --- /dev/null +++ b/newsfragments/25.feature.rst @@ -0,0 +1 @@ +Added serialization with JSON/raw codecs, custom codec interface, encode/decode functions, and a dynamic codec registry. diff --git a/tests/test_serialization.py b/tests/test_serialization.py new file mode 100644 index 0000000..6b928bc --- /dev/null +++ b/tests/test_serialization.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python +"""Tests for serialization module.""" + +import pytest + +from multicodec import ( + Codec, + CodecError, + DecodeError, + EncodeError, + JSONCodec, + MulticodecError, + RawCodec, + UnknownCodecError, + decode, + encode, + get_registered_codec, + is_codec_registered, + json_codec, + list_registered_codecs, + raw_codec, + register_codec, + unregister_codec, +) + + +class JSONCodecTestCase: + """Tests for the JSONCodec class.""" + + def test_codec_properties(self): + """Test codec name and code properties.""" + codec = JSONCodec() + assert codec.name == "json" + assert codec.code == 0x0200 + + def test_encode_simple_dict(self): + """Test encoding a simple dictionary.""" + codec = JSONCodec() + data = {"hello": "world"} + encoded = codec.encode(data) + # Should have prefix (0x0200 as varint) + JSON bytes + assert encoded.startswith(b"\x80\x04") # varint for 0x0200 + assert b"hello" in encoded + assert b"world" in encoded + + def test_encode_decode_roundtrip(self): + """Test encoding and decoding preserves data.""" + codec = JSONCodec() + test_cases = [ + {"hello": "world"}, + [1, 2, 3, 4, 5], + {"nested": {"key": "value"}, "list": [1, 2, 3]}, + "simple string", + 123, + 123.456, + True, + False, + None, + ] + for data in test_cases: + encoded = codec.encode(data) + decoded = codec.decode(encoded) + assert decoded == data, f"Failed for {data}" + + def test_encode_unicode(self): + """Test encoding Unicode data.""" + codec = JSONCodec() + data = {"emoji": "🎉", "chinese": "你好", "arabic": "مرحبا"} + encoded = codec.encode(data) + decoded = codec.decode(encoded) + assert decoded == data + + def test_encode_non_serializable_raises(self): + """Test encoding non-JSON-serializable data raises EncodeError.""" + codec = JSONCodec() + with pytest.raises(EncodeError) as excinfo: + codec.encode({"func": lambda x: x}) + assert "not JSON serializable" in str(excinfo.value) + + def test_decode_invalid_json_raises(self): + """Test decoding invalid JSON raises DecodeError.""" + codec = JSONCodec() + # Create a properly prefixed but invalid JSON payload + prefix = b"\x80\x04" # varint for 0x0200 + invalid_json = prefix + b"not valid json" + with pytest.raises(DecodeError) as excinfo: + codec.decode(invalid_json) + assert "Invalid JSON data" in str(excinfo.value) + + def test_decode_wrong_codec_raises(self): + """Test decoding with wrong codec prefix raises DecodeError.""" + codec = JSONCodec() + # Use raw codec prefix (0x55) + raw_prefixed = b"\x55" + b'{"hello": "world"}' + with pytest.raises(DecodeError) as excinfo: + codec.decode(raw_prefixed) + assert "Codec mismatch" in str(excinfo.value) + + def test_decode_raw(self): + """Test decoding without prefix using decode_raw.""" + codec = JSONCodec() + raw_json = b'{"hello": "world"}' + decoded = codec.decode_raw(raw_json) + assert decoded == {"hello": "world"} + + def test_repr(self): + """Test codec string representation.""" + codec = JSONCodec() + repr_str = repr(codec) + assert "JSONCodec" in repr_str + assert "json" in repr_str + assert "0x200" in repr_str + + +class RawCodecTestCase: + """Tests for the RawCodec class.""" + + def test_codec_properties(self): + """Test codec name and code properties.""" + codec = RawCodec() + assert codec.name == "raw" + assert codec.code == 0x55 + + def test_encode_bytes(self): + """Test encoding bytes.""" + codec = RawCodec() + data = b"binary data" + encoded = codec.encode(data) + # Should have prefix (0x55 as varint) + raw bytes + assert encoded.startswith(b"\x55") + assert encoded[1:] == data + + def test_encode_decode_roundtrip(self): + """Test encoding and decoding preserves data.""" + codec = RawCodec() + test_cases = [ + b"hello world", + b"\x00\x01\x02\x03", + b"", + bytes(range(256)), + ] + for data in test_cases: + encoded = codec.encode(data) + decoded = codec.decode(encoded) + assert decoded == data, f"Failed for {data!r}" + + def test_encode_non_bytes_raises(self): + """Test encoding non-bytes data raises EncodeError.""" + codec = RawCodec() + with pytest.raises(EncodeError) as excinfo: + codec.encode("not bytes") # type: ignore + assert "expects bytes" in str(excinfo.value) + + def test_decode_wrong_codec_raises(self): + """Test decoding with wrong codec prefix raises DecodeError.""" + codec = RawCodec() + # Use JSON codec prefix (0x0200) + json_prefixed = b"\x80\x04" + b"some data" + with pytest.raises(DecodeError) as excinfo: + codec.decode(json_prefixed) + assert "Codec mismatch" in str(excinfo.value) + + def test_repr(self): + """Test codec string representation.""" + codec = RawCodec() + repr_str = repr(codec) + assert "RawCodec" in repr_str + assert "raw" in repr_str + assert "0x55" in repr_str + + +class SingletonCodecsTestCase: + """Tests for singleton codec instances.""" + + def test_json_codec_instance(self): + """Test json_codec is a JSONCodec instance.""" + assert isinstance(json_codec, JSONCodec) + assert json_codec.name == "json" + + def test_raw_codec_instance(self): + """Test raw_codec is a RawCodec instance.""" + assert isinstance(raw_codec, RawCodec) + assert raw_codec.name == "raw" + + +class CodecRegistryTestCase: + """Tests for codec registry functions.""" + + def test_list_registered_codecs(self): + """Test listing registered codecs.""" + codecs = list_registered_codecs() + assert "json" in codecs + assert "raw" in codecs + + def test_is_codec_registered(self): + """Test checking if codec is registered.""" + assert is_codec_registered("json") + assert is_codec_registered("raw") + assert not is_codec_registered("nonexistent") + + def test_get_registered_codec(self): + """Test getting registered codec.""" + codec = get_registered_codec("json") + assert isinstance(codec, JSONCodec) + + def test_get_unregistered_codec_raises(self): + """Test getting unregistered codec raises UnknownCodecError.""" + with pytest.raises(UnknownCodecError) as excinfo: + get_registered_codec("nonexistent") + assert "not registered" in str(excinfo.value) + + def test_register_custom_codec(self): + """Test registering a custom codec.""" + + class CustomCodec(Codec[str]): + @property + def name(self) -> str: + return "custom" + + @property + def code(self) -> int: + return 0x9999 + + def _encode(self, data: str) -> bytes: + return data.encode("utf-8") + + def _decode(self, data: bytes) -> str: + return data.decode("utf-8") + + codec = CustomCodec() + try: + register_codec(codec) + assert is_codec_registered("custom") + assert get_registered_codec("custom") is codec + finally: + # Clean up + unregister_codec("custom") + + def test_register_duplicate_raises(self): + """Test registering duplicate codec raises ValueError.""" + with pytest.raises(ValueError) as excinfo: + register_codec(json_codec) + assert "already registered" in str(excinfo.value) + + def test_unregister_codec(self): + """Test unregistering a codec.""" + + class TempCodec(Codec[str]): + @property + def name(self) -> str: + return "temp" + + @property + def code(self) -> int: + return 0x8888 + + def _encode(self, data: str) -> bytes: + return data.encode() + + def _decode(self, data: bytes) -> str: + return data.decode() + + codec = TempCodec() + register_codec(codec) + assert is_codec_registered("temp") + unregister_codec("temp") + assert not is_codec_registered("temp") + + def test_unregister_nonexistent_raises(self): + """Test unregistering nonexistent codec raises KeyError.""" + with pytest.raises(KeyError) as excinfo: + unregister_codec("nonexistent") + assert "not registered" in str(excinfo.value) + + +class GenericEncodeDecodeTestCase: + """Tests for generic encode/decode functions.""" + + def test_encode_with_json(self): + """Test generic encode with JSON codec.""" + data = {"key": "value"} + encoded = encode("json", data) + assert encoded.startswith(b"\x80\x04") + + def test_encode_with_raw(self): + """Test generic encode with raw codec.""" + data = b"binary data" + encoded = encode("raw", data) + assert encoded.startswith(b"\x55") + + def test_encode_unknown_codec_raises(self): + """Test encoding with unknown codec raises UnknownCodecError.""" + with pytest.raises(UnknownCodecError): + encode("nonexistent", {"data": "value"}) + + def test_decode_with_codec_name(self): + """Test decode with explicit codec name.""" + encoded = encode("json", {"hello": "world"}) + decoded = decode(encoded, "json") + assert decoded == {"hello": "world"} + + def test_decode_auto_detect(self): + """Test decode with auto-detection.""" + # JSON + json_encoded = encode("json", {"hello": "world"}) + json_decoded = decode(json_encoded) + assert json_decoded == {"hello": "world"} + + # Raw + raw_encoded = encode("raw", b"binary data") + raw_decoded = decode(raw_encoded) + assert raw_decoded == b"binary data" + + def test_decode_unknown_prefix_raises(self): + """Test decoding unknown prefix raises DecodeError.""" + # Use an unknown prefix + unknown_data = b"\xff\xff\xff\x07" + b"some data" # Large unknown prefix + with pytest.raises(DecodeError) as excinfo: + decode(unknown_data) + assert "Unknown codec prefix" in str(excinfo.value) + + def test_decode_unregistered_codec_raises(self): + """Test decoding with unregistered codec raises UnknownCodecError.""" + # Use a valid multicodec prefix that's not registered (e.g., cbor = 0x51) + import varint as varint_lib + + cbor_prefix = varint_lib.encode(0x51) # cbor codec + data = cbor_prefix + b"\xa1\x65hello\x65world" # some CBOR-ish data + with pytest.raises(UnknownCodecError) as excinfo: + decode(data) + assert "not registered" in str(excinfo.value) + + def test_decode_invalid_varint_raises(self): + """Test decoding invalid varint raises DecodeError.""" + with pytest.raises(DecodeError) as excinfo: + decode(b"\xff") # Invalid varint + assert "Invalid varint prefix" in str(excinfo.value) + + +class ExceptionsTestCase: + """Tests for exception hierarchy.""" + + def test_exception_hierarchy(self): + """Test exception inheritance.""" + assert issubclass(CodecError, MulticodecError) + assert issubclass(EncodeError, CodecError) + assert issubclass(DecodeError, CodecError) + assert issubclass(UnknownCodecError, CodecError) + assert issubclass(MulticodecError, Exception) + + def test_encode_error_message(self): + """Test EncodeError message.""" + err = EncodeError("test error") + assert str(err) == "test error" + + def test_decode_error_message(self): + """Test DecodeError message.""" + err = DecodeError("test error") + assert str(err) == "test error" + + def test_unknown_codec_error_message(self): + """Test UnknownCodecError message.""" + err = UnknownCodecError("test error") + assert str(err) == "test error" + + +class EdgeCasesTestCase: + """Tests for edge cases.""" + + def test_empty_json_object(self): + """Test encoding/decoding empty JSON object.""" + codec = JSONCodec() + encoded = codec.encode({}) + decoded = codec.decode(encoded) + assert decoded == {} + + def test_empty_json_array(self): + """Test encoding/decoding empty JSON array.""" + codec = JSONCodec() + encoded = codec.encode([]) + decoded = codec.decode(encoded) + assert decoded == [] + + def test_empty_raw_bytes(self): + """Test encoding/decoding empty bytes.""" + codec = RawCodec() + encoded = codec.encode(b"") + decoded = codec.decode(encoded) + assert decoded == b"" + + def test_large_json_data(self): + """Test encoding/decoding large JSON data.""" + codec = JSONCodec() + data = {"items": list(range(10000))} + encoded = codec.encode(data) + decoded = codec.decode(encoded) + assert decoded == data + + def test_large_raw_data(self): + """Test encoding/decoding large raw data.""" + codec = RawCodec() + data = bytes(range(256)) * 1000 + encoded = codec.encode(data) + decoded = codec.decode(encoded) + assert decoded == data + + def test_deeply_nested_json(self): + """Test encoding/decoding deeply nested JSON.""" + codec = JSONCodec() + # Create deeply nested structure + data: dict = {"level": 0} + current = data + for i in range(1, 50): + current["nested"] = {"level": i} + current = current["nested"] + encoded = codec.encode(data) + decoded = codec.decode(encoded) + assert decoded == data