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  }