summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/pkg/diff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
committermo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
commit20ef0d92694465ac86b550df139e8366a0a2b4fa (patch)
tree3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/authzed/spicedb/pkg/diff
parent44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff)
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/diff')
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/diff/caveats/diff.go164
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/diff/diff.go170
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/diff/doc.go2
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go321
-rw-r--r--vendor/github.com/authzed/spicedb/pkg/diff/namespace/diffexpr.go386
5 files changed, 1043 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/pkg/diff/caveats/diff.go b/vendor/github.com/authzed/spicedb/pkg/diff/caveats/diff.go
new file mode 100644
index 0000000..74e196b
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/diff/caveats/diff.go
@@ -0,0 +1,164 @@
+package caveats
+
+import (
+ "bytes"
+
+ "golang.org/x/exp/maps"
+ "golang.org/x/exp/slices"
+
+ caveattypes "github.com/authzed/spicedb/pkg/caveats/types"
+ "github.com/authzed/spicedb/pkg/genutil/mapz"
+ nspkg "github.com/authzed/spicedb/pkg/namespace"
+ core "github.com/authzed/spicedb/pkg/proto/core/v1"
+)
+
+// DeltaType defines the type of caveat deltas.
+type DeltaType string
+
+const (
+ // CaveatAdded indicates that the caveat was newly added/created.
+ CaveatAdded DeltaType = "caveat-added"
+
+ // CaveatRemoved indicates that the caveat was removed.
+ CaveatRemoved DeltaType = "caveat-removed"
+
+ // CaveatCommentsChanged indicates that the comment(s) on the caveat were changed.
+ CaveatCommentsChanged DeltaType = "caveat-comments-changed"
+
+ // AddedParameter indicates that the parameter was added to the caveat.
+ AddedParameter DeltaType = "added-parameter"
+
+ // RemovedParameter indicates that the parameter was removed from the caveat.
+ RemovedParameter DeltaType = "removed-parameter"
+
+ // ParameterTypeChanged indicates that the type of the parameter was changed.
+ ParameterTypeChanged DeltaType = "parameter-type-changed"
+
+ // CaveatExpressionChanged indicates that the expression of the caveat has changed.
+ CaveatExpressionChanged DeltaType = "expression-has-changed"
+)
+
+// Diff holds the diff between two caveats.
+type Diff struct {
+ existing *core.CaveatDefinition
+ updated *core.CaveatDefinition
+ deltas []Delta
+}
+
+// Deltas returns the deltas between the two caveats.
+func (cd Diff) Deltas() []Delta {
+ return cd.deltas
+}
+
+// Delta holds a single change of a caveat.
+type Delta struct {
+ // Type is the type of this delta.
+ Type DeltaType
+
+ // ParameterName is the name of the parameter to which this delta applies, if any.
+ ParameterName string
+
+ // PreviousType is the previous type of the parameter changed, if any.
+ PreviousType *core.CaveatTypeReference
+
+ // CurrentType is the current type of the parameter changed, if any.
+ CurrentType *core.CaveatTypeReference
+}
+
+// DiffCaveats performs a diff between two caveat definitions. One or both of the definitions
+// can be `nil`, which will be treated as an add/remove as applicable.
+func DiffCaveats(existing *core.CaveatDefinition, updated *core.CaveatDefinition, caveatTypeSet *caveattypes.TypeSet) (*Diff, error) {
+ // Check for the caveats themselves.
+ if existing == nil && updated == nil {
+ return &Diff{existing, updated, []Delta{}}, nil
+ }
+
+ if existing != nil && updated == nil {
+ return &Diff{
+ existing: existing,
+ updated: updated,
+ deltas: []Delta{
+ {
+ Type: CaveatRemoved,
+ },
+ },
+ }, nil
+ }
+
+ if existing == nil && updated != nil {
+ return &Diff{
+ existing: existing,
+ updated: updated,
+ deltas: []Delta{
+ {
+ Type: CaveatAdded,
+ },
+ },
+ }, nil
+ }
+
+ deltas := make([]Delta, 0, len(existing.ParameterTypes)+len(updated.ParameterTypes))
+
+ // Check the caveats's comments.
+ existingComments := nspkg.GetComments(existing.Metadata)
+ updatedComments := nspkg.GetComments(updated.Metadata)
+ if !slices.Equal(existingComments, updatedComments) {
+ deltas = append(deltas, Delta{
+ Type: CaveatCommentsChanged,
+ })
+ }
+
+ existingParameterNames := mapz.NewSet(maps.Keys(existing.ParameterTypes)...)
+ updatedParameterNames := mapz.NewSet(maps.Keys(updated.ParameterTypes)...)
+
+ for _, removed := range existingParameterNames.Subtract(updatedParameterNames).AsSlice() {
+ deltas = append(deltas, Delta{
+ Type: RemovedParameter,
+ ParameterName: removed,
+ })
+ }
+
+ for _, added := range updatedParameterNames.Subtract(existingParameterNames).AsSlice() {
+ deltas = append(deltas, Delta{
+ Type: AddedParameter,
+ ParameterName: added,
+ })
+ }
+
+ for _, shared := range existingParameterNames.Intersect(updatedParameterNames).AsSlice() {
+ existingParamType := existing.ParameterTypes[shared]
+ updatedParamType := updated.ParameterTypes[shared]
+
+ existingType, err := caveattypes.DecodeParameterType(caveatTypeSet, existingParamType)
+ if err != nil {
+ return nil, err
+ }
+
+ updatedType, err := caveattypes.DecodeParameterType(caveatTypeSet, updatedParamType)
+ if err != nil {
+ return nil, err
+ }
+
+ // Compare types.
+ if existingType.String() != updatedType.String() {
+ deltas = append(deltas, Delta{
+ Type: ParameterTypeChanged,
+ ParameterName: shared,
+ PreviousType: existingParamType,
+ CurrentType: updatedParamType,
+ })
+ }
+ }
+
+ if !bytes.Equal(existing.SerializedExpression, updated.SerializedExpression) {
+ deltas = append(deltas, Delta{
+ Type: CaveatExpressionChanged,
+ })
+ }
+
+ return &Diff{
+ existing: existing,
+ updated: updated,
+ deltas: deltas,
+ }, nil
+}
diff --git a/vendor/github.com/authzed/spicedb/pkg/diff/diff.go b/vendor/github.com/authzed/spicedb/pkg/diff/diff.go
new file mode 100644
index 0000000..34aa927
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/diff/diff.go
@@ -0,0 +1,170 @@
+package diff
+
+import (
+ caveattypes "github.com/authzed/spicedb/pkg/caveats/types"
+ "github.com/authzed/spicedb/pkg/diff/caveats"
+ "github.com/authzed/spicedb/pkg/diff/namespace"
+ "github.com/authzed/spicedb/pkg/genutil/mapz"
+ core "github.com/authzed/spicedb/pkg/proto/core/v1"
+ "github.com/authzed/spicedb/pkg/schemadsl/compiler"
+)
+
+// DiffableSchema is a schema that can be diffed.
+type DiffableSchema struct {
+ // ObjectDefinitions holds the object definitions in the schema.
+ ObjectDefinitions []*core.NamespaceDefinition
+
+ // CaveatDefinitions holds the caveat definitions in the schema.
+ CaveatDefinitions []*core.CaveatDefinition
+}
+
+func (ds *DiffableSchema) GetNamespace(namespaceName string) (*core.NamespaceDefinition, bool) {
+ for _, ns := range ds.ObjectDefinitions {
+ if ns.Name == namespaceName {
+ return ns, true
+ }
+ }
+
+ return nil, false
+}
+
+func (ds *DiffableSchema) GetRelation(nsName string, relationName string) (*core.Relation, bool) {
+ ns, ok := ds.GetNamespace(nsName)
+ if !ok {
+ return nil, false
+ }
+
+ for _, relation := range ns.Relation {
+ if relation.Name == relationName {
+ return relation, true
+ }
+ }
+
+ return nil, false
+}
+
+func (ds *DiffableSchema) GetCaveat(caveatName string) (*core.CaveatDefinition, bool) {
+ for _, caveat := range ds.CaveatDefinitions {
+ if caveat.Name == caveatName {
+ return caveat, true
+ }
+ }
+
+ return nil, false
+}
+
+// NewDiffableSchemaFromCompiledSchema creates a new DiffableSchema from a CompiledSchema.
+func NewDiffableSchemaFromCompiledSchema(compiled *compiler.CompiledSchema) DiffableSchema {
+ return DiffableSchema{
+ ObjectDefinitions: compiled.ObjectDefinitions,
+ CaveatDefinitions: compiled.CaveatDefinitions,
+ }
+}
+
+// SchemaDiff holds the diff between two schemas.
+type SchemaDiff struct {
+ // AddedNamespaces are the namespaces that were added.
+ AddedNamespaces []string
+
+ // RemovedNamespaces are the namespaces that were removed.
+ RemovedNamespaces []string
+
+ // AddedCaveats are the caveats that were added.
+ AddedCaveats []string
+
+ // RemovedCaveats are the caveats that were removed.
+ RemovedCaveats []string
+
+ // ChangedNamespaces are the namespaces that were changed.
+ ChangedNamespaces map[string]namespace.Diff
+
+ // ChangedCaveats are the caveats that were changed.
+ ChangedCaveats map[string]caveats.Diff
+}
+
+// DiffSchemas compares two schemas and returns the diff.
+func DiffSchemas(existing DiffableSchema, comparison DiffableSchema, caveatTypeSet *caveattypes.TypeSet) (*SchemaDiff, error) {
+ existingNamespacesByName := make(map[string]*core.NamespaceDefinition, len(existing.ObjectDefinitions))
+ existingNamespaceNames := mapz.NewSet[string]()
+ for _, nsDef := range existing.ObjectDefinitions {
+ existingNamespacesByName[nsDef.Name] = nsDef
+ existingNamespaceNames.Add(nsDef.Name)
+ }
+
+ existingCaveatsByName := make(map[string]*core.CaveatDefinition, len(existing.CaveatDefinitions))
+ existingCaveatsByNames := mapz.NewSet[string]()
+ for _, caveatDef := range existing.CaveatDefinitions {
+ existingCaveatsByName[caveatDef.Name] = caveatDef
+ existingCaveatsByNames.Add(caveatDef.Name)
+ }
+
+ comparisonNamespacesByName := make(map[string]*core.NamespaceDefinition, len(comparison.ObjectDefinitions))
+ comparisonNamespaceNames := mapz.NewSet[string]()
+ for _, nsDef := range comparison.ObjectDefinitions {
+ comparisonNamespacesByName[nsDef.Name] = nsDef
+ comparisonNamespaceNames.Add(nsDef.Name)
+ }
+
+ comparisonCaveatsByName := make(map[string]*core.CaveatDefinition, len(comparison.CaveatDefinitions))
+ comparisonCaveatsByNames := mapz.NewSet[string]()
+ for _, caveatDef := range comparison.CaveatDefinitions {
+ comparisonCaveatsByName[caveatDef.Name] = caveatDef
+ comparisonCaveatsByNames.Add(caveatDef.Name)
+ }
+
+ changedNamespaces := make(map[string]namespace.Diff, 0)
+ commonNamespaceNames := existingNamespaceNames.Intersect(comparisonNamespaceNames)
+ if err := commonNamespaceNames.ForEach(func(name string) error {
+ existingNamespace := existingNamespacesByName[name]
+ comparisonNamespace := comparisonNamespacesByName[name]
+
+ diff, err := namespace.DiffNamespaces(existingNamespace, comparisonNamespace)
+ if err != nil {
+ return err
+ }
+
+ if len(diff.Deltas()) > 0 {
+ changedNamespaces[name] = *diff
+ }
+
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+
+ commonCaveatNames := existingCaveatsByNames.Intersect(comparisonCaveatsByNames)
+ changedCaveats := make(map[string]caveats.Diff, 0)
+ if err := commonCaveatNames.ForEach(func(name string) error {
+ existingCaveat := existingCaveatsByName[name]
+ comparisonCaveat := comparisonCaveatsByName[name]
+
+ diff, err := caveats.DiffCaveats(existingCaveat, comparisonCaveat, caveatTypeSet)
+ if err != nil {
+ return err
+ }
+
+ if len(diff.Deltas()) > 0 {
+ changedCaveats[name] = *diff
+ }
+
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+
+ if len(changedNamespaces) == 0 {
+ changedNamespaces = nil
+ }
+ if len(changedCaveats) == 0 {
+ changedCaveats = nil
+ }
+
+ return &SchemaDiff{
+ AddedNamespaces: comparisonNamespaceNames.Subtract(existingNamespaceNames).AsSlice(),
+ RemovedNamespaces: existingNamespaceNames.Subtract(comparisonNamespaceNames).AsSlice(),
+ AddedCaveats: comparisonCaveatsByNames.Subtract(existingCaveatsByNames).AsSlice(),
+ RemovedCaveats: existingCaveatsByNames.Subtract(comparisonCaveatsByNames).AsSlice(),
+ ChangedNamespaces: changedNamespaces,
+ ChangedCaveats: changedCaveats,
+ }, nil
+}
diff --git a/vendor/github.com/authzed/spicedb/pkg/diff/doc.go b/vendor/github.com/authzed/spicedb/pkg/diff/doc.go
new file mode 100644
index 0000000..ff3e1d0
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/diff/doc.go
@@ -0,0 +1,2 @@
+// Package diff contains code for things that can be diffed (e.g. namespaces and caveats).
+package diff
diff --git a/vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go b/vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go
new file mode 100644
index 0000000..af50f0f
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go
@@ -0,0 +1,321 @@
+package namespace
+
+import (
+ "github.com/google/go-cmp/cmp"
+ "golang.org/x/exp/slices"
+ "google.golang.org/protobuf/testing/protocmp"
+
+ nsinternal "github.com/authzed/spicedb/internal/namespace"
+ "github.com/authzed/spicedb/pkg/genutil/mapz"
+ nspkg "github.com/authzed/spicedb/pkg/namespace"
+ core "github.com/authzed/spicedb/pkg/proto/core/v1"
+ iv1 "github.com/authzed/spicedb/pkg/proto/impl/v1"
+ "github.com/authzed/spicedb/pkg/schema"
+)
+
+// DeltaType defines the type of namespace deltas.
+type DeltaType string
+
+const (
+ // NamespaceAdded indicates that the namespace was newly added/created.
+ NamespaceAdded DeltaType = "namespace-added"
+
+ // NamespaceRemoved indicates that the namespace was removed.
+ NamespaceRemoved DeltaType = "namespace-removed"
+
+ // NamespaceCommentsChanged indicates that the comment(s) on the namespace were changed.
+ NamespaceCommentsChanged DeltaType = "namespace-comments-changed"
+
+ // AddedRelation indicates that the relation was added to the namespace.
+ AddedRelation DeltaType = "added-relation"
+
+ // RemovedRelation indicates that the relation was removed from the namespace.
+ RemovedRelation DeltaType = "removed-relation"
+
+ // AddedPermission indicates that the permission was added to the namespace.
+ AddedPermission DeltaType = "added-permission"
+
+ // RemovedPermission indicates that the permission was removed from the namespace.
+ RemovedPermission DeltaType = "removed-permission"
+
+ // ChangedPermissionImpl indicates that the implementation of the permission has changed in some
+ // way.
+ ChangedPermissionImpl DeltaType = "changed-permission-implementation"
+
+ // ChangedPermissionComment indicates that the comment of the permission has changed in some way.
+ ChangedPermissionComment DeltaType = "changed-permission-comment"
+
+ // LegacyChangedRelationImpl indicates that the implementation of the relation has changed in some
+ // way. This is for legacy checks and should not apply to any modern namespaces created
+ // via schema.
+ LegacyChangedRelationImpl DeltaType = "legacy-changed-relation-implementation"
+
+ // RelationAllowedTypeAdded indicates that an allowed relation type has been added to
+ // the relation.
+ RelationAllowedTypeAdded DeltaType = "relation-allowed-type-added"
+
+ // RelationAllowedTypeRemoved indicates that an allowed relation type has been removed from
+ // the relation.
+ RelationAllowedTypeRemoved DeltaType = "relation-allowed-type-removed"
+
+ // ChangedRelationComment indicates that the comment of the relation has changed in some way.
+ ChangedRelationComment DeltaType = "changed-relation-comment"
+)
+
+// Diff holds the diff between two namespaces.
+type Diff struct {
+ existing *core.NamespaceDefinition
+ updated *core.NamespaceDefinition
+ deltas []Delta
+}
+
+// Deltas returns the deltas between the two namespaces.
+func (nd Diff) Deltas() []Delta {
+ return nd.deltas
+}
+
+// Delta holds a single change of a namespace.
+type Delta struct {
+ // Type is the type of this delta.
+ Type DeltaType
+
+ // RelationName is the name of the relation to which this delta applies, if any.
+ RelationName string
+
+ // AllowedType is the allowed relation type added or removed, if any.
+ AllowedType *core.AllowedRelation
+}
+
+// DiffNamespaces performs a diff between two namespace definitions. One or both of the definitions
+// can be `nil`, which will be treated as an add/remove as applicable.
+func DiffNamespaces(existing *core.NamespaceDefinition, updated *core.NamespaceDefinition) (*Diff, error) {
+ // Check for the namespaces themselves.
+ if existing == nil && updated == nil {
+ return &Diff{existing, updated, []Delta{}}, nil
+ }
+
+ if existing != nil && updated == nil {
+ return &Diff{
+ existing: existing,
+ updated: updated,
+ deltas: []Delta{
+ {
+ Type: NamespaceRemoved,
+ },
+ },
+ }, nil
+ }
+
+ if existing == nil && updated != nil {
+ return &Diff{
+ existing: existing,
+ updated: updated,
+ deltas: []Delta{
+ {
+ Type: NamespaceAdded,
+ },
+ },
+ }, nil
+ }
+
+ deltas := []Delta{}
+
+ // Check the namespace's comments.
+ existingComments := nspkg.GetComments(existing.Metadata)
+ updatedComments := nspkg.GetComments(updated.Metadata)
+ if !slices.Equal(existingComments, updatedComments) {
+ deltas = append(deltas, Delta{
+ Type: NamespaceCommentsChanged,
+ })
+ }
+
+ // Collect up relations and check.
+ existingRels := map[string]*core.Relation{}
+ existingRelNames := mapz.NewSet[string]()
+
+ existingPerms := map[string]*core.Relation{}
+ existingPermNames := mapz.NewSet[string]()
+
+ updatedRels := map[string]*core.Relation{}
+ updatedRelNames := mapz.NewSet[string]()
+
+ updatedPerms := map[string]*core.Relation{}
+ updatedPermNames := mapz.NewSet[string]()
+
+ for _, relation := range existing.Relation {
+ _, ok := existingRels[relation.Name]
+ if ok {
+ return nil, nsinternal.NewDuplicateRelationError(existing.Name, relation.Name)
+ }
+
+ if isPermission(relation) {
+ existingPerms[relation.Name] = relation
+ existingPermNames.Add(relation.Name)
+ } else {
+ existingRels[relation.Name] = relation
+ existingRelNames.Add(relation.Name)
+ }
+ }
+
+ for _, relation := range updated.Relation {
+ _, ok := updatedRels[relation.Name]
+ if ok {
+ return nil, nsinternal.NewDuplicateRelationError(updated.Name, relation.Name)
+ }
+
+ if isPermission(relation) {
+ updatedPerms[relation.Name] = relation
+ updatedPermNames.Add(relation.Name)
+ } else {
+ updatedRels[relation.Name] = relation
+ updatedRelNames.Add(relation.Name)
+ }
+ }
+
+ _ = existingRelNames.Subtract(updatedRelNames).ForEach(func(removed string) error {
+ deltas = append(deltas, Delta{
+ Type: RemovedRelation,
+ RelationName: removed,
+ })
+ return nil
+ })
+
+ _ = updatedRelNames.Subtract(existingRelNames).ForEach(func(added string) error {
+ deltas = append(deltas, Delta{
+ Type: AddedRelation,
+ RelationName: added,
+ })
+ return nil
+ })
+
+ _ = existingPermNames.Subtract(updatedPermNames).ForEach(func(removed string) error {
+ deltas = append(deltas, Delta{
+ Type: RemovedPermission,
+ RelationName: removed,
+ })
+ return nil
+ })
+
+ _ = updatedPermNames.Subtract(existingPermNames).ForEach(func(added string) error {
+ deltas = append(deltas, Delta{
+ Type: AddedPermission,
+ RelationName: added,
+ })
+ return nil
+ })
+
+ _ = existingPermNames.Intersect(updatedPermNames).ForEach(func(shared string) error {
+ existingPerm := existingPerms[shared]
+ updatedPerm := updatedPerms[shared]
+
+ // Compare implementations.
+ if areDifferentExpressions(existingPerm.UsersetRewrite, updatedPerm.UsersetRewrite) {
+ deltas = append(deltas, Delta{
+ Type: ChangedPermissionImpl,
+ RelationName: shared,
+ })
+ }
+
+ // Compare comments.
+ existingComments := nspkg.GetComments(existingPerm.Metadata)
+ updatedComments := nspkg.GetComments(updatedPerm.Metadata)
+ if !slices.Equal(existingComments, updatedComments) {
+ deltas = append(deltas, Delta{
+ Type: ChangedPermissionComment,
+ RelationName: shared,
+ })
+ }
+ return nil
+ })
+
+ _ = existingRelNames.Intersect(updatedRelNames).ForEach(func(shared string) error {
+ existingRel := existingRels[shared]
+ updatedRel := updatedRels[shared]
+
+ // Compare implementations (legacy).
+ if areDifferentExpressions(existingRel.UsersetRewrite, updatedRel.UsersetRewrite) {
+ deltas = append(deltas, Delta{
+ Type: LegacyChangedRelationImpl,
+ RelationName: shared,
+ })
+ }
+
+ // Compare comments.
+ existingComments := nspkg.GetComments(existingRel.Metadata)
+ updatedComments := nspkg.GetComments(updatedRel.Metadata)
+ if !slices.Equal(existingComments, updatedComments) {
+ deltas = append(deltas, Delta{
+ Type: ChangedRelationComment,
+ RelationName: shared,
+ })
+ }
+
+ // Compare type information.
+ existingTypeInfo := existingRel.TypeInformation
+ if existingTypeInfo == nil {
+ existingTypeInfo = &core.TypeInformation{}
+ }
+
+ updatedTypeInfo := updatedRel.TypeInformation
+ if updatedTypeInfo == nil {
+ updatedTypeInfo = &core.TypeInformation{}
+ }
+
+ existingAllowedRels := mapz.NewSet[string]()
+ updatedAllowedRels := mapz.NewSet[string]()
+ allowedRelsBySource := map[string]*core.AllowedRelation{}
+
+ for _, existingAllowed := range existingTypeInfo.AllowedDirectRelations {
+ source := schema.SourceForAllowedRelation(existingAllowed)
+ allowedRelsBySource[source] = existingAllowed
+ existingAllowedRels.Add(source)
+ }
+
+ for _, updatedAllowed := range updatedTypeInfo.AllowedDirectRelations {
+ source := schema.SourceForAllowedRelation(updatedAllowed)
+ allowedRelsBySource[source] = updatedAllowed
+ updatedAllowedRels.Add(source)
+ }
+
+ _ = existingAllowedRels.Subtract(updatedAllowedRels).ForEach(func(removed string) error {
+ deltas = append(deltas, Delta{
+ Type: RelationAllowedTypeRemoved,
+ RelationName: shared,
+ AllowedType: allowedRelsBySource[removed],
+ })
+ return nil
+ })
+
+ _ = updatedAllowedRels.Subtract(existingAllowedRels).ForEach(func(added string) error {
+ deltas = append(deltas, Delta{
+ Type: RelationAllowedTypeAdded,
+ RelationName: shared,
+ AllowedType: allowedRelsBySource[added],
+ })
+ return nil
+ })
+
+ return nil
+ })
+
+ return &Diff{
+ existing: existing,
+ updated: updated,
+ deltas: deltas,
+ }, nil
+}
+
+func isPermission(relation *core.Relation) bool {
+ return nspkg.GetRelationKind(relation) == iv1.RelationMetadata_PERMISSION
+}
+
+func areDifferentExpressions(existing *core.UsersetRewrite, updated *core.UsersetRewrite) bool {
+ // Return whether the rewrites are different, ignoring the SourcePosition message type.
+ delta := cmp.Diff(
+ existing,
+ updated,
+ protocmp.Transform(),
+ protocmp.IgnoreMessages(&core.SourcePosition{}),
+ )
+ return delta != ""
+}
diff --git a/vendor/github.com/authzed/spicedb/pkg/diff/namespace/diffexpr.go b/vendor/github.com/authzed/spicedb/pkg/diff/namespace/diffexpr.go
new file mode 100644
index 0000000..359728c
--- /dev/null
+++ b/vendor/github.com/authzed/spicedb/pkg/diff/namespace/diffexpr.go
@@ -0,0 +1,386 @@
+package namespace
+
+import (
+ core "github.com/authzed/spicedb/pkg/proto/core/v1"
+ "github.com/authzed/spicedb/pkg/spiceerrors"
+)
+
+// ExpressionChangeType defines the type of expression changes.
+type ExpressionChangeType string
+
+const (
+ // ExpressionUnchanged indicates that the expression was unchanged.
+ ExpressionUnchanged ExpressionChangeType = "expression-unchanged"
+
+ // ExpressionOperationChanged indicates that the operation type of the expression was changed.
+ ExpressionOperationChanged ExpressionChangeType = "operation-changed"
+
+ // ExpressionChildrenChanged indicates that the children of the expression were changed.
+ ExpressionChildrenChanged ExpressionChangeType = "children-changed"
+
+ // ExpressionOperationExpanded indicates that the operation type of the expression was expanded
+ // from a union of a single child to multiple children under a union, intersection or another
+ // operation.
+ ExpressionOperationExpanded ExpressionChangeType = "operation-expanded"
+)
+
+// ExpressionDiff holds the diff between two expressions.
+type ExpressionDiff struct {
+ existing *core.UsersetRewrite
+ updated *core.UsersetRewrite
+ change ExpressionChangeType
+
+ childDiffs []*OperationDiff
+}
+
+// Existing returns the existing expression, if any.
+func (ed *ExpressionDiff) Existing() *core.UsersetRewrite {
+ return ed.existing
+}
+
+// Updated returns the updated expression, if any.
+func (ed *ExpressionDiff) Updated() *core.UsersetRewrite {
+ return ed.updated
+}
+
+// Change returns the type of change that occurred.
+func (ed *ExpressionDiff) Change() ExpressionChangeType {
+ return ed.change
+}
+
+// ChildDiffs returns the child diffs, if any.
+func (ed *ExpressionDiff) ChildDiffs() []*OperationDiff {
+ return ed.childDiffs
+}
+
+// SetOperationChangeType defines the type of set operation changes.
+type SetOperationChangeType string
+
+const (
+ // OperationUnchanged indicates that the set operation was unchanged.
+ OperationUnchanged SetOperationChangeType = "operation-changed"
+
+ // OperationAdded indicates that a set operation was added.
+ OperationAdded SetOperationChangeType = "operation-added"
+
+ // OperationRemoved indicates that a set operation was removed.
+ OperationRemoved SetOperationChangeType = "operation-removed"
+
+ // OperationTypeChanged indicates that the type of set operation was changed.
+ OperationTypeChanged SetOperationChangeType = "operation-type-changed"
+
+ // OperationComputedUsersetChanged indicates that the computed userset of the operation was changed.
+ OperationComputedUsersetChanged SetOperationChangeType = "operation-computed-userset-changed"
+
+ // OperationTuplesetChanged indicates that the tupleset of the operation was changed.
+ OperationTuplesetChanged SetOperationChangeType = "operation-tupleset-changed"
+
+ // OperationChildExpressionChanged indicates that the child expression of the operation was changed.
+ OperationChildExpressionChanged SetOperationChangeType = "operation-child-expression-changed"
+)
+
+// OperationDiff holds the diff between two set operations.
+type OperationDiff struct {
+ existing *core.SetOperation_Child
+ updated *core.SetOperation_Child
+ change SetOperationChangeType
+ childExprDiff *ExpressionDiff
+}
+
+// Existing returns the existing set operation, if any.
+func (od *OperationDiff) Existing() *core.SetOperation_Child {
+ return od.existing
+}
+
+// Updated returns the updated set operation, if any.
+func (od *OperationDiff) Updated() *core.SetOperation_Child {
+ return od.updated
+}
+
+// Change returns the type of change that occurred.
+func (od *OperationDiff) Change() SetOperationChangeType {
+ return od.change
+}
+
+// ChildExpressionDiff returns the child expression diff, if any.
+func (od *OperationDiff) ChildExpressionDiff() *ExpressionDiff {
+ return od.childExprDiff
+}
+
+// DiffExpressions diffs two expressions.
+func DiffExpressions(existing *core.UsersetRewrite, updated *core.UsersetRewrite) (*ExpressionDiff, error) {
+ // Check for a difference in the operation type.
+ var existingType string
+ var existingOperation *core.SetOperation
+ var updatedType string
+ var updatedOperation *core.SetOperation
+
+ switch t := existing.RewriteOperation.(type) {
+ case *core.UsersetRewrite_Union:
+ existingType = "union"
+ existingOperation = t.Union
+
+ case *core.UsersetRewrite_Intersection:
+ existingType = "intersection"
+ existingOperation = t.Intersection
+
+ case *core.UsersetRewrite_Exclusion:
+ existingType = "exclusion"
+ existingOperation = t.Exclusion
+
+ default:
+ return nil, spiceerrors.MustBugf("unknown operation type %T", existing.RewriteOperation)
+ }
+
+ switch t := updated.RewriteOperation.(type) {
+ case *core.UsersetRewrite_Union:
+ updatedType = "union"
+ updatedOperation = t.Union
+
+ case *core.UsersetRewrite_Intersection:
+ updatedType = "intersection"
+ updatedOperation = t.Intersection
+
+ case *core.UsersetRewrite_Exclusion:
+ updatedType = "exclusion"
+ updatedOperation = t.Exclusion
+
+ default:
+ return nil, spiceerrors.MustBugf("unknown operation type %T", updated.RewriteOperation)
+ }
+
+ childChangeKind := ExpressionChildrenChanged
+ if existingType != updatedType {
+ // If the expression has changed from a union with a single child, then
+ // treat this as a special case, since there wasn't really an operation
+ // before.
+ if existingType != "union" || len(existingOperation.Child) != 1 {
+ return &ExpressionDiff{
+ existing: existing,
+ updated: updated,
+ change: ExpressionOperationChanged,
+ }, nil
+ }
+
+ childChangeKind = ExpressionOperationExpanded
+ }
+
+ childDiffs := make([]*OperationDiff, 0, abs(len(updatedOperation.Child)-len(existingOperation.Child)))
+ if len(existingOperation.Child) < len(updatedOperation.Child) {
+ for _, updatedChild := range updatedOperation.Child[len(existingOperation.Child):] {
+ childDiffs = append(childDiffs, &OperationDiff{
+ change: OperationAdded,
+ updated: updatedChild,
+ })
+ }
+ }
+
+ if len(existingOperation.Child) > len(updatedOperation.Child) {
+ for _, existingChild := range existingOperation.Child[len(updatedOperation.Child):] {
+ childDiffs = append(childDiffs, &OperationDiff{
+ change: OperationRemoved,
+ existing: existingChild,
+ })
+ }
+ }
+
+ for i := 0; i < len(existingOperation.Child) && i < len(updatedOperation.Child); i++ {
+ childDiff, err := compareChildren(existingOperation.Child[i], updatedOperation.Child[i])
+ if err != nil {
+ return nil, err
+ }
+
+ if childDiff.change != OperationUnchanged {
+ childDiffs = append(childDiffs, childDiff)
+ }
+ }
+
+ if len(childDiffs) > 0 {
+ return &ExpressionDiff{
+ existing: existing,
+ updated: updated,
+ change: childChangeKind,
+ childDiffs: childDiffs,
+ }, nil
+ }
+
+ return &ExpressionDiff{
+ existing: existing,
+ updated: updated,
+ change: ExpressionUnchanged,
+ }, nil
+}
+
+func abs(i int) int {
+ if i < 0 {
+ return -i
+ }
+ return i
+}
+
+func compareChildren(existing *core.SetOperation_Child, updated *core.SetOperation_Child) (*OperationDiff, error) {
+ existingType, err := typeOfSetOperationChild(existing)
+ if err != nil {
+ return nil, err
+ }
+
+ updatedType, err := typeOfSetOperationChild(updated)
+ if err != nil {
+ return nil, err
+ }
+
+ if existingType != updatedType {
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationTypeChanged,
+ }, nil
+ }
+
+ switch existingType {
+ case "usersetrewrite":
+ childDiff, err := DiffExpressions(existing.GetUsersetRewrite(), updated.GetUsersetRewrite())
+ if err != nil {
+ return nil, err
+ }
+
+ if childDiff.change != ExpressionUnchanged {
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationChildExpressionChanged,
+ childExprDiff: childDiff,
+ }, nil
+ }
+
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationUnchanged,
+ }, nil
+
+ case "computed":
+ if existing.GetComputedUserset().Relation != updated.GetComputedUserset().Relation {
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationComputedUsersetChanged,
+ }, nil
+ }
+
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationUnchanged,
+ }, nil
+
+ case "_this":
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationUnchanged,
+ }, nil
+
+ case "nil":
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationUnchanged,
+ }, nil
+
+ case "ttu":
+ existingTTU := existing.GetTupleToUserset()
+ updatedTTU := updated.GetTupleToUserset()
+
+ if existingTTU.GetComputedUserset().Relation != updatedTTU.GetComputedUserset().Relation {
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationComputedUsersetChanged,
+ }, nil
+ }
+
+ if existingTTU.Tupleset.Relation != updatedTTU.Tupleset.Relation {
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationTuplesetChanged,
+ }, nil
+ }
+
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationUnchanged,
+ }, nil
+
+ case "anyttu":
+ fallthrough
+
+ case "intersectionttu":
+ existingTTU := existing.GetFunctionedTupleToUserset()
+ updatedTTU := updated.GetFunctionedTupleToUserset()
+
+ if existingTTU.GetComputedUserset().Relation != updatedTTU.GetComputedUserset().Relation {
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationComputedUsersetChanged,
+ }, nil
+ }
+
+ if existingTTU.Tupleset.Relation != updatedTTU.Tupleset.Relation {
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationTuplesetChanged,
+ }, nil
+ }
+
+ return &OperationDiff{
+ existing: existing,
+ updated: updated,
+ change: OperationUnchanged,
+ }, nil
+
+ default:
+ return nil, spiceerrors.MustBugf("unknown child type %s", existingType)
+ }
+}
+
+func typeOfSetOperationChild(child *core.SetOperation_Child) (string, error) {
+ switch t := child.ChildType.(type) {
+ case *core.SetOperation_Child_XThis:
+ return "_this", nil
+
+ case *core.SetOperation_Child_ComputedUserset:
+ return "computed", nil
+
+ case *core.SetOperation_Child_UsersetRewrite:
+ return "usersetrewrite", nil
+
+ case *core.SetOperation_Child_TupleToUserset:
+ return "ttu", nil
+
+ case *core.SetOperation_Child_FunctionedTupleToUserset:
+ switch t.FunctionedTupleToUserset.Function {
+ case core.FunctionedTupleToUserset_FUNCTION_UNSPECIFIED:
+ return "", spiceerrors.MustBugf("function type unspecified")
+
+ case core.FunctionedTupleToUserset_FUNCTION_ANY:
+ return "anyttu", nil
+
+ case core.FunctionedTupleToUserset_FUNCTION_ALL:
+ return "intersectionttu", nil
+
+ default:
+ return "", spiceerrors.MustBugf("unknown function type %v", t.FunctionedTupleToUserset.Function)
+ }
+
+ case *core.SetOperation_Child_XNil:
+ return "nil", nil
+
+ default:
+ return "", spiceerrors.MustBugf("unknown child type %T", t)
+ }
+}