Skip to content

Commit

Permalink
Merge pull request #1567 from josephschorr/export-typesystem
Browse files Browse the repository at this point in the history
Extract the type system and reachability graph into pkg
  • Loading branch information
josephschorr authored Oct 6, 2023
2 parents e2ba731 + 4ec74f7 commit ab6946b
Show file tree
Hide file tree
Showing 26 changed files with 545 additions and 309 deletions.
27 changes: 14 additions & 13 deletions internal/graph/reachableresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1"
"github.com/authzed/spicedb/pkg/spiceerrors"
"github.com/authzed/spicedb/pkg/tuple"
"github.com/authzed/spicedb/pkg/typesystem"
)

// dispatchVersion defines the "version" of this dispatcher. Must be incremented
Expand Down Expand Up @@ -111,7 +112,7 @@ func (crr *CursoredReachableResources) afterSameType(
return err
}

rg := namespace.ReachabilityGraphFor(typeSystem.AsValidated())
rg := typesystem.ReachabilityGraphFor(typeSystem.AsValidated())
entrypoints, err := rg.OptimizedEntrypointsForSubjectToResource(ctx, &core.RelationReference{
Namespace: req.SubjectRelation.Namespace,
Relation: req.SubjectRelation.Relation,
Expand All @@ -122,7 +123,7 @@ func (crr *CursoredReachableResources) afterSameType(

// For each entrypoint, load the necessary data and re-dispatch if a subproblem was found.
return withParallelizedStreamingIterableInCursor(ctx, ci, entrypoints, parentStream, crr.concurrencyLimit,
func(ctx context.Context, ci cursorInformation, entrypoint namespace.ReachabilityEntrypoint, stream dispatch.ReachableResourcesStream) error {
func(ctx context.Context, ci cursorInformation, entrypoint typesystem.ReachabilityEntrypoint, stream dispatch.ReachableResourcesStream) error {
switch entrypoint.EntrypointKind() {
case core.ReachabilityEntrypoint_RELATION_ENTRYPOINT:
return crr.lookupRelationEntrypoint(ctx, ci, entrypoint, rg, reader, req, stream, dispatched)
Expand Down Expand Up @@ -161,8 +162,8 @@ func (crr *CursoredReachableResources) afterSameType(
func (crr *CursoredReachableResources) lookupRelationEntrypoint(
ctx context.Context,
ci cursorInformation,
entrypoint namespace.ReachabilityEntrypoint,
rg *namespace.ReachabilityGraph,
entrypoint typesystem.ReachabilityEntrypoint,
rg *typesystem.ReachabilityGraph,
reader datastore.Reader,
req ValidatedReachableResourcesRequest,
stream dispatch.ReachableResourcesStream,
Expand All @@ -189,7 +190,7 @@ func (crr *CursoredReachableResources) lookupRelationEntrypoint(
}

subjectIds := make([]string, 0, len(req.SubjectIds)+1)
if isDirectAllowed == namespace.DirectRelationValid {
if isDirectAllowed == typesystem.DirectRelationValid {
subjectIds = append(subjectIds, req.SubjectIds...)
}

Expand All @@ -199,7 +200,7 @@ func (crr *CursoredReachableResources) lookupRelationEntrypoint(
return err
}

if isWildcardAllowed == namespace.PublicSubjectAllowed {
if isWildcardAllowed == typesystem.PublicSubjectAllowed {
subjectIds = append(subjectIds, "*")
}
}
Expand Down Expand Up @@ -248,8 +249,8 @@ type redispatchOverDatabaseConfig struct {
sourceResourceType *core.RelationReference
foundResourceType *core.RelationReference

entrypoint namespace.ReachabilityEntrypoint
rg *namespace.ReachabilityGraph
entrypoint typesystem.ReachabilityEntrypoint
rg *typesystem.ReachabilityGraph

concurrencyLimit uint16
parentStream dispatch.ReachableResourcesStream
Expand Down Expand Up @@ -339,8 +340,8 @@ func (crr *CursoredReachableResources) redispatchOrReportOverDatabaseQuery(

func (crr *CursoredReachableResources) lookupTTUEntrypoint(ctx context.Context,
ci cursorInformation,
entrypoint namespace.ReachabilityEntrypoint,
rg *namespace.ReachabilityGraph,
entrypoint typesystem.ReachabilityEntrypoint,
rg *typesystem.ReachabilityGraph,
reader datastore.Reader,
req ValidatedReachableResourcesRequest,
stream dispatch.ReachableResourcesStream,
Expand All @@ -366,7 +367,7 @@ func (crr *CursoredReachableResources) lookupTTUEntrypoint(ctx context.Context,
return err
}

if isAllowed != namespace.AllowedNamespaceValid {
if isAllowed != typesystem.AllowedNamespaceValid {
return nil
}

Expand Down Expand Up @@ -408,8 +409,8 @@ func (crr *CursoredReachableResources) redispatchOrReport(
ci cursorInformation,
foundResourceType *core.RelationReference,
foundResources dispatchableResourcesSubjectMap,
rg *namespace.ReachabilityGraph,
entrypoint namespace.ReachabilityEntrypoint,
rg *typesystem.ReachabilityGraph,
entrypoint typesystem.ReachabilityEntrypoint,
parentStream dispatch.ReachableResourcesStream,
parentRequest ValidatedReachableResourcesRequest,
dispatched *syncONRSet,
Expand Down
8 changes: 5 additions & 3 deletions internal/namespace/aliasing.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ package namespace

import (
"sort"

"github.com/authzed/spicedb/pkg/typesystem"
)

// computePermissionAliases computes a map of aliases between the various permissions in a
// namespace. A permission is considered an alias if it *directly* refers to another permission
// or relation without any other form of expression.
func computePermissionAliases(typeSystem *ValidatedNamespaceTypeSystem) (map[string]string, error) {
func computePermissionAliases(typeSystem *typesystem.ValidatedNamespaceTypeSystem) (map[string]string, error) {
aliases := map[string]string{}
done := map[string]struct{}{}
unresolvedAliases := map[string]string{}

for _, rel := range typeSystem.nsDef.Relation {
for _, rel := range typeSystem.Namespace().Relation {
// Ensure the relation has a rewrite...
if rel.GetUsersetRewrite() == nil {
done[rel.Name] = struct{}{}
Expand Down Expand Up @@ -72,7 +74,7 @@ func computePermissionAliases(typeSystem *ValidatedNamespaceTypeSystem) (map[str
keys = append(keys, key)
}
sort.Strings(keys)
return nil, NewPermissionsCycleErr(typeSystem.nsDef.Name, keys)
return nil, NewPermissionsCycleErr(typeSystem.Namespace().Name, keys)
}
}

Expand Down
3 changes: 2 additions & 1 deletion internal/namespace/aliasing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/require"

core "github.com/authzed/spicedb/pkg/proto/core/v1"
"github.com/authzed/spicedb/pkg/typesystem"

"github.com/authzed/spicedb/internal/datastore/memdb"
ns "github.com/authzed/spicedb/pkg/namespace"
Expand Down Expand Up @@ -200,7 +201,7 @@ func TestAliasing(t *testing.T) {
lastRevision, err := ds.HeadRevision(context.Background())
require.NoError(err)

ts, err := NewNamespaceTypeSystem(tc.toCheck, ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
ts, err := typesystem.NewNamespaceTypeSystem(tc.toCheck, typesystem.ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
require.NoError(err)

ctx := context.Background()
Expand Down
6 changes: 4 additions & 2 deletions internal/namespace/annotate.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package namespace

import "github.com/authzed/spicedb/pkg/typesystem"

// AnnotateNamespace annotates the namespace in the type system with computed aliasing and cache key
// metadata for more efficient dispatching.
func AnnotateNamespace(ts *ValidatedNamespaceTypeSystem) error {
func AnnotateNamespace(ts *typesystem.ValidatedNamespaceTypeSystem) error {
aliases, aerr := computePermissionAliases(ts)
if aerr != nil {
return aerr
Expand All @@ -13,7 +15,7 @@ func AnnotateNamespace(ts *ValidatedNamespaceTypeSystem) error {
return cerr
}

for _, rel := range ts.nsDef.Relation {
for _, rel := range ts.Namespace().Relation {
if alias, ok := aliases[rel.Name]; ok {
rel.AliasingRelation = alias
}
Expand Down
19 changes: 10 additions & 9 deletions internal/namespace/annotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/authzed/spicedb/internal/datastore/memdb"
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
"github.com/authzed/spicedb/pkg/schemadsl/input"
"github.com/authzed/spicedb/pkg/typesystem"
)

func TestAnnotateNamespace(t *testing.T) {
Expand All @@ -35,7 +36,7 @@ func TestAnnotateNamespace(t *testing.T) {
lastRevision, err := ds.HeadRevision(context.Background())
require.NoError(err)

ts, err := NewNamespaceTypeSystem(compiled.ObjectDefinitions[0], ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
ts, err := typesystem.NewNamespaceTypeSystem(compiled.ObjectDefinitions[0], typesystem.ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
require.NoError(err)

ctx := context.Background()
Expand All @@ -45,13 +46,13 @@ func TestAnnotateNamespace(t *testing.T) {
aerr := AnnotateNamespace(vts)
require.NoError(aerr)

require.NotEmpty(ts.relationMap["aliased"].AliasingRelation)
require.NotEmpty(ts.relationMap["also_aliased"].AliasingRelation)
require.Empty(ts.relationMap["computed"].AliasingRelation)
require.Empty(ts.relationMap["other"].AliasingRelation)
require.NotEmpty(ts.MustGetRelation("aliased").AliasingRelation)
require.NotEmpty(ts.MustGetRelation("also_aliased").AliasingRelation)
require.Empty(ts.MustGetRelation("computed").AliasingRelation)
require.Empty(ts.MustGetRelation("other").AliasingRelation)

require.NotEmpty(ts.relationMap["also_aliased"].CanonicalCacheKey)
require.NotEmpty(ts.relationMap["aliased"].CanonicalCacheKey)
require.NotEmpty(ts.relationMap["computed"].CanonicalCacheKey)
require.NotEmpty(ts.relationMap["other"].CanonicalCacheKey)
require.NotEmpty(ts.MustGetRelation("also_aliased").CanonicalCacheKey)
require.NotEmpty(ts.MustGetRelation("aliased").CanonicalCacheKey)
require.NotEmpty(ts.MustGetRelation("computed").CanonicalCacheKey)
require.NotEmpty(ts.MustGetRelation("other").CanonicalCacheKey)
}
9 changes: 5 additions & 4 deletions internal/namespace/canonicalization.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"hash/fnv"

"github.com/authzed/spicedb/pkg/spiceerrors"
"github.com/authzed/spicedb/pkg/typesystem"

"github.com/dalzilio/rudd"

Expand Down Expand Up @@ -54,8 +55,8 @@ const computedKeyPrefix = "%"
// canonical representation of the binary expression. These hashes can then be used for caching,
// representing the same *logical* expressions for a permission, even if the relations have
// different names.
func computeCanonicalCacheKeys(typeSystem *ValidatedNamespaceTypeSystem, aliasMap map[string]string) (map[string]string, error) {
varMap, err := buildBddVarMap(typeSystem.nsDef.Relation, aliasMap)
func computeCanonicalCacheKeys(typeSystem *typesystem.ValidatedNamespaceTypeSystem, aliasMap map[string]string) (map[string]string, error) {
varMap, err := buildBddVarMap(typeSystem.Namespace().Relation, aliasMap)
if err != nil {
return nil, err
}
Expand All @@ -70,8 +71,8 @@ func computeCanonicalCacheKeys(typeSystem *ValidatedNamespaceTypeSystem, aliasMa
}

// For each permission, build a canonicalized cache key based on its expression.
cacheKeys := make(map[string]string, len(typeSystem.nsDef.Relation))
for _, rel := range typeSystem.nsDef.Relation {
cacheKeys := make(map[string]string, len(typeSystem.Namespace().Relation))
for _, rel := range typeSystem.Namespace().Relation {
rewrite := rel.GetUsersetRewrite()
if rewrite == nil {
// If the relation has no rewrite (making it a pure relation), then its canonical
Expand Down
5 changes: 3 additions & 2 deletions internal/namespace/canonicalization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"

core "github.com/authzed/spicedb/pkg/proto/core/v1"
"github.com/authzed/spicedb/pkg/typesystem"

"github.com/authzed/spicedb/internal/datastore/memdb"
ns "github.com/authzed/spicedb/pkg/namespace"
Expand Down Expand Up @@ -389,7 +390,7 @@ func TestCanonicalization(t *testing.T) {
lastRevision, err := ds.HeadRevision(context.Background())
require.NoError(err)

ts, err := NewNamespaceTypeSystem(tc.toCheck, ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
ts, err := typesystem.NewNamespaceTypeSystem(tc.toCheck, typesystem.ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
require.NoError(err)

vts, terr := ts.Validate(ctx)
Expand Down Expand Up @@ -524,7 +525,7 @@ func TestCanonicalizationComparison(t *testing.T) {
lastRevision, err := ds.HeadRevision(context.Background())
require.NoError(err)

ts, err := NewNamespaceTypeSystem(compiled.ObjectDefinitions[0], ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
ts, err := typesystem.NewNamespaceTypeSystem(compiled.ObjectDefinitions[0], typesystem.ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
require.NoError(err)

vts, terr := ts.Validate(ctx)
Expand Down
11 changes: 6 additions & 5 deletions internal/namespace/caveats.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/authzed/spicedb/pkg/caveats"
caveattypes "github.com/authzed/spicedb/pkg/caveats/types"
core "github.com/authzed/spicedb/pkg/proto/core/v1"
"github.com/authzed/spicedb/pkg/typesystem"
)

// ValidateCaveatDefinition validates the parameters and types within the given caveat
Expand All @@ -16,7 +17,7 @@ func ValidateCaveatDefinition(caveat *core.CaveatDefinition) error {
// Ensure all parameters are used by the caveat expression itself.
parameterTypes, err := caveattypes.DecodeParameterTypes(caveat.ParameterTypes)
if err != nil {
return newTypeErrorWithSource(
return typesystem.NewTypeErrorWithSource(
fmt.Errorf("could not decode caveat parameters `%s`: %w", caveat.Name, err),
caveat,
caveat.Name,
Expand All @@ -25,15 +26,15 @@ func ValidateCaveatDefinition(caveat *core.CaveatDefinition) error {

deserialized, err := caveats.DeserializeCaveat(caveat.SerializedExpression, parameterTypes)
if err != nil {
return newTypeErrorWithSource(
return typesystem.NewTypeErrorWithSource(
fmt.Errorf("could not decode caveat `%s`: %w", caveat.Name, err),
caveat,
caveat.Name,
)
}

if len(caveat.ParameterTypes) == 0 {
return newTypeErrorWithSource(
return typesystem.NewTypeErrorWithSource(
fmt.Errorf("caveat `%s` must have at least one parameter defined", caveat.Name),
caveat,
caveat.Name,
Expand All @@ -44,15 +45,15 @@ func ValidateCaveatDefinition(caveat *core.CaveatDefinition) error {
for paramName, paramType := range caveat.ParameterTypes {
_, err := caveattypes.DecodeParameterType(paramType)
if err != nil {
return newTypeErrorWithSource(
return typesystem.NewTypeErrorWithSource(
fmt.Errorf("type error for parameter `%s` for caveat `%s`: %w", paramName, caveat.Name, err),
caveat,
paramName,
)
}

if !referencedNames.Has(paramName) {
return newTypeErrorWithSource(
return typesystem.NewTypeErrorWithSource(
NewUnusedCaveatParameterErr(caveat.Name, paramName),
caveat,
paramName,
Expand Down
5 changes: 3 additions & 2 deletions internal/namespace/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
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/typesystem"
)

// DeltaType defines the type of namespace deltas.
Expand Down Expand Up @@ -219,13 +220,13 @@ func DiffNamespaces(existing *core.NamespaceDefinition, updated *core.NamespaceD
allowedRelsBySource := map[string]*core.AllowedRelation{}

for _, existingAllowed := range existingTypeInfo.AllowedDirectRelations {
source := SourceForAllowedRelation(existingAllowed)
source := typesystem.SourceForAllowedRelation(existingAllowed)
allowedRelsBySource[source] = existingAllowed
existingAllowedRels.Add(source)
}

for _, updatedAllowed := range updatedTypeInfo.AllowedDirectRelations {
source := SourceForAllowedRelation(updatedAllowed)
source := typesystem.SourceForAllowedRelation(updatedAllowed)
allowedRelsBySource[source] = updatedAllowed
updatedAllowedRels.Add(source)
}
Expand Down
Loading

0 comments on commit ab6946b

Please sign in to comment.