Compare commits

...

18 Commits

Author SHA1 Message Date
Pavel Merzlyakov 219428c7f6 reading float32 from float64 without error 2024-03-15 16:36:12 +03:00
Sergey Polygalin 9324aeae5f Add string key for changed fields 2023-12-17 11:42:39 +03:00
Pavel Merzlyakov 19f50973f1 fix reading null virtual field of a struct 2023-11-23 17:11:43 +03:00
Pavel Merzlyakov 1bfc33e774 ReadableWritable interface added 2023-09-11 20:09:11 +03:00
Pavel Merzlyakov 74717f3b79 fix mod name 2023-09-11 17:41:16 +03:00
Pavel Merzlyakov 5a2988dfc3 msgpack assoc reader added 2023-09-11 16:52:12 +03:00
Pavel Merzlyakov d90fcf33e1 mspgack reader spliting 2023-09-11 16:51:34 +03:00
Pavel Merzlyakov 11e46da3ce fix write generic helper 2023-09-01 17:21:04 +03:00
Pavel Merzlyakov 03c024c1a8 fix read/write generic helpers 2023-09-01 17:14:09 +03:00
Pavel Merzlyakov 521d19d172 extend Writable interface. WritableClass added 2023-09-01 15:23:18 +03:00
Pavel Merzlyakov 62a85e0392 read/write struct generic 2023-09-01 01:37:06 +03:00
Pavel Merzlyakov ac4c53305e fix reading 2023-07-31 18:37:16 +03:00
Pavel Merzlyakov 8779ce5f96 fix reading mask 2023-07-27 02:04:35 +03:00
Pavel Merzlyakov de9695a724 reading mask without preceding nil value 2023-07-27 01:51:27 +03:00
Pavel Merzlyakov 800c5d04d0 fix reading containers 2023-07-27 00:27:56 +03:00
Pavel Merzlyakov 04e68cc197 introduce FieldNotFound and NoOpenContainer errors 2023-07-26 23:03:37 +03:00
Pavel Merzlyakov fe1e485334 improve writer interface 2023-07-03 22:09:39 +03:00
Pavel Merzlyakov abe543a275 additional methods for FieldsMask and ChangedFields 2023-06-17 10:45:52 +03:00
16 changed files with 1359 additions and 658 deletions

View File

@ -1,12 +1,18 @@
package meta package meta
import "bytes"
type ChangedFields struct { type ChangedFields struct {
fieldNames map[string]struct{} fieldNames map[string]struct{}
fieldsKey *bytes.Buffer
} }
func NewChangedFields(fieldCount int) ChangedFields { func NewChangedFields(fieldCount int) ChangedFields {
keyBuffer := bytes.NewBuffer(make([]byte, 0, fieldCount*2))
cf := ChangedFields{ cf := ChangedFields{
fieldNames: make(map[string]struct{}, fieldCount), fieldNames: make(map[string]struct{}, fieldCount),
fieldsKey: keyBuffer,
} }
return cf return cf
} }
@ -27,11 +33,28 @@ func (cf ChangedFields) Changed(field string) bool {
} }
func (cf *ChangedFields) SetChanged(fields ...string) { func (cf *ChangedFields) SetChanged(fields ...string) {
if cf.fieldNames == nil {
cf.fieldNames = make(map[string]struct{})
}
for _, field := range fields { for _, field := range fields {
if _, exists := cf.fieldNames[field]; exists {
continue
}
cf.fieldNames[field] = struct{}{} cf.fieldNames[field] = struct{}{}
cf.fieldsKey.WriteString(field)
} }
} }
func (cf ChangedFields) Empty() bool { func (cf ChangedFields) Empty() bool {
return len(cf.fieldNames) == 0 return len(cf.fieldNames) == 0
} }
func (cf ChangedFields) IsNil() bool {
return cf.fieldNames == nil
}
func (cf ChangedFields) GetFieldsKey() string {
return cf.fieldsKey.String()
}

33
changed_fields_test.go Normal file
View File

@ -0,0 +1,33 @@
package meta
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestChangedFields(t *testing.T) {
t.Run("#SetChanged", func(t *testing.T) {
changedFields := NewChangedFields(3)
expectedFieldsMap := map[string]struct{}{
"a": {},
"b": {},
"c": {},
}
changedFields.SetChanged("a", "b", "c", "a")
assert.Equal(t, expectedFieldsMap, changedFields.fieldNames)
})
t.Run("#GetFieldsKey", func(t *testing.T) {
changedFields := NewChangedFields(3)
changedFields.SetChanged("b", "c", "a")
expectedKey := "bca"
actualKey := changedFields.GetFieldsKey()
assert.Equal(t, expectedKey, actualKey)
})
}

View File

@ -52,6 +52,18 @@ func (fm FieldsMask) IsFilled() bool {
return false return false
} }
func (fm *FieldsMask) Reset() {
for i := range fm.parts {
fm.parts[i] = 0
}
}
func (fm *FieldsMask) SetChangedAll() {
for i := range fm.parts {
fm.parts[i] = 1<<64 - 1
}
}
func (fm FieldsMask) partIndex(index uint64) uint64 { func (fm FieldsMask) partIndex(index uint64) uint64 {
return index / FieldsMaskPartBitSize return index / FieldsMaskPartBitSize
} }

View File

@ -3,7 +3,7 @@ package meta_test
import ( import (
"testing" "testing"
"git.bit5.ru/backend/meta/v2" "git.bit5.ru/backend/meta/v5"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"

2
go.mod
View File

@ -1,4 +1,4 @@
module git.bit5.ru/backend/meta/v2 module git.bit5.ru/backend/meta/v5
go 1.20 go 1.20

View File

@ -22,6 +22,9 @@ type Reader interface {
BeginContainer(field string) error BeginContainer(field string) error
EndContainer() error EndContainer() error
BeginCollection(field string) error
EndCollection() error
ContainerSize() (int, error) ContainerSize() (int, error)
IsContainerAssoc() (bool, error) IsContainerAssoc() (bool, error)
@ -48,8 +51,10 @@ type Writer interface {
WriteString(v string, field string) error WriteString(v string, field string) error
WriteBytes(v []byte, field string) error WriteBytes(v []byte, field string) error
BeginCollection(length int, field string) error
EndCollection() error
BeginContainer(length int, field string) error BeginContainer(length int, field string) error
BeginAssocContainer(length int, field string) error
EndContainer() error EndContainer() error
} }
@ -67,6 +72,7 @@ type Readable interface {
type Writable interface { type Writable interface {
Write(Writer) error Write(Writer) error
WriteFields(Writer) error WriteFields(Writer) error
FieldsCount() int
} }
type Struct interface { type Struct interface {
@ -75,9 +81,21 @@ type Struct interface {
Writable Writable
} }
type StructFactory func(classId uint32) (Readable, error)
type Bitmasked interface { type Bitmasked interface {
FieldChanged(index uint64) bool FieldChanged(index uint64) bool
SetFieldChanged(index uint64) SetFieldChanged(index uint64)
HasChangedFields() bool HasChangedFields() bool
FieldsMask() FieldsMask FieldsMask() FieldsMask
} }
type WritableClass interface {
Class
Writable
}
type ReadableWritable interface {
Readable
Writable
}

53
meta.go Normal file
View File

@ -0,0 +1,53 @@
package meta
const classField = "$id"
func ReadGeneric(r Reader, createFunc StructFactory, field string) (Readable, error) {
if err := r.BeginContainer(field); err != nil {
return nil, err
}
size, err := r.ContainerSize()
if err != nil {
return nil, err
}
if size == 0 {
if err := r.EndContainer(); err != nil {
return nil, err
}
return nil, nil
}
var classId uint32
if err := r.ReadUint32(&classId, classField); err != nil {
return nil, err
}
s, err := createFunc(classId)
if err != nil {
return nil, err
}
if err := s.ReadFields(r); err != nil {
return nil, err
}
if err := r.EndContainer(); err != nil {
return nil, err
}
return s, nil
}
func WriteGeneric(w Writer, s WritableClass, field string) error {
if err := w.BeginContainer(s.FieldsCount()+1, field); err != nil {
return err
}
if err := w.WriteUint32(s.ClassId(), classField); err != nil {
return err
}
if err := s.WriteFields(w); err != nil {
return err
}
return w.EndContainer()
}

629
msgpack_assoc_reader.go Normal file
View File

@ -0,0 +1,629 @@
package meta
import (
"bytes"
"io"
"github.com/pkg/errors"
"github.com/vmihailenco/msgpack/v5"
"github.com/vmihailenco/msgpack/v5/msgpcode"
)
type msgpackAssocReader struct {
dec *msgpack.Decoder
stack []assocReadContainer
curr assocReadContainer
}
type assocReadContainer struct {
started bool
length int
assoc bool
values map[string]msgpack.RawMessage
reader io.Reader
readCnt int
}
func NewMsgpackAssocReader(r io.Reader) Reader {
return &msgpackAssocReader{
dec: msgpack.NewDecoder(r),
stack: make([]assocReadContainer, 0, 2),
curr: assocReadContainer{
reader: r,
},
}
}
func (rd *msgpackAssocReader) readField() (string, error) {
field, err := rd.dec.DecodeString()
if err != nil {
return "", errors.WithStack(err)
}
return field, nil
}
func (rd *msgpackAssocReader) ReadInt8(v *int8, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt8(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt8(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt8(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadInt16(v *int16, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt16(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt16(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt16(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadInt32(v *int32, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt32(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt32(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt32(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadInt64(v *int64, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt64(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt64(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt64(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadUint8(v *uint8, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint8(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint8(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint8(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadUint16(v *uint16, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint16(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint16(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint16(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadUint32(v *uint32, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint32(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint32(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint32(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadUint64(v *uint64, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint64(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint64(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint64(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadBool(v *bool, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeBool(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeBool(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeBool(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadFloat32(v *float32, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeFloat32(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeFloat32(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeFloat32(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadFloat64(v *float64, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeFloat64(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeFloat64(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeFloat64(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadString(v *string, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeString(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeString(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeString(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) ReadBytes(v *[]byte, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeBytes(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeBytes(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeBytes(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) BeginContainer(targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return rd.beginContainer(targetField)
}
if b, ok := rd.curr.values[targetField]; ok {
rd.dec.Reset(bytes.NewReader(b))
return rd.beginContainer(targetField)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return rd.beginContainer(targetField)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) EndContainer() error {
return rd.endContainer()
}
func (rd *msgpackAssocReader) BeginCollection(targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return rd.beginCollection(targetField)
}
if b, ok := rd.curr.values[targetField]; ok {
rd.dec.Reset(bytes.NewReader(b))
return rd.beginCollection(targetField)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return rd.beginCollection(targetField)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
rd.curr.readCnt = i + 1
}
return FieldNotFound
}
func (rd *msgpackAssocReader) EndCollection() error {
return rd.endContainer()
}
func (rd *msgpackAssocReader) ContainerSize() (int, error) {
return rd.curr.length, nil
}
func (rd *msgpackAssocReader) IsContainerAssoc() (bool, error) {
return rd.curr.assoc, nil
}
func (rd *msgpackAssocReader) Skip() error {
return errors.WithStack(rd.dec.Skip())
}
func (rd *msgpackAssocReader) TryReadMask() (bool, FieldsMask, error) {
if rd.curr.assoc {
return false, FieldsMask{}, nil
}
maskLen, err := rd.dec.DecodeArrayLen()
if err != nil {
if err == io.EOF {
return false, FieldsMask{}, nil
}
return false, FieldsMask{}, errors.WithStack(err)
}
var mask FieldsMask
for i := 0; i < maskLen; i++ {
maskPart, err := rd.dec.DecodeUint64()
if err != nil {
return false, FieldsMask{}, errors.WithStack(err)
}
mask.SetPartFromUint64(i, maskPart)
}
return true, mask, nil
}
func (rd *msgpackAssocReader) beginContainer(field string) error {
code, err := rd.dec.PeekCode()
if err != nil {
return errors.WithStack(err)
}
switch {
case code == msgpcode.Nil:
if err := rd.dec.DecodeNil(); err != nil {
return errors.WithStack(err)
}
rd.stack = append(rd.stack, rd.curr)
rd.curr = assocReadContainer{
started: true,
length: 0,
assoc: true,
values: make(map[string]msgpack.RawMessage),
reader: rd.dec.Buffered(),
}
case msgpcode.IsFixedMap(code), code == msgpcode.Map16, code == msgpcode.Map32:
l, err := rd.dec.DecodeMapLen()
if err != nil {
return errors.WithStack(err)
}
rd.stack = append(rd.stack, rd.curr)
rd.curr = assocReadContainer{
started: true,
length: l,
assoc: true,
values: make(map[string]msgpack.RawMessage, l),
reader: rd.dec.Buffered(),
}
default:
return errors.Errorf("field `%s` is not a map", field)
}
return nil
}
func (rd *msgpackAssocReader) beginCollection(field string) error {
code, err := rd.dec.PeekCode()
if err != nil {
return errors.WithStack(err)
}
switch {
case msgpcode.IsFixedArray(code), code == msgpcode.Array16, code == msgpcode.Array32:
l, err := rd.dec.DecodeArrayLen()
if err != nil {
return errors.WithStack(err)
}
rd.stack = append(rd.stack, rd.curr)
rd.curr = assocReadContainer{
started: true,
length: l,
assoc: false,
reader: rd.dec.Buffered(),
}
default:
return errors.Errorf("field `%s` is not an array", field)
}
return nil
}
func (rd *msgpackAssocReader) endContainer() error {
if len(rd.stack) == 0 {
return NoOpenContainer
}
rd.curr = rd.stack[len(rd.stack)-1]
rd.stack = rd.stack[:len(rd.stack)-1]
rd.dec.Reset(rd.curr.reader)
return nil
}

View File

@ -0,0 +1,120 @@
package meta_test
import (
"bytes"
"encoding/hex"
"testing"
"git.bit5.ru/backend/meta/v5"
"github.com/stretchr/testify/require"
)
func TestAssocMsgpackReader(t *testing.T) {
t.Run("reading parent", func(t *testing.T) {
// {"f1":"blabla","f3":[2,4,6],"f2":{"field":1},"f4":[{"field":10},{"field":1024}],"f5":null}
src := "85A26631A6626C61626C61A2663393020406A2663281A56669656C6401A266349281A56669656C640A81A56669656C64CD0400A26635C0"
expected := TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackAssocReader(bytes.NewReader(data))
var actual TestParent
readErr := actual.Read(rdr)
require.NoError(t, readErr)
require.EqualValues(t, expected, actual)
})
t.Run("reading child", func(t *testing.T) {
// {"f":"qwerty","f1":"blabla","f3":[2,4,6],"f2":{"field":1},"f4":[{"field":10},{"field":1024}]}
src := "85A166A6717765727479A26631A6626C61626C61A2663393020406A2663281A56669656C6401A266349281A56669656C640A81A56669656C64CD0400"
expected := TestChild{
Field: "qwerty",
TestParent: TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
},
}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackAssocReader(bytes.NewReader(data))
var actual TestChild
readErr := actual.Read(rdr)
require.NoError(t, readErr)
require.EqualValues(t, expected, actual)
})
t.Run("fail reading parent as array", func(t *testing.T) {
// ["blabla",[1],[2,4,6],[[10],[1024]],null]
src := "95A6626C61626C6191019302040692910A91CD0400C0"
expected := TestParent{}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackAssocReader(bytes.NewReader(data))
var actual TestParent
readErr := actual.Read(rdr)
require.ErrorContains(t, readErr, "field `` is not a map")
require.EqualValues(t, expected, actual)
})
t.Run("fail reading parent as array with maps", func(t *testing.T) {
// ["blabla",{"field":1},[2,4,6],[{"field":10},{"field":1024}],null]
src := "95A6626C61626C6181A56669656C6401930204069281A56669656C640A81A56669656C64CD0400C0"
expected := TestParent{}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackAssocReader(bytes.NewReader(data))
var actual TestParent
readErr := actual.Read(rdr)
require.ErrorContains(t, readErr, "field `` is not a map")
require.EqualValues(t, expected, actual)
})
t.Run("fail reading child as array", func(t *testing.T) {
// ["blabla",[1],[2,4,6],[[10],[1024]],null,"qwerty"]
src := "96A6626C61626C6191019302040692910A91CD0400C0A6717765727479"
expected := TestChild{}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackAssocReader(bytes.NewReader(data))
var actual TestChild
readErr := actual.Read(rdr)
require.ErrorContains(t, readErr, "field `` is not a map")
require.EqualValues(t, expected, actual)
})
}

171
msgpack_assoc_writer.go Normal file
View File

@ -0,0 +1,171 @@
package meta
import (
"io"
"github.com/pkg/errors"
"github.com/vmihailenco/msgpack/v5"
)
type msgpackAssocWriter struct {
enc *msgpack.Encoder
containers []writeContainer
}
type writeContainer struct {
length int
assoc bool
}
func NewMsgpackAssocWriter(w io.Writer) Writer {
return &msgpackAssocWriter{
enc: msgpack.NewEncoder(w),
containers: make([]writeContainer, 0, 1),
}
}
func (wr *msgpackAssocWriter) currentContainer() writeContainer {
if wr == nil || len(wr.containers) == 0 {
return writeContainer{}
}
return wr.containers[len(wr.containers)-1]
}
func (wr *msgpackAssocWriter) writeFieldName(field string) error {
if !wr.currentContainer().assoc {
return nil
}
return errors.WithStack(wr.enc.EncodeString(field))
}
func (wr *msgpackAssocWriter) WriteInt8(v int8, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(int64(v)))
}
func (wr *msgpackAssocWriter) WriteInt16(v int16, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(int64(v)))
}
func (wr *msgpackAssocWriter) WriteInt32(v int32, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(int64(v)))
}
func (wr *msgpackAssocWriter) WriteInt64(v int64, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(v))
}
func (wr *msgpackAssocWriter) WriteUint8(v uint8, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
}
func (wr *msgpackAssocWriter) WriteUint16(v uint16, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
}
func (wr *msgpackAssocWriter) WriteUint32(v uint32, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
}
func (wr *msgpackAssocWriter) WriteUint64(v uint64, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
}
func (wr *msgpackAssocWriter) WriteBool(v bool, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeBool(v))
}
func (wr *msgpackAssocWriter) WriteFloat32(v float32, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeFloat32(v))
}
func (wr *msgpackAssocWriter) WriteFloat64(v float64, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeFloat64(v))
}
func (wr *msgpackAssocWriter) WriteString(v string, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeString(v))
}
func (wr *msgpackAssocWriter) WriteBytes(v []byte, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeBytes(v))
}
func (wr *msgpackAssocWriter) BeginContainer(length int, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
if err := wr.enc.EncodeMapLen(length); err != nil {
return errors.WithStack(err)
}
wr.containers = append(wr.containers, writeContainer{
length: length,
assoc: true,
})
return nil
}
func (wr *msgpackAssocWriter) EndContainer() error {
if len(wr.containers) == 0 {
return errors.New("there is no open containers")
}
wr.containers = wr.containers[:len(wr.containers)-1]
return nil
}
func (wr *msgpackAssocWriter) BeginCollection(length int, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
if err := wr.enc.EncodeArrayLen(length); err != nil {
return errors.WithStack(err)
}
wr.containers = append(wr.containers, writeContainer{
length: length,
assoc: false,
})
return nil
}
func (wr *msgpackAssocWriter) EndCollection() error {
return wr.EndContainer()
}

View File

@ -0,0 +1,63 @@
package meta_test
import (
"bytes"
"encoding/hex"
"testing"
"git.bit5.ru/backend/meta/v5"
"github.com/stretchr/testify/require"
)
func TestMsgpackAssocWriter(t *testing.T) {
t.Run("write struct", func(t *testing.T) {
var buf bytes.Buffer
wr := meta.NewMsgpackAssocWriter(&buf)
s := TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
}
err := s.Write(wr)
require.NoError(t, err)
expected := "84a26631a6626c61626c61a2663281a56669656c6401a2663393020406a266349281a56669656c640a81a56669656c64cd0400"
actual := hex.EncodeToString(buf.Bytes())
require.EqualValues(t, expected, actual)
})
t.Run("write child struct", func(t *testing.T) {
var buf bytes.Buffer
wr := meta.NewMsgpackAssocWriter(&buf)
s := TestChild{
Field: "qwerty",
TestParent: TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
},
}
err := s.Write(wr)
require.NoError(t, err)
expected := "85a26631a6626c61626c61a2663281a56669656c6401a2663393020406a266349281a56669656c640a81a56669656c64cd0400a166a6717765727479"
actual := hex.EncodeToString(buf.Bytes())
require.EqualValues(t, expected, actual)
})
}

View File

@ -1,7 +1,6 @@
package meta package meta
import ( import (
"bytes"
"io" "io"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -9,6 +8,11 @@ import (
"github.com/vmihailenco/msgpack/v5/msgpcode" "github.com/vmihailenco/msgpack/v5/msgpcode"
) )
var (
FieldNotFound = errors.New("field not found")
NoOpenContainer = errors.New("there is no open container")
)
type msgpackReader struct { type msgpackReader struct {
dec *msgpack.Decoder dec *msgpack.Decoder
stack []readContainer stack []readContainer
@ -16,500 +20,83 @@ type msgpackReader struct {
} }
type readContainer struct { type readContainer struct {
started bool
length int length int
assoc bool
values map[string]msgpack.RawMessage
reader io.Reader
readCnt int
} }
func NewMsgpackReader(r io.Reader) Reader { func NewMsgpackReader(r io.Reader) Reader {
return &msgpackReader{ return &msgpackReader{
dec: msgpack.NewDecoder(r), dec: msgpack.NewDecoder(r),
stack: make([]readContainer, 0, 2), stack: make([]readContainer, 0, 2),
curr: readContainer{ curr: readContainer{},
reader: r,
},
} }
} }
func (rd *msgpackReader) readField() (string, error) {
field, err := rd.dec.DecodeString()
if err != nil {
return "", errors.WithStack(err)
}
return field, nil
}
func (rd *msgpackReader) ReadInt8(v *int8, targetField string) error { func (rd *msgpackReader) ReadInt8(v *int8, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt8(rd.dec, v) return decodeInt8(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt8(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt8(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadInt16(v *int16, targetField string) error { func (rd *msgpackReader) ReadInt16(v *int16, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt16(rd.dec, v) return decodeInt16(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt16(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt16(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadInt32(v *int32, targetField string) error { func (rd *msgpackReader) ReadInt32(v *int32, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt32(rd.dec, v) return decodeInt32(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt32(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt32(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadInt64(v *int64, targetField string) error { func (rd *msgpackReader) ReadInt64(v *int64, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeInt64(rd.dec, v) return decodeInt64(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeInt64(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeInt64(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadUint8(v *uint8, targetField string) error { func (rd *msgpackReader) ReadUint8(v *uint8, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint8(rd.dec, v) return decodeUint8(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint8(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint8(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadUint16(v *uint16, targetField string) error { func (rd *msgpackReader) ReadUint16(v *uint16, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint16(rd.dec, v) return decodeUint16(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint16(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint16(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadUint32(v *uint32, targetField string) error { func (rd *msgpackReader) ReadUint32(v *uint32, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint32(rd.dec, v) return decodeUint32(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint32(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint32(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadUint64(v *uint64, targetField string) error { func (rd *msgpackReader) ReadUint64(v *uint64, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeUint64(rd.dec, v) return decodeUint64(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeUint64(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeUint64(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadBool(v *bool, targetField string) error { func (rd *msgpackReader) ReadBool(v *bool, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeBool(rd.dec, v) return decodeBool(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeBool(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeBool(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadFloat32(v *float32, targetField string) error { func (rd *msgpackReader) ReadFloat32(v *float32, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeFloat32(rd.dec, v) return decodeFloat32(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeFloat32(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeFloat32(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadFloat64(v *float64, targetField string) error { func (rd *msgpackReader) ReadFloat64(v *float64, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeFloat64(rd.dec, v) return decodeFloat64(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeFloat64(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeFloat64(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadString(v *string, targetField string) error { func (rd *msgpackReader) ReadString(v *string, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeString(rd.dec, v) return decodeString(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeString(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeString(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) ReadBytes(v *[]byte, targetField string) error { func (rd *msgpackReader) ReadBytes(v *[]byte, targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return decodeBytes(rd.dec, v) return decodeBytes(rd.dec, v)
}
if b, ok := rd.curr.values[targetField]; ok {
dec := msgpack.NewDecoder(bytes.NewReader(b))
return decodeBytes(dec, v)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return decodeBytes(rd.dec, v)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
} }
func (rd *msgpackReader) BeginContainer(targetField string) error { func (rd *msgpackReader) BeginContainer(targetField string) error {
if !rd.curr.started || !rd.curr.assoc {
return rd.beginContainer(targetField) return rd.beginContainer(targetField)
}
if b, ok := rd.curr.values[targetField]; ok {
rd.dec.Reset(bytes.NewReader(b))
return rd.beginContainer(targetField)
}
for i := rd.curr.readCnt; i < rd.curr.length; i++ {
field, err := rd.readField()
if err != nil {
return err
}
if field == targetField {
rd.curr.readCnt = i + 1
return rd.beginContainer(targetField)
}
raw, err := rd.dec.DecodeRaw()
if err != nil {
return errors.WithStack(err)
}
rd.curr.values[field] = raw
}
return errors.Errorf("field `%s` not found", targetField)
}
func (rd *msgpackReader) beginContainer(field string) error {
code, err := rd.dec.PeekCode()
if err != nil {
return errors.WithStack(err)
}
switch {
case msgpcode.IsFixedMap(code), code == msgpcode.Map16, code == msgpcode.Map32:
l, err := rd.dec.DecodeMapLen()
if err != nil {
return errors.WithStack(err)
}
rd.stack = append(rd.stack, rd.curr)
rd.curr = readContainer{
started: true,
length: l,
assoc: true,
values: make(map[string]msgpack.RawMessage, l),
reader: rd.dec.Buffered(),
}
case msgpcode.IsFixedArray(code), code == msgpcode.Array16, code == msgpcode.Array32:
l, err := rd.dec.DecodeArrayLen()
if err != nil {
return errors.WithStack(err)
}
rd.stack = append(rd.stack, rd.curr)
rd.curr = readContainer{
started: true,
length: l,
assoc: false,
reader: rd.dec.Buffered(),
}
default:
return errors.Errorf("there is no container for field `%s`", field)
}
return nil
} }
func (rd *msgpackReader) EndContainer() error { func (rd *msgpackReader) EndContainer() error {
if len(rd.stack) == 0 { return rd.endContainer()
return errors.New("there is no open containers") }
}
rd.curr = rd.stack[len(rd.stack)-1] func (rd *msgpackReader) BeginCollection(targetField string) error {
rd.stack = rd.stack[:len(rd.stack)-1] return rd.beginContainer(targetField)
rd.dec.Reset(rd.curr.reader) }
return nil
func (rd *msgpackReader) EndCollection() error {
return rd.endContainer()
} }
func (rd *msgpackReader) ContainerSize() (int, error) { func (rd *msgpackReader) ContainerSize() (int, error) {
@ -517,7 +104,7 @@ func (rd *msgpackReader) ContainerSize() (int, error) {
} }
func (rd *msgpackReader) IsContainerAssoc() (bool, error) { func (rd *msgpackReader) IsContainerAssoc() (bool, error) {
return rd.curr.assoc, nil return false, nil
} }
func (rd *msgpackReader) Skip() error { func (rd *msgpackReader) Skip() error {
@ -525,25 +112,15 @@ func (rd *msgpackReader) Skip() error {
} }
func (rd *msgpackReader) TryReadMask() (bool, FieldsMask, error) { func (rd *msgpackReader) TryReadMask() (bool, FieldsMask, error) {
code, err := rd.dec.PeekCode() maskLen, err := rd.dec.DecodeArrayLen()
if err != nil { if err != nil {
return false, FieldsMask{}, errors.WithStack(err) if err == io.EOF {
}
if code != msgpcode.Nil {
return false, FieldsMask{}, nil return false, FieldsMask{}, nil
} }
if err := rd.dec.Skip(); err != nil {
return false, FieldsMask{}, errors.WithStack(err) return false, FieldsMask{}, errors.WithStack(err)
} }
var mask FieldsMask var mask FieldsMask
maskLen, err := rd.dec.DecodeArrayLen()
if err != nil {
return false, FieldsMask{}, errors.WithStack(err)
}
for i := 0; i < maskLen; i++ { for i := 0; i < maskLen; i++ {
maskPart, err := rd.dec.DecodeUint64() maskPart, err := rd.dec.DecodeUint64()
if err != nil { if err != nil {
@ -555,6 +132,47 @@ func (rd *msgpackReader) TryReadMask() (bool, FieldsMask, error) {
return true, mask, nil return true, mask, nil
} }
func (rd *msgpackReader) beginContainer(field string) error {
code, err := rd.dec.PeekCode()
if err != nil {
return errors.WithStack(err)
}
switch {
case code == msgpcode.Nil:
if err := rd.dec.DecodeNil(); err != nil {
return errors.WithStack(err)
}
rd.stack = append(rd.stack, rd.curr)
rd.curr = readContainer{
length: 0,
}
case msgpcode.IsFixedArray(code), code == msgpcode.Array16, code == msgpcode.Array32:
l, err := rd.dec.DecodeArrayLen()
if err != nil {
return errors.WithStack(err)
}
rd.stack = append(rd.stack, rd.curr)
rd.curr = readContainer{
length: l,
}
default:
return errors.Errorf("field `%s` is not an array", field)
}
return nil
}
func (rd *msgpackReader) endContainer() error {
if len(rd.stack) == 0 {
return NoOpenContainer
}
rd.curr = rd.stack[len(rd.stack)-1]
rd.stack = rd.stack[:len(rd.stack)-1]
return nil
}
func decodeUint8(dec *msgpack.Decoder, v *uint8) error { func decodeUint8(dec *msgpack.Decoder, v *uint8) error {
tmp, err := dec.DecodeUint8() tmp, err := dec.DecodeUint8()
if err != nil { if err != nil {
@ -637,6 +255,21 @@ func decodeBool(dec *msgpack.Decoder, v *bool) error {
} }
func decodeFloat32(dec *msgpack.Decoder, v *float32) error { func decodeFloat32(dec *msgpack.Decoder, v *float32) error {
code, err := dec.PeekCode()
if err != nil {
return errors.WithStack(err)
}
if code == msgpcode.Double {
var tmp float64
if err := decodeFloat64(dec, &tmp); err != nil {
return err
}
*v = float32(tmp)
return nil
}
tmp, err := dec.DecodeFloat32() tmp, err := dec.DecodeFloat32()
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)

View File

@ -5,14 +5,14 @@ import (
"encoding/hex" "encoding/hex"
"testing" "testing"
"git.bit5.ru/backend/meta/v2" "git.bit5.ru/backend/meta/v5"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestMsgpackReader(t *testing.T) { func TestMsgpackReader(t *testing.T) {
t.Run("reading struct as map", func(t *testing.T) { t.Run("reading parent", func(t *testing.T) {
// {"f1":"blabla","f3":[2,4,6],"f2":{"field":1},"f4":[{"field":10},{"field":1024}]} // ["blabla",[1],[2,4,6],[[10],[1024]],null]
src := "84a26631a6626c61626c61a2663393020406a2663281a56669656c6401a266349281a56669656c640a81a56669656c64cd0400" src := "95A6626C61626C6191019302040692910A91CD0400C0"
expected := TestParent{ expected := TestParent{
Field1: "blabla", Field1: "blabla",
@ -37,20 +37,61 @@ func TestMsgpackReader(t *testing.T) {
require.EqualValues(t, expected, actual) require.EqualValues(t, expected, actual)
}) })
t.Run("reading struct as array with maps", func(t *testing.T) { t.Run("reading child", func(t *testing.T) {
// ["blabla",[1],[2,4,6],[[10],[1024]],null,"qwerty"]
src := "96A6626C61626C6191019302040692910A91CD0400C0A6717765727479"
expected := TestChild{
Field: "qwerty",
TestParent: TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
},
}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackReader(bytes.NewReader(data))
var actual TestChild
readErr := actual.Read(rdr)
require.NoError(t, readErr)
require.EqualValues(t, expected, actual)
})
t.Run("fail reading parent as map", func(t *testing.T) {
// {"f1":"blabla","f3":[2,4,6],"f2":{"field":1},"f4":[{"field":10},{"field":1024}]}
src := "84a26631a6626c61626c61a2663393020406a2663281a56669656c6401a266349281a56669656c640a81a56669656c64cd0400"
expected := TestParent{}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackReader(bytes.NewReader(data))
var actual TestParent
readErr := actual.Read(rdr)
require.ErrorContains(t, readErr, "field `` is not an array")
require.EqualValues(t, expected, actual)
})
t.Run("fail reading parent as array with maps", func(t *testing.T) {
// ["blabla",{"field":1},[2,4,6],[{"field":10},{"field":1024}]] // ["blabla",{"field":1},[2,4,6],[{"field":10},{"field":1024}]]
src := "94a6626c61626c6181a56669656c6401930204069281a56669656c640a81a56669656c64cd0400" src := "94a6626c61626c6181a56669656c6401930204069281a56669656c640a81a56669656c64cd0400"
expected := TestParent{ expected := TestParent{
Field1: "blabla", Field1: "blabla",
Field2: TestFoo{ Field3: []int8{},
Field: 1, Field4: []TestFoo{},
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
} }
data, err := hex.DecodeString(src) data, err := hex.DecodeString(src)
@ -60,55 +101,15 @@ func TestMsgpackReader(t *testing.T) {
var actual TestParent var actual TestParent
readErr := actual.Read(rdr) readErr := actual.Read(rdr)
require.NoError(t, readErr) require.ErrorContains(t, readErr, "field `f2` is not an array")
require.EqualValues(t, expected, actual) require.EqualValues(t, expected, actual)
}) })
t.Run("reading struct only as array", func(t *testing.T) { t.Run("fail reading child struct as map", func(t *testing.T) {
// ["blabla",[1],[2,4,6],[[10],[1024]]]
src := "94a6626c61626c6191019302040692910a91cd0400"
expected := TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackReader(bytes.NewReader(data))
var actual TestParent
readErr := actual.Read(rdr)
require.NoError(t, readErr)
require.EqualValues(t, expected, actual)
})
t.Run("reading child struct as map", func(t *testing.T) {
// {"f":"qwerty","f1":"blabla","f3":[2,4,6],"f2":{"field":1},"f4":[{"field":10},{"field":1024}]} // {"f":"qwerty","f1":"blabla","f3":[2,4,6],"f2":{"field":1},"f4":[{"field":10},{"field":1024}]}
src := "85a166a6717765727479a26631a6626c61626c61a2663393020406a2663281a56669656c6401a266349281a56669656c640a81a56669656c64cd0400" src := "85a166a6717765727479a26631a6626c61626c61a2663393020406a2663281a56669656c6401a266349281a56669656c640a81a56669656c64cd0400"
expected := TestChild{ expected := TestChild{}
Field: "qwerty",
TestParent: TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
},
}
data, err := hex.DecodeString(src) data, err := hex.DecodeString(src)
require.NoError(t, err) require.NoError(t, err)
@ -117,37 +118,7 @@ func TestMsgpackReader(t *testing.T) {
var actual TestChild var actual TestChild
readErr := actual.Read(rdr) readErr := actual.Read(rdr)
require.NoError(t, readErr) require.ErrorContains(t, readErr, "field `` is not an array")
require.EqualValues(t, expected, actual)
})
t.Run("reading child struct as array", func(t *testing.T) {
// ["blabla",[1],[2,4,6],[[10],[1024]],"qwerty"]
src := "95a6626c61626c6191019302040692910a91cd0400a6717765727479"
expected := TestChild{
Field: "qwerty",
TestParent: TestParent{
Field1: "blabla",
Field2: TestFoo{
Field: 1,
},
Field3: []int8{2, 4, 6},
Field4: []TestFoo{
{Field: 10},
{Field: 1024},
},
},
}
data, err := hex.DecodeString(src)
require.NoError(t, err)
rdr := meta.NewMsgpackReader(bytes.NewReader(data))
var actual TestChild
readErr := actual.Read(rdr)
require.NoError(t, readErr)
require.EqualValues(t, expected, actual) require.EqualValues(t, expected, actual)
}) })
} }

View File

@ -9,156 +9,85 @@ import (
type msgpackWriter struct { type msgpackWriter struct {
enc *msgpack.Encoder enc *msgpack.Encoder
containers []writeContainer containers []struct{}
}
type writeContainer struct {
length int
assoc bool
} }
func NewMsgpackWriter(w io.Writer) Writer { func NewMsgpackWriter(w io.Writer) Writer {
return &msgpackWriter{ return &msgpackWriter{
enc: msgpack.NewEncoder(w), enc: msgpack.NewEncoder(w),
containers: make([]writeContainer, 0, 1), containers: make([]struct{}, 0, 1),
} }
} }
func (wr *msgpackWriter) currentContainer() writeContainer {
if wr == nil || len(wr.containers) == 0 {
return writeContainer{}
}
return wr.containers[len(wr.containers)-1]
}
func (wr *msgpackWriter) writeFieldName(field string) error {
if !wr.currentContainer().assoc {
return nil
}
return errors.WithStack(wr.enc.EncodeString(field))
}
func (wr *msgpackWriter) WriteInt8(v int8, field string) error { func (wr *msgpackWriter) WriteInt8(v int8, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(int64(v))) return errors.WithStack(wr.enc.EncodeInt(int64(v)))
} }
func (wr *msgpackWriter) WriteInt16(v int16, field string) error { func (wr *msgpackWriter) WriteInt16(v int16, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(int64(v))) return errors.WithStack(wr.enc.EncodeInt(int64(v)))
} }
func (wr *msgpackWriter) WriteInt32(v int32, field string) error { func (wr *msgpackWriter) WriteInt32(v int32, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(int64(v))) return errors.WithStack(wr.enc.EncodeInt(int64(v)))
} }
func (wr *msgpackWriter) WriteInt64(v int64, field string) error { func (wr *msgpackWriter) WriteInt64(v int64, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeInt(v)) return errors.WithStack(wr.enc.EncodeInt(v))
} }
func (wr *msgpackWriter) WriteUint8(v uint8, field string) error { func (wr *msgpackWriter) WriteUint8(v uint8, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v))) return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
} }
func (wr *msgpackWriter) WriteUint16(v uint16, field string) error { func (wr *msgpackWriter) WriteUint16(v uint16, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v))) return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
} }
func (wr *msgpackWriter) WriteUint32(v uint32, field string) error { func (wr *msgpackWriter) WriteUint32(v uint32, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v))) return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
} }
func (wr *msgpackWriter) WriteUint64(v uint64, field string) error { func (wr *msgpackWriter) WriteUint64(v uint64, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeUint(uint64(v))) return errors.WithStack(wr.enc.EncodeUint(uint64(v)))
} }
func (wr *msgpackWriter) WriteBool(v bool, field string) error { func (wr *msgpackWriter) WriteBool(v bool, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeBool(v)) return errors.WithStack(wr.enc.EncodeBool(v))
} }
func (wr *msgpackWriter) WriteFloat32(v float32, field string) error { func (wr *msgpackWriter) WriteFloat32(v float32, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeFloat32(v)) return errors.WithStack(wr.enc.EncodeFloat32(v))
} }
func (wr *msgpackWriter) WriteFloat64(v float64, field string) error { func (wr *msgpackWriter) WriteFloat64(v float64, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeFloat64(v)) return errors.WithStack(wr.enc.EncodeFloat64(v))
} }
func (wr *msgpackWriter) WriteString(v string, field string) error { func (wr *msgpackWriter) WriteString(v string, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeString(v)) return errors.WithStack(wr.enc.EncodeString(v))
} }
func (wr *msgpackWriter) WriteBytes(v []byte, field string) error { func (wr *msgpackWriter) WriteBytes(v []byte, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
return errors.WithStack(wr.enc.EncodeBytes(v)) return errors.WithStack(wr.enc.EncodeBytes(v))
} }
func (wr *msgpackWriter) BeginContainer(length int, field string) error { func (wr *msgpackWriter) BeginContainer(length int, field string) error {
if err := wr.writeFieldName(field); err != nil { return wr.BeginCollection(length, field)
return err
}
if err := wr.enc.EncodeArrayLen(length); err != nil {
return errors.WithStack(err)
}
wr.containers = append(wr.containers, writeContainer{
length: length,
assoc: false,
})
return nil
}
func (wr *msgpackWriter) BeginAssocContainer(length int, field string) error {
if err := wr.writeFieldName(field); err != nil {
return err
}
if err := wr.enc.EncodeMapLen(length); err != nil {
return errors.WithStack(err)
}
wr.containers = append(wr.containers, writeContainer{
length: length,
assoc: true,
})
return nil
} }
func (wr *msgpackWriter) EndContainer() error { func (wr *msgpackWriter) EndContainer() error {
return wr.EndCollection()
}
func (wr *msgpackWriter) BeginCollection(length int, field string) error {
if err := wr.enc.EncodeArrayLen(length); err != nil {
return errors.WithStack(err)
}
wr.containers = append(wr.containers, struct{}{})
return nil
}
func (wr *msgpackWriter) EndCollection() error {
if len(wr.containers) == 0 { if len(wr.containers) == 0 {
return errors.New("there is no open containers") return errors.New("there is no open containers")
} }

View File

@ -5,7 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"testing" "testing"
"git.bit5.ru/backend/meta/v2" "git.bit5.ru/backend/meta/v5"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -29,7 +29,7 @@ func TestMsgpackWriter(t *testing.T) {
err := s.Write(wr) err := s.Write(wr)
require.NoError(t, err) require.NoError(t, err)
expected := "84a26631a6626c61626c61a2663281a56669656c6401a2663393020406a266349281a56669656c640a81a56669656c64cd0400" expected := "94a6626c61626c6191019302040692910a91cd0400"
actual := hex.EncodeToString(buf.Bytes()) actual := hex.EncodeToString(buf.Bytes())
require.EqualValues(t, expected, actual) require.EqualValues(t, expected, actual)
}) })
@ -56,7 +56,7 @@ func TestMsgpackWriter(t *testing.T) {
err := s.Write(wr) err := s.Write(wr)
require.NoError(t, err) require.NoError(t, err)
expected := "85a26631a6626c61626c61a2663281a56669656c6401a2663393020406a266349281a56669656c640a81a56669656c64cd0400a166a6717765727479" expected := "95a6626c61626c6191019302040692910a91cd0400a6717765727479"
actual := hex.EncodeToString(buf.Bytes()) actual := hex.EncodeToString(buf.Bytes())
require.EqualValues(t, expected, actual) require.EqualValues(t, expected, actual)
}) })

View File

@ -1,14 +1,40 @@
package meta_test package meta_test
import ( import (
"git.bit5.ru/backend/meta/v2" "git.bit5.ru/backend/meta/v5"
"github.com/pkg/errors"
) )
func CreateTestParent(id uint32) (meta.Readable, error) {
if id == 111 {
return &TestParent{}, nil
}
return nil, errors.Errorf("wrong id")
}
type TestParent struct { type TestParent struct {
Field1 string `json:"f1" msgpack:"f1"` Field1 string `json:"f1" msgpack:"f1"`
Field2 TestFoo `json:"f2" msgpack:"f2"` Field2 TestFoo `json:"f2" msgpack:"f2"`
Field3 []int8 `json:"f3" msgpack:"f3"` Field3 []int8 `json:"f3" msgpack:"f3"`
Field4 []TestFoo `json:"f4" msgpack:"f4"` Field4 []TestFoo `json:"f4" msgpack:"f4"`
Field5 ITestParent `json:"f5" msgpack:"f5"`
}
type ITestParent interface {
meta.Struct
PtrTestParent() *TestParent
}
func (s *TestParent) PtrTestParent() *TestParent {
return s
}
func (s *TestParent) ClassId() uint32 {
return 111
}
func (s *TestParent) ClassName() string {
return "TestParent"
} }
func (s *TestParent) Reset() { func (s *TestParent) Reset() {
@ -26,6 +52,8 @@ func (s *TestParent) Reset() {
s.Field4 = s.Field4[:0] s.Field4 = s.Field4[:0]
} }
s.Field5 = nil
} }
func (s *TestParent) Read(reader meta.Reader) error { func (s *TestParent) Read(reader meta.Reader) error {
@ -46,8 +74,8 @@ func (s *TestParent) ReadFields(reader meta.Reader) error {
return err return err
} }
if contSize < 4 { if contSize < 5 {
contSize = 4 contSize = 5
} }
if contSize <= 0 { if contSize <= 0 {
@ -79,7 +107,7 @@ func (s *TestParent) ReadFields(reader meta.Reader) error {
} }
contSize-- contSize--
if err := reader.BeginContainer("f3"); err != nil { if err := reader.BeginCollection("f3"); err != nil {
return err return err
} }
field3Size, err := reader.ContainerSize() field3Size, err := reader.ContainerSize()
@ -94,7 +122,7 @@ func (s *TestParent) ReadFields(reader meta.Reader) error {
s.Field3 = append(s.Field3, tmpField3) s.Field3 = append(s.Field3, tmpField3)
} }
if err := reader.EndContainer(); err != nil { if err := reader.EndCollection(); err != nil {
return err return err
} }
@ -103,7 +131,7 @@ func (s *TestParent) ReadFields(reader meta.Reader) error {
} }
contSize-- contSize--
if err := reader.BeginContainer("f4"); err != nil { if err := reader.BeginCollection("f4"); err != nil {
return err return err
} }
field4Size, err := reader.ContainerSize() field4Size, err := reader.ContainerSize()
@ -124,15 +152,30 @@ func (s *TestParent) ReadFields(reader meta.Reader) error {
s.Field4 = append(s.Field4, tmpField4) s.Field4 = append(s.Field4, tmpField4)
} }
if err := reader.EndContainer(); err != nil { if err := reader.EndCollection(); err != nil {
return err return err
} }
if contSize <= 0 {
return nil
}
contSize--
if v, err := meta.ReadGeneric(reader, CreateTestParent, "f5"); err != nil {
if err != meta.FieldNotFound {
return err
}
} else {
if v != nil {
s.Field5 = v.(ITestParent)
}
}
return nil return nil
} }
func (s *TestParent) Write(writer meta.Writer) error { func (s *TestParent) Write(writer meta.Writer) error {
if err := writer.BeginAssocContainer(4, ""); err != nil { if err := writer.BeginContainer(4, ""); err != nil {
return err return err
} }
if err := s.WriteFields(writer); err != nil { if err := s.WriteFields(writer); err != nil {
@ -147,7 +190,7 @@ func (s *TestParent) WriteFields(writer meta.Writer) error {
return err return err
} }
if err := writer.BeginAssocContainer(1, "f2"); err != nil { if err := writer.BeginContainer(1, "f2"); err != nil {
return err return err
} }
if err := s.Field2.WriteFields(writer); err != nil { if err := s.Field2.WriteFields(writer); err != nil {
@ -157,24 +200,23 @@ func (s *TestParent) WriteFields(writer meta.Writer) error {
return err return err
} }
if err := writer.BeginContainer(len(s.Field3), "f3"); err != nil { if err := writer.BeginCollection(len(s.Field3), "f3"); err != nil {
return err return err
} }
for _, v := range s.Field3 { for _, v := range s.Field3 {
if err := writer.WriteInt8(v, ""); err != nil { if err := writer.WriteInt8(v, ""); err != nil {
return err return err
} }
} }
if err := writer.EndContainer(); err != nil { if err := writer.EndCollection(); err != nil {
return err return err
} }
if err := writer.BeginContainer(len(s.Field4), "f4"); err != nil { if err := writer.BeginCollection(len(s.Field4), "f4"); err != nil {
return err return err
} }
for _, v := range s.Field4 { for _, v := range s.Field4 {
if err := writer.BeginAssocContainer(1, ""); err != nil { if err := writer.BeginContainer(1, ""); err != nil {
return err return err
} }
if err := v.WriteFields(writer); err != nil { if err := v.WriteFields(writer); err != nil {
@ -185,13 +227,17 @@ func (s *TestParent) WriteFields(writer meta.Writer) error {
} }
} }
if err := writer.EndContainer(); err != nil { if err := writer.EndCollection(); err != nil {
return err return err
} }
return nil return nil
} }
func (s *TestParent) FieldsCount() int {
return 5
}
type TestChild struct { type TestChild struct {
TestParent TestParent
@ -244,7 +290,7 @@ func (s *TestChild) ReadFields(reader meta.Reader) error {
} }
func (s *TestChild) Write(writer meta.Writer) error { func (s *TestChild) Write(writer meta.Writer) error {
if err := writer.BeginAssocContainer(5, ""); err != nil { if err := writer.BeginContainer(5, ""); err != nil {
return err return err
} }
if err := s.WriteFields(writer); err != nil { if err := s.WriteFields(writer); err != nil {
@ -310,7 +356,7 @@ func (s *TestFoo) ReadFields(reader meta.Reader) error {
} }
func (s *TestFoo) Write(writer meta.Writer) error { func (s *TestFoo) Write(writer meta.Writer) error {
if err := writer.BeginAssocContainer(1, ""); err != nil { if err := writer.BeginContainer(1, ""); err != nil {
return err return err
} }
if err := s.WriteFields(writer); err != nil { if err := s.WriteFields(writer); err != nil {