github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/diff/diff.go (about) 1 package diff 2 3 import ( 4 "github.com/authzed/spicedb/pkg/diff/caveats" 5 "github.com/authzed/spicedb/pkg/diff/namespace" 6 "github.com/authzed/spicedb/pkg/genutil/mapz" 7 core "github.com/authzed/spicedb/pkg/proto/core/v1" 8 "github.com/authzed/spicedb/pkg/schemadsl/compiler" 9 ) 10 11 // DiffableSchema is a schema that can be diffed. 12 type DiffableSchema struct { 13 // ObjectDefinitions holds the object definitions in the schema. 14 ObjectDefinitions []*core.NamespaceDefinition 15 16 // CaveatDefinitions holds the caveat definitions in the schema. 17 CaveatDefinitions []*core.CaveatDefinition 18 } 19 20 func (ds *DiffableSchema) GetNamespace(namespaceName string) (*core.NamespaceDefinition, bool) { 21 for _, ns := range ds.ObjectDefinitions { 22 if ns.Name == namespaceName { 23 return ns, true 24 } 25 } 26 27 return nil, false 28 } 29 30 func (ds *DiffableSchema) GetRelation(nsName string, relationName string) (*core.Relation, bool) { 31 ns, ok := ds.GetNamespace(nsName) 32 if !ok { 33 return nil, false 34 } 35 36 for _, relation := range ns.Relation { 37 if relation.Name == relationName { 38 return relation, true 39 } 40 } 41 42 return nil, false 43 } 44 45 func (ds *DiffableSchema) GetCaveat(caveatName string) (*core.CaveatDefinition, bool) { 46 for _, caveat := range ds.CaveatDefinitions { 47 if caveat.Name == caveatName { 48 return caveat, true 49 } 50 } 51 52 return nil, false 53 } 54 55 // NewDiffableSchemaFromCompiledSchema creates a new DiffableSchema from a CompiledSchema. 56 func NewDiffableSchemaFromCompiledSchema(compiled *compiler.CompiledSchema) DiffableSchema { 57 return DiffableSchema{ 58 ObjectDefinitions: compiled.ObjectDefinitions, 59 CaveatDefinitions: compiled.CaveatDefinitions, 60 } 61 } 62 63 // SchemaDiff holds the diff between two schemas. 64 type SchemaDiff struct { 65 // AddedNamespaces are the namespaces that were added. 66 AddedNamespaces []string 67 68 // RemovedNamespaces are the namespaces that were removed. 69 RemovedNamespaces []string 70 71 // AddedCaveats are the caveats that were added. 72 AddedCaveats []string 73 74 // RemovedCaveats are the caveats that were removed. 75 RemovedCaveats []string 76 77 // ChangedNamespaces are the namespaces that were changed. 78 ChangedNamespaces map[string]namespace.Diff 79 80 // ChangedCaveats are the caveats that were changed. 81 ChangedCaveats map[string]caveats.Diff 82 } 83 84 // DiffSchemas compares two schemas and returns the diff. 85 func DiffSchemas(existing DiffableSchema, comparison DiffableSchema) (*SchemaDiff, error) { 86 existingNamespacesByName := make(map[string]*core.NamespaceDefinition, len(existing.ObjectDefinitions)) 87 existingNamespaceNames := mapz.NewSet[string]() 88 for _, nsDef := range existing.ObjectDefinitions { 89 existingNamespacesByName[nsDef.Name] = nsDef 90 existingNamespaceNames.Add(nsDef.Name) 91 } 92 93 existingCaveatsByName := make(map[string]*core.CaveatDefinition, len(existing.CaveatDefinitions)) 94 existingCaveatsByNames := mapz.NewSet[string]() 95 for _, caveatDef := range existing.CaveatDefinitions { 96 existingCaveatsByName[caveatDef.Name] = caveatDef 97 existingCaveatsByNames.Add(caveatDef.Name) 98 } 99 100 comparisonNamespacesByName := make(map[string]*core.NamespaceDefinition, len(comparison.ObjectDefinitions)) 101 comparisonNamespaceNames := mapz.NewSet[string]() 102 for _, nsDef := range comparison.ObjectDefinitions { 103 comparisonNamespacesByName[nsDef.Name] = nsDef 104 comparisonNamespaceNames.Add(nsDef.Name) 105 } 106 107 comparisonCaveatsByName := make(map[string]*core.CaveatDefinition, len(comparison.CaveatDefinitions)) 108 comparisonCaveatsByNames := mapz.NewSet[string]() 109 for _, caveatDef := range comparison.CaveatDefinitions { 110 comparisonCaveatsByName[caveatDef.Name] = caveatDef 111 comparisonCaveatsByNames.Add(caveatDef.Name) 112 } 113 114 changedNamespaces := make(map[string]namespace.Diff, 0) 115 commonNamespaceNames := existingNamespaceNames.Intersect(comparisonNamespaceNames) 116 if err := commonNamespaceNames.ForEach(func(name string) error { 117 existingNamespace := existingNamespacesByName[name] 118 comparisonNamespace := comparisonNamespacesByName[name] 119 120 diff, err := namespace.DiffNamespaces(existingNamespace, comparisonNamespace) 121 if err != nil { 122 return err 123 } 124 125 if len(diff.Deltas()) > 0 { 126 changedNamespaces[name] = *diff 127 } 128 129 return nil 130 }); err != nil { 131 return nil, err 132 } 133 134 commonCaveatNames := existingCaveatsByNames.Intersect(comparisonCaveatsByNames) 135 changedCaveats := make(map[string]caveats.Diff, 0) 136 if err := commonCaveatNames.ForEach(func(name string) error { 137 existingCaveat := existingCaveatsByName[name] 138 comparisonCaveat := comparisonCaveatsByName[name] 139 140 diff, err := caveats.DiffCaveats(existingCaveat, comparisonCaveat) 141 if err != nil { 142 return err 143 } 144 145 if len(diff.Deltas()) > 0 { 146 changedCaveats[name] = *diff 147 } 148 149 return nil 150 }); err != nil { 151 return nil, err 152 } 153 154 if len(changedNamespaces) == 0 { 155 changedNamespaces = nil 156 } 157 if len(changedCaveats) == 0 { 158 changedCaveats = nil 159 } 160 161 return &SchemaDiff{ 162 AddedNamespaces: comparisonNamespaceNames.Subtract(existingNamespaceNames).AsSlice(), 163 RemovedNamespaces: existingNamespaceNames.Subtract(comparisonNamespaceNames).AsSlice(), 164 AddedCaveats: comparisonCaveatsByNames.Subtract(existingCaveatsByNames).AsSlice(), 165 RemovedCaveats: existingCaveatsByNames.Subtract(comparisonCaveatsByNames).AsSlice(), 166 ChangedNamespaces: changedNamespaces, 167 ChangedCaveats: changedCaveats, 168 }, nil 169 }