Skip to content

Commit

Permalink
Extract the type system and reachability graph into pkg
Browse files Browse the repository at this point in the history
This will allow external tooling such as zed to use these systems, for example for creating well-typed client libraries
  • Loading branch information
josephschorr committed Oct 6, 2023
1 parent 9e6080a commit 4ec74f7
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 4ec74f7

Please sign in to comment.