diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-22 17:35:49 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-22 17:35:49 -0600 |
| commit | 20ef0d92694465ac86b550df139e8366a0a2b4fa (patch) | |
| tree | 3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go | |
| parent | 44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff) | |
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go')
| -rw-r--r-- | vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go | 321 |
1 files changed, 321 insertions, 0 deletions
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 != "" +} |
