summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/pkg/diff/namespace/diff.go
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/namespace/diff.go
parent44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (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.go321
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 != ""
+}