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 }