github.com/cilium/cilium@v1.16.2/pkg/hubble/parser/fieldmask/fieldmask.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Hubble 3 4 package fieldmask 5 6 import ( 7 "fmt" 8 "strings" 9 10 "google.golang.org/protobuf/reflect/protoreflect" 11 "google.golang.org/protobuf/types/known/fieldmaskpb" 12 13 flowpb "github.com/cilium/cilium/api/v1/flow" 14 ) 15 16 type FieldMask map[string]FieldMask 17 18 // New constructs a tree filter based on validated and normalized field 19 // mask fm. Use Active() to check if applying a filter will have any effect. 20 func New(fm *fieldmaskpb.FieldMask) (FieldMask, error) { 21 if fm == nil { 22 return nil, nil 23 } 24 if !fm.IsValid(&flowpb.Flow{}) { 25 return nil, fmt.Errorf("invalid fieldmask") 26 } 27 fm.Normalize() 28 29 f := make(FieldMask) 30 for _, path := range fm.GetPaths() { 31 f.add(path) 32 } 33 return f, nil 34 } 35 36 // add recursively converts path into tree based on its dot separated 37 // components. 38 func (f FieldMask) add(path string) { 39 prefix, suffix, found := strings.Cut(path, ".") 40 if !found { 41 // ["src.id", "src"] doesn't occur due to fm.Normalize() 42 f[path] = nil 43 return 44 } 45 if m, ok := f[prefix]; !ok || m == nil { 46 f[prefix] = make(FieldMask) 47 } 48 f[prefix].add(suffix) 49 } 50 51 // Copy sets fields in dst to values from src based on filter. 52 // It has no effect when called on an empty filter (dst remains unchanged). 53 func (f FieldMask) Copy(dst, src protoreflect.Message) { 54 fds := dst.Descriptor().Fields() 55 for name, next := range f { 56 fd := fds.ByName(protoreflect.Name(name)) 57 if len(next) == 0 { 58 if src.Has(fd) { 59 dst.Set(fd, src.Get(fd)) 60 } else { 61 dst.Clear(fd) 62 } 63 } else { 64 if sub := dst.Get(fd); sub.Message().IsValid() { 65 next.Copy(sub.Message(), src.Get(fd).Message()) 66 } else { 67 next.Copy(dst.Mutable(fd).Message(), src.Get(fd).Message()) 68 } 69 } 70 } 71 } 72 73 // Alloc creates all nested protoreflect.Message fields to avoid allocation later. 74 func (f FieldMask) Alloc(src protoreflect.Message) { 75 fds := src.Descriptor().Fields() 76 for i := 0; i < fds.Len(); i++ { 77 fd := fds.Get(i) 78 if next, ok := f[string(fd.Name())]; ok { 79 if len(next) > 0 { 80 // Call to Mutable allocates a composite value - protoreflect.Message in this case. 81 // See: https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect#Message 82 next.Alloc(src.Mutable(fd).Message()) 83 } 84 } 85 } 86 } 87 88 // Active checks if applying a filter will have any effect. 89 func (f FieldMask) Active() bool { 90 return len(f) > 0 91 }