diff --git a/maps.go b/maps.go index d216817..5e1249d 100644 --- a/maps.go +++ b/maps.go @@ -72,27 +72,25 @@ func (smk sortableMapKeys) Less(i, j int) bool { } // encCheckedMap encodes a compatible map as a REZI map. -func encCheckedMap(v interface{}, ti typeInfo) ([]byte, error) { - if ti.Main != mtMap { +func encCheckedMap(value analyzed[any]) ([]byte, error) { + if value.ti.Main != mtMap { panic("not a map type") } - return encWithNilCheck(v, ti, func(val interface{}) ([]byte, error) { - return encMap(val, *ti.KeyType) - }, reflect.Value.Interface) + return encWithNilCheck(value, encMap, reflect.Value.Interface) } -func encMap(v interface{}, keyType typeInfo) ([]byte, error) { - refVal := reflect.ValueOf(v) - - if v == nil || refVal.IsNil() { +// requires keyType type info to be avail under *mapVal.ti.KeyType and ref to be +// set. +func encMap(val analyzed[any]) ([]byte, error) { + if val.native == nil || val.ref.IsNil() { return encNilHeader(0), nil } - mapKeys := refVal.MapKeys() + mapKeys := val.ref.MapKeys() keysToSort := sortableMapKeys{ keys: mapKeys, - ti: keyType, + ti: *val.ti.KeyType, } sort.Sort(keysToSort) mapKeys = keysToSort.keys @@ -101,7 +99,7 @@ func encMap(v interface{}, keyType typeInfo) ([]byte, error) { for i := range mapKeys { k := mapKeys[i] - v := refVal.MapIndex(k) + v := val.ref.MapIndex(k) keyData, err := Enc(k.Interface()) if err != nil { @@ -116,46 +114,44 @@ func encMap(v interface{}, keyType typeInfo) ([]byte, error) { enc = append(enc, valData...) } - enc = append(encInt(tLen(len(enc))), enc...) + enc = append(encCount(len(enc), nil), enc...) return enc, nil } // decCheckedMap decodes a REZI map as a compatible map type. -func decCheckedMap(data []byte, v interface{}, ti typeInfo) (int, error) { - if ti.Main != mtMap { +func decCheckedMap(data []byte, v analyzed[any]) (int, error) { + if v.ti.Main != mtMap { panic("not a map type") } - m, n, err := decWithNilCheck(data, v, ti, fn_DecToWrappedReceiver(v, ti, + _, di, n, err := decWithNilCheck(data, v, fn_DecToWrappedReceiver(v, func(t reflect.Type) bool { return t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Map }, - func(b []byte, i interface{}) (interface{}, int, error) { - decN, err := decMap(b, i) - return nil, decN, err - }, + decMap, )) if err != nil { return n, err } - if ti.Indir == 0 { - refReceiver := reflect.ValueOf(v) - refReceiver.Elem().Set(reflect.ValueOf(m)) + if v.ti.Indir == 0 { + refReceiver := v.ref + refReceiver.Elem().Set(di.Ref) } return n, err } -func decMap(data []byte, v interface{}) (int, error) { +func decMap(data []byte, v analyzed[any]) (decInfo, int, error) { + var di decInfo var totalConsumed int toConsume, _, n, err := decInt[tLen](data) if err != nil { - return 0, errorDecf(0, "decode byte count: %s", err) + return di, 0, errorDecf(0, "decode byte count: %s", err) } data = data[n:] totalConsumed += n - refVal := reflect.ValueOf(v) + refVal := v.ref refMapType := refVal.Type().Elem() if toConsume == 0 { @@ -164,14 +160,16 @@ func decMap(data []byte, v interface{}) (int, error) { // set it to the value refVal.Elem().Set(emptyMap) - return totalConsumed, nil + di.Ref = emptyMap + return di, totalConsumed, nil } else if toConsume == -1 { // initialize to the nil map nilMap := reflect.Zero(refMapType) // set it to the value refVal.Elem().Set(nilMap) - return totalConsumed, nil + di.Ref = nilMap + return di, totalConsumed, nil } if len(data) < toConsume { @@ -183,7 +181,7 @@ func decMap(data []byte, v interface{}) (int, error) { } const errFmt = "decoded map byte count is %d but only %d byte%s remain%s in data at offset" err := errorDecf(totalConsumed, errFmt, toConsume, len(data), s, verbS).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return totalConsumed, err + return di, totalConsumed, err } // clamp values we are allowed to read so we don't try to read other data @@ -200,7 +198,7 @@ func decMap(data []byte, v interface{}) (int, error) { refKey := reflect.New(refKType) n, err := Dec(data, refKey.Interface()) if err != nil { - return totalConsumed, errorDecf(totalConsumed, "map key: %v", err) + return di, totalConsumed, errorDecf(totalConsumed, "map key: %v", err) } totalConsumed += n i += n @@ -209,7 +207,7 @@ func decMap(data []byte, v interface{}) (int, error) { refValue := reflect.New(refVType) n, err = Dec(data, refValue.Interface()) if err != nil { - return totalConsumed, errorDecf(totalConsumed, "map value[%v]: %v", refKey.Elem().Interface(), err) + return di, totalConsumed, errorDecf(totalConsumed, "map value[%v]: %v", refKey.Elem().Interface(), err) } totalConsumed += n i += n @@ -219,6 +217,6 @@ func decMap(data []byte, v interface{}) (int, error) { } refVal.Elem().Set(m) - - return totalConsumed, nil + di.Ref = m + return di, totalConsumed, nil } diff --git a/primitives.go b/primitives.go index 07f74ef..cdb1b43 100644 --- a/primitives.go +++ b/primitives.go @@ -71,84 +71,84 @@ type anyComplex interface { // correct level of indirection of pointer that the passed-in pointer was nil // at, which is retrieved by a call to decCheckedPrim with a pointer to *that* // type. -func encCheckedPrim(value interface{}, ti typeInfo) ([]byte, error) { - switch ti.Main { +func encCheckedPrim(value analyzed[any]) ([]byte, error) { + switch value.ti.Main { case mtString: - return encWithNilCheck(value, ti, nilErrEncoder(encString), reflect.Value.String) + return encWithNilCheck(value, nilErrEncoder(encString), reflect.Value.String) case mtBool: - return encWithNilCheck(value, ti, nilErrEncoder(encBool), reflect.Value.Bool) + return encWithNilCheck(value, nilErrEncoder(encBool), reflect.Value.Bool) case mtIntegral: - if ti.Signed { - switch ti.Bits { + if value.ti.Signed { + switch value.ti.Bits { case 8: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[int8]), func(r reflect.Value) int8 { + return encWithNilCheck(value, nilErrEncoder(encInt[int8]), func(r reflect.Value) int8 { return int8(r.Int()) }) case 16: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[int16]), func(r reflect.Value) int16 { + return encWithNilCheck(value, nilErrEncoder(encInt[int16]), func(r reflect.Value) int16 { return int16(r.Int()) }) case 32: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[int32]), func(r reflect.Value) int32 { + return encWithNilCheck(value, nilErrEncoder(encInt[int32]), func(r reflect.Value) int32 { return int32(r.Int()) }) case 64: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[int64]), reflect.Value.Int) + return encWithNilCheck(value, nilErrEncoder(encInt[int64]), reflect.Value.Int) default: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[int]), func(r reflect.Value) int { + return encWithNilCheck(value, nilErrEncoder(encInt[int]), func(r reflect.Value) int { return int(r.Int()) }) } } else { - switch ti.Bits { + switch value.ti.Bits { case 8: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[uint8]), func(r reflect.Value) uint8 { + return encWithNilCheck(value, nilErrEncoder(encInt[uint8]), func(r reflect.Value) uint8 { return uint8(r.Uint()) }) case 16: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[uint16]), func(r reflect.Value) uint16 { + return encWithNilCheck(value, nilErrEncoder(encInt[uint16]), func(r reflect.Value) uint16 { return uint16(r.Uint()) }) case 32: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[uint32]), func(r reflect.Value) uint32 { + return encWithNilCheck(value, nilErrEncoder(encInt[uint32]), func(r reflect.Value) uint32 { return uint32(r.Uint()) }) case 64: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[uint64]), reflect.Value.Uint) + return encWithNilCheck(value, nilErrEncoder(encInt[uint64]), reflect.Value.Uint) default: - return encWithNilCheck(value, ti, nilErrEncoder(encInt[uint]), func(r reflect.Value) uint { + return encWithNilCheck(value, nilErrEncoder(encInt[uint]), func(r reflect.Value) uint { return uint(r.Uint()) }) } } case mtFloat: - switch ti.Bits { + switch value.ti.Bits { case 32: - return encWithNilCheck(value, ti, nilErrEncoder(encFloat[float32]), func(r reflect.Value) float32 { + return encWithNilCheck(value, nilErrEncoder(encFloat[float32]), func(r reflect.Value) float32 { return float32(r.Float()) }) default: fallthrough case 64: - return encWithNilCheck(value, ti, nilErrEncoder(encFloat[float64]), reflect.Value.Float) + return encWithNilCheck(value, nilErrEncoder(encFloat[float64]), reflect.Value.Float) } case mtComplex: - switch ti.Bits { + switch value.ti.Bits { case 64: - return encWithNilCheck(value, ti, nilErrEncoder(encComplex[complex64]), func(r reflect.Value) complex64 { + return encWithNilCheck(value, nilErrEncoder(encComplex[complex64]), func(r reflect.Value) complex64 { return complex64(r.Complex()) }) default: fallthrough case 128: - return encWithNilCheck(value, ti, nilErrEncoder(encComplex[complex128]), reflect.Value.Complex) + return encWithNilCheck(value, nilErrEncoder(encComplex[complex128]), reflect.Value.Complex) } case mtBinary: - return encWithNilCheck(value, ti, encBinary, func(r reflect.Value) encoding.BinaryMarshaler { + return encWithNilCheck(value, encBinary, func(r reflect.Value) encoding.BinaryMarshaler { return r.Interface().(encoding.BinaryMarshaler) }) case mtText: - return encWithNilCheck(value, ti, encText, func(r reflect.Value) encoding.TextMarshaler { + return encWithNilCheck(value, encText, func(r reflect.Value) encoding.TextMarshaler { return r.Interface().(encoding.TextMarshaler) }) default: @@ -158,13 +158,13 @@ func encCheckedPrim(value interface{}, ti typeInfo) ([]byte, error) { // zeroIndirAssign performs the assignment of decoded to v, performing a type // conversion if needed. -func zeroIndirAssign[E any](decoded E, v interface{}, ti typeInfo) { - if ti.Underlying { +func zeroIndirAssign[E any](decoded E, val analyzed[any]) { + if val.ti.Underlying { // need to get fancier - refVal := reflect.ValueOf(v) + refVal := val.ref refVal.Elem().Set(reflect.ValueOf(decoded).Convert(refVal.Type().Elem())) } else { - tVal := v.(*E) + tVal := val.native.(*E) *tVal = decoded } } @@ -174,127 +174,127 @@ func zeroIndirAssign[E any](decoded E, v interface{}, ti typeInfo) { // string, float, complex), or implement encoding.BinaryUnmarshaler, or // implement encoding.TextUnmarshaler, or be a pointer to one of those types // with any level of indirection. -func decCheckedPrim(data []byte, v interface{}, ti typeInfo) (int, error) { +func decCheckedPrim(data []byte, value analyzed[any]) (int, error) { // by nature of doing an encoding, v MUST be a pointer to the typeinfo type, // or an implementor of BinaryUnmarshaler. - switch ti.Main { + switch value.ti.Main { case mtString: - s, n, err := decWithNilCheck(data, v, ti, decString) + s, _, n, err := decWithNilCheck(data, value, decString) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(s, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(s, value) } return n, nil case mtBool: - b, n, err := decWithNilCheck(data, v, ti, decBool) + b, _, n, err := decWithNilCheck(data, value, decBool) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(b, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(b, value) } return n, nil case mtIntegral: var n int var err error - if ti.Signed { - switch ti.Bits { + if value.ti.Signed { + switch value.ti.Bits { case 64: var i int64 - i, n, err = decWithNilCheck(data, v, ti, decInt[int64]) + i, _, n, err = decWithNilCheck(data, value, decInt[int64]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } case 32: var i int32 - i, n, err = decWithNilCheck(data, v, ti, decInt[int32]) + i, _, n, err = decWithNilCheck(data, value, decInt[int32]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } case 16: var i int16 - i, n, err = decWithNilCheck(data, v, ti, decInt[int16]) + i, _, n, err = decWithNilCheck(data, value, decInt[int16]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } case 8: var i int8 - i, n, err = decWithNilCheck(data, v, ti, decInt[int8]) + i, _, n, err = decWithNilCheck(data, value, decInt[int8]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } default: var i int - i, n, err = decWithNilCheck(data, v, ti, decInt[int]) + i, _, n, err = decWithNilCheck(data, value, decInt[int]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } } } else { - switch ti.Bits { + switch value.ti.Bits { case 64: var i uint64 - i, n, err = decWithNilCheck(data, v, ti, decInt[uint64]) + i, _, n, err = decWithNilCheck(data, value, decInt[uint64]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } case 32: var i uint32 - i, n, err = decWithNilCheck(data, v, ti, decInt[uint32]) + i, _, n, err = decWithNilCheck(data, value, decInt[uint32]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } case 16: var i uint16 - i, n, err = decWithNilCheck(data, v, ti, decInt[uint16]) + i, _, n, err = decWithNilCheck(data, value, decInt[uint16]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } case 8: var i uint8 - i, n, err = decWithNilCheck(data, v, ti, decInt[uint8]) + i, _, n, err = decWithNilCheck(data, value, decInt[uint8]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } default: var i uint - i, n, err = decWithNilCheck(data, v, ti, decInt[uint]) + i, _, n, err = decWithNilCheck(data, value, decInt[uint]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(i, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(i, value) } } } @@ -304,26 +304,26 @@ func decCheckedPrim(data []byte, v interface{}, ti typeInfo) (int, error) { var n int var err error - switch ti.Bits { + switch value.ti.Bits { case 32: var f float32 - f, n, err = decWithNilCheck(data, v, ti, decFloat[float32]) + f, _, n, err = decWithNilCheck(data, value, decFloat[float32]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(f, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(f, value) } default: fallthrough case 64: var f float64 - f, n, err = decWithNilCheck(data, v, ti, decFloat[float64]) + f, _, n, err = decWithNilCheck(data, value, decFloat[float64]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(f, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(f, value) } } @@ -332,26 +332,26 @@ func decCheckedPrim(data []byte, v interface{}, ti typeInfo) (int, error) { var n int var err error - switch ti.Bits { + switch value.ti.Bits { case 64: var c complex64 - c, n, err = decWithNilCheck(data, v, ti, decComplex[complex64]) + c, _, n, err = decWithNilCheck(data, value, decComplex[complex64]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(c, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(c, value) } default: fallthrough case 128: var c complex128 - c, n, err = decWithNilCheck(data, v, ti, decComplex[complex128]) + c, _, n, err = decWithNilCheck(data, value, decComplex[complex128]) if err != nil { return n, err } - if ti.Indir == 0 { - zeroIndirAssign(c, v, ti) + if value.ti.Indir == 0 { + zeroIndirAssign(c, value) } } @@ -359,65 +359,72 @@ func decCheckedPrim(data []byte, v interface{}, ti typeInfo) (int, error) { case mtBinary: // if we just got handed a pointer-to binaryUnmarshaler, we need to undo // that - bu, n, err := decWithNilCheck(data, v, ti, fn_DecToWrappedReceiver(v, ti, + bu, _, n, err := decWithNilCheck(data, value, fn_DecToWrappedReceiver(value, func(t reflect.Type) bool { return t.Implements(refBinaryUnmarshalerType) }, - func(b []byte, unwrapped interface{}) (interface{}, int, error) { - recv := unwrapped.(encoding.BinaryUnmarshaler) + func(b []byte, unwrapped analyzed[any]) (decInfo, int, error) { + recv := unwrapped.native.(encoding.BinaryUnmarshaler) decN, err := decBinary(b, recv) - return nil, decN, err + return decInfo{}, decN, err }, )) if err != nil { return n, err } - if ti.Indir == 0 { + if value.ti.Indir == 0 { // assume v is a *T, no future-proofing here. // due to complicated forcing of decBinary into the decFunc API, // we do now have a T (as an interface{}). We must use reflection to // assign it. - refReceiver := reflect.ValueOf(v) + // do NOT use zeroIndirAssign; that is only for underlying type + // detection which we do not need if operating on an mtBinary + + refReceiver := value.ref refReceiver.Elem().Set(reflect.ValueOf(bu)) } return n, nil case mtText: // if we just got handed a pointer-to TextUnmarshaler, we need to undo // that - tu, n, err := decWithNilCheck(data, v, ti, fn_DecToWrappedReceiver(v, ti, + tu, _, n, err := decWithNilCheck(data, value, fn_DecToWrappedReceiver(value, func(t reflect.Type) bool { return t.Implements(refTextUnmarshalerType) }, - func(b []byte, unwrapped interface{}) (interface{}, int, error) { - recv := unwrapped.(encoding.TextUnmarshaler) + // TODO: make most of the decCollection funcs be usable directly here, and see if we can do same for ALL decX funcs + func(b []byte, unwrapped analyzed[any]) (decInfo, int, error) { + recv := unwrapped.native.(encoding.TextUnmarshaler) decN, err := decText(b, recv) - return nil, decN, err + return decInfo{}, decN, err }, )) if err != nil { return n, err } - if ti.Indir == 0 { + if value.ti.Indir == 0 { // assume v is a *T, no future-proofing here. // due to complicated forcing of decText into the decFunc API, // we do now have a T (as an interface{}). We must use reflection to // assign it. - refReceiver := reflect.ValueOf(v) + // do NOT use zeroIndirAssign; that is only for underlying type + // detection which we do not need if operating on an metText + + refReceiver := value.ref refReceiver.Elem().Set(reflect.ValueOf(tu)) } return n, nil default: - panic(fmt.Sprintf("%T cannot receive decoded REZI primitive type", v)) + panic(fmt.Sprintf("%T cannot receive decoded REZI primitive type", value.native)) } } // Negative, NilAt, and Length from extra are all ignored. func encCount(count tLen, extra *countHeader) []byte { - intBytes := encInt(count) + intBytes := encInt(analyzed[tLen]{native: count}) if extra == nil { // normal int enc @@ -496,7 +503,10 @@ func decCountHeader(data []byte) (countHeader, int, error) { return hdr, hdr.DecodedCount, err } -func encBool(b bool) []byte { +// does not actually use analysis data, only native value. accepts +// analyzed[bool] only to conform to encFunc. +func encBool(val analyzed[bool]) []byte { + b := val.native enc := make([]byte, 1) if b { @@ -508,23 +518,28 @@ func encBool(b bool) []byte { return enc } -// returned interface{} is only there to implement decFunc and will always be -// nil -func decBool(data []byte) (bool, interface{}, int, error) { +// returned decInfo is only to implement decFunc and will always be empty. +func decBool(data []byte) (bool, decInfo, int, error) { + var di decInfo + if len(data) < 1 { - return false, nil, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) + return false, di, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) } if data[0] == 0 { - return false, nil, 1, nil + return false, di, 1, nil } else if data[0] == 1 { - return true, nil, 1, nil + return true, di, 1, nil } else { - return false, nil, 0, errorDecf(0, "not a bool value 0x00 or 0x01: %#02x", data[0]).wrap(ErrMalformedData) + return false, di, 0, errorDecf(0, "not a bool value 0x00 or 0x01: %#02x", data[0]).wrap(ErrMalformedData) } } -func encComplex[E anyComplex](v E) []byte { +// does not actually use analysis data, only native value. accepts +// analyzed[anyComplex] only to conform to encFunc. +func encComplex[E anyComplex](val analyzed[E]) []byte { + v := val.native + // go 1.18 compat, real() and imag() cannot be done to our E type // // TODO: if we want 1.18 compat then our go.mod should be set to that too. @@ -544,8 +559,8 @@ func encComplex[E anyComplex](v E) []byte { } // encode the parts - realEnc := encFloat(rv) - imagEnc := encFloat(iv) + realEnc := encFloat(analyzed[float64]{native: rv}) + imagEnc := encFloat(analyzed[float64]{native: iv}) hdrEnc := encCount(len(realEnc)+len(imagEnc), &countHeader{ByteLength: true}) @@ -556,21 +571,23 @@ func encComplex[E anyComplex](v E) []byte { return enc } -// return interface is just to implement decFunc and will always be nils -func decComplex[E anyComplex](data []byte) (E, interface{}, int, error) { +// returned decInfo is only to implement decFunc and will always be empty. +func decComplex[E anyComplex](data []byte) (E, decInfo, int, error) { + var di decInfo + if len(data) < 1 { - return 0.0, nil, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) + return 0.0, di, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) } // special case single-byte 0's check if data[0] == 0x00 { - return E(0.0 + 0.0i), nil, 1, nil + return E(0.0 + 0.0i), di, 1, nil } else if data[0] == 0x80 { // only way to reliably get a -0.0 value is by direct calculation on var // (cannot be result of consts, I tried, at least as of Go 1.19.4) var val float64 val *= -1.0 - return E(complex(val, val)), nil, 1, nil + return E(complex(val, val)), di, 1, nil } // do normal decoding of full-form @@ -584,7 +601,7 @@ func decComplex[E anyComplex](data []byte) (E, interface{}, int, error) { // get the byte count as an int byteCount, _, n, err = decInt[tLen](data[offset:]) if err != nil { - return E(0.0 + 0.0i), nil, 0, err + return E(0.0 + 0.0i), di, 0, err } offset += n @@ -598,7 +615,7 @@ func decComplex[E anyComplex](data []byte) (E, interface{}, int, error) { } const errFmt = "decoded complex value byte count is %d but only %d byte%s remain%s at offset" err := errorDecf(offset, errFmt, byteCount, len(data), s, verbS).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return E(0.0 + 0.0i), nil, 0, err + return E(0.0 + 0.0i), di, 0, err } // clamp data to len @@ -607,23 +624,27 @@ func decComplex[E anyComplex](data []byte) (E, interface{}, int, error) { // real part rPart, _, n, err = decFloat[float64](data[offset:]) if err != nil { - return E(0.0 + 0.0i), nil, 0, errorDecf(offset, "%s", err) + return E(0.0 + 0.0i), di, 0, errorDecf(offset, "%s", err) } offset += n // imaginary part iPart, _, n, err = decFloat[float64](data[offset:]) if err != nil { - return E(0.0 + 0.0i), nil, 0, errorDecf(offset, "%s", err) + return E(0.0 + 0.0i), di, 0, errorDecf(offset, "%s", err) } offset += n var v128 complex128 = complex(rPart, iPart) - return E(v128), nil, offset, nil + return E(v128), di, offset, nil } -func encFloat[E anyFloat](v E) []byte { +// does not actually use analysis data, only native value. accepts +// analyzed[anyFloat] only to conform to encFunc. +func encFloat[E anyFloat](val analyzed[E]) []byte { + v := val.native + // first off, if it is 0, than we can return special 0-value if v == 0.0 { if math.Signbit(float64(v)) { @@ -734,21 +755,23 @@ func encFloat[E anyFloat](v E) []byte { return enc } -// returned interface{} is just to implement decFunc; will always be nil -func decFloat[E anyFloat](data []byte) (E, interface{}, int, error) { +// returned decInfo is only to implement decFunc and will always be empty. +func decFloat[E anyFloat](data []byte) (E, decInfo, int, error) { + var di decInfo + if len(data) < 1 { - return 0.0, nil, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) + return 0.0, di, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) } byteCount := data[0] // special case single-byte 0's check if byteCount == 0 { - return E(0.0), nil, 1, nil + return E(0.0), di, 1, nil } else if byteCount == 0x80 { var val float64 val *= -1.0 - return E(val), nil, 1, nil + return E(val), di, 1, nil } // pull count and sign out of byteCount @@ -762,7 +785,7 @@ func decFloat[E anyFloat](data []byte) (E, interface{}, int, error) { if len(data[1:]) < 1 { const errFmt = "count header indicates extension byte follows, but at end of data" err := errorDecf(numHeaderBytes, errFmt).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return E(0.0), nil, 0, err + return E(0.0), di, 0, err } data = data[1:] numHeaderBytes++ @@ -779,14 +802,14 @@ func decFloat[E anyFloat](data []byte) (E, interface{}, int, error) { if negative { val *= -1.0 } - return E(val), nil, numHeaderBytes, nil + return E(val), di, numHeaderBytes, nil } if int(byteCount) < 2 { // the absolute minimum is 2 if not 0 const errFmt = "min data len for non-zero float is 2, but count from header specifies len of %d starting at offset" err := errorDecf(numHeaderBytes, errFmt, int(byteCount)).wrap(ErrMalformedData) - return E(0.0), nil, 0, err + return E(0.0), di, 0, err } if len(data) < int(byteCount) { @@ -798,7 +821,7 @@ func decFloat[E anyFloat](data []byte) (E, interface{}, int, error) { } const errFmt = "decoded float byte count is %d but only %d byte%s remain%s at offset" err := errorDecf(numHeaderBytes, errFmt, byteCount, len(data), s, verbS).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return E(0.0), nil, 0, err + return E(0.0), di, 0, err } floatData := data[:byteCount] @@ -853,10 +876,13 @@ func decFloat[E anyFloat](data []byte) (E, interface{}, int, error) { fVal := math.Float64frombits(iVal) - return E(fVal), nil, int(byteCount) + numHeaderBytes, nil + return E(fVal), di, int(byteCount) + numHeaderBytes, nil } -func encInt[E integral](v E) []byte { +// does not actually use analysis data, only native value. accepts +// analyzed[integral] only to conform to encFunc. +func encInt[E integral](val analyzed[E]) []byte { + v := val.native if v == 0 { return []byte{0x00} } @@ -907,17 +933,18 @@ func encInt[E integral](v E) []byte { // number of bytes to decode after all count header bytes and interprets it as // such. does not do further checks on count header. // -// returned interface{} is included only to implement decFunc and will always be -// nil -func decInt[E integral](data []byte) (E, interface{}, int, error) { +// returned decInfo is only to implement decFunc and will always be empty. +func decInt[E integral](data []byte) (E, decInfo, int, error) { + var di decInfo + if len(data) < 1 { - return 0, nil, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) + return 0, di, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) } byteCount := data[0] if byteCount == 0 { - return 0, nil, 1, nil + return 0, di, 1, nil } // pull count and sign out of byteCount @@ -931,7 +958,7 @@ func decInt[E integral](data []byte) (E, interface{}, int, error) { if len(data[1:]) < 1 { const errFmt = "count header indicates extension byte follows, but at end of data" err := errorDecf(numHeaderBytes, errFmt).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return 0, nil, 0, err + return 0, di, 0, err } data = data[1:] numHeaderBytes++ @@ -951,7 +978,7 @@ func decInt[E integral](data []byte) (E, interface{}, int, error) { } const errFmt = "decoded int byte count is %d but only %d byte%s remain%s at offset" err := errorDecf(numHeaderBytes, errFmt, byteCount, len(data), s, verbS).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return 0, nil, 0, err + return 0, di, 0, err } intData := data[:byteCount] @@ -982,10 +1009,14 @@ func decInt[E integral](data []byte) (E, interface{}, int, error) { iVal |= (uint64(intData[6]) << 8) iVal |= (uint64(intData[7])) - return E(iVal), nil, int(byteCount) + numHeaderBytes, nil + return E(iVal), di, int(byteCount) + numHeaderBytes, nil } -func encString(s string) []byte { +// does not actually use analysis data, only native value. accepts +// analyzed[string] only to conform to encFunc. +func encString(val analyzed[string]) []byte { + s := val.native + if s == "" { return []byte{0x00} } @@ -1008,20 +1039,22 @@ func encString(s string) []byte { // decString decodes a string of any version. Assumes header is not nil. // -// returned interface{} is only to implement decFunc and will always be nil -func decString(data []byte) (string, interface{}, int, error) { +// returned decInfo is only to implement decFunc and will always be empty. +func decString(data []byte) (string, decInfo, int, error) { + var di decInfo + if len(data) < 1 { - return "", nil, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) + return "", di, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) } // special case; 0x00 is the empty string in all variants if data[0] == 0 { - return "", nil, 1, nil + return "", di, 1, nil } hdr, _, err := decCountHeader(data) if err != nil { - return "", nil, 0, err + return "", di, 0, err } // compatibility with older format @@ -1032,19 +1065,21 @@ func decString(data []byte) (string, interface{}, int, error) { return decStringV1(data) } -// returned interface{} is only to implement decFunc and will always be nil -func decStringV1(data []byte) (string, interface{}, int, error) { +// returned decInfo is only to implement decFunc and will always be empty. +func decStringV1(data []byte) (string, decInfo, int, error) { + var di decInfo + if len(data) < 1 { - return "", nil, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) + return "", di, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) } strLength, _, countLen, err := decInt[tLen](data) if err != nil { - return "", nil, 0, errorDecf(0, "decode string byte count: %s", err) + return "", di, 0, errorDecf(0, "decode string byte count: %s", err) } data = data[countLen:] if strLength < 0 { - return "", nil, 0, errorDecf(countLen, "string byte count < 0").wrap(ErrMalformedData) + return "", di, 0, errorDecf(countLen, "string byte count < 0").wrap(ErrMalformedData) } if len(data) < strLength { @@ -1056,7 +1091,7 @@ func decStringV1(data []byte) (string, interface{}, int, error) { } const errFmt = "decoded string byte count is %d but only %d byte%s remain%s at offset" err := errorDecf(countLen, errFmt, strLength, len(data), s, verbS).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return "", nil, 0, err + return "", di, 0, err } // clamp it data = data[:strLength] @@ -1067,7 +1102,7 @@ func decStringV1(data []byte) (string, interface{}, int, error) { for readBytes-countLen < strLength { ch, charBytesRead, err := decUTF8Codepoint(data) if err != nil { - return "", nil, 0, errorDecf(readBytes, "%s", err) + return "", di, 0, errorDecf(readBytes, "%s", err) } sb.WriteRune(ch) @@ -1075,22 +1110,24 @@ func decStringV1(data []byte) (string, interface{}, int, error) { data = data[charBytesRead:] } - return sb.String(), nil, readBytes, nil + return sb.String(), di, readBytes, nil } -// returned interface{} is only to implement decFunc and will always be nil -func decStringV0(data []byte) (string, interface{}, int, error) { +// returned decInfo is only to implement decFunc and will always be empty. +func decStringV0(data []byte) (string, decInfo, int, error) { + var di decInfo + if len(data) < 1 { - return "", nil, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) + return "", di, 0, errorDecf(0, "%s", io.ErrUnexpectedEOF).wrap(ErrMalformedData) } runeCount, _, n, err := decInt[int](data) if err != nil { - return "", nil, 0, errorDecf(0, "decode string rune count: %s", err) + return "", di, 0, errorDecf(0, "decode string rune count: %s", err) } data = data[n:] if runeCount < 0 { - return "", nil, 0, errorDecf(0, "string rune count < 0").wrap(ErrMalformedData) + return "", di, 0, errorDecf(0, "string rune count < 0").wrap(ErrMalformedData) } readBytes := n @@ -1100,7 +1137,7 @@ func decStringV0(data []byte) (string, interface{}, int, error) { for i := 0; i < runeCount; i++ { ch, charBytesRead, err := decUTF8Codepoint(data) if err != nil { - return "", nil, 0, errorDecf(readBytes, "%s", err) + return "", di, 0, errorDecf(readBytes, "%s", err) } sb.WriteRune(ch) @@ -1108,7 +1145,7 @@ func decStringV0(data []byte) (string, interface{}, int, error) { data = data[charBytesRead:] } - return sb.String(), nil, readBytes, nil + return sb.String(), di, readBytes, nil } func decUTF8Codepoint(data []byte) (rune, int, error) { @@ -1125,7 +1162,11 @@ func decUTF8Codepoint(data []byte) (rune, int, error) { return ch, charBytesRead, nil } -func encText(t encoding.TextMarshaler) ([]byte, error) { +// does not actually use analysis data, only native value. accepts +// analyzed[encoding.TextMarshaler] only to conform to encFunc. +func encText(val analyzed[encoding.TextMarshaler]) ([]byte, error) { + t := val.native + if t == nil { return encNilHeader(0), nil } @@ -1136,7 +1177,7 @@ func encText(t encoding.TextMarshaler) ([]byte, error) { } tText := string(tTextSlice) - return encString(tText), nil + return encString(analyzed[string]{native: tText}), nil } func decText(data []byte, t encoding.TextUnmarshaler) (int, error) { @@ -1157,7 +1198,11 @@ func decText(data []byte, t encoding.TextUnmarshaler) (int, error) { return readBytes, nil } -func encBinary(b encoding.BinaryMarshaler) ([]byte, error) { +// does not actually use analysis data, only native value. accepts +// analyzed[encoding.BinaryMarshaler] only to conform to encFunc. +func encBinary(val analyzed[encoding.BinaryMarshaler]) ([]byte, error) { + b := val.native + if b == nil { return encNilHeader(0), nil } @@ -1167,7 +1212,7 @@ func encBinary(b encoding.BinaryMarshaler) ([]byte, error) { return nil, errorf("%s: %s", ErrMarshalBinary, marshalErr) } - enc = append(encInt(len(enc)), enc...) + enc = append(encCount(len(enc), nil), enc...) return enc, nil } diff --git a/primitives_test.go b/primitives_test.go index 9ab6fd1..32e3895 100644 --- a/primitives_test.go +++ b/primitives_test.go @@ -36,7 +36,7 @@ func Test_encBool(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - actual := encBool(tc.input) + actual := encBool(withNoAnalysis(tc.input)) assert.Equal(tc.expect, actual) }) @@ -157,7 +157,7 @@ func Test_encInt(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - actual := encInt(tc.input) + actual := encInt(withNoAnalysis(tc.input)) assert.Equal(tc.expect, actual) }) @@ -293,7 +293,7 @@ func Test_encFloat(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - actual := encFloat(tc.input) + actual := encFloat(withNoAnalysis(tc.input)) assert.Equal(tc.expect, actual) }) @@ -508,7 +508,7 @@ func Test_encComplex(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - actual := encComplex(tc.input) + actual := encComplex(withNoAnalysis(tc.input)) assert.Equal(tc.expect, actual) }) @@ -670,7 +670,7 @@ func Test_encString(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - actual := encString(tc.input) + actual := encString(withNoAnalysis(tc.input)) assert.Equal(tc.expect, actual) }) @@ -823,7 +823,7 @@ func Test_encText(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - actual, _ := encText(tc.input) + actual, _ := encText(withNoAnalysis(tc.input)) assert.Equal(tc.expect, actual) }) @@ -952,7 +952,7 @@ func Test_encBinary(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert := assert.New(t) - actual, err := encBinary(tc.input) + actual, err := encBinary(withNoAnalysis(tc.input)) if !assert.NoError(err) { return } diff --git a/rezi.go b/rezi.go index d81557c..7eec9bf 100644 --- a/rezi.go +++ b/rezi.go @@ -548,19 +548,47 @@ import ( "reflect" ) +// decInfo holds information gained during decoding to be used in further +// processing of the decoded data. +type decInfo struct { + // Fields is only included in decInfo when a struct has been decoded and + // gives a slice of all valid fields that were detected (and read) during + // the decode. This is used to inform which fields to overwrite in the + // receiver pointer. + Fields []fieldInfo + + // Ref is the value that was decoded but as a reflect.Value. This is so that + // creating it more than once can be avoided. Not always set by every decode + // function; check with IsValid() before using. + Ref reflect.Value +} + type ( tLen = int tNilLevel = int - // the interface{} in decFunc is any additional info needed for further - // stages of decode, nil in all cases except for struct decoding. - decFunc[E any] func([]byte) (E, interface{}, int, error) - encFunc[E any] func(E) ([]byte, error) + // the decInfo in decFunc is any additional info needed for further + // stages of decode. It can be empty if no further info is required. + decFunc[E any] func([]byte) (E, decInfo, int, error) + encFunc[E any] func(analyzed[E]) ([]byte, error) ) -func nilErrEncoder[E any](fn func(E) []byte) encFunc[E] { - return func(e E) ([]byte, error) { - return fn(e), nil +// analyzed is used to pass around a value along with its type info and +// reflect.Value to different subroutines. it's mostly just used for argument +// grouping. +type analyzed[E any] struct { + native E + ref reflect.Value + ti typeInfo +} + +func preAnalyzed[E any](oldAnalysis analyzed[any], newVal E) analyzed[E] { + return analyzed[E]{native: newVal, ref: oldAnalysis.ref, ti: oldAnalysis.ti} +} + +func nilErrEncoder[E any](fn func(analyzed[E]) []byte) encFunc[E] { + return func(val analyzed[E]) ([]byte, error) { + return fn(val), nil } } @@ -603,16 +631,22 @@ func Enc(v interface{}) (data []byte, err error) { return nil, err } + value := analyzed[any]{ + native: v, + ref: reflect.ValueOf(v), + ti: info, + } + if info.Primitive() { - return encCheckedPrim(v, info) + return encCheckedPrim(value) } else if info.Main == mtNil { return encNilHeader(0), nil } else if info.Main == mtMap { - return encCheckedMap(v, info) + return encCheckedMap(value) } else if info.Main == mtSlice || info.Main == mtArray { - return encCheckedSlice(v, info) + return encCheckedSlice(value) } else if info.Main == mtStruct { - return encCheckedStruct(v, info) + return encCheckedStruct(value) } else { panic("no possible encoding") } @@ -668,27 +702,33 @@ func Dec(data []byte, v interface{}) (n int, err error) { return 0, err } + value := analyzed[any]{ + native: v, + ref: reflect.ValueOf(v), + ti: info, + } + if info.Primitive() { - return decCheckedPrim(data, v, info) + return decCheckedPrim(data, value) } else if info.Main == mtMap { - return decCheckedMap(data, v, info) + return decCheckedMap(data, value) } else if info.Main == mtSlice || info.Main == mtArray { - return decCheckedSlice(data, v, info) + return decCheckedSlice(data, value) } else if info.Main == mtStruct { - return decCheckedStruct(data, v, info) + return decCheckedStruct(data, value) } else { panic("no possible decoding") } } -func encWithNilCheck[E any](value interface{}, ti typeInfo, encFn encFunc[E], convFn func(reflect.Value) E) ([]byte, error) { - if ti.Indir > 0 { +func encWithNilCheck[E any](v analyzed[any], encFn encFunc[E], convFn func(reflect.Value) E) ([]byte, error) { + if v.ti.Indir > 0 { // we cannot directly encode, we must get at the reel value. - encodeTarget := reflect.ValueOf(value) + encodeTarget := v.ref // encodeTarget is a *THING but we want a THING nilLevel := -1 - for i := 0; i < ti.Indir; i++ { + for i := 0; i < v.ti.Indir; i++ { if encodeTarget.IsNil() { // so if it were a *string we deal w, nil level can only be 0. // if it were a **string we deal w, nil level can be 0 or 1. @@ -702,14 +742,20 @@ func encWithNilCheck[E any](value interface{}, ti typeInfo, encFn encFunc[E], co if nilLevel > -1 { return encNilHeader(nilLevel), nil } - return encFn(convFn(encodeTarget)) + convTarget := convFn(encodeTarget) + reAnalyzed := analyzed[E]{ + native: convTarget, + ref: reflect.ValueOf(convTarget), + ti: v.ti, + } + return encFn(reAnalyzed) } else { // if the type we have is actually a new UDT with some underlying basic // Go type, then in fact we want to encode it as the actual kind type. - if ti.Underlying { - value = convFn(reflect.ValueOf(value)) + if v.ti.Underlying { + v.native = convFn(v.ref) } - return encFn(value.(E)) + return encFn(preAnalyzed(v, v.native.(E))) } } @@ -717,42 +763,40 @@ func encWithNilCheck[E any](value interface{}, ti typeInfo, encFn encFunc[E], co // indirection level. If ti.Indir == 0, this will not assign. Callers should use // that check to determine if it is safe to do their own assignment of the // decoded value this function returns. -func decWithNilCheck[E any](data []byte, v interface{}, ti typeInfo, decFn decFunc[E]) (decoded E, n int, err error) { +func decWithNilCheck[E any](data []byte, v analyzed[any], decFn decFunc[E]) (decoded E, di decInfo, n int, err error) { var hdr countHeader - if ti.Indir > 0 { + if v.ti.Indir > 0 { hdr, n, err = decCountHeader(data) if err != nil { - return decoded, n, errorDecf(0, "check count header: %s", err) + return decoded, di, n, errorDecf(0, "check count header: %s", err) } } countHeaderBytes := n effectiveExtraIndirs := hdr.ExtraNilIndirections() - var extraInfo interface{} - if !hdr.IsNil() { - effectiveExtraIndirs = ti.Indir - decoded, extraInfo, n, err = decFn(data) + effectiveExtraIndirs = v.ti.Indir + decoded, di, n, err = decFn(data) if err != nil { - return decoded, n, errorDecf(countHeaderBytes, "%s", err) + return decoded, di, n, errorDecf(countHeaderBytes, "%s", err) } } - if ti.Indir > 0 { + if v.ti.Indir > 0 { // the user has passed in a ptr-ptr-to. We cannot directly assign. - assignTarget := reflect.ValueOf(v) + assignTarget := v.ref // assignTarget is a **string but we want a *string // if it's a struct, we must get the original value, if one exists, in order // to preserve the original member values var origStructVal reflect.Value - if ti.Main == mtStruct { + if v.ti.Main == mtStruct { origStructVal = unwrapOriginalStructValue(assignTarget) } - for i := 0; i < ti.Indir && i < effectiveExtraIndirs; i++ { + for i := 0; i < v.ti.Indir && i < effectiveExtraIndirs; i++ { // *double indirection ALL THE WAY~* // *acrosssss the sky* // *what does it mean* @@ -764,13 +808,16 @@ func decWithNilCheck[E any](data []byte, v interface{}, ti typeInfo, decFn decFu } if !hdr.IsNil() { - refDecoded := reflect.ValueOf(decoded) - if ti.Underlying { + refDecoded := di.Ref + if !di.Ref.IsValid() { + refDecoded = reflect.ValueOf(decoded) + } + if v.ti.Underlying { refDecoded = refDecoded.Convert(assignTarget.Type().Elem()) } - if ti.Main == mtStruct && origStructVal.IsValid() { - refDecoded = setStructMembers(origStructVal, refDecoded, extraInfo.([]fieldInfo)) + if v.ti.Main == mtStruct && origStructVal.IsValid() { + refDecoded = setStructMembers(origStructVal, refDecoded, di) } assignTarget.Elem().Set(refDecoded) @@ -780,24 +827,24 @@ func decWithNilCheck[E any](data []byte, v interface{}, ti typeInfo, decFn decFu } } - return decoded, n, nil + return decoded, di, n, nil } // decToUnwrappedFn takes the encoded bytes and an interface to decode to and // returns any extra data (may be nil), bytes consumed, and error status. -func fn_DecToWrappedReceiver(wrapped interface{}, ti typeInfo, assertFn func(reflect.Type) bool, decToUnwrappedFn func([]byte, interface{}) (interface{}, int, error)) decFunc[interface{}] { - return func(data []byte) (interface{}, interface{}, int, error) { +func fn_DecToWrappedReceiver(wrapped analyzed[any], assertFn func(reflect.Type) bool, decToUnwrappedFn func([]byte, analyzed[any]) (decInfo, int, error)) decFunc[interface{}] { + return func(data []byte) (interface{}, decInfo, int, error) { // v is *(...*)T, ret-val of decFn (this lambda) is T. - refWrapped := reflect.ValueOf(wrapped) + refWrapped := wrapped.ref receiverType := refWrapped.Type() refUnwrapped := refWrapped if receiverType.Kind() == reflect.Pointer { // future-proofing - binary unmarshaler might come in as a T // for every * in the (...*) part of *(...*)T up until the // implementor/slice-ptr, do a deref. - for i := 0; i < ti.Indir; i++ { + for i := 0; i < wrapped.ti.Indir; i++ { receiverType = receiverType.Elem() - if (ti.Main == mtText || ti.Main == mtBinary) && refUnwrapped.IsValid() { + if (wrapped.ti.Main == mtText || wrapped.ti.Main == mtBinary) && refUnwrapped.IsValid() { if !refUnwrapped.IsNil() { refUnwrapped = refUnwrapped.Elem() } else { @@ -819,7 +866,7 @@ func fn_DecToWrappedReceiver(wrapped interface{}, ti typeInfo, assertFn func(ref if receiverType.Elem().Kind() == reflect.Func { // if we have been given a *function* pointer, reject it, we // cannot do this. - return nil, nil, 0, errorDecf(0, "function pointer type receiver is not supported").wrap(ErrInvalidType) + return nil, decInfo{}, 0, errorDecf(0, "function pointer type receiver is not supported").wrap(ErrInvalidType) } // receiverType is *T @@ -829,14 +876,14 @@ func fn_DecToWrappedReceiver(wrapped interface{}, ti typeInfo, assertFn func(ref // automatic and we have full control; we don't (and can't) rely on // already set things and use reflect after actual decoding to copy // the decoded values into the passed-in pointer. - if (ti.Main == mtText || ti.Main == mtBinary) && refUnwrapped.IsValid() && !refUnwrapped.IsNil() { + if (wrapped.ti.Main == mtText || wrapped.ti.Main == mtBinary) && refUnwrapped.IsValid() && !refUnwrapped.IsNil() { receiverValue = refUnwrapped } else { receiverValue = reflect.New(receiverType.Elem()) } } else { // receiverType is itself T (future-proofing) - if (ti.Main == mtText || ti.Main == mtBinary) && refUnwrapped.IsValid() { + if (wrapped.ti.Main == mtText || wrapped.ti.Main == mtBinary) && refUnwrapped.IsValid() { receiverValue = refUnwrapped } else { receiverValue = reflect.Zero(receiverType) @@ -846,7 +893,8 @@ func fn_DecToWrappedReceiver(wrapped interface{}, ti typeInfo, assertFn func(ref var decoded interface{} receiver := receiverValue.Interface() - extraInfo, decConsumed, decErr := decToUnwrappedFn(data, receiver) + recvAnalyzed := analyzed[any]{native: receiver, ref: receiverValue, ti: wrapped.ti} + extraInfo, decConsumed, decErr := decToUnwrappedFn(data, recvAnalyzed) if decErr != nil { return nil, extraInfo, decConsumed, decErr @@ -989,7 +1037,7 @@ func (hdr countHeader) MarshalBinary() ([]byte, error) { // okay, if nilAt is > 1 then we need to additionally encode an int of that // value if hdr.NilAt > 1 { - encoded = append(encoded, encInt(hdr.NilAt-1)...) + encoded = append(encoded, encInt(analyzed[int]{native: hdr.NilAt - 1})...) } return encoded, nil diff --git a/rezi_test.go b/rezi_test.go index 3e33e4a..31b36fd 100644 --- a/rezi_test.go +++ b/rezi_test.go @@ -280,6 +280,10 @@ func ref[E any](v E) *E { return &v } +func withNoAnalysis[E any](newVal E) analyzed[E] { + return analyzed[E]{native: newVal} +} + // testText is a small struct that implements TextMarshaler and TextUnmarshaler. // It has three fields, that it lays out as such in encoding: "value", a uint16, // followed by "enabled", a bool, followed by "name", a string. Each field is diff --git a/slices.go b/slices.go index eba4ae9..3dd667b 100644 --- a/slices.go +++ b/slices.go @@ -9,26 +9,25 @@ import ( ) // encMap encodes a compatible slice as a REZI map. -func encCheckedSlice(v interface{}, ti typeInfo) ([]byte, error) { - if ti.Main != mtSlice && ti.Main != mtArray { +func encCheckedSlice(value analyzed[any]) ([]byte, error) { + if value.ti.Main != mtSlice && value.ti.Main != mtArray { panic("not a slice or array type") } - return encWithNilCheck(v, ti, encSlice, reflect.Value.Interface) + return encWithNilCheck(value, encSlice, reflect.Value.Interface) } -func encSlice(v interface{}) ([]byte, error) { - refVal := reflect.ValueOf(v) - isArray := reflect.TypeOf(v).Kind() == reflect.Array +func encSlice(val analyzed[any]) ([]byte, error) { + isArray := val.ref.Type().Kind() == reflect.Array - if v == nil || (!isArray && refVal.IsNil()) { + if val.native == nil || (!isArray && val.ref.IsNil()) { return encNilHeader(0), nil } enc := make([]byte, 0) - for i := 0; i < refVal.Len(); i++ { - v := refVal.Index(i) + for i := 0; i < val.ref.Len(); i++ { + v := val.ref.Index(i) encData, err := Enc(v.Interface()) if err != nil { if isArray { @@ -40,45 +39,44 @@ func encSlice(v interface{}) ([]byte, error) { enc = append(enc, encData...) } - enc = append(encInt(tLen(len(enc))), enc...) + enc = append(encCount(len(enc), nil), enc...) return enc, nil } -func decCheckedSlice(data []byte, v interface{}, ti typeInfo) (int, error) { - if ti.Main != mtSlice && ti.Main != mtArray { +func decCheckedSlice(data []byte, v analyzed[any]) (int, error) { + if v.ti.Main != mtSlice && v.ti.Main != mtArray { panic("not a slice or array type") } - sl, n, err := decWithNilCheck(data, v, ti, fn_DecToWrappedReceiver(v, ti, + _, di, n, err := decWithNilCheck(data, v, fn_DecToWrappedReceiver(v, func(t reflect.Type) bool { - return t.Kind() == reflect.Pointer && ((ti.Main == mtSlice && t.Elem().Kind() == reflect.Slice) || (ti.Main == mtArray && t.Elem().Kind() == reflect.Array)) - }, - func(b []byte, i interface{}) (interface{}, int, error) { - decN, err := decSlice(b, i) - return nil, decN, err + return t.Kind() == reflect.Pointer && ((v.ti.Main == mtSlice && t.Elem().Kind() == reflect.Slice) || (v.ti.Main == mtArray && t.Elem().Kind() == reflect.Array)) }, + decSlice, )) if err != nil { return n, err } - if ti.Indir == 0 { - refReceiver := reflect.ValueOf(v) - refReceiver.Elem().Set(reflect.ValueOf(sl)) + if v.ti.Indir == 0 { + refReceiver := v.ref + refReceiver.Elem().Set(di.Ref) } return n, err } -func decSlice(data []byte, v interface{}) (int, error) { +func decSlice(data []byte, v analyzed[any]) (decInfo, int, error) { + var di decInfo + var totalConsumed int toConsume, _, n, err := decInt[tLen](data) if err != nil { - return 0, errorDecf(0, "decode byte count: %s", err) + return di, 0, errorDecf(0, "decode byte count: %s", err) } data = data[n:] totalConsumed += n - refSliceVal := reflect.ValueOf(v) + refSliceVal := v.ref refSliceType := refSliceVal.Type().Elem() isArray := refSliceType.Kind() == reflect.Array sliceOrArrStr := "slice" @@ -97,7 +95,8 @@ func decSlice(data []byte, v interface{}) (int, error) { empty = reflect.MakeSlice(refSliceType, 0, 0) } refSliceVal.Elem().Set(empty) - return totalConsumed, nil + di.Ref = refSliceVal.Elem() + return di, totalConsumed, nil } else if toConsume == -1 { var nilVal reflect.Value if isArray { @@ -106,7 +105,8 @@ func decSlice(data []byte, v interface{}) (int, error) { nilVal = reflect.Zero(refSliceType) } refSliceVal.Elem().Set(nilVal) - return totalConsumed, nil + di.Ref = nilVal + return di, totalConsumed, nil } if len(data) < toConsume { @@ -118,7 +118,7 @@ func decSlice(data []byte, v interface{}) (int, error) { } const errFmt = "decoded %s byte count is %d but only %d byte%s remain%s in data at offset" err := errorDecf(totalConsumed, errFmt, sliceOrArrStr, toConsume, len(data), s, verbS).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return totalConsumed, err + return di, totalConsumed, err } // clamp values we are allowed to read so we don't try to read other data @@ -139,7 +139,7 @@ func decSlice(data []byte, v interface{}) (int, error) { refValue := reflect.New(refVType) n, err := Dec(data, refValue.Interface()) if err != nil { - return totalConsumed, errorDecf(totalConsumed, "%s item[%d]: %s", sliceOrArrStr, itemIdx, err) + return di, totalConsumed, errorDecf(totalConsumed, "%s item[%d]: %s", sliceOrArrStr, itemIdx, err) } totalConsumed += n i += n @@ -153,6 +153,7 @@ func decSlice(data []byte, v interface{}) (int, error) { itemIdx++ } + di.Ref = sl refSliceVal.Elem().Set(sl) - return totalConsumed, nil + return di, totalConsumed, nil } diff --git a/slices_test.go b/slices_test.go index 3746042..3045579 100644 --- a/slices_test.go +++ b/slices_test.go @@ -32,6 +32,26 @@ func Test_Enc_Slice_NoIndirection(t *testing.T) { assert.Equal(expect, actual) }) + t.Run("empty []int", func(t *testing.T) { + // setup + assert := assert.New(t) + var ( + input = []int{} + expect = []byte{ + 0x00, + } + ) + + // execute + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + // assert + assert.Equal(expect, actual) + }) + t.Run("[]int", func(t *testing.T) { // setup assert := assert.New(t) @@ -1557,6 +1577,30 @@ func Test_Dec_Slice_NoIndirection(t *testing.T) { assert.Nil(actual) }) + t.Run("empty []int", func(t *testing.T) { + // setup + assert := assert.New(t) + var ( + input = []byte{ + 0x00, + } + expectConsumed = 1 + ) + + // execute + actual := []int{1, 2} // start with a value so we can check it is set to empty + consumed, err := Dec(input, &actual) + + // assert + if !assert.NoError(err) { + return + } + + assert.NotNil(actual) + assert.Empty(actual) + assert.Equal(expectConsumed, consumed) + }) + t.Run("[]int", func(t *testing.T) { // setup assert := assert.New(t) diff --git a/structs.go b/structs.go index 46cb87b..0977d05 100644 --- a/structs.go +++ b/structs.go @@ -6,27 +6,23 @@ import ( ) // encCheckedStruct encodes a compatible struct as a REZI . -func encCheckedStruct(v interface{}, ti typeInfo) ([]byte, error) { - if ti.Main != mtStruct { +func encCheckedStruct(value analyzed[any]) ([]byte, error) { + if value.ti.Main != mtStruct { panic("not a struct type") } - return encWithNilCheck(v, ti, func(val interface{}) ([]byte, error) { - return encStruct(val, ti) - }, reflect.Value.Interface) + return encWithNilCheck(value, encStruct, reflect.Value.Interface) } -func encStruct(v interface{}, ti typeInfo) ([]byte, error) { - refVal := reflect.ValueOf(v) - +func encStruct(val analyzed[any]) ([]byte, error) { enc := make([]byte, 0) - for _, fi := range ti.Fields.ByOrder { - v := refVal.Field(fi.Index) + for _, fi := range val.ti.Fields.ByOrder { + v := val.ref.Field(fi.Index) fNameData, err := Enc(fi.Name) if err != nil { - msgTypeName := reflect.ValueOf(v).Type().Name() + msgTypeName := val.ref.Type().Name() if msgTypeName == "" { msgTypeName = "(anonymous type)" } @@ -34,7 +30,7 @@ func encStruct(v interface{}, ti typeInfo) ([]byte, error) { } fValData, err := Enc(v.Interface()) if err != nil { - msgTypeName := reflect.ValueOf(v).Type().Name() + msgTypeName := val.ref.Type().Name() if msgTypeName == "" { msgTypeName = "(anonymous type)" } @@ -45,45 +41,40 @@ func encStruct(v interface{}, ti typeInfo) ([]byte, error) { enc = append(enc, fValData...) } - enc = append(encInt(tLen(len(enc))), enc...) + enc = append(encCount(len(enc), nil), enc...) return enc, nil } // decCheckedStruct decodes a REZI bytes representation of a struct into a // compatible struct type. -func decCheckedStruct(data []byte, v interface{}, ti typeInfo) (int, error) { - if ti.Main != mtStruct { +func decCheckedStruct(data []byte, v analyzed[any]) (int, error) { + if v.ti.Main != mtStruct { panic("not a struct type") } - var extraInfo []fieldInfo - st, n, err := decWithNilCheck(data, v, ti, fn_DecToWrappedReceiver(v, ti, + _, di, n, err := decWithNilCheck(data, v, fn_DecToWrappedReceiver(v, func(t reflect.Type) bool { return t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Struct }, - func(data []byte, v interface{}) (interface{}, int, error) { - fi, n, err := decStruct(data, v, ti) - extraInfo = fi - return fi, n, err - }, + decStruct, )) if err != nil { return n, err } - if ti.Indir == 0 { - refReceiver := reflect.ValueOf(v) + if v.ti.Indir == 0 { + refReceiver := v.ref // if it's a struct, we must get the original value, if one exists, in order // to preserve the original member values var origStructVal reflect.Value - if ti.Main == mtStruct { + if v.ti.Main == mtStruct { origStructVal = unwrapOriginalStructValue(refReceiver) } - refSt := reflect.ValueOf(st) + refSt := di.Ref - if ti.Main == mtStruct && origStructVal.IsValid() { - refSt = setStructMembers(origStructVal, refSt, extraInfo) + if v.ti.Main == mtStruct && origStructVal.IsValid() { + refSt = setStructMembers(origStructVal, refSt, di) } refReceiver.Elem().Set(refSt) @@ -91,12 +82,13 @@ func decCheckedStruct(data []byte, v interface{}, ti typeInfo) (int, error) { return n, err } -// the fieldInfo is the successfully decoded fields. -func decStruct(data []byte, v interface{}, ti typeInfo) ([]fieldInfo, int, error) { - var decFields []fieldInfo +// decInfo will have Fields set to the successfully decoded fields. +func decStruct(data []byte, v analyzed[any]) (decInfo, int, error) { + di := decInfo{} + var totalConsumed int - refVal := reflect.ValueOf(v) + refVal := v.ref refStructType := refVal.Type().Elem() msgTypeName := refStructType.Name() if msgTypeName == "" { @@ -105,7 +97,7 @@ func decStruct(data []byte, v interface{}, ti typeInfo) ([]fieldInfo, int, error toConsume, _, n, err := decInt[tLen](data) if err != nil { - return decFields, 0, errorDecf(0, "decode %s byte count: %s", msgTypeName, err) + return di, 0, errorDecf(0, "decode %s byte count: %s", msgTypeName, err) } data = data[n:] totalConsumed += n @@ -116,7 +108,8 @@ func decStruct(data []byte, v interface{}, ti typeInfo) ([]fieldInfo, int, error // set it to the value refVal.Elem().Set(emptyStruct.Elem()) - return decFields, totalConsumed, nil + di.Ref = emptyStruct.Elem() + return di, totalConsumed, nil } if len(data) < toConsume { @@ -128,7 +121,7 @@ func decStruct(data []byte, v interface{}, ti typeInfo) ([]fieldInfo, int, error } const errFmt = "decoded %s byte count is %d but only %d byte%s remain%s in data at offset" err := errorDecf(totalConsumed, errFmt, msgTypeName, toConsume, len(data), s, verbS).wrap(io.ErrUnexpectedEOF, ErrMalformedData) - return decFields, totalConsumed, err + return di, totalConsumed, err } // clamp values we are allowed to read so we don't try to read other data @@ -141,36 +134,37 @@ func decStruct(data []byte, v interface{}, ti typeInfo) ([]fieldInfo, int, error var fNameVal string n, err = Dec(data, &fNameVal) if err != nil { - return decFields, totalConsumed, errorDecf(totalConsumed, "decode %s field name: %s", msgTypeName, err) + return di, totalConsumed, errorDecf(totalConsumed, "decode %s field name: %s", msgTypeName, err) } totalConsumed += n i += n data = data[n:] // get field info from name - fi, ok := ti.Fields.ByName[fNameVal] + fi, ok := v.ti.Fields.ByName[fNameVal] if !ok { - return decFields, totalConsumed, errorDecf(totalConsumed, "field name .%s does not exist in decoded-to %s", fNameVal, msgTypeName).wrap(ErrMalformedData, ErrInvalidType) + return di, totalConsumed, errorDecf(totalConsumed, "field name .%s does not exist in decoded-to %s", fNameVal, msgTypeName).wrap(ErrMalformedData, ErrInvalidType) } fieldPtr := target.Field(fi.Index).Addr() n, err = Dec(data, fieldPtr.Interface()) if err != nil { - return decFields, totalConsumed, errorDecf(totalConsumed, "%s.%s: %v", msgTypeName, fi.Name, err) + return di, totalConsumed, errorDecf(totalConsumed, "%s.%s: %v", msgTypeName, fi.Name, err) } totalConsumed += n i += n data = data[n:] - decFields = append(decFields, fi) + di.Fields = append(di.Fields, fi) } - return decFields, totalConsumed, nil + di.Ref = target + return di, totalConsumed, nil } -func setStructMembers(initial, decoded reflect.Value, decodedFields []fieldInfo) reflect.Value { +func setStructMembers(initial, decoded reflect.Value, info decInfo) reflect.Value { newVal := reflect.New(initial.Type()) newVal.Elem().Set(initial) - for _, fi := range decodedFields { + for _, fi := range info.Fields { destPtr := newVal.Elem().Field(fi.Index).Addr() fieldVal := decoded.Field(fi.Index) destPtr.Elem().Set(fieldVal) diff --git a/structs_test.go b/structs_test.go index 0987ca2..ade811a 100644 --- a/structs_test.go +++ b/structs_test.go @@ -65,319 +65,2360 @@ func Test_Enc_Struct(t *testing.T) { Value float64 } - runEncTests(t, "empty struct", testStructEmpty{}, []byte{0x00}) - runEncTests(t, "one member", testStructOneMember{Value: 4}, []byte{ - 0x01, 0x0a, // len=10 + // normal value test + t.Run("empty struct", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructEmpty{} + expect = []byte{0x00} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(empty struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructEmpty{} + expect = []byte{0x00} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(empty struct), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructEmpty + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(empty struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructEmpty{} + input = &inputPtr + expect = []byte{0x00} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(empty struct), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructEmpty + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("one member", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructOneMember{Value: 4} + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(one member)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructOneMember{Value: 4} + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(one member), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructOneMember + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(one member)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructOneMember{Value: 4} + input = &inputPtr + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(one member), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructOneMember + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("multi member", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructMultiMember{Value: 4, Name: "NEPETA"} + expect = []byte{ + 0x01, 0x1a, // len=26 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(multi member)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructMultiMember{Value: 4, Name: "NEPETA"} + expect = []byte{ + 0x01, 0x1a, // len=26 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(multi member), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructMultiMember + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(multi member)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructMultiMember{Value: 4, Name: "NEPETA"} + input = &inputPtr + expect = []byte{ + 0x01, 0x1a, // len=26 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(multi member), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructMultiMember + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("with unexported", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructWithUnexported{Value: 4, unexported: 9} + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(with unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructWithUnexported{Value: 4, unexported: 9} + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(with unexported), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructWithUnexported + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(with unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructWithUnexported{Value: 4, unexported: 9} + input = &inputPtr + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(with unexported), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructWithUnexported + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("with unexported case distinguished", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructWithUnexportedCaseDistinguished{Value: 4, value: 3.2} + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(with unexported case distinguished)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructWithUnexportedCaseDistinguished{Value: 4, value: 3.2} + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(with unexported case distinguished), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructWithUnexportedCaseDistinguished + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(with unexported case distinguished)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructWithUnexportedCaseDistinguished{Value: 4, value: 3.2} + input = &inputPtr + expect = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(with unexported case distinguished), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructWithUnexportedCaseDistinguished + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("only unexported", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructOnlyUnexported{value: 3, name: "test"} + expect = []byte{0x00} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(only unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructOnlyUnexported{value: 3, name: "test"} + expect = []byte{0x00} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(only unexported), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructOnlyUnexported + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(only unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructOnlyUnexported{value: 3, name: "test"} + input = &inputPtr + expect = []byte{0x00} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(only unexported), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructOnlyUnexported + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("many fields", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: make(chan int), + inc: 17, + enabled: &sync.Mutex{}, + } + expect = []byte{ + 0x01, 0x39, // len=57 + + 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" + 0x01, // true + + 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x02, 0x01, 0x9d, // 413 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(many fields)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: make(chan int), + inc: 17, + enabled: &sync.Mutex{}, + } + expect = []byte{ + 0x01, 0x39, // len=57 + + 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" + 0x01, // true + + 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x02, 0x01, 0x9d, // 413 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(many fields), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructManyFields + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(many fields)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: make(chan int), + inc: 17, + enabled: &sync.Mutex{}, + } + input = &inputPtr + expect = []byte{ + 0x01, 0x39, // len=57 + + 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" + 0x01, // true + + 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x02, 0x01, 0x9d, // 413 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(many fields), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructManyFields + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("with embedded", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } + expect = []byte{ + 0x01, 0x30, // len=48 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(with embedded)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } + expect = []byte{ + 0x01, 0x30, // len=48 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(with embedded), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructWithEmbedded + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(with embedded)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } + input = &inputPtr + expect = []byte{ + 0x01, 0x30, // len=48 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(with embedded), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructWithEmbedded + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // normal value test + t.Run("with embedded overlap", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } + expect = []byte{ + 0x01, 0x3c, // len=60 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, filled + t.Run("*(with embedded overlap)", func(t *testing.T) { + assert := assert.New(t) + + var ( + input = &testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } + expect = []byte{ + 0x01, 0x3c, // len=60 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // single pointer, nil + t.Run("*(with embedded overlap), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + input *testStructWithEmbeddedOverlap + expect = []byte{0xa0} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, filled + t.Run("**(with embedded overlap)", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr = &testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } + input = &inputPtr + expect = []byte{ + 0x01, 0x3c, // len=60 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + } + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) + + // double pointer, nil at first level + t.Run("**(with embedded overlap), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + inputPtr *testStructWithEmbeddedOverlap + input = &inputPtr + expect = []byte{0xb0, 0x01, 0x01} + ) + + actual, err := Enc(input) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual) + }) +} + +func Test_Dec_Struct(t *testing.T) { + // we require an exported struct in order to test embedded struct fields. + // we will declare it and other struct types it is embedded in here in this + // function to avoid adding a new exported type to the rezi package. + type TestStructToEmbed struct { + Value int + } + type testStructWithEmbedded struct { + TestStructToEmbed + Name string + } + + type testStructWithEmbeddedOverlap struct { + TestStructToEmbed + Name string + Value float64 + } + + t.Run("no-member struct", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructEmpty + input = []byte{0x00} + expect = testStructEmpty{} + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // 0-len struct + t.Run("no-member struct, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructEmpty + input = []byte{0x00} + expect testStructEmpty + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, filled + t.Run("*(no-member struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual *testStructEmpty + input = []byte{0x00} + expectVal = testStructEmpty{} + expect = &expectVal + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, nil + t.Run("*(no-member struct), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructEmpty{} // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructEmpty + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, filled + t.Run("**(no-member struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual **testStructEmpty + input = []byte{0x00} + expectVal = testStructEmpty{} + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(no-member struct), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructEmpty{} + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructEmpty + expect = &expectPtr + expectConsumed = 3 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("one-member struct", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructOneMember + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expect = testStructOneMember{Value: 4} + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // 0-len struct + t.Run("one-member struct, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructOneMember + input = []byte{0x00} + expect testStructOneMember + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, filled + t.Run("*(one-member struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual *testStructOneMember + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructOneMember{Value: 4} + expect = &expectVal + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, nil + t.Run("*(one-member struct), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructOneMember{Value: 4} // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructOneMember + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, filled + t.Run("**(one-member struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual **testStructOneMember + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructOneMember{Value: 4} + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(one-member struct), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructOneMember{Value: 4} + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructOneMember + expect = &expectPtr + expectConsumed = 3 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("multi-member struct", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructMultiMember + input = []byte{ + 0x01, 0x1a, // len=26 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expect = testStructMultiMember{Value: 4, Name: "NEPETA"} + expectConsumed = 28 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // 0-len struct + t.Run("multi-member struct, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructMultiMember + input = []byte{0x00} + expect testStructMultiMember + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, filled + t.Run("*(multi-member struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual *testStructMultiMember + input = []byte{ + 0x01, 0x1a, // len=26 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructMultiMember{Value: 4, Name: "NEPETA"} + expect = &expectVal + expectConsumed = 28 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, nil + t.Run("*(multi-member struct), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructMultiMember{Value: 4, Name: "NEPETA"} // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructMultiMember + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, filled + t.Run("**(multi-member struct)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual **testStructMultiMember + input = []byte{ + 0x01, 0x1a, // len=26 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructMultiMember{Value: 4, Name: "NEPETA"} + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 28 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(multi-member struct), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructMultiMember{Value: 4, Name: "NEPETA"} + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructMultiMember + expect = &expectPtr + expectConsumed = 3 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("with unexported", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructWithUnexported + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expect = testStructWithUnexported{Value: 4, unexported: 0} + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // 0-len struct + t.Run("with unexported, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructWithUnexported + input = []byte{0x00} + expect testStructWithUnexported + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, filled + t.Run("*(with unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual *testStructWithUnexported + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructWithUnexported{Value: 4, unexported: 0} + expect = &expectVal + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, nil + t.Run("*(with unexported), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructWithUnexported{Value: 4, unexported: 0} // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructWithUnexported + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, filled + t.Run("**(with unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual **testStructWithUnexported + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructWithUnexported{Value: 4, unexported: 0} + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(with unexported), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructWithUnexported{Value: 4, unexported: 0} + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructWithUnexported + expect = &expectPtr + expectConsumed = 3 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("with unexported values set", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = testStructWithUnexported{unexported: 12} + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expect = testStructWithUnexported{Value: 4, unexported: 12} + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // 0-len struct + t.Run("with unexported values set, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = testStructWithUnexported{unexported: 12} + input = []byte{0x00} + expect = testStructWithUnexported{unexported: 12} + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, filled + t.Run("*(with unexported values set)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructWithUnexported{unexported: 12} + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructWithUnexported{Value: 4, unexported: 12} + expect = &expectVal + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, nil + t.Run("*(with unexported values set), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructWithUnexported{Value: 4, unexported: 12} // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructWithUnexported + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, filled + t.Run("**(with unexported values set)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = ref(&testStructWithUnexported{unexported: 12}) + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructWithUnexported{Value: 4, unexported: 12} + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(with unexported values set), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructWithUnexported{Value: 4, unexported: 12} + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructWithUnexported + expect = &expectPtr + expectConsumed = 3 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("with unexported case distinguished", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructWithUnexportedCaseDistinguished + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expect = testStructWithUnexportedCaseDistinguished{Value: 4, value: 0} + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // 0-len struct + t.Run("with unexported case distinguished, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructWithUnexportedCaseDistinguished + input = []byte{0x00} + expect testStructWithUnexportedCaseDistinguished + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, filled + t.Run("*(with unexported case distinguished)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual *testStructWithUnexportedCaseDistinguished + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructWithUnexportedCaseDistinguished{Value: 4, value: 0} + expect = &expectVal + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, nil + t.Run("*(with unexported case distinguished), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructWithUnexportedCaseDistinguished{Value: 4, value: 0} // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructWithUnexportedCaseDistinguished + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, filled + t.Run("**(with unexported case distinguished)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual **testStructWithUnexportedCaseDistinguished + input = []byte{ + 0x01, 0x0a, // len=10 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructWithUnexportedCaseDistinguished{Value: 4, value: 0} + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 12 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(with unexported case distinguished), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructWithUnexportedCaseDistinguished{Value: 4, value: 0} + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructWithUnexportedCaseDistinguished + expect = &expectPtr + expectConsumed = 3 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("only unexported", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructOnlyUnexported + input = []byte{0x00} + expect = testStructOnlyUnexported{value: 0, name: ""} + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // 0-len struct + t.Run("only unexported, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructOnlyUnexported + input = []byte{0x00} + expect testStructOnlyUnexported + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, filled + t.Run("*(only unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual *testStructOnlyUnexported + input = []byte{0x00} + expectVal = testStructOnlyUnexported{value: 0, name: ""} + expect = &expectVal + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // single pointer, nil + t.Run("*(only unexported), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructOnlyUnexported{value: 0, name: ""} // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructOnlyUnexported + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, filled + t.Run("**(only unexported)", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual **testStructOnlyUnexported + input = []byte{0x00} + expectVal = testStructOnlyUnexported{value: 0, name: ""} + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(only unexported), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructOnlyUnexported{value: 0, name: ""} + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructOnlyUnexported + expect = &expectPtr + expectConsumed = 3 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("many fields", func(t *testing.T) { + assert := assert.New(t) - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }) - runEncTests(t, "multi member", testStructMultiMember{Value: 4, Name: "NEPETA"}, []byte{ - 0x01, 0x1a, // len=26 + var ( + actual testStructManyFields + input = []byte{ + 0x01, 0x39, // len=57 - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" + 0x01, // true - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }) - runEncTests(t, "with unexported", testStructWithUnexported{Value: 4, unexported: 9}, []byte{ - 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x02, 0x01, 0x9d, // 413 + } + expect = testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: nil, + inc: 0, + enabled: nil, + } + expectConsumed = 59 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - runEncTests(t, "with unexported case distinguished", testStructWithUnexportedCaseDistinguished{Value: 4, value: 3.2}, []byte{ - 0x01, 0x0a, // len=10 - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 + // 0-len struct + t.Run("many fields, no values encoded", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructManyFields + input = []byte{0x00} + expect testStructManyFields + expectConsumed = 1 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } + + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - runEncTests(t, "only unexported", testStructOnlyUnexported{value: 3, name: "test"}, []byte{0x00}) - runEncTests(t, "many fields", testStructManyFields{ - Value: 413, - Name: "Rose Lalonde", - Enabled: true, - Factor: 8.25, - hidden: make(chan int), - inc: 17, - enabled: &sync.Mutex{}, - }, []byte{ - 0x01, 0x39, // len=57 + // single pointer, filled + t.Run("*(many fields)", func(t *testing.T) { + assert := assert.New(t) - 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" - 0x01, // true + var ( + actual *testStructManyFields + input = []byte{ + 0x01, 0x39, // len=57 - 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" - 0x03, 0xc0, 0x20, 0x80, // 8.25 + 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" + 0x01, // true - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" + 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" + 0x03, 0xc0, 0x20, 0x80, // 8.25 - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x02, 0x01, 0x9d, // 413 - }) - runEncTests(t, "with embedded", testStructWithEmbedded{ - TestStructToEmbed: TestStructToEmbed{ - Value: 4, - }, - Name: "NEPETA", - }, []byte{ - 0x01, 0x30, // len=48 + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x02, 0x01, 0x9d, // 413 + } + expectVal = testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: nil, + inc: 0, + enabled: nil, + } + expect = &expectVal + expectConsumed = 59 + ) + + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } - 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" - 0x01, 0x0a, // len=10 - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - runEncTests(t, "with embedded overlap", testStructWithEmbeddedOverlap{ - TestStructToEmbed: TestStructToEmbed{ - Value: 4, - }, - Value: 8.25, - Name: "NEPETA", - }, []byte{ - 0x01, 0x3c, // len=60 - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + // single pointer, nil + t.Run("*(many fields), nil", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual = &testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: nil, + inc: 0, + enabled: nil, + } // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructManyFields + expectConsumed = 1 + ) - 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" - 0x01, 0x0a, // len=10 - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x03, 0xc0, 0x20, 0x80, // 8.25 + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) -} -func runEncTests[E any](t *testing.T, name string, inputVal E, expect []byte) { - // TODO: too complicated, just make the test cases instead of going off to a function - // normal value test - t.Run(name, func(t *testing.T) { + // double pointer, filled + t.Run("**(many fields)", func(t *testing.T) { assert := assert.New(t) - input := inputVal + var ( + actual **testStructManyFields + input = []byte{ + 0x01, 0x39, // len=57 + + 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" + 0x01, // true - actual, err := Enc(input) + 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x02, 0x01, 0x9d, // 413 + } + expectVal = testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: nil, + inc: 0, + enabled: nil, + } + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 59 + ) + + consumed, err := Dec(input, &actual) if !assert.NoError(err) { return } - assert.Equal(expect, actual) + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - // single pointer, filled - t.Run("*("+name+")", func(t *testing.T) { + // double pointer, nil at first level + t.Run("**(many fields), nil at first level", func(t *testing.T) { assert := assert.New(t) - input := &inputVal + var ( + actualInitialPtr = &testStructManyFields{ + Value: 413, + Name: "Rose Lalonde", + Enabled: true, + Factor: 8.25, + + hidden: nil, + inc: 0, + enabled: nil, + } + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructManyFields + expect = &expectPtr + expectConsumed = 3 + ) - actual, err := Enc(input) + consumed, err := Dec(input, &actual) if !assert.NoError(err) { return } - assert.Equal(expect, actual) + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - // single pointer, nil - t.Run("*("+name+"), nil", func(t *testing.T) { + // normal value test + t.Run("with embedded", func(t *testing.T) { assert := assert.New(t) - var input *E - var nilExp = []byte{0xa0} + var ( + actual testStructWithEmbedded + input = []byte{ + 0x01, 0x30, // len=48 - actual, err := Enc(input) + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expect = testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } + expectConsumed = 50 + ) + + consumed, err := Dec(input, &actual) if !assert.NoError(err) { return } - assert.Equal(nilExp, actual) + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - // double pointer, filled - t.Run("**("+name+")", func(t *testing.T) { + // 0-len struct + t.Run("with embedded, no values encoded", func(t *testing.T) { assert := assert.New(t) - inputPtr := &inputVal - input := &inputPtr + var ( + actual testStructWithEmbedded + input = []byte{0x00} + expect testStructWithEmbedded + expectConsumed = 1 + ) - actual, err := Enc(input) + consumed, err := Dec(input, &actual) if !assert.NoError(err) { return } - assert.Equal(expect, actual) + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - // double pointer, nil at first level - t.Run("**("+name+"), nil at first level", func(t *testing.T) { + // single pointer, filled + t.Run("*(with embedded)", func(t *testing.T) { assert := assert.New(t) - var inputPtr *E - var input = &inputPtr - var nilFirstLevelExp = []byte{0xb0, 0x01, 0x01} + var ( + actual *testStructWithEmbedded + input = []byte{ + 0x01, 0x30, // len=48 - actual, err := Enc(input) + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + } + expectVal = testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } + expect = &expectVal + expectConsumed = 50 + ) + + consumed, err := Dec(input, &actual) if !assert.NoError(err) { return } - assert.Equal(nilFirstLevelExp, actual) + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) -} -func Test_Dec_Struct(t *testing.T) { - // we require an exported struct in order to test embedded struct fields. - // we will declare it and other struct types it is embedded in here in this - // function to avoid adding a new exported type to the rezi package. - type TestStructToEmbed struct { - Value int - } - type testStructWithEmbedded struct { - TestStructToEmbed - Name string - } + // single pointer, nil + t.Run("*(with embedded), nil", func(t *testing.T) { + assert := assert.New(t) - type testStructWithEmbeddedOverlap struct { - TestStructToEmbed - Name string - Value float64 - } + var ( + actual = &testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } // initially set to enshore it's cleared + input = []byte{0xa0} + expect *testStructWithEmbedded + expectConsumed = 1 + ) - runDecTests(t, "no-member struct", nil, []byte{0x00}, testStructEmpty{}, nil, 1) + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return + } - runDecTests(t, "one-member struct", nil, - []byte{ - 0x01, 0x0a, // len=10 + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }, testStructOneMember{Value: 4}, nil, 12) - - runDecTests(t, "multi-member struct", nil, - []byte{ - 0x01, 0x1a, // len=26 - - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" - - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }, testStructMultiMember{Value: 4, Name: "NEPETA"}, nil, 28) - - runDecTests(t, "with unexported", nil, - []byte{ - 0x01, 0x0a, // len=10 - - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }, testStructWithUnexported{Value: 4, unexported: 0}, nil, 12) - - runDecTests(t, "with unexported values set", &testStructWithUnexported{unexported: 12}, - []byte{ - 0x01, 0x0a, // len=10 - - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }, testStructWithUnexported{Value: 4, unexported: 12}, &testStructWithUnexported{unexported: 12}, 12) - - runDecTests(t, "with unexported case distinguished", nil, - []byte{ - 0x01, 0x0a, // len=10 - - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }, testStructWithUnexportedCaseDistinguished{Value: 4, value: 0}, nil, 12) - - runDecTests(t, "only unexported", nil, []byte{0x00}, testStructOnlyUnexported{value: 0, name: ""}, nil, 1) - - runDecTests(t, "many fields", nil, - []byte{ - 0x01, 0x39, // len=57 - - 0x41, 0x82, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, // "Enabled" - 0x01, // true - - 0x41, 0x82, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, // "Factor" - 0x03, 0xc0, 0x20, 0x80, // 8.25 - - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x0c, 0x52, 0x6f, 0x73, 0x65, 0x20, 0x4c, 0x61, 0x6c, 0x6f, 0x6e, 0x64, 0x65, // "Rose Lalonde" - - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x02, 0x01, 0x9d, // 413 - }, testStructManyFields{ - Value: 413, - Name: "Rose Lalonde", - Enabled: true, - Factor: 8.25, - - hidden: nil, - inc: 0, - enabled: nil, - }, nil, 59) - - runDecTests(t, "with embedded", nil, - []byte{ - 0x01, 0x30, // len=48 - - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" - - 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" - 0x01, 0x0a, // len=10 - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - }, testStructWithEmbedded{ - TestStructToEmbed: TestStructToEmbed{ - Value: 4, - }, - Name: "NEPETA", - }, nil, 50) - - runDecTests(t, "with embedded overlap", nil, - []byte{ - 0x01, 0x3c, // len=60 - - 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" - 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" - - 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" - 0x01, 0x0a, // len=10 - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x01, 0x04, // 4 - - 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" - 0x03, 0xc0, 0x20, 0x80, // 8.25 - }, testStructWithEmbeddedOverlap{ - TestStructToEmbed: TestStructToEmbed{ - Value: 4, - }, - Value: 8.25, - Name: "NEPETA", - }, nil, 62) - - // // specialized test we cannot abstract easily - t.Run("missing values in encoded are left alone", func(t *testing.T) { + // double pointer, filled + t.Run("**(with embedded)", func(t *testing.T) { assert := assert.New(t) var ( - actual = testStructMultiMember{Value: 8, Name: "JOHN"} + actual **testStructWithEmbedded input = []byte{ - 0x01, 0x10, // len=16 + 0x01, 0x30, // len=48 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 } - expect = testStructMultiMember{Value: 8, Name: "NEPETA"} - expectConsumed = 18 + expectVal = testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } + expectPtr = &expectVal + expect = &expectPtr + expectConsumed = 50 ) consumed, err := Dec(input, &actual) @@ -388,29 +2429,64 @@ func Test_Dec_Struct(t *testing.T) { assert.Equal(expect, actual, "value mismatch") assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) -} -// expectConsumed used only in sub-tests where expect is the actual expected. -// -// if initVal is nil it will be set to an empty value -func runDecTests[E any](t *testing.T, name string, initVal *E, filledInput []byte, filledExpect E, emptyExpect *E, filledExpectConsumed int) { - // TODO: too complicated, just make the test cases instead of going off to a function - - // normal value test - t.Run(name, func(t *testing.T) { + // double pointer, nil at first level + t.Run("**(with embedded), nil at first level", func(t *testing.T) { assert := assert.New(t) var ( - actual E - input = filledInput - expect = filledExpect - expectConsumed = filledExpectConsumed + actualInitialPtr = &testStructWithEmbedded{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Name: "NEPETA", + } + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructWithEmbedded + expect = &expectPtr + expectConsumed = 3 ) - if initVal != nil { - actual = *initVal + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return } + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // normal value test + t.Run("with embedded overlap", func(t *testing.T) { + assert := assert.New(t) + + var ( + actual testStructWithEmbeddedOverlap + input = []byte{ + 0x01, 0x3c, // len=60 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + } + expect = testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } + expectConsumed = 62 + ) + consumed, err := Dec(input, &actual) if !assert.NoError(err) { return @@ -421,24 +2497,16 @@ func runDecTests[E any](t *testing.T, name string, initVal *E, filledInput []byt }) // 0-len struct - t.Run(name+", no values encoded", func(t *testing.T) { + t.Run("with embedded overlap, no values encoded", func(t *testing.T) { assert := assert.New(t) var ( - actual E + actual testStructWithEmbeddedOverlap input = []byte{0x00} - expect E + expect testStructWithEmbeddedOverlap expectConsumed = 1 ) - if initVal != nil { - actual = *initVal - } - - if emptyExpect != nil { - expect = *emptyExpect - } - consumed, err := Dec(input, &actual) if !assert.NoError(err) { return @@ -449,22 +2517,36 @@ func runDecTests[E any](t *testing.T, name string, initVal *E, filledInput []byt }) // single pointer, filled - t.Run("*("+name+")", func(t *testing.T) { + t.Run("*(with embedded overlap)", func(t *testing.T) { assert := assert.New(t) var ( - actual *E - input = filledInput - expectVal = filledExpect + actual *testStructWithEmbeddedOverlap + input = []byte{ + 0x01, 0x3c, // len=60 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + } + expectVal = testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } expect = &expectVal - expectConsumed = filledExpectConsumed + expectConsumed = 62 ) - if initVal != nil { - actual = new(E) - *actual = (*initVal) - } - consumed, err := Dec(input, &actual) if !assert.NoError(err) { return @@ -475,13 +2557,19 @@ func runDecTests[E any](t *testing.T, name string, initVal *E, filledInput []byt }) // single pointer, nil - t.Run("*("+name+"), nil", func(t *testing.T) { + t.Run("*(with embedded overlap), nil", func(t *testing.T) { assert := assert.New(t) var ( - actual = &filledExpect // initially set to enshore it's cleared + actual = &testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } // initially set to enshore it's cleared input = []byte{0xa0} - expect *E + expect *testStructWithEmbeddedOverlap expectConsumed = 1 ) @@ -495,24 +2583,65 @@ func runDecTests[E any](t *testing.T, name string, initVal *E, filledInput []byt }) // double pointer, filled - t.Run("**("+name+")", func(t *testing.T) { + t.Run("**(with embedded overlap)", func(t *testing.T) { assert := assert.New(t) var ( - actual **E - input = filledInput - expectVal = filledExpect + actual **testStructWithEmbeddedOverlap + input = []byte{ + 0x01, 0x3c, // len=60 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + + 0x41, 0x82, 0x11, 0x54, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x54, 0x6f, 0x45, 0x6d, 0x62, 0x65, 0x64, // "TestStructToEmbed" + 0x01, 0x0a, // len=10 + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x01, 0x04, // 4 + + 0x41, 0x82, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, // "Value" + 0x03, 0xc0, 0x20, 0x80, // 8.25 + } + expectVal = testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } expectPtr = &expectVal expect = &expectPtr - expectConsumed = filledExpectConsumed + expectConsumed = 62 ) - if initVal != nil { - actual = new(*E) - *actual = new(E) - **actual = (*initVal) + consumed, err := Dec(input, &actual) + if !assert.NoError(err) { + return } + assert.Equal(expect, actual, "value mismatch") + assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") + }) + + // double pointer, nil at first level + t.Run("**(with embedded overlap), nil at first level", func(t *testing.T) { + assert := assert.New(t) + + var ( + actualInitialPtr = &testStructWithEmbeddedOverlap{ + TestStructToEmbed: TestStructToEmbed{ + Value: 4, + }, + Value: 8.25, + Name: "NEPETA", + } + actual = &actualInitialPtr // initially set to enshore it's cleared + input = []byte{0xb0, 0x01, 0x01} + expectPtr *testStructWithEmbeddedOverlap + expect = &expectPtr + expectConsumed = 3 + ) + consumed, err := Dec(input, &actual) if !assert.NoError(err) { return @@ -522,17 +2651,19 @@ func runDecTests[E any](t *testing.T, name string, initVal *E, filledInput []byt assert.Equal(expectConsumed, consumed, "consumed bytes mismatch") }) - // double pointer, nil at first level - t.Run("**("+name+"), nil at first level", func(t *testing.T) { + t.Run("missing values in encoded are left alone", func(t *testing.T) { assert := assert.New(t) var ( - actualInitialPtr = &filledExpect - actual = &actualInitialPtr // initially set to enshore it's cleared - input = []byte{0xb0, 0x01, 0x01} - expectPtr *E - expect = &expectPtr - expectConsumed = 3 + actual = testStructMultiMember{Value: 8, Name: "JOHN"} + input = []byte{ + 0x01, 0x10, // len=16 + + 0x41, 0x82, 0x04, 0x4e, 0x61, 0x6d, 0x65, // "Name" + 0x41, 0x82, 0x06, 0x4e, 0x45, 0x50, 0x45, 0x54, 0x41, // "NEPETA" + } + expect = testStructMultiMember{Value: 8, Name: "NEPETA"} + expectConsumed = 18 ) consumed, err := Dec(input, &actual)