github.com/bakjos/protoreflect@v1.9.2/internal/unrecognized.go (about)

     1  package internal
     2  
     3  import (
     4  	"reflect"
     5  
     6  	"github.com/golang/protobuf/proto"
     7  )
     8  
     9  var typeOfBytes = reflect.TypeOf([]byte(nil))
    10  
    11  // GetUnrecognized fetches the bytes of unrecognized fields for the given message.
    12  func GetUnrecognized(msg proto.Message) []byte {
    13  	val := reflect.Indirect(reflect.ValueOf(msg))
    14  	u := val.FieldByName("XXX_unrecognized")
    15  	if u.IsValid() && u.Type() == typeOfBytes {
    16  		return u.Interface().([]byte)
    17  	}
    18  
    19  	// Fallback to reflection for API v2 messages
    20  	get, _, _, ok := unrecognizedGetSetMethods(val)
    21  	if !ok {
    22  		return nil
    23  	}
    24  
    25  	return get.Call([]reflect.Value(nil))[0].Convert(typeOfBytes).Interface().([]byte)
    26  }
    27  
    28  // SetUnrecognized adds the given bytes to the unrecognized fields for the given message.
    29  func SetUnrecognized(msg proto.Message, data []byte) {
    30  	val := reflect.Indirect(reflect.ValueOf(msg))
    31  	u := val.FieldByName("XXX_unrecognized")
    32  	if u.IsValid() && u.Type() == typeOfBytes {
    33  		// Just store the bytes in the unrecognized field
    34  		ub := u.Interface().([]byte)
    35  		ub = append(ub, data...)
    36  		u.Set(reflect.ValueOf(ub))
    37  		return
    38  	}
    39  
    40  	// Fallback to reflection for API v2 messages
    41  	get, set, argType, ok := unrecognizedGetSetMethods(val)
    42  	if !ok {
    43  		return
    44  	}
    45  
    46  	existing := get.Call([]reflect.Value(nil))[0].Convert(typeOfBytes).Interface().([]byte)
    47  	if len(existing) > 0 {
    48  		data = append(existing, data...)
    49  	}
    50  	set.Call([]reflect.Value{reflect.ValueOf(data).Convert(argType)})
    51  }
    52  
    53  func unrecognizedGetSetMethods(val reflect.Value) (get reflect.Value, set reflect.Value, argType reflect.Type, ok bool) {
    54  	// val could be an APIv2 message. We use reflection to interact with
    55  	// this message so that we don't have a hard dependency on the new
    56  	// version of the protobuf package.
    57  	refMethod := val.MethodByName("ProtoReflect")
    58  	if !refMethod.IsValid() {
    59  		if val.CanAddr() {
    60  			refMethod = val.Addr().MethodByName("ProtoReflect")
    61  		}
    62  		if !refMethod.IsValid() {
    63  			return
    64  		}
    65  	}
    66  	refType := refMethod.Type()
    67  	if refType.NumIn() != 0 || refType.NumOut() != 1 {
    68  		return
    69  	}
    70  	ref := refMethod.Call([]reflect.Value(nil))
    71  	getMethod, setMethod := ref[0].MethodByName("GetUnknown"), ref[0].MethodByName("SetUnknown")
    72  	if !getMethod.IsValid() || !setMethod.IsValid() {
    73  		return
    74  	}
    75  	getType := getMethod.Type()
    76  	setType := setMethod.Type()
    77  	if getType.NumIn() != 0 || getType.NumOut() != 1 || setType.NumIn() != 1 || setType.NumOut() != 0 {
    78  		return
    79  	}
    80  	arg := setType.In(0)
    81  	if !arg.ConvertibleTo(typeOfBytes) || getType.Out(0) != arg {
    82  		return
    83  	}
    84  
    85  	return getMethod, setMethod, arg, true
    86  }