Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serde options refactor #506

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
79 changes: 53 additions & 26 deletions pkg/sr/serde.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,8 @@ func (s *Serde) MustAppendEncode(b []byte, v any, opts ...EncodingOpt) []byte {
// Serde does not handle references in schemas; it is up to you to register the
// full decode function for any top-level ID, regardless of how many other
// schemas are referenced in top-level ID.
func (s *Serde) Decode(b []byte, v any) error {
b, t, err := s.decodeFind(b)
func (s *Serde) Decode(b []byte, v any, opts ...EncodingOpt) error {
b, t, err := s.decodeFind(b, opts)
if err != nil {
return err
}
Expand All @@ -399,8 +399,8 @@ func (s *Serde) Decode(b []byte, v any) error {
// the input value. If DecodeFn was not used, this returns ErrNotRegistered.
// GenerateFn can be used to control the instantiation of a new value,
// otherwise this uses reflect.New(reflect.TypeOf(v)).Interface().
func (s *Serde) DecodeNew(b []byte) (any, error) {
b, t, err := s.decodeFind(b)
func (s *Serde) DecodeNew(b []byte, opts ...EncodingOpt) (any, error) {
b, t, err := s.decodeFind(b, opts)
if err != nil {
return nil, err
}
Expand All @@ -415,34 +415,61 @@ func (s *Serde) DecodeNew(b []byte) (any, error) {
return v, t.decode(b, v)
}

func (s *Serde) decodeFind(b []byte) ([]byte, tserde, error) {
if len(b) < 5 || b[0] != 0 {
return nil, tserde{}, ErrBadHeader
func (s *Serde) decodeFind(b []byte, opts []EncodingOpt) ([]byte, tserde, error) {
optsToApply, idopt, indexopt, err := s.filterIDAndIndexOpt(opts)
if err != nil {
return nil, tserde{}, err
}
id := binary.BigEndian.Uint32(b[1:5])
b = b[5:]

t := s.loadIDs()[int(id)]
if len(t.subindex) > 0 {
r := bReader{b}
br := io.ByteReader(&r)
l, err := binary.ReadVarint(br)
if l == 0 { // length 0 is a shortcut for length 1, index 0
t = t.subindex[0]
var t tserde
if idopt != nil {
// Load tserde based on the supplied ID.
t = s.loadIDs()[int(idopt.ID())]
Copy link
Contributor Author

@lovromazgon lovromazgon Jul 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last commit adds the ability to supply EncodingOpt to Decode, simply to have parity with Encode, but I'm not convinced if it makes sense. I included it to run it by you, but I can revert the change if needed.

This is how it works. If you supply options ID and Index to Decode it will find tserde based solely on the supplied options, it will not decode the header. In fact, it will assume there is no header and that it's dealing only with the raw encoded bytes. What I don't like about this is that Serde can't encode bytes without a header unless you specify your own SerdeHeader implementation which skips prepending the header. It would allow you to track the ID and index separately from the encoded payload, although I'm not sure such a use case exists, so it might needlessly complicate the API.

// Traverse to the right index, if Index option is supplied.
if indexopt != nil {
for _, i := range indexopt.Index() {
if len(t.subindex) <= i {
return nil, tserde{}, ErrNotRegistered
}
t = t.subindex[i]
}
}
for err == nil && t.subindex != nil && l > 0 {
var idx int64
idx, err = binary.ReadVarint(br)
t = t.subindex[int(idx)]
l--
} else {
// Load tserde based on the header
if len(b) < 5 || b[0] != 0 {
return nil, tserde{}, ErrBadHeader
}
if err != nil {
return nil, t, err
id := binary.BigEndian.Uint32(b[1:5])
b = b[5:]

t = s.loadIDs()[int(id)]
if len(t.subindex) > 0 {
r := bReader{b}
br := io.ByteReader(&r)
l, err := binary.ReadVarint(br)
if l == 0 { // length 0 is a shortcut for length 1, index 0
t = t.subindex[0]
}
for err == nil && t.subindex != nil && l > 0 {
var idx int64
idx, err = binary.ReadVarint(br)
t = t.subindex[int(idx)]
l--
}
if err != nil {
return nil, t, err
}
b = r.b
}
b = r.b
}
if !t.exists {
return nil, t, ErrNotRegistered

// Check if we loaded a valid tserde.
if !t.exists || t.decode == nil {
return nil, tserde{}, ErrNotRegistered
}

for _, opt := range optsToApply {
opt.apply(&t)
}
return b, t, nil
}
Expand Down
17 changes: 17 additions & 0 deletions pkg/sr/serde_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func TestSerde(t *testing.T) {
expEnc []byte
expDec any
expErr bool
expMap map[string]any
}{
{
enc: overridden{},
Expand All @@ -70,27 +71,33 @@ func TestSerde(t *testing.T) {
{
enc: overrides{"foo"},
expEnc: append([]byte{0, 0, 0, 0, 127}, `{"one":"foo"}`...),
expMap: map[string]any{"one": "foo"},
},
{
enc: idx1{Two: 2, Three: 3},
expEnc: append([]byte{0, 0, 0, 0, 3, 0}, `{"two":2,"three":3}`...),
expMap: map[string]any{"two": float64(2), "three": float64(3)},
},
{
enc: idx2{Biz: "bizzy", Baz: "bazzy"},
expEnc: append([]byte{0, 0, 0, 0, 3, 2, 2}, `{"biz":"bizzy","baz":"bazzy"}`...),
expMap: map[string]any{"biz": "bizzy", "baz": "bazzy"},
},
{
enc: idx3{Boz: 8},
expEnc: append([]byte{0, 0, 0, 0, 3, 4, 0, 0}, `{"boz":8}`...),
expMap: map[string]any{"boz": float64(8)},
},
{
enc: idx4{Bingo: "bango"},
expEnc: append([]byte{0, 0, 0, 0, 3, 6, 0, 0, 2}, `{"bingo":"bango"}`...),
expMap: map[string]any{"bingo": "bango"},
},
{
enc: oneidx{Bar: "bar"},
expEnc: append([]byte{0, 0, 0, 0, 5, 0}, `{"bar":"bar"}`...),
expDec: oneidx{Foo: "defoo", Bar: "bar"},
expMap: map[string]any{"bar": "bar"},
},
} {
if _, err := serde.Encode(test.enc, ID(99)); err != ErrNotRegistered {
Expand Down Expand Up @@ -139,6 +146,16 @@ func TestSerde(t *testing.T) {
}
if !reflect.DeepEqual(v, exp) {
t.Errorf("#%d round trip: got %v != exp %v", i, v, exp)
continue
}

gotMap, err := serde.DecodeNew(b[bytes.IndexByte(b, '{'):], ID(100), Index(0), GenerateFn(func() any { return new(map[string]any) }))
if err != nil {
t.Errorf("#%d DecodeNew: got unexpected err %v", i, err)
}
if !reflect.DeepEqual(gotMap, &test.expMap) {
t.Errorf("#%d decode new map: got %v != exp %v", i, gotMap, test.expMap)
continue
}
}

Expand Down