github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/diff/caveats/diff.go (about)

     1  package caveats
     2  
     3  import (
     4  	"bytes"
     5  
     6  	"golang.org/x/exp/maps"
     7  	"golang.org/x/exp/slices"
     8  
     9  	"github.com/authzed/spicedb/pkg/caveats/types"
    10  	"github.com/authzed/spicedb/pkg/genutil/mapz"
    11  	nspkg "github.com/authzed/spicedb/pkg/namespace"
    12  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    13  )
    14  
    15  // DeltaType defines the type of caveat deltas.
    16  type DeltaType string
    17  
    18  const (
    19  	// CaveatAdded indicates that the caveat was newly added/created.
    20  	CaveatAdded DeltaType = "caveat-added"
    21  
    22  	// CaveatRemoved indicates that the caveat was removed.
    23  	CaveatRemoved DeltaType = "caveat-removed"
    24  
    25  	// CaveatCommentsChanged indicates that the comment(s) on the caveat were changed.
    26  	CaveatCommentsChanged DeltaType = "caveat-comments-changed"
    27  
    28  	// AddedParameter indicates that the parameter was added to the caveat.
    29  	AddedParameter DeltaType = "added-parameter"
    30  
    31  	// RemovedParameter indicates that the parameter was removed from the caveat.
    32  	RemovedParameter DeltaType = "removed-parameter"
    33  
    34  	// ParameterTypeChanged indicates that the type of the parameter was changed.
    35  	ParameterTypeChanged DeltaType = "parameter-type-changed"
    36  
    37  	// CaveatExpressionChanged indicates that the expression of the caveat has changed.
    38  	CaveatExpressionChanged DeltaType = "expression-has-changed"
    39  )
    40  
    41  // Diff holds the diff between two caveats.
    42  type Diff struct {
    43  	existing *core.CaveatDefinition
    44  	updated  *core.CaveatDefinition
    45  	deltas   []Delta
    46  }
    47  
    48  // Deltas returns the deltas between the two caveats.
    49  func (cd Diff) Deltas() []Delta {
    50  	return cd.deltas
    51  }
    52  
    53  // Delta holds a single change of a caveat.
    54  type Delta struct {
    55  	// Type is the type of this delta.
    56  	Type DeltaType
    57  
    58  	// ParameterName is the name of the parameter to which this delta applies, if any.
    59  	ParameterName string
    60  
    61  	// PreviousType is the previous type of the parameter changed, if any.
    62  	PreviousType *core.CaveatTypeReference
    63  
    64  	// CurrentType is the current type of the parameter changed, if any.
    65  	CurrentType *core.CaveatTypeReference
    66  }
    67  
    68  // DiffCaveats performs a diff between two caveat definitions. One or both of the definitions
    69  // can be `nil`, which will be treated as an add/remove as applicable.
    70  func DiffCaveats(existing *core.CaveatDefinition, updated *core.CaveatDefinition) (*Diff, error) {
    71  	// Check for the caveats themselves.
    72  	if existing == nil && updated == nil {
    73  		return &Diff{existing, updated, []Delta{}}, nil
    74  	}
    75  
    76  	if existing != nil && updated == nil {
    77  		return &Diff{
    78  			existing: existing,
    79  			updated:  updated,
    80  			deltas: []Delta{
    81  				{
    82  					Type: CaveatRemoved,
    83  				},
    84  			},
    85  		}, nil
    86  	}
    87  
    88  	if existing == nil && updated != nil {
    89  		return &Diff{
    90  			existing: existing,
    91  			updated:  updated,
    92  			deltas: []Delta{
    93  				{
    94  					Type: CaveatAdded,
    95  				},
    96  			},
    97  		}, nil
    98  	}
    99  
   100  	deltas := make([]Delta, 0, len(existing.ParameterTypes)+len(updated.ParameterTypes))
   101  
   102  	// Check the caveats's comments.
   103  	existingComments := nspkg.GetComments(existing.Metadata)
   104  	updatedComments := nspkg.GetComments(updated.Metadata)
   105  	if !slices.Equal(existingComments, updatedComments) {
   106  		deltas = append(deltas, Delta{
   107  			Type: CaveatCommentsChanged,
   108  		})
   109  	}
   110  
   111  	existingParameterNames := mapz.NewSet(maps.Keys(existing.ParameterTypes)...)
   112  	updatedParameterNames := mapz.NewSet(maps.Keys(updated.ParameterTypes)...)
   113  
   114  	for _, removed := range existingParameterNames.Subtract(updatedParameterNames).AsSlice() {
   115  		deltas = append(deltas, Delta{
   116  			Type:          RemovedParameter,
   117  			ParameterName: removed,
   118  		})
   119  	}
   120  
   121  	for _, added := range updatedParameterNames.Subtract(existingParameterNames).AsSlice() {
   122  		deltas = append(deltas, Delta{
   123  			Type:          AddedParameter,
   124  			ParameterName: added,
   125  		})
   126  	}
   127  
   128  	for _, shared := range existingParameterNames.Intersect(updatedParameterNames).AsSlice() {
   129  		existingParamType := existing.ParameterTypes[shared]
   130  		updatedParamType := updated.ParameterTypes[shared]
   131  
   132  		existingType, err := types.DecodeParameterType(existingParamType)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  
   137  		updatedType, err := types.DecodeParameterType(updatedParamType)
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  
   142  		// Compare types.
   143  		if existingType.String() != updatedType.String() {
   144  			deltas = append(deltas, Delta{
   145  				Type:          ParameterTypeChanged,
   146  				ParameterName: shared,
   147  				PreviousType:  existingParamType,
   148  				CurrentType:   updatedParamType,
   149  			})
   150  		}
   151  	}
   152  
   153  	if !bytes.Equal(existing.SerializedExpression, updated.SerializedExpression) {
   154  		deltas = append(deltas, Delta{
   155  			Type: CaveatExpressionChanged,
   156  		})
   157  	}
   158  
   159  	return &Diff{
   160  		existing: existing,
   161  		updated:  updated,
   162  		deltas:   deltas,
   163  	}, nil
   164  }