| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- // Copyright 2022 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- // Package rlpstruct implements struct processing for RLP encoding/decoding.
- //
- // In particular, this package handles all rules around field filtering,
- // struct tags and nil value determination.
- package rlpstruct
- import (
- "fmt"
- "reflect"
- "strings"
- )
- // Field represents a struct field.
- type Field struct {
- Name string
- Index int
- Exported bool
- Type Type
- Tag string
- }
- // Type represents the attributes of a Go type.
- type Type struct {
- Name string
- Kind reflect.Kind
- IsEncoder bool // whether type implements rlp.Encoder
- IsDecoder bool // whether type implements rlp.Decoder
- Elem *Type // non-nil for Kind values of Ptr, Slice, Array
- }
- // defaultNilValue determines whether a nil pointer to t encodes/decodes
- // as an empty string or empty list.
- func (t Type) DefaultNilValue() NilKind {
- k := t.Kind
- if isUint(k) || k == reflect.String || k == reflect.Bool || isByteArray(t) {
- return NilKindString
- }
- return NilKindList
- }
- // NilKind is the RLP value encoded in place of nil pointers.
- type NilKind uint8
- const (
- NilKindString NilKind = 0x80
- NilKindList NilKind = 0xC0
- )
- // Tags represents struct tags.
- type Tags struct {
- // rlp:"nil" controls whether empty input results in a nil pointer.
- // nilKind is the kind of empty value allowed for the field.
- NilKind NilKind
- NilOK bool
- // rlp:"optional" allows for a field to be missing in the input list.
- // If this is set, all subsequent fields must also be optional.
- 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
- // rlp:"-" ignores fields.
- Ignored bool
- }
- // TagError is raised for invalid struct tags.
- type TagError struct {
- StructType string
- // These are set by this package.
- Field string
- Tag string
- Err string
- }
- func (e TagError) Error() string {
- field := "field " + e.Field
- if e.StructType != "" {
- field = e.StructType + "." + e.Field
- }
- return fmt.Sprintf("rlp: invalid struct tag %q for %s (%s)", e.Tag, field, e.Err)
- }
- // ProcessFields filters the given struct fields, returning only fields
- // that should be considered for encoding/decoding.
- func ProcessFields(allFields []Field) ([]Field, []Tags, error) {
- lastPublic := lastPublicField(allFields)
- // Gather all exported fields and their tags.
- var fields []Field
- var tags []Tags
- for _, field := range allFields {
- if !field.Exported {
- continue
- }
- ts, err := parseTag(field, lastPublic)
- if err != nil {
- return nil, nil, err
- }
- if ts.Ignored {
- continue
- }
- fields = append(fields, field)
- tags = append(tags, ts)
- }
- // Verify optional field consistency. If any optional field exists,
- // all fields after it must also be optional. Note: optional + tail
- // is supported.
- var anyOptional bool
- var firstOptionalName string
- for i, ts := range tags {
- name := fields[i].Name
- if ts.Optional || ts.Tail {
- if !anyOptional {
- firstOptionalName = name
- }
- anyOptional = true
- } else {
- if anyOptional {
- msg := fmt.Sprintf("must be optional because preceding field %q is optional", firstOptionalName)
- return nil, nil, TagError{Field: name, Err: msg}
- }
- }
- }
- return fields, tags, nil
- }
- func parseTag(field Field, lastPublic int) (Tags, error) {
- name := field.Name
- tag := reflect.StructTag(field.Tag)
- var ts Tags
- for _, t := range strings.Split(tag.Get("rlp"), ",") {
- switch t = strings.TrimSpace(t); t {
- case "":
- // empty tag is allowed for some reason
- case "-":
- ts.Ignored = true
- case "nil", "nilString", "nilList":
- ts.NilOK = true
- if field.Type.Kind != reflect.Ptr {
- return ts, TagError{Field: name, Tag: t, Err: "field is not a pointer"}
- }
- switch t {
- case "nil":
- ts.NilKind = field.Type.Elem.DefaultNilValue()
- case "nilString":
- ts.NilKind = NilKindString
- case "nilList":
- ts.NilKind = NilKindList
- }
- case "optional":
- ts.Optional = true
- if ts.Tail {
- return ts, TagError{Field: name, Tag: t, Err: `also has "tail" tag`}
- }
- case "tail":
- ts.Tail = true
- if field.Index != lastPublic {
- return ts, TagError{Field: name, Tag: t, Err: "must be on last field"}
- }
- if ts.Optional {
- return ts, TagError{Field: name, Tag: t, Err: `also has "optional" tag`}
- }
- if field.Type.Kind != reflect.Slice {
- return ts, TagError{Field: name, Tag: t, Err: "field type is not slice"}
- }
- default:
- return ts, TagError{Field: name, Tag: t, Err: "unknown tag"}
- }
- }
- return ts, nil
- }
- func lastPublicField(fields []Field) int {
- last := 0
- for _, f := range fields {
- if f.Exported {
- last = f.Index
- }
- }
- return last
- }
- func isUint(k reflect.Kind) bool {
- return k >= reflect.Uint && k <= reflect.Uintptr
- }
- func isByte(typ Type) bool {
- return typ.Kind == reflect.Uint8 && !typ.IsEncoder
- }
- func isByteArray(typ Type) bool {
- return (typ.Kind == reflect.Slice || typ.Kind == reflect.Array) && isByte(*typ.Elem)
- }
|