Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/compact_encoding/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from .integer import int_codec as int
from .json import json_codec as json
from .record import record
from .state import State
from .string import utf8
from .string import utf8 as string
Expand All @@ -49,6 +50,7 @@
"int48",
"int56",
"json",
"record",
"string",
"uint",
"uint8",
Expand Down
31 changes: 31 additions & 0 deletions src/compact_encoding/record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from . import integer


class _Record:
def __init__(self, key_codec, value_codec):
self._key = key_codec
self._value = value_codec

def preencode(self, state, v):
integer.uint.preencode(state, len(v))
for k, value in v.items():
self._key.preencode(state, k)
self._value.preencode(state, value)

def encode(self, state, v):
integer.uint.encode(state, len(v))
for k, value in v.items():
self._key.encode(state, k)
self._value.encode(state, value)

def decode(self, state):
n = integer.uint.decode(state)
out = {}
for _ in range(n):
key = self._key.decode(state)
out[key] = self._value.decode(state)
return out


def record(key_codec, value_codec):
return _Record(key_codec, value_codec)
32 changes: 32 additions & 0 deletions tests/test_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest

import compact_encoding as cenc

# record(string, uint): uint(count) + (key, value) pairs in insertion order.
# Vectors from hyperschema-test fixture 23.
RECORD_CASES = [
({}, "00"),
({"x": 0}, "01017800"),
({"alice": 10, "bob": 20}, "0205616c6963650a03626f6214"),
]


@pytest.mark.parametrize("value,hexbytes", RECORD_CASES)
def test_record_encode(value, hexbytes):
assert cenc.encode(cenc.record(cenc.string, cenc.uint), value).hex() == hexbytes


@pytest.mark.parametrize("value,hexbytes", RECORD_CASES)
def test_record_decode(value, hexbytes):
assert (
cenc.decode(cenc.record(cenc.string, cenc.uint), bytes.fromhex(hexbytes))
== value
)


def test_record_preserves_insertion_order_not_sorted():
# {"b": 2, "a": 1} must encode b before a (insertion order), NOT sorted.
# count 02 + "b"(0162)+uint2(02) + "a"(0161)+uint1(01)
codec = cenc.record(cenc.string, cenc.uint)
assert cenc.encode(codec, {"b": 2, "a": 1}).hex() == "02016202016101"
assert cenc.decode(codec, bytes.fromhex("02016202016101")) == {"b": 2, "a": 1}
1 change: 1 addition & 0 deletions tests/test_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
(cenc.json, {"a": [1, 2], "b": None}),
(cenc.frame(cenc.uint), 42),
(cenc.array(cenc.uint), [1, 2, 3]),
(cenc.record(cenc.string, cenc.uint), {"a": 1, "b": 2}),
]


Expand Down
Loading