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 }