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  }