rlp: add support for optional struct fields (#22832)
This adds support for a new struct tag "optional". Using this tag, structs used for RLP encoding/decoding can be extended in a backwards-compatible way, by adding new fields at the end.
This commit is contained in:
parent
8a070e8f7d
commit
700df1442d
@ -229,7 +229,7 @@ func decodeBigInt(s *Stream, val reflect.Value) error {
|
|||||||
i = new(big.Int)
|
i = new(big.Int)
|
||||||
val.Set(reflect.ValueOf(i))
|
val.Set(reflect.ValueOf(i))
|
||||||
}
|
}
|
||||||
// Reject leading zero bytes
|
// Reject leading zero bytes.
|
||||||
if len(b) > 0 && b[0] == 0 {
|
if len(b) > 0 && b[0] == 0 {
|
||||||
return wrapStreamError(ErrCanonInt, val.Type())
|
return wrapStreamError(ErrCanonInt, val.Type())
|
||||||
}
|
}
|
||||||
@ -394,9 +394,16 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) {
|
|||||||
if _, err := s.List(); err != nil {
|
if _, err := s.List(); err != nil {
|
||||||
return wrapStreamError(err, typ)
|
return wrapStreamError(err, typ)
|
||||||
}
|
}
|
||||||
for _, f := range fields {
|
for i, f := range fields {
|
||||||
err := f.info.decoder(s, val.Field(f.index))
|
err := f.info.decoder(s, val.Field(f.index))
|
||||||
if err == EOL {
|
if err == EOL {
|
||||||
|
if f.optional {
|
||||||
|
// The field is optional, so reaching the end of the list before
|
||||||
|
// reaching the last field is acceptable. All remaining undecoded
|
||||||
|
// fields are zeroed.
|
||||||
|
zeroFields(val, fields[i:])
|
||||||
|
break
|
||||||
|
}
|
||||||
return &decodeError{msg: "too few elements", typ: typ}
|
return &decodeError{msg: "too few elements", typ: typ}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return addErrorContext(err, "."+typ.Field(f.index).Name)
|
return addErrorContext(err, "."+typ.Field(f.index).Name)
|
||||||
@ -407,6 +414,13 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) {
|
|||||||
return dec, nil
|
return dec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func zeroFields(structval reflect.Value, fields []field) {
|
||||||
|
for _, f := range fields {
|
||||||
|
fv := structval.Field(f.index)
|
||||||
|
fv.Set(reflect.Zero(fv.Type()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// makePtrDecoder creates a decoder that decodes into the pointer's element type.
|
// makePtrDecoder creates a decoder that decodes into the pointer's element type.
|
||||||
func makePtrDecoder(typ reflect.Type, tag tags) (decoder, error) {
|
func makePtrDecoder(typ reflect.Type, tag tags) (decoder, error) {
|
||||||
etype := typ.Elem()
|
etype := typ.Elem()
|
||||||
|
@ -369,6 +369,39 @@ type intField struct {
|
|||||||
X int
|
X int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type optionalFields struct {
|
||||||
|
A uint
|
||||||
|
B uint `rlp:"optional"`
|
||||||
|
C uint `rlp:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionalAndTailField struct {
|
||||||
|
A uint
|
||||||
|
B uint `rlp:"optional"`
|
||||||
|
Tail []uint `rlp:"tail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionalBigIntField struct {
|
||||||
|
A uint
|
||||||
|
B *big.Int `rlp:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionalPtrField struct {
|
||||||
|
A uint
|
||||||
|
B *[3]byte `rlp:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionalPtrFieldNil struct {
|
||||||
|
A uint
|
||||||
|
B *[3]byte `rlp:"optional,nil"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ignoredField struct {
|
||||||
|
A uint
|
||||||
|
B uint `rlp:"-"`
|
||||||
|
C uint
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
veryBigInt = big.NewInt(0).Add(
|
veryBigInt = big.NewInt(0).Add(
|
||||||
big.NewInt(0).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16),
|
big.NewInt(0).Lsh(big.NewInt(0xFFFFFFFFFFFFFF), 16),
|
||||||
@ -376,12 +409,6 @@ var (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
type hasIgnoredField struct {
|
|
||||||
A uint
|
|
||||||
B uint `rlp:"-"`
|
|
||||||
C uint
|
|
||||||
}
|
|
||||||
|
|
||||||
var decodeTests = []decodeTest{
|
var decodeTests = []decodeTest{
|
||||||
// booleans
|
// booleans
|
||||||
{input: "01", ptr: new(bool), value: true},
|
{input: "01", ptr: new(bool), value: true},
|
||||||
@ -551,8 +578,8 @@ var decodeTests = []decodeTest{
|
|||||||
// struct tag "-"
|
// struct tag "-"
|
||||||
{
|
{
|
||||||
input: "C20102",
|
input: "C20102",
|
||||||
ptr: new(hasIgnoredField),
|
ptr: new(ignoredField),
|
||||||
value: hasIgnoredField{A: 1, C: 2},
|
value: ignoredField{A: 1, C: 2},
|
||||||
},
|
},
|
||||||
|
|
||||||
// struct tag "nilList"
|
// struct tag "nilList"
|
||||||
@ -592,6 +619,110 @@ var decodeTests = []decodeTest{
|
|||||||
value: nilStringSlice{X: &[]uint{3}},
|
value: nilStringSlice{X: &[]uint{3}},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// struct tag "optional"
|
||||||
|
{
|
||||||
|
input: "C101",
|
||||||
|
ptr: new(optionalFields),
|
||||||
|
value: optionalFields{1, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20102",
|
||||||
|
ptr: new(optionalFields),
|
||||||
|
value: optionalFields{1, 2, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C3010203",
|
||||||
|
ptr: new(optionalFields),
|
||||||
|
value: optionalFields{1, 2, 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C401020304",
|
||||||
|
ptr: new(optionalFields),
|
||||||
|
error: "rlp: input list has too many elements for rlp.optionalFields",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C101",
|
||||||
|
ptr: new(optionalAndTailField),
|
||||||
|
value: optionalAndTailField{A: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20102",
|
||||||
|
ptr: new(optionalAndTailField),
|
||||||
|
value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C401020304",
|
||||||
|
ptr: new(optionalAndTailField),
|
||||||
|
value: optionalAndTailField{A: 1, B: 2, Tail: []uint{3, 4}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C101",
|
||||||
|
ptr: new(optionalBigIntField),
|
||||||
|
value: optionalBigIntField{A: 1, B: nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20102",
|
||||||
|
ptr: new(optionalBigIntField),
|
||||||
|
value: optionalBigIntField{A: 1, B: big.NewInt(2)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C101",
|
||||||
|
ptr: new(optionalPtrField),
|
||||||
|
value: optionalPtrField{A: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20180", // not accepted because "optional" doesn't enable "nil"
|
||||||
|
ptr: new(optionalPtrField),
|
||||||
|
error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20102",
|
||||||
|
ptr: new(optionalPtrField),
|
||||||
|
error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrField).B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C50183010203",
|
||||||
|
ptr: new(optionalPtrField),
|
||||||
|
value: optionalPtrField{A: 1, B: &[3]byte{1, 2, 3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C101",
|
||||||
|
ptr: new(optionalPtrFieldNil),
|
||||||
|
value: optionalPtrFieldNil{A: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20180", // accepted because "nil" tag allows empty input
|
||||||
|
ptr: new(optionalPtrFieldNil),
|
||||||
|
value: optionalPtrFieldNil{A: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20102",
|
||||||
|
ptr: new(optionalPtrFieldNil),
|
||||||
|
error: "rlp: input string too short for [3]uint8, decoding into (rlp.optionalPtrFieldNil).B",
|
||||||
|
},
|
||||||
|
|
||||||
|
// struct tag "optional" field clearing
|
||||||
|
{
|
||||||
|
input: "C101",
|
||||||
|
ptr: &optionalFields{A: 9, B: 8, C: 7},
|
||||||
|
value: optionalFields{A: 1, B: 0, C: 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20102",
|
||||||
|
ptr: &optionalFields{A: 9, B: 8, C: 7},
|
||||||
|
value: optionalFields{A: 1, B: 2, C: 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C20102",
|
||||||
|
ptr: &optionalAndTailField{A: 9, B: 8, Tail: []uint{7, 6, 5}},
|
||||||
|
value: optionalAndTailField{A: 1, B: 2, Tail: []uint{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "C101",
|
||||||
|
ptr: &optionalPtrField{A: 9, B: &[3]byte{8, 7, 6}},
|
||||||
|
value: optionalPtrField{A: 1},
|
||||||
|
},
|
||||||
|
|
||||||
// RawValue
|
// RawValue
|
||||||
{input: "01", ptr: new(RawValue), value: RawValue(unhex("01"))},
|
{input: "01", ptr: new(RawValue), value: RawValue(unhex("01"))},
|
||||||
{input: "82FFFF", ptr: new(RawValue), value: RawValue(unhex("82FFFF"))},
|
{input: "82FFFF", ptr: new(RawValue), value: RawValue(unhex("82FFFF"))},
|
||||||
@ -822,6 +953,40 @@ func TestDecoderFunc(t *testing.T) {
|
|||||||
x()
|
x()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This tests the validity checks for fields with struct tag "optional".
|
||||||
|
func TestInvalidOptionalField(t *testing.T) {
|
||||||
|
type (
|
||||||
|
invalid1 struct {
|
||||||
|
A uint `rlp:"optional"`
|
||||||
|
B uint
|
||||||
|
}
|
||||||
|
invalid2 struct {
|
||||||
|
T []uint `rlp:"tail,optional"`
|
||||||
|
}
|
||||||
|
invalid3 struct {
|
||||||
|
T []uint `rlp:"optional,tail"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
v interface{}
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{v: new(invalid1), err: `rlp: struct field rlp.invalid1.B needs "optional" tag`},
|
||||||
|
{v: new(invalid2), err: `rlp: invalid struct tag "optional" for rlp.invalid2.T (also has "tail" tag)`},
|
||||||
|
{v: new(invalid3), err: `rlp: invalid struct tag "tail" for rlp.invalid3.T (also has "optional" tag)`},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
err := DecodeBytes(unhex("C20102"), test.v)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("no error for %T", test.v)
|
||||||
|
} else if err.Error() != test.err {
|
||||||
|
t.Errorf("wrong error for %T: %v", test.v, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleDecode() {
|
func ExampleDecode() {
|
||||||
input, _ := hex.DecodeString("C90A1486666F6F626172")
|
input, _ := hex.DecodeString("C90A1486666F6F626172")
|
||||||
|
|
||||||
|
67
rlp/doc.go
67
rlp/doc.go
@ -102,29 +102,60 @@ Signed integers, floating point numbers, maps, channels and functions cannot be
|
|||||||
|
|
||||||
Struct Tags
|
Struct Tags
|
||||||
|
|
||||||
Package rlp honours certain struct tags: "-", "tail", "nil", "nilList" and "nilString".
|
As with other encoding packages, the "-" tag ignores fields.
|
||||||
|
|
||||||
The "-" tag ignores fields.
|
type StructWithIgnoredField struct{
|
||||||
|
Ignored uint `rlp:"-"`
|
||||||
The "tail" tag, which may only be used on the last exported struct field, allows slurping
|
Field uint
|
||||||
up any excess list elements into a slice. See examples for more details.
|
|
||||||
|
|
||||||
The "nil" tag applies to pointer-typed fields and changes the decoding rules for the field
|
|
||||||
such that input values of size zero decode as a nil pointer. This tag can be useful when
|
|
||||||
decoding recursive types.
|
|
||||||
|
|
||||||
type StructWithOptionalFoo struct {
|
|
||||||
Foo *[20]byte `rlp:"nil"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Go struct values encode/decode as RLP lists. There are two ways of influencing the mapping
|
||||||
|
of fields to list elements. The "tail" tag, which may only be used on the last exported
|
||||||
|
struct field, allows slurping up any excess list elements into a slice.
|
||||||
|
|
||||||
|
type StructWithTail struct{
|
||||||
|
Field uint
|
||||||
|
Tail []string `rlp:"tail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
The "optional" tag says that the field may be omitted if it is zero-valued. If this tag is
|
||||||
|
used on a struct field, all subsequent public fields must also be declared optional.
|
||||||
|
|
||||||
|
When encoding a struct with optional fields, the output RLP list contains all values up to
|
||||||
|
the last non-zero optional field.
|
||||||
|
|
||||||
|
When decoding into a struct, optional fields may be omitted from the end of the input
|
||||||
|
list. For the example below, this means input lists of one, two, or three elements are
|
||||||
|
accepted.
|
||||||
|
|
||||||
|
type StructWithOptionalFields struct{
|
||||||
|
Required uint
|
||||||
|
Optional1 uint `rlp:"optional"`
|
||||||
|
Optional2 uint `rlp:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
The "nil", "nilList" and "nilString" tags apply to pointer-typed fields only, and change
|
||||||
|
the decoding rules for the field type. For regular pointer fields without the "nil" tag,
|
||||||
|
input values must always match the required input length exactly and the decoder does not
|
||||||
|
produce nil values. When the "nil" tag is set, input values of size zero decode as a nil
|
||||||
|
pointer. This is especially useful for recursive types.
|
||||||
|
|
||||||
|
type StructWithNilField struct {
|
||||||
|
Field *[3]byte `rlp:"nil"`
|
||||||
|
}
|
||||||
|
|
||||||
|
In the example above, Field allows two possible input sizes. For input 0xC180 (a list
|
||||||
|
containing an empty string) Field is set to nil after decoding. For input 0xC483000000 (a
|
||||||
|
list containing a 3-byte string), Field is set to a non-nil array pointer.
|
||||||
|
|
||||||
RLP supports two kinds of empty values: empty lists and empty strings. When using the
|
RLP supports two kinds of empty values: empty lists and empty strings. When using the
|
||||||
"nil" tag, the kind of empty value allowed for a type is chosen automatically. A struct
|
"nil" tag, the kind of empty value allowed for a type is chosen automatically. A field
|
||||||
field whose Go type is a pointer to an unsigned integer, string, boolean or byte
|
whose Go type is a pointer to an unsigned integer, string, boolean or byte array/slice
|
||||||
array/slice expects an empty RLP string. Any other pointer field type encodes/decodes as
|
expects an empty RLP string. Any other pointer field type encodes/decodes as an empty RLP
|
||||||
an empty RLP list.
|
list.
|
||||||
|
|
||||||
The choice of null value can be made explicit with the "nilList" and "nilString" struct
|
The choice of null value can be made explicit with the "nilList" and "nilString" struct
|
||||||
tags. Using these tags encodes/decodes a Go nil pointer value as the kind of empty
|
tags. Using these tags encodes/decodes a Go nil pointer value as the empty RLP value kind
|
||||||
RLP value defined by the tag.
|
defined by the tag.
|
||||||
*/
|
*/
|
||||||
package rlp
|
package rlp
|
||||||
|
@ -546,7 +546,12 @@ func makeStructWriter(typ reflect.Type) (writer, error) {
|
|||||||
return nil, structFieldError{typ, f.index, f.info.writerErr}
|
return nil, structFieldError{typ, f.index, f.info.writerErr}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer := func(val reflect.Value, w *encbuf) error {
|
|
||||||
|
var writer writer
|
||||||
|
firstOptionalField := firstOptionalField(fields)
|
||||||
|
if firstOptionalField == len(fields) {
|
||||||
|
// This is the writer function for structs without any optional fields.
|
||||||
|
writer = func(val reflect.Value, w *encbuf) error {
|
||||||
lh := w.list()
|
lh := w.list()
|
||||||
for _, f := range fields {
|
for _, f := range fields {
|
||||||
if err := f.info.writer(val.Field(f.index), w); err != nil {
|
if err := f.info.writer(val.Field(f.index), w); err != nil {
|
||||||
@ -556,6 +561,26 @@ func makeStructWriter(typ reflect.Type) (writer, error) {
|
|||||||
w.listEnd(lh)
|
w.listEnd(lh)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If there are any "optional" fields, the writer needs to perform additional
|
||||||
|
// checks to determine the output list length.
|
||||||
|
writer = func(val reflect.Value, w *encbuf) error {
|
||||||
|
lastField := len(fields) - 1
|
||||||
|
for ; lastField >= firstOptionalField; lastField-- {
|
||||||
|
if !val.Field(fields[lastField].index).IsZero() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lh := w.list()
|
||||||
|
for i := 0; i <= lastField; i++ {
|
||||||
|
if err := fields[i].info.writer(val.Field(fields[i].index), w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.listEnd(lh)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return writer, nil
|
return writer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,12 +257,30 @@ var encTests = []encTest{
|
|||||||
{val: simplestruct{A: 3, B: "foo"}, output: "C50383666F6F"},
|
{val: simplestruct{A: 3, B: "foo"}, output: "C50383666F6F"},
|
||||||
{val: &recstruct{5, nil}, output: "C205C0"},
|
{val: &recstruct{5, nil}, output: "C205C0"},
|
||||||
{val: &recstruct{5, &recstruct{4, &recstruct{3, nil}}}, output: "C605C404C203C0"},
|
{val: &recstruct{5, &recstruct{4, &recstruct{3, nil}}}, output: "C605C404C203C0"},
|
||||||
|
{val: &intField{X: 3}, error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)"},
|
||||||
|
|
||||||
|
// struct tag "-"
|
||||||
|
{val: &ignoredField{A: 1, B: 2, C: 3}, output: "C20103"},
|
||||||
|
|
||||||
|
// struct tag "tail"
|
||||||
{val: &tailRaw{A: 1, Tail: []RawValue{unhex("02"), unhex("03")}}, output: "C3010203"},
|
{val: &tailRaw{A: 1, Tail: []RawValue{unhex("02"), unhex("03")}}, output: "C3010203"},
|
||||||
{val: &tailRaw{A: 1, Tail: []RawValue{unhex("02")}}, output: "C20102"},
|
{val: &tailRaw{A: 1, Tail: []RawValue{unhex("02")}}, output: "C20102"},
|
||||||
{val: &tailRaw{A: 1, Tail: []RawValue{}}, output: "C101"},
|
{val: &tailRaw{A: 1, Tail: []RawValue{}}, output: "C101"},
|
||||||
{val: &tailRaw{A: 1, Tail: nil}, output: "C101"},
|
{val: &tailRaw{A: 1, Tail: nil}, output: "C101"},
|
||||||
{val: &hasIgnoredField{A: 1, B: 2, C: 3}, output: "C20103"},
|
|
||||||
{val: &intField{X: 3}, error: "rlp: type int is not RLP-serializable (struct field rlp.intField.X)"},
|
// struct tag "optional"
|
||||||
|
{val: &optionalFields{}, output: "C180"},
|
||||||
|
{val: &optionalFields{A: 1}, output: "C101"},
|
||||||
|
{val: &optionalFields{A: 1, B: 2}, output: "C20102"},
|
||||||
|
{val: &optionalFields{A: 1, B: 2, C: 3}, output: "C3010203"},
|
||||||
|
{val: &optionalFields{A: 1, B: 0, C: 3}, output: "C3018003"},
|
||||||
|
{val: &optionalAndTailField{A: 1}, output: "C101"},
|
||||||
|
{val: &optionalAndTailField{A: 1, B: 2}, output: "C20102"},
|
||||||
|
{val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"},
|
||||||
|
{val: &optionalAndTailField{A: 1, Tail: []uint{5, 6}}, output: "C401800506"},
|
||||||
|
{val: &optionalBigIntField{A: 1}, output: "C101"},
|
||||||
|
{val: &optionalPtrField{A: 1}, output: "C101"},
|
||||||
|
{val: &optionalPtrFieldNil{A: 1}, output: "C101"},
|
||||||
|
|
||||||
// nil
|
// nil
|
||||||
{val: (*uint)(nil), output: "80"},
|
{val: (*uint)(nil), output: "80"},
|
||||||
|
@ -38,15 +38,16 @@ type typeinfo struct {
|
|||||||
// tags represents struct tags.
|
// tags represents struct tags.
|
||||||
type tags struct {
|
type tags struct {
|
||||||
// rlp:"nil" controls whether empty input results in a nil pointer.
|
// rlp:"nil" controls whether empty input results in a nil pointer.
|
||||||
|
// nilKind is the kind of empty value allowed for the field.
|
||||||
|
nilKind Kind
|
||||||
nilOK bool
|
nilOK bool
|
||||||
|
|
||||||
// This controls whether nil pointers are encoded/decoded as empty strings
|
// rlp:"optional" allows for a field to be missing in the input list.
|
||||||
// or empty lists.
|
// If this is set, all subsequent fields must also be optional.
|
||||||
nilKind Kind
|
optional bool
|
||||||
|
|
||||||
// rlp:"tail" controls whether this field swallows additional list
|
// rlp:"tail" controls whether this field swallows additional list elements. It can
|
||||||
// elements. It can only be set for the last field, which must be
|
// only be set for the last field, which must be of slice type.
|
||||||
// of slice type.
|
|
||||||
tail bool
|
tail bool
|
||||||
|
|
||||||
// rlp:"-" ignores fields.
|
// rlp:"-" ignores fields.
|
||||||
@ -106,26 +107,49 @@ func cachedTypeInfo1(typ reflect.Type, tags tags) *typeinfo {
|
|||||||
type field struct {
|
type field struct {
|
||||||
index int
|
index int
|
||||||
info *typeinfo
|
info *typeinfo
|
||||||
|
optional bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// structFields resolves the typeinfo of all public fields in a struct type.
|
||||||
func structFields(typ reflect.Type) (fields []field, err error) {
|
func structFields(typ reflect.Type) (fields []field, err error) {
|
||||||
lastPublic := lastPublicField(typ)
|
var (
|
||||||
|
lastPublic = lastPublicField(typ)
|
||||||
|
anyOptional = false
|
||||||
|
)
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
if f := typ.Field(i); f.PkgPath == "" { // exported
|
if f := typ.Field(i); f.PkgPath == "" { // exported
|
||||||
tags, err := parseStructTag(typ, i, lastPublic)
|
tags, err := parseStructTag(typ, i, lastPublic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip rlp:"-" fields.
|
||||||
if tags.ignored {
|
if tags.ignored {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// If any field has the "optional" tag, subsequent fields must also have it.
|
||||||
|
if tags.optional || tags.tail {
|
||||||
|
anyOptional = true
|
||||||
|
} else if anyOptional {
|
||||||
|
return nil, fmt.Errorf(`rlp: struct field %v.%s needs "optional" tag`, typ, f.Name)
|
||||||
|
}
|
||||||
info := cachedTypeInfo1(f.Type, tags)
|
info := cachedTypeInfo1(f.Type, tags)
|
||||||
fields = append(fields, field{i, info})
|
fields = append(fields, field{i, info, tags.optional})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fields, nil
|
return fields, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// anyOptionalFields returns the index of the first field with "optional" tag.
|
||||||
|
func firstOptionalField(fields []field) int {
|
||||||
|
for i, f := range fields {
|
||||||
|
if f.optional {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(fields)
|
||||||
|
}
|
||||||
|
|
||||||
type structFieldError struct {
|
type structFieldError struct {
|
||||||
typ reflect.Type
|
typ reflect.Type
|
||||||
field int
|
field int
|
||||||
@ -166,11 +190,19 @@ func parseStructTag(typ reflect.Type, fi, lastPublic int) (tags, error) {
|
|||||||
case "nilList":
|
case "nilList":
|
||||||
ts.nilKind = List
|
ts.nilKind = List
|
||||||
}
|
}
|
||||||
|
case "optional":
|
||||||
|
ts.optional = true
|
||||||
|
if ts.tail {
|
||||||
|
return ts, structTagError{typ, f.Name, t, `also has "tail" tag`}
|
||||||
|
}
|
||||||
case "tail":
|
case "tail":
|
||||||
ts.tail = true
|
ts.tail = true
|
||||||
if fi != lastPublic {
|
if fi != lastPublic {
|
||||||
return ts, structTagError{typ, f.Name, t, "must be on last field"}
|
return ts, structTagError{typ, f.Name, t, "must be on last field"}
|
||||||
}
|
}
|
||||||
|
if ts.optional {
|
||||||
|
return ts, structTagError{typ, f.Name, t, `also has "optional" tag`}
|
||||||
|
}
|
||||||
if f.Type.Kind() != reflect.Slice {
|
if f.Type.Kind() != reflect.Slice {
|
||||||
return ts, structTagError{typ, f.Name, t, "field type is not slice"}
|
return ts, structTagError{typ, f.Name, t, "field type is not slice"}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user