github.com/MetalBlockchain/metalgo@v1.11.9/codec/reflectcodec/struct_fielder.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package reflectcodec
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"sync"
    10  
    11  	"github.com/MetalBlockchain/metalgo/codec"
    12  )
    13  
    14  // TagValue is the value the tag must have to be serialized.
    15  const TagValue = "true"
    16  
    17  var _ StructFielder = (*structFielder)(nil)
    18  
    19  // StructFielder handles discovery of serializable fields in a struct.
    20  type StructFielder interface {
    21  	// Returns the fields that have been marked as serializable in [t], which is
    22  	// a struct type.
    23  	// Returns an error if a field has tag "[tagName]: [TagValue]" but the field
    24  	// is un-exported.
    25  	// GetSerializedField(Foo) --> [1,5,8] means Foo.Field(1), Foo.Field(5),
    26  	// Foo.Field(8) are to be serialized/deserialized.
    27  	GetSerializedFields(t reflect.Type) ([]int, error)
    28  }
    29  
    30  func NewStructFielder(tagNames []string) StructFielder {
    31  	return &structFielder{
    32  		tags:                   tagNames,
    33  		serializedFieldIndices: make(map[reflect.Type][]int),
    34  	}
    35  }
    36  
    37  type structFielder struct {
    38  	lock sync.RWMutex
    39  
    40  	// multiple tags per field can be specified. A field is serialized/deserialized
    41  	// if it has at least one of the specified tags.
    42  	tags []string
    43  
    44  	// Key: a struct type
    45  	// Value: Slice where each element is index in the struct type of a field
    46  	// that is serialized/deserialized e.g. Foo --> [1,5,8] means Foo.Field(1),
    47  	// etc. are to be serialized/deserialized. We assume this cache is pretty
    48  	// small (a few hundred keys at most) and doesn't take up much memory.
    49  	serializedFieldIndices map[reflect.Type][]int
    50  }
    51  
    52  func (s *structFielder) GetSerializedFields(t reflect.Type) ([]int, error) {
    53  	if serializedFields, ok := s.getCachedSerializedFields(t); ok { // use pre-computed result
    54  		return serializedFields, nil
    55  	}
    56  
    57  	s.lock.Lock()
    58  	defer s.lock.Unlock()
    59  
    60  	numFields := t.NumField()
    61  	serializedFields := make([]int, 0, numFields)
    62  	for i := 0; i < numFields; i++ { // Go through all fields of this struct
    63  		field := t.Field(i)
    64  
    65  		// Multiple tags per fields can be specified.
    66  		// Serialize/Deserialize field if it has
    67  		// any tag with the right value
    68  		var captureField bool
    69  		for _, tag := range s.tags {
    70  			if field.Tag.Get(tag) == TagValue {
    71  				captureField = true
    72  				break
    73  			}
    74  		}
    75  		if !captureField {
    76  			continue
    77  		}
    78  		if !field.IsExported() { // Can only marshal exported fields
    79  			return nil, fmt.Errorf("can not marshal %w: %s",
    80  				codec.ErrUnexportedField,
    81  				field.Name,
    82  			)
    83  		}
    84  		serializedFields = append(serializedFields, i)
    85  	}
    86  	s.serializedFieldIndices[t] = serializedFields // cache result
    87  	return serializedFields, nil
    88  }
    89  
    90  func (s *structFielder) getCachedSerializedFields(t reflect.Type) ([]int, bool) {
    91  	s.lock.RLock()
    92  	defer s.lock.RUnlock()
    93  
    94  	cachedFields, ok := s.serializedFieldIndices[t]
    95  	return cachedFields, ok
    96  }