go.ligato.io/vpp-agent/v3@v3.5.0/pkg/util/proto.go (about)

     1  //  Copyright (c) 2019 Cisco and/or its affiliates.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at:
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package util
    16  
    17  import (
    18  	"reflect"
    19  
    20  	"google.golang.org/protobuf/proto"
    21  	"google.golang.org/protobuf/reflect/protoreflect"
    22  )
    23  
    24  func ExtractProtos(from ...interface{}) (protos []proto.Message) {
    25  	for _, v := range from {
    26  		if reflect.ValueOf(v).IsNil() {
    27  			continue
    28  		}
    29  		val := reflect.ValueOf(v).Elem()
    30  		typ := val.Type()
    31  		if typ.Kind() != reflect.Struct {
    32  			return
    33  		}
    34  		for i := 0; i < typ.NumField(); i++ {
    35  			field := val.Field(i)
    36  			if field.Kind() == reflect.Slice {
    37  				for idx := 0; idx < field.Len(); idx++ {
    38  					elem := field.Index(idx)
    39  					if msg, ok := elem.Interface().(proto.Message); ok {
    40  						protos = append(protos, msg)
    41  					}
    42  				}
    43  			} else if field.Kind() == reflect.Ptr && !field.IsNil() {
    44  				if msg, ok := field.Interface().(proto.Message); ok && !field.IsNil() {
    45  					protos = append(protos, msg)
    46  				}
    47  			}
    48  		}
    49  	}
    50  	return
    51  }
    52  
    53  func PlaceProtos(protos map[string]proto.Message, dsts ...interface{}) {
    54  	for _, prot := range protos {
    55  		protTyp := reflect.TypeOf(prot)
    56  		for _, dst := range dsts {
    57  			dstVal := reflect.ValueOf(dst).Elem()
    58  			dstTyp := dstVal.Type()
    59  			if dstTyp.Kind() != reflect.Struct {
    60  				return
    61  			}
    62  			for i := 0; i < dstTyp.NumField(); i++ {
    63  				field := dstVal.Field(i)
    64  				if field.Kind() == reflect.Slice {
    65  					if protTyp.AssignableTo(field.Type().Elem()) {
    66  						field.Set(reflect.Append(field, reflect.ValueOf(prot)))
    67  					}
    68  				} else {
    69  					if field.Type() == protTyp {
    70  						field.Set(reflect.ValueOf(prot))
    71  					}
    72  				}
    73  			}
    74  		}
    75  	}
    76  }
    77  
    78  // PlaceProtosIntoProtos fills dsts proto messages (direct or transitive) fields with protos values.
    79  // The matching is done by message descriptor's full name. The <clearIgnoreLayerCount> variable controls
    80  // how many top model structure hierarchy layers can have empty values for messages (see
    81  // util.placeProtosInProto(...) for details)
    82  func PlaceProtosIntoProtos(protos []proto.Message, clearIgnoreLayerCount int, dsts ...proto.Message) {
    83  	// create help structure for insertion proto messages
    84  	// (map values are protoreflect.Message(s) that contains proto message and its type. These messages will be
    85  	// later wrapped into protoreflect.Value(s) and filled into destination proto message using proto reflection.
    86  	// We could have used protoreflect.Value(s) as map values in this help structure, but protoreflect.Value type
    87  	// is cheap (really thin wrapper) and it is unknown whether using the same value on multiple fields could
    88  	// cause problems, so we will generate it for each field.
    89  	messageMap := make(map[string][]protoreflect.Message)
    90  	for _, protoMsg := range protos {
    91  		protoName := string(protoMsg.ProtoReflect().Descriptor().FullName())
    92  		messageMap[protoName] = append(messageMap[protoName], protoMsg.ProtoReflect())
    93  	}
    94  
    95  	// insert proto message to all destination containers (also proto messages)
    96  	for _, dst := range dsts {
    97  		placeProtosInProto(dst, messageMap, clearIgnoreLayerCount)
    98  	}
    99  }
   100  
   101  // placeProtosInProto fills dst proto message (direct or transitive) fields with protos values from messageMap
   102  // (convenient map[proto descriptor full name]= protoreflect message containing proto message and proto type).
   103  // The matching is done by message descriptor's full name. The function is recursive and one run is handling
   104  // only one level of proto message structure tree (only handling Message references and ignoring
   105  // scalar/enum/... values). The <clearIgnoreLayerCount> controls how many top layer can have empty values
   106  // for their message fields (as the algorithm backtracks the descriptor model tree, it unfortunately initialize
   107  // empty value for visited fields). The layer below <clearIgnoreLayerCount> top layer will be cleared
   108  // from the fake empty value. Currently unsupported are maps as fields.
   109  func placeProtosInProto(dst proto.Message, messageMap map[string][]protoreflect.Message, clearIgnoreLayerCount int) bool {
   110  	changed := false
   111  	fields := dst.ProtoReflect().Descriptor().Fields()
   112  	for i := 0; i < fields.Len(); i++ {
   113  		field := fields.Get(i)
   114  		fieldMessageDesc := field.Message()
   115  		if fieldMessageDesc != nil { // only interested in MessageKind or GroupKind fields
   116  			if messageForField, typeMatch := messageMap[string(fieldMessageDesc.FullName())]; typeMatch {
   117  				// fill value(s)
   118  				if field.IsList() {
   119  					list := dst.ProtoReflect().Mutable(field).List()
   120  					for _, message := range messageForField {
   121  						list.Append(protoreflect.ValueOf(message))
   122  						changed = true
   123  					}
   124  				} else if field.IsMap() { // unsupported
   125  				} else {
   126  					dst.ProtoReflect().Set(field, protoreflect.ValueOf(messageForField[0]))
   127  					changed = true
   128  				}
   129  			} else {
   130  				// no type match -> check deeper structure layers
   131  				// Note: dst.ProtoReflect().Mutable(field) creates empty value that creates problems later
   132  				// (i.e. by outputing to json/yaml) => need to check whether actual value has been assigned
   133  				// and if not then clear the field. Additionally there is clearIgnoreLayerCount variable
   134  				// disabling this clearing functionality for upper <clearIgnoreLayerCount> layers
   135  				if field.IsList() {
   136  					list := dst.ProtoReflect().Mutable(field).List()
   137  					changeOnLowerLayer := false
   138  					for j := 0; j < list.Len(); j++ {
   139  						changeOnLowerLayer = changeOnLowerLayer ||
   140  							placeProtosInProto(list.Get(j).Message().Interface(), messageMap, clearIgnoreLayerCount-1)
   141  					}
   142  					if !changeOnLowerLayer && clearIgnoreLayerCount <= 0 {
   143  						dst.ProtoReflect().Clear(field)
   144  					}
   145  					changed = changed || changeOnLowerLayer
   146  				} else if field.IsMap() { // unsupported
   147  				} else {
   148  					changeOnLowerLayer := placeProtosInProto(dst.ProtoReflect().Mutable(field).
   149  						Message().Interface(), messageMap, clearIgnoreLayerCount-1)
   150  					if !changeOnLowerLayer && clearIgnoreLayerCount <= 0 {
   151  						dst.ProtoReflect().Clear(field)
   152  					}
   153  					changed = changed || changeOnLowerLayer
   154  				}
   155  			}
   156  		}
   157  	}
   158  	return changed
   159  }