Passed
Pull Request — master (#1460)
by Tolga
02:54
created

internal/schema/walker.go   A

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 83
dl 0
loc 161
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
C schema.*Walker.Walk 0 49 9
A schema.NewWalker 0 4 1
B schema.*Walker.WalkRewrite 0 23 7
C schema.*Walker.WalkLeaf 0 48 9
A schema.*Walker.WalkComputedUserSet 0 5 1
1
package schema
2
3
import (
4
	"errors"
5
6
	"github.com/Permify/permify/pkg/dsl/utils"
7
	base "github.com/Permify/permify/pkg/pb/base/v1"
8
)
9
10
// Walker is a struct used for traversing a schema
11
type Walker struct {
12
	schema *base.SchemaDefinition
13
14
	// map used to track visited nodes and avoid infinite recursion
15
	visited map[string]struct{}
16
}
17
18
// NewWalker is a constructor for the Walker struct
19
func NewWalker(schema *base.SchemaDefinition) *Walker {
20
	return &Walker{
21
		schema:  schema,
22
		visited: make(map[string]struct{}),
23
	}
24
}
25
26
// Walk traverses the schema based on entity type and permission
27
func (w *Walker) Walk(
28
	entityType string,
29
	permission string,
30
) error {
31
	// Generate a unique key for the entityType and permission combination
32
	key := utils.Key(entityType, permission)
33
34
	// Check if the entity-permission combination has already been visited
35
	if _, ok := w.visited[key]; ok {
36
		// If already visited, exit early to avoid redundant processing or infinite recursion
37
		return nil
38
	}
39
40
	// Mark the entity-permission combination as visited
41
	w.visited[key] = struct{}{}
42
43
	// Lookup the entity definition in the schema
44
	def, ok := w.schema.EntityDefinitions[entityType]
45
	if !ok {
46
		// Error is returned if entity definition is not found
47
		return errors.New(base.ErrorCode_ERROR_CODE_ENTITY_DEFINITION_NOT_FOUND.String())
48
	}
49
50
	// Switch on the type of reference specified by the permission
51
	switch def.References[permission] {
52
	case base.EntityDefinition_REFERENCE_PERMISSION:
53
		// If the reference type is a permission, look up the permission
54
		permission, ok := def.Permissions[permission]
55
		if !ok {
56
			// Error is returned if permission is not found
57
			return errors.New(base.ErrorCode_ERROR_CODE_PERMISSION_NOT_FOUND.String())
58
		}
59
		// Check if the permission has a child element
60
		child := permission.GetChild()
61
		// If the child has a rewrite rule, walk the rewrite rule
62
		if child.GetRewrite() != nil {
63
			return w.WalkRewrite(entityType, child.GetRewrite())
64
		}
65
		// Otherwise, walk the leaf node
66
		return w.WalkLeaf(entityType, child.GetLeaf())
67
	case base.EntityDefinition_REFERENCE_RELATION:
68
		// If the reference type is a relation, nothing to do, return nil
69
		return nil
70
	case base.EntityDefinition_REFERENCE_ATTRIBUTE:
71
		// If the reference type is an attribute, not implemented, return error
72
		return ErrUnimplemented
73
	default:
74
		// For any other reference type, not implemented, return error
75
		return errors.New(base.ErrorCode_ERROR_CODE_UNDEFINED_CHILD_KIND.String())
76
	}
77
}
78
79
// WalkRewrite is a method that walks through the rewrite part of the schema
80
func (w *Walker) WalkRewrite(
81
	entityType string,
82
	rewrite *base.Rewrite,
83
) error {
84
	// Loop through each child in the rewrite
85
	for _, child := range rewrite.GetChildren() {
86
		// Switch on the type of the child
87
		switch child.GetType().(type) {
88
		case *base.Child_Rewrite:
89
			if err := w.WalkRewrite(entityType, child.GetRewrite()); err != nil {
90
				return err
91
			}
92
		case *base.Child_Leaf:
93
			if err := w.WalkLeaf(entityType, child.GetLeaf()); err != nil {
94
				return err
95
			}
96
		default:
97
			// For any other child type, return an error indicating an undefined child type
98
			return errors.New(base.ErrorCode_ERROR_CODE_UNDEFINED_CHILD_KIND.String())
99
		}
100
	}
101
	// If no errors occurred during the loop, return nil
102
	return nil
103
}
104
105
// WalkComputedUserSet walk the relation within the ComputedUserSet for the given entityType.
106
func (w *Walker) WalkComputedUserSet(
107
	entityType string,
108
	cu *base.ComputedUserSet,
109
) error {
110
	return w.Walk(entityType, cu.GetRelation())
111
}
112
113
// WalkLeaf is a method that walks through the leaf part of the schema
114
func (w *Walker) WalkLeaf(
115
	entityType string,
116
	leaf *base.Leaf,
117
) error {
118
	// Switch on the type of the leaf
119
	switch t := leaf.GetType().(type) {
120
	case *base.Leaf_TupleToUserSet:
121
		// Handle case where the leaf is a tuple to user set
122
		tupleSet := t.TupleToUserSet.GetTupleSet().GetRelation()
123
		computedUserSet := t.TupleToUserSet.GetComputed()
124
125
		// Look up the entity definition
126
		entityDefinitions, exists := w.schema.EntityDefinitions[entityType]
127
		if !exists {
128
			// Return error if entity definition is not found
129
			return errors.New(base.ErrorCode_ERROR_CODE_ENTITY_DEFINITION_NOT_FOUND.String())
130
		}
131
132
		// Look up the relations in the entity definition
133
		relations, exists := entityDefinitions.Relations[tupleSet]
134
		if !exists {
135
			// Return error if relations is not found
136
			return errors.New(base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
137
		}
138
139
		// Walk each relation reference
140
		for _, rel := range relations.GetRelationReferences() {
141
			return w.WalkComputedUserSet(rel.GetType(), computedUserSet)
142
		}
143
144
		// If no errors occur, return nil
145
		return nil
146
	case *base.Leaf_ComputedUserSet:
147
		// Handle case where the leaf is a computed user set
148
		// Walk the entity type and relation
149
		return w.WalkComputedUserSet(entityType, t.ComputedUserSet)
150
	case *base.Leaf_ComputedAttribute:
151
		// Handle case where the leaf is a computed attribute
152
		// This is currently unimplemented, so return an error
153
		return ErrUnimplemented
154
	case *base.Leaf_Call:
155
		// Handle case where the leaf is a call
156
		// This is currently unimplemented, so return an error
157
		return ErrUnimplemented
158
	default:
159
		// Handle any other type of leaf
160
		// Return an error indicating the leaf type is undefined
161
		return ErrUndefinedLeafType
162
	}
163
}
164