graph.Builder.RuleToGraph   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nop 1
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
package graph
2
3
import (
4
	"errors"
5
	"fmt"
6
7
	"github.com/rs/xid"
8
9
	"github.com/Permify/permify/internal/schema"
10
	base "github.com/Permify/permify/pkg/pb/base/v1"
11
)
12
13
type Builder struct {
14
	schema *base.SchemaDefinition
15
}
16
17
// NewBuilder creates a new Builder object.
18
func NewBuilder(schema *base.SchemaDefinition) Builder {
19
	return Builder{schema: schema}
20
}
21
22
// SchemaToGraph converts a schema definition into a graph representation.
23
func (b Builder) SchemaToGraph() (g Graph, err error) {
24
	for _, entity := range b.schema.GetEntityDefinitions() {
25
		eg, err := b.EntityToGraph(entity)
26
		if err != nil {
27
			return Graph{}, fmt.Errorf("failed to convert entity to graph: %w", err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
28
		}
29
		g.AddNodes(eg.Nodes())
30
		g.AddEdges(eg.Edges())
31
	}
32
	for _, rule := range b.schema.GetRuleDefinitions() {
33
		rg, err := b.RuleToGraph(rule)
34
		if err != nil {
35
			return Graph{}, fmt.Errorf("failed to convert entity to graph: %w", err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
36
		}
37
		g.AddNodes(rg.Nodes())
38
	}
39
	return
40
}
41
42
// EntityToGraph takes an entity definition and converts it into a graph
43
// representation, returning the created graph and an error if any occurs.
44
func (b Builder) EntityToGraph(entity *base.EntityDefinition) (g Graph, err error) {
45
	// Create a node for the entity
46
	enNode := &Node{
47
		Type:  "entity",
48
		ID:    entity.GetName(),
49
		Label: entity.GetName(),
50
	}
51
	g.AddNode(enNode)
52
53
	// Iterate through the relations in the entity
54
	for _, re := range entity.GetRelations() {
55
		// Create a node for each relation
56
		reNode := &Node{
57
			Type:  "relation",
58
			ID:    fmt.Sprintf("%s#%s", entity.GetName(), re.GetName()),
59
			Label: re.GetName(),
60
		}
61
62
		// Iterate through the relation references
63
		for _, ref := range re.GetRelationReferences() {
64
			if ref.GetRelation() != "" {
65
				g.AddEdge(reNode, &Node{
66
					Type:  "relation",
67
					ID:    fmt.Sprintf("%s#%s", ref.GetType(), ref.GetRelation()),
68
					Label: re.GetName(),
69
				})
70
			} else {
71
				g.AddEdge(reNode, &Node{
72
					Type:  "entity",
73
					ID:    ref.GetType(),
74
					Label: re.GetName(),
75
				})
76
			}
77
		}
78
79
		// Add relation node and edge to the graph
80
		g.AddNode(reNode)
81
		g.AddEdge(enNode, reNode)
82
	}
83
84
	// Iterate through the permissions in the entity
85
	for _, permission := range entity.GetPermissions() {
86
		// Create a node for each permission
87
		acNode := &Node{
88
			Type:  "permission",
89
			ID:    fmt.Sprintf("%s#%s", entity.GetName(), permission.GetName()),
90
			Label: permission.GetName(),
91
		}
92
		g.AddNode(acNode)
93
		g.AddEdge(enNode, acNode)
94
		// Build permission graph for each permission
95
		ag, err := b.buildPermissionGraph(entity, acNode, []*base.Child{permission.GetChild()})
96
		if err != nil {
97
			return Graph{}, err
98
		}
99
		// Add nodes and edges from permission graph to entity graph
100
		g.AddNodes(ag.Nodes())
101
		g.AddEdges(ag.Edges())
102
	}
103
104
	// Iterate through the relations in the entity
105
	for _, at := range entity.GetAttributes() {
106
		// Create a node for each relation
107
		reNode := &Node{
108
			Type:  "attribute",
109
			ID:    fmt.Sprintf("%s$%s", entity.GetName(), at.GetName()),
110
			Label: at.GetName(),
111
		}
112
113
		g.AddNode(reNode)
114
		g.AddEdge(enNode, reNode)
115
	}
116
117
	return
118
}
119
120
// RuleToGraph converts a RuleDefinition into a graph.
121
// It takes a RuleDefinition as input and constructs a graph representing the rule.
122
// The graph consists of a single node representing the rule itself.
123
func (b Builder) RuleToGraph(rule *base.RuleDefinition) (g Graph, err error) {
124
	// Create a node for the rule
125
	enNode := &Node{
126
		Type:  "rule",
127
		ID:    rule.GetName(),
128
		Label: rule.GetName(),
129
	}
130
131
	// Add the rule node to the graph
132
	g.AddNode(enNode)
133
134
	return
135
}
136
137
// buildPermissionGraph recursively builds a permission graph.
138
// It takes an entity definition, a starting node, and a list of children as input.
139
// It returns the constructed graph and any encountered errors.
140
func (b Builder) buildPermissionGraph(entity *base.EntityDefinition, from *Node, children []*base.Child) (g Graph, err error) {
141
	// Iterate through the list of children
142
	for _, child := range children {
143
		switch child.GetType().(type) {
144
		// Handle Rewrite type children
145
		case *base.Child_Rewrite:
146
			rw := &Node{
147
				Type:  "operation",
148
				ID:    xid.New().String(),
149
				Label: child.GetRewrite().GetRewriteOperation().String(),
150
			}
151
152
			// Add the Rewrite node to the graph
153
			g.AddNode(rw)
154
155
			// Connect the Rewrite node to the current 'from' node
156
			g.AddEdge(from, rw)
157
158
			// Recursively build the permission graph for Rewrite children
159
			ag, err := b.buildPermissionGraph(entity, rw, child.GetRewrite().GetChildren())
160
			if err != nil {
161
				return Graph{}, err
162
			}
163
164
			// Add the nodes and edges from the recursively built graph to the current graph
165
			g.AddNodes(ag.Nodes())
166
			g.AddEdges(ag.Edges())
167
168
		// Handle Leaf type children
169
		case *base.Child_Leaf:
170
			leaf := child.GetLeaf()
171
172
			switch leaf.GetType().(type) {
173
			// Handle TupleToUserSet type leaf
174
			case *base.Leaf_TupleToUserSet:
175
				re, err := schema.GetRelationByNameInEntityDefinition(entity, leaf.GetTupleToUserSet().GetTupleSet().GetRelation())
176
				if err != nil {
177
					return Graph{}, errors.New(base.ErrorCode_ERROR_CODE_RELATION_DEFINITION_NOT_FOUND.String())
178
				}
179
180
				// Add edges from the current 'from' node to referenced relations
181
				for _, r := range re.GetRelationReferences() {
182
					ag, err := b.addEdgeFromRelation(from, r, leaf)
183
					if err != nil {
184
						return Graph{}, err
185
					}
186
					g.AddNodes(ag.Nodes())
187
					g.AddEdges(ag.Edges())
188
				}
189
190
			// Handle ComputedUserSet type leaf
191
			case *base.Leaf_ComputedUserSet:
192
				g.AddEdge(from, &Node{
193
					Type:  "relation",
194
					ID:    fmt.Sprintf("%s#%s", entity.GetName(), leaf.GetComputedUserSet().GetRelation()),
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
195
					Label: leaf.GetComputedUserSet().GetRelation(),
196
				})
197
198
			// Handle ComputedAttribute type leaf
199
			case *base.Leaf_ComputedAttribute:
200
				g.AddEdge(from, &Node{
201
					Type:  "attribute",
202
					ID:    fmt.Sprintf("%s$%s", entity.GetName(), leaf.GetComputedAttribute().GetName()),
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
203
					Label: leaf.GetComputedAttribute().GetName(),
204
				})
205
206
			// Handle Call type leaf
207
			case *base.Leaf_Call:
208
				g.AddEdge(from, &Node{
209
					Type:  "rule",
210
					ID:    leaf.GetCall().GetRuleName(),
211
					Label: leaf.GetCall().GetRuleName(),
212
				})
213
214
				// Add edges for arguments of Call type leaf
215
				for _, arg := range leaf.GetCall().GetArguments() {
216
					switch op := arg.GetType().(type) {
217
					case *base.Argument_ComputedAttribute:
218
						g.AddEdge(&Node{
219
							Type:  "attribute",
220
							ID:    fmt.Sprintf("%s$%s", entity.GetName(), op.ComputedAttribute.GetName()),
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
221
							Label: op.ComputedAttribute.GetName(),
222
						}, &Node{
223
							Type:  "rule",
224
							ID:    leaf.GetCall().GetRuleName(),
225
							Label: leaf.GetCall().GetRuleName(),
226
						})
227
					default:
228
						break
229
					}
230
				}
231
			default:
232
				break
233
			}
234
		}
235
	}
236
	return
237
}
238
239
// AddEdgeFromRelation adds an edge to the graph from the relation information
240
func (b Builder) addEdgeFromRelation(from *Node, reference *base.RelationReference, leaf *base.Leaf) (g Graph, err error) {
241
	if reference.GetRelation() != "" {
242
		upperen, err := schema.GetEntityByName(b.schema, reference.GetType())
243
		if err != nil {
244
			return Graph{}, err
245
		}
246
		re, err := schema.GetRelationByNameInEntityDefinition(upperen, leaf.GetTupleToUserSet().GetTupleSet().GetRelation())
247
		if err != nil {
248
			return Graph{}, errors.New(base.ErrorCode_ERROR_CODE_RELATION_DEFINITION_NOT_FOUND.String())
249
		}
250
		for _, r := range re.GetRelationReferences() {
251
			ag, err := b.addEdgeFromRelation(from, r, leaf)
252
			if err != nil {
253
				return Graph{}, err
254
			}
255
			g.AddNodes(ag.Nodes())
256
			g.AddEdges(ag.Edges())
257
		}
258
	} else {
259
		// Add an edge between the parent node and the tuple set relation node
260
		g.AddEdge(from, &Node{
261
			Type:  "relation",
262
			ID:    fmt.Sprintf("%s#%s", reference.GetType(), leaf.GetTupleToUserSet().GetComputed().GetRelation()),
263
			Label: leaf.GetTupleToUserSet().GetComputed().GetRelation(),
264
		})
265
	}
266
	return
267
}
268