github.com/kamalshkeir/kencoding@v0.0.2-0.20230409043843-44b609a0475a/thrift/struct.go (about)

     1  package thrift
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  )
     9  
    10  type flags int16
    11  
    12  const (
    13  	enum     flags = 1 << 0
    14  	union    flags = 1 << 1
    15  	required flags = 1 << 2
    16  	optional flags = 1 << 3
    17  	strict   flags = 1 << 4
    18  
    19  	featuresBitOffset  = 8
    20  	useDeltaEncoding   = flags(UseDeltaEncoding) << featuresBitOffset
    21  	coalesceBoolFields = flags(CoalesceBoolFields) << featuresBitOffset
    22  
    23  	structFlags   flags = enum | union | required | optional
    24  	encodeFlags   flags = strict | protocolFlags
    25  	decodeFlags   flags = strict | protocolFlags
    26  	protocolFlags flags = useDeltaEncoding | coalesceBoolFields
    27  )
    28  
    29  func (f flags) have(x flags) bool {
    30  	return (f & x) == x
    31  }
    32  
    33  func (f flags) only(x flags) flags {
    34  	return f & x
    35  }
    36  
    37  func (f flags) with(x flags) flags {
    38  	return f | x
    39  }
    40  
    41  func (f flags) without(x flags) flags {
    42  	return f & ^x
    43  }
    44  
    45  type structField struct {
    46  	typ   reflect.Type
    47  	index []int
    48  	id    int16
    49  	flags flags
    50  }
    51  
    52  func forEachStructField(t reflect.Type, index []int, do func(structField)) {
    53  	for i, n := 0, t.NumField(); i < n; i++ {
    54  		f := t.Field(i)
    55  
    56  		if f.PkgPath != "" && !f.Anonymous { // unexported
    57  			continue
    58  		}
    59  
    60  		fieldIndex := append(index, i)
    61  		fieldIndex = fieldIndex[:len(fieldIndex):len(fieldIndex)]
    62  
    63  		if f.Anonymous {
    64  			fieldType := f.Type
    65  
    66  			for fieldType.Kind() == reflect.Ptr {
    67  				fieldType = fieldType.Elem()
    68  			}
    69  
    70  			if fieldType.Kind() == reflect.Struct {
    71  				forEachStructField(fieldType, fieldIndex, do)
    72  				continue
    73  			}
    74  		}
    75  
    76  		tag := f.Tag.Get("thrift")
    77  		if tag == "" {
    78  			continue
    79  		}
    80  		tags := strings.Split(tag, ",")
    81  		flags := flags(0)
    82  
    83  		for _, opt := range tags[1:] {
    84  			switch opt {
    85  			case "enum":
    86  				flags = flags.with(enum)
    87  			case "union":
    88  				flags = flags.with(union)
    89  			case "required":
    90  				flags = flags.with(required)
    91  			case "optional":
    92  				flags = flags.with(optional)
    93  			default:
    94  				panic(fmt.Errorf("thrift struct field contains an unknown tag option %q in `thrift:\"%s\"`", opt, tag))
    95  			}
    96  		}
    97  
    98  		if flags.have(optional | required) {
    99  			panic(fmt.Errorf("thrift struct field cannot be both optional and required in `thrift:\"%s\"`", tag))
   100  		}
   101  
   102  		if flags.have(union) {
   103  			if f.Type.Kind() != reflect.Interface {
   104  				panic(fmt.Errorf("thrift union tag found on a field which is not an interface type `thrift:\"%s\"`", tag))
   105  			}
   106  
   107  			if tags[0] != "" {
   108  				panic(fmt.Errorf("invalid thrift field id on union field `thrift:\"%s\"`", tag))
   109  			}
   110  
   111  			do(structField{
   112  				typ:   f.Type,
   113  				index: fieldIndex,
   114  				flags: flags,
   115  			})
   116  		} else {
   117  			if flags.have(enum) {
   118  				switch f.Type.Kind() {
   119  				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   120  				case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   121  				default:
   122  					panic(fmt.Errorf("thrift enum tag found on a field which is not an integer type `thrift:\"%s\"`", tag))
   123  				}
   124  			}
   125  
   126  			if id, err := strconv.ParseInt(tags[0], 10, 16); err != nil {
   127  				panic(fmt.Errorf("invalid thrift field id found in struct tag `thrift:\"%s\"`: %w", tag, err))
   128  			} else if id <= 0 {
   129  				panic(fmt.Errorf("invalid thrift field id found in struct tag `thrift:\"%s\"`: %d <= 0", tag, id))
   130  			} else {
   131  				do(structField{
   132  					typ:   f.Type,
   133  					index: fieldIndex,
   134  					id:    int16(id),
   135  					flags: flags,
   136  				})
   137  			}
   138  		}
   139  	}
   140  }