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,15 +546,40 @@ 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 { | 
 | ||||||
| 		lh := w.list() | 	var writer writer | ||||||
| 		for _, f := range fields { | 	firstOptionalField := firstOptionalField(fields) | ||||||
| 			if err := f.info.writer(val.Field(f.index), w); err != nil { | 	if firstOptionalField == len(fields) { | ||||||
| 				return err | 		// This is the writer function for structs without any optional fields.
 | ||||||
|  | 		writer = func(val reflect.Value, w *encbuf) error { | ||||||
|  | 			lh := w.list() | ||||||
|  | 			for _, f := range fields { | ||||||
|  | 				if err := f.info.writer(val.Field(f.index), w); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  | 			w.listEnd(lh) | ||||||
|  | 			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 | ||||||
| 		} | 		} | ||||||
| 		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.
 | ||||||
| 	nilOK bool | 	// nilKind is the kind of empty value allowed for the field.
 | ||||||
| 
 |  | ||||||
| 	// This controls whether nil pointers are encoded/decoded as empty strings
 |  | ||||||
| 	// or empty lists.
 |  | ||||||
| 	nilKind Kind | 	nilKind Kind | ||||||
|  | 	nilOK   bool | ||||||
| 
 | 
 | ||||||
| 	// rlp:"tail" controls whether this field swallows additional list
 | 	// rlp:"optional" allows for a field to be missing in the input list.
 | ||||||
| 	// elements. It can only be set for the last field, which must be
 | 	// If this is set, all subsequent fields must also be optional.
 | ||||||
| 	// of slice type.
 | 	optional bool | ||||||
|  | 
 | ||||||
|  | 	// rlp:"tail" controls whether this field swallows additional list elements. It can
 | ||||||
|  | 	// only be set for the last field, which must be of slice type.
 | ||||||
| 	tail bool | 	tail bool | ||||||
| 
 | 
 | ||||||
| 	// rlp:"-" ignores fields.
 | 	// rlp:"-" ignores fields.
 | ||||||
| @ -104,28 +105,51 @@ 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