package thrift import ( "fmt" "reflect" "strconv" "strings" ) type flags int16 const ( enum flags = 1 << 0 union flags = 1 << 1 required flags = 1 << 2 optional flags = 1 << 3 strict flags = 1 << 4 featuresBitOffset = 8 useDeltaEncoding = flags(UseDeltaEncoding) << featuresBitOffset coalesceBoolFields = flags(CoalesceBoolFields) << featuresBitOffset structFlags flags = enum | union | required | optional encodeFlags flags = strict | protocolFlags decodeFlags flags = strict | protocolFlags protocolFlags flags = useDeltaEncoding | coalesceBoolFields ) func (f flags) have(x flags) bool { return (f & x) == x } func (f flags) only(x flags) flags { return f & x } func (f flags) with(x flags) flags { return f | x } func (f flags) without(x flags) flags { return f & ^x } type structField struct { typ reflect.Type index []int id int16 flags flags } func forEachStructField(t reflect.Type, index []int, do func(structField)) { for i, n := 0, t.NumField(); i < n; i++ { f := t.Field(i) if f.PkgPath != "" && !f.Anonymous { // unexported continue } fieldIndex := append(index, i) fieldIndex = fieldIndex[:len(fieldIndex):len(fieldIndex)] if f.Anonymous { fieldType := f.Type for fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } if fieldType.Kind() == reflect.Struct { forEachStructField(fieldType, fieldIndex, do) continue } } tag := f.Tag.Get("thrift") if tag == "" { continue } tags := strings.Split(tag, ",") flags := flags(0) for _, opt := range tags[1:] { switch opt { case "enum": flags = flags.with(enum) case "union": flags = flags.with(union) case "required": flags = flags.with(required) case "optional": flags = flags.with(optional) default: panic(fmt.Errorf("thrift struct field contains an unknown tag option %q in `thrift:\"%s\"`", opt, tag)) } } if flags.have(optional | required) { panic(fmt.Errorf("thrift struct field cannot be both optional and required in `thrift:\"%s\"`", tag)) } if flags.have(union) { if f.Type.Kind() != reflect.Interface { panic(fmt.Errorf("thrift union tag found on a field which is not an interface type `thrift:\"%s\"`", tag)) } if tags[0] != "" { panic(fmt.Errorf("invalid thrift field id on union field `thrift:\"%s\"`", tag)) } do(structField{ typ: f.Type, index: fieldIndex, flags: flags, }) } else { if flags.have(enum) { switch f.Type.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: default: panic(fmt.Errorf("thrift enum tag found on a field which is not an integer type `thrift:\"%s\"`", tag)) } } if id, err := strconv.ParseInt(tags[0], 10, 16); err != nil { panic(fmt.Errorf("invalid thrift field id found in struct tag `thrift:\"%s\"`: %w", tag, err)) } else if id <= 0 { panic(fmt.Errorf("invalid thrift field id found in struct tag `thrift:\"%s\"`: %d <= 0", tag, id)) } else { do(structField{ typ: f.Type, index: fieldIndex, id: int16(id), flags: flags, }) } } } }