...

Source file src/go.einride.tech/aip/fieldbehavior/immutable.go

Documentation: go.einride.tech/aip/fieldbehavior

     1  package fieldbehavior
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"google.golang.org/genproto/googleapis/api/annotations"
     7  	"google.golang.org/protobuf/proto"
     8  	"google.golang.org/protobuf/reflect/protoreflect"
     9  	"google.golang.org/protobuf/types/known/fieldmaskpb"
    10  )
    11  
    12  // ValidateImmutableFieldsWithMask returns a validation error if the message
    13  // or field mask contains a field that is immutable and a change to an immutable field is requested.
    14  // This can be used when validating update requests and want to return
    15  // INVALID_ARGUMENT to the user.
    16  // If you want to ignore immutable fields rather than error then use ClearFields().
    17  //
    18  // See: https://aip.dev/203
    19  func ValidateImmutableFieldsWithMask(m proto.Message, mask *fieldmaskpb.FieldMask) error {
    20  	return validateImmutableFields(m.ProtoReflect(), mask, "")
    21  }
    22  
    23  func validateImmutableFields(m protoreflect.Message, mask *fieldmaskpb.FieldMask, path string) error {
    24  	for i := 0; i < m.Descriptor().Fields().Len(); i++ {
    25  		field := m.Descriptor().Fields().Get(i)
    26  		currPath := path
    27  		if len(currPath) > 0 {
    28  			currPath += "."
    29  		}
    30  
    31  		currPath += string(field.Name())
    32  		if isImmutable(field) && hasPath(mask, currPath) {
    33  			return fmt.Errorf("field is immutable: %s", currPath)
    34  		}
    35  
    36  		if field.Kind() == protoreflect.MessageKind {
    37  			value := m.Get(field)
    38  			switch {
    39  			case field.IsList():
    40  				for i := 0; i < value.List().Len(); i++ {
    41  					if err := validateImmutableFields(value.List().Get(i).Message(), mask, currPath); err != nil {
    42  						return err
    43  					}
    44  				}
    45  			case field.IsMap():
    46  				if field.MapValue().Kind() != protoreflect.MessageKind {
    47  					continue
    48  				}
    49  				var mapErr error
    50  				value.Map().Range(func(_ protoreflect.MapKey, value protoreflect.Value) bool {
    51  					if err := validateImmutableFields(value.Message(), mask, currPath); err != nil {
    52  						mapErr = err
    53  						return false
    54  					}
    55  
    56  					return true
    57  				})
    58  				if mapErr != nil {
    59  					return mapErr
    60  				}
    61  			default:
    62  				if err := validateImmutableFields(value.Message(), mask, currPath); err != nil {
    63  					return err
    64  				}
    65  			}
    66  		}
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  func isImmutable(field protoreflect.FieldDescriptor) bool {
    73  	return Has(field, annotations.FieldBehavior_IMMUTABLE)
    74  }
    75  

View as plain text