Passed
Pull Request — master (#1603)
by Tolga
03:57
created

compiler.*Compiler.compileCall   D

Complexity

Conditions 12

Size

Total Lines 87
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 40
nop 2
dl 0
loc 87
rs 4.8
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like compiler.*Compiler.compileCall often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package compiler
2
3
import (
4
	"errors"
5
	"fmt"
6
	"strings"
7
8
	"github.com/google/cel-go/cel"
9
10
	"github.com/Permify/permify/pkg/dsl/ast"
11
	"github.com/Permify/permify/pkg/dsl/token"
12
	"github.com/Permify/permify/pkg/dsl/utils"
13
	base "github.com/Permify/permify/pkg/pb/base/v1"
14
)
15
16
// Compiler compiles an AST schema into a list of entity definitions.
17
type Compiler struct {
18
	// The AST schema to be compiled
19
	schema *ast.Schema
20
	// Whether to skip reference validation during compilation
21
	withReferenceValidation bool
22
}
23
24
// NewCompiler returns a new Compiler instance with the given schema and reference validation flag.
25
func NewCompiler(w bool, sch *ast.Schema) *Compiler {
26
	return &Compiler{
27
		withReferenceValidation: w,
28
		schema:                  sch,
29
	}
30
}
31
32
// Compile compiles the schema into a list of entity definitions.
33
// Returns a slice of EntityDefinition pointers and an error, if any.
34
func (t *Compiler) Compile() ([]*base.EntityDefinition, []*base.RuleDefinition, error) {
35
	// If withoutReferenceValidation is not set to true, validate the schema for reference errors.
36
	if t.withReferenceValidation {
37
		err := t.schema.Validate()
38
		if err != nil {
39
			return nil, nil, err
40
		}
41
	}
42
43
	// Create an empty slice to hold the entity definitions.
44
	entities := make([]*base.EntityDefinition, 0, len(t.schema.Statements))
45
	rules := make([]*base.RuleDefinition, 0, len(t.schema.Statements))
46
47
	// Loop through each statement in the schema.
48
	for _, statement := range t.schema.Statements {
49
		switch statement.(type) {
50
		case *ast.EntityStatement:
51
			// Check if the statement is an EntityStatement.
52
			entityStatement, ok := statement.(*ast.EntityStatement)
53
			if !ok {
54
				// If the statement is not an EntityStatement, return a compile error.
55
				return nil, nil, compileError(entityStatement.Entity.PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
56
			}
57
58
			// Compile the EntityStatement into an EntityDefinition.
59
			entityDef, err := t.compileEntity(entityStatement)
60
			if err != nil {
61
				return nil, nil, err
62
			}
63
64
			// Append the EntityDefinition to the slice of entity definitions.
65
			entities = append(entities, entityDef)
66
		case *ast.RuleStatement:
67
			// Check if the statement is a RuleStatement.
68
			ruleStatement, ok := statement.(*ast.RuleStatement)
69
			if !ok {
70
				// If the statement is not a RuleStatement, return a compile error.
71
				return nil, nil, compileError(ruleStatement.Rule.PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
72
			}
73
74
			// Compile the RuleStatement into a RuleDefinition.
75
			ruleDef, err := t.compileRule(ruleStatement)
76
			if err != nil {
77
				return nil, nil, err
78
			}
79
80
			// Append the RuleDefinition to the slice of rule definitions.
81
			rules = append(rules, ruleDef)
82
		default:
83
			return nil, nil, errors.New("invalid statement")
84
		}
85
	}
86
87
	return entities, rules, nil
88
}
89
90
// compile - compiles an EntityStatement into an EntityDefinition
91
func (t *Compiler) compileEntity(sc *ast.EntityStatement) (*base.EntityDefinition, error) {
92
	// Initialize the entity definition
93
	entityDefinition := &base.EntityDefinition{
94
		Name:        sc.Name.Literal,
95
		Relations:   map[string]*base.RelationDefinition{},
96
		Attributes:  map[string]*base.AttributeDefinition{},
97
		Permissions: map[string]*base.PermissionDefinition{},
98
		References:  map[string]base.EntityDefinition_Reference{},
99
	}
100
101
	// Compile relations
102
	for _, rs := range sc.RelationStatements {
103
		// Cast the relation statement
104
		st, okRs := rs.(*ast.RelationStatement)
105
		if !okRs {
106
			return nil, compileError(st.Relation.PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
107
		}
108
109
		// Initialize the relation definition
110
		relationDefinition := &base.RelationDefinition{
111
			Name:               st.Name.Literal,
112
			RelationReferences: []*base.RelationReference{},
113
		}
114
115
		// Compile the relation types
116
		for _, rts := range st.RelationTypes {
117
			relationDefinition.RelationReferences = append(relationDefinition.RelationReferences, &base.RelationReference{
118
				Type:     rts.Type.Literal,
119
				Relation: rts.Relation.Literal,
120
			})
121
		}
122
123
		// Add the relation definition and reference
124
		entityDefinition.Relations[relationDefinition.GetName()] = relationDefinition
125
		entityDefinition.References[relationDefinition.GetName()] = base.EntityDefinition_REFERENCE_RELATION
126
	}
127
128
	for _, as := range sc.AttributeStatements {
129
		st, okAs := as.(*ast.AttributeStatement)
130
		if !okAs {
131
			return nil, compileError(st.Attribute.PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
132
		}
133
134
		typ, err := getArgumentTypeIfExist(st.AttributeType)
135
		if err != nil {
136
			return nil, err
137
		}
138
139
		attributeDefinition := &base.AttributeDefinition{
140
			Name: st.Name.Literal,
141
			Type: typ,
142
		}
143
144
		entityDefinition.Attributes[attributeDefinition.GetName()] = attributeDefinition
145
		entityDefinition.References[attributeDefinition.GetName()] = base.EntityDefinition_REFERENCE_ATTRIBUTE
146
	}
147
148
	// Compile permissions
149
	for _, ps := range sc.PermissionStatements {
150
		// Cast the permission statement
151
		st, okAs := ps.(*ast.PermissionStatement)
152
		if !okAs {
153
			return nil, compileError(st.Permission.PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
154
		}
155
156
		// Compile the child expression
157
		ch, err := t.compileExpressionStatement(entityDefinition.GetName(), st.ExpressionStatement.(*ast.ExpressionStatement))
158
		if err != nil {
159
			return nil, err
160
		}
161
162
		// Initialize the permission definition and reference
163
		permissionDefinition := &base.PermissionDefinition{
164
			Name:  st.Name.Literal,
165
			Child: ch,
166
		}
167
		entityDefinition.Permissions[permissionDefinition.GetName()] = permissionDefinition
168
		entityDefinition.References[permissionDefinition.GetName()] = base.EntityDefinition_REFERENCE_PERMISSION
169
	}
170
171
	return entityDefinition, nil
172
}
173
174
// compileRule compiles an ast.RuleStatement into a base.RuleDefinition object.
175
// It takes an *ast.RuleStatement as input, processes its arguments, and
176
// returns a *base.RuleDefinition or an error.
177
func (t *Compiler) compileRule(sc *ast.RuleStatement) (*base.RuleDefinition, error) {
178
	// Initialize a new base.RuleDefinition with the name and body from the rule statement.
179
	// The Arguments field is initialized as an empty map.
180
	ruleDefinition := &base.RuleDefinition{
181
		Name:      sc.Name.Literal,
182
		Arguments: map[string]base.AttributeType{},
183
	}
184
185
	var envOptions []cel.EnvOption
186
	envOptions = append(envOptions, cel.Variable("context", cel.DynType))
187
188
	// Iterate over the arguments in the rule statement.
189
	for name, ty := range sc.Arguments {
190
		// For each argument, use the getArgumentTypeIfExist function to determine the attribute type.
191
		typ, err := getArgumentTypeIfExist(ty)
192
		// If the attribute type is not recognized, return an error.
193
		if err != nil {
194
			return nil, err
195
		}
196
197
		cType, err := utils.GetCelType(typ)
198
		if err != nil {
199
			return nil, err
200
		}
201
202
		// Add the argument name and its corresponding attribute type to the Arguments map in the rule definition.
203
		ruleDefinition.Arguments[name.Literal] = typ
204
		envOptions = append(envOptions, cel.Variable(name.Literal, cType))
205
	}
206
207
	// Variables used within this expression environment.
208
	env, err := cel.NewEnv(envOptions...)
209
	if err != nil {
210
		return nil, err
211
	}
212
213
	// Compile and type-check the expression.
214
	compiledExp, issues := env.Compile(sc.Expression)
215
	if issues != nil && issues.Err() != nil {
216
		pi := sc.Name.PositionInfo
217
		pi.LinePosition++
218
		return nil, compileError(pi, issues.Err().Error())
219
	}
220
221
	if compiledExp.OutputType() != cel.BoolType {
222
		return nil, compileError(sc.Name.PositionInfo, fmt.Sprintf("rule expression must result in a boolean type not %s", compiledExp.OutputType().String()))
223
	}
224
225
	expr, err := cel.AstToCheckedExpr(compiledExp)
226
	if err != nil {
227
		return nil, err
228
	}
229
230
	ruleDefinition.Expression = expr
231
232
	// Return the completed rule definition and no error.
233
	return ruleDefinition, nil
234
}
235
236
// compileExpressionStatement compiles an ExpressionStatement into a Child node that can be used to construct an PermissionDefinition.
237
// It calls compileChildren to compile the expression into Child node(s).
238
// entityName is passed as an argument to the function to use it as a reference to the parent entity.
239
// Returns a pointer to a Child and an error if the compilation process fails.
240
func (t *Compiler) compileExpressionStatement(entityName string, expression *ast.ExpressionStatement) (*base.Child, error) {
241
	return t.compileChildren(entityName, expression.Expression)
242
}
243
244
// compileChildren - compiles the child nodes of an expression and returns a Child struct that represents them.
245
func (t *Compiler) compileChildren(entityName string, expression ast.Expression) (*base.Child, error) {
246
	if expression.IsInfix() {
247
		return t.compileRewrite(entityName, expression.(*ast.InfixExpression))
248
	}
249
	return t.compileLeaf(entityName, expression)
250
}
251
252
// compileRewrite - Compiles an InfixExpression node of type OR or AND to a base.Child struct with a base.Rewrite struct
253
// representing the logical operation of the expression. Recursively calls compileChildren to compile the child nodes.
254
// Parameters:
255
// - entityName: The name of the entity being compiled
256
// - exp: The InfixExpression node being compiled
257
// Returns:
258
// - *base.Child: A pointer to a base.Child struct representing the expression
259
// - error: An error if one occurred during compilation
260
func (t *Compiler) compileRewrite(entityName string, exp *ast.InfixExpression) (*base.Child, error) {
261
	var err error
262
263
	child := &base.Child{}
264
	rewrite := &base.Rewrite{}
265
266
	switch exp.Operator {
267
	case ast.OR:
268
		rewrite.RewriteOperation = base.Rewrite_OPERATION_UNION
269
	case ast.AND:
270
		rewrite.RewriteOperation = base.Rewrite_OPERATION_INTERSECTION
271
	case ast.NOT:
272
		rewrite.RewriteOperation = base.Rewrite_OPERATION_EXCLUSION
273
	default:
274
		rewrite.RewriteOperation = base.Rewrite_OPERATION_UNSPECIFIED
275
	}
276
277
	var ch []*base.Child
278
279
	var leftChild *base.Child
280
	leftChild, err = t.compileChildren(entityName, exp.Left)
281
	if err != nil {
282
		return nil, err
283
	}
284
285
	var rightChild *base.Child
286
	rightChild, err = t.compileChildren(entityName, exp.Right)
287
	if err != nil {
288
		return nil, err
289
	}
290
291
	ch = append(ch, []*base.Child{leftChild, rightChild}...)
292
293
	rewrite.Children = ch
294
	child.Type = &base.Child_Rewrite{Rewrite: rewrite}
295
	child.GetRewrite().Children = ch
296
	return child, nil
297
}
298
299
// compileLeaf is responsible for compiling a given AST (Abstract Syntax Tree) expression into a base.Child node.
300
// It does this based on the type of the provided expression.
301
// It expects either an identifier (a variable, a constant, etc.) or a function call.
302
// If the expression is neither of these types, it returns an error indicating that the relation definition was not found.
303
func (t *Compiler) compileLeaf(entityName string, expression ast.Expression) (*base.Child, error) {
304
	// Switch on the type of the expression.
305
	switch expression.GetType() {
306
307
	// Case when the expression is an Identifier (a variable, a constant, etc.).
308
	case ast.IDENTIFIER:
309
		// Type assertion to get the underlying Identifier.
310
		ident := expression.(*ast.Identifier)
311
312
		// Compile the identifier and return the result.
313
		return t.compileIdentifier(entityName, ident)
314
315
	// Case when the expression is a Call (a function call).
316
	case ast.CALL:
317
		// Type assertion to get the underlying Call.
318
		call := expression.(*ast.Call)
319
320
		// Compile the call and return the result.
321
		return t.compileCall(entityName, call)
322
323
	// Default case when the expression type is neither an Identifier nor a Call.
324
	default:
325
		// Return a nil Child and an error indicating that the relation definition was not found.
326
		return nil, compileError(token.PositionInfo{}, base.ErrorCode_ERROR_CODE_RELATION_DEFINITION_NOT_FOUND.String())
327
	}
328
}
329
330
// compileIdentifier compiles an ast.Identifier into a base.Child object.
331
// Depending on the length of the identifier and its type, it returns different types of Child object.
332
func (t *Compiler) compileIdentifier(entityName string, ident *ast.Identifier) (*base.Child, error) {
333
	// Initialize a new base.Child
334
	child := &base.Child{}
335
336
	// If the identifier has no segments, return an error
337
	if len(ident.Idents) == 0 {
338
		return nil, compileError(token.PositionInfo{
339
			LinePosition:   1,
340
			ColumnPosition: 1,
341
		}, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
342
	}
343
344
	// If the identifier has one segment
345
	if len(ident.Idents) == 1 {
346
		// Check the type of the reference from the schema
347
		typ, exist := t.schema.GetReferences().GetReferenceType(utils.Key(entityName, ident.Idents[0].Literal))
348
349
		// If reference validation is enabled and the reference does not exist, return an error
350
		if t.withReferenceValidation {
351
			if !exist {
352
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
353
			}
354
		}
355
356
		// If the reference type is an attribute
357
		if typ == ast.ATTRIBUTE {
358
			// Get the attribute reference type from the schema
359
			at, exist := t.schema.GetReferences().GetAttributeReferenceTypeIfExist(utils.Key(entityName, ident.Idents[0].Literal))
360
			// If the attribute reference type does not exist or is not boolean, return an error
361
			if !exist || at.String() != "boolean" {
362
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
363
			}
364
365
			// Compile the identifier into a ComputedAttributeIdentifier
366
			leaf, err := t.compileComputedAttributeIdentifier(ident.Idents[0].Literal)
367
			if err != nil {
368
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
369
			}
370
371
			// Set the Type of the Child to the compiled Leaf
372
			child.Type = &base.Child_Leaf{Leaf: leaf}
373
			return child, nil
374
		} else { // The reference type is a user set
375
			// Compile the identifier into a ComputedUserSetIdentifier
376
			leaf, err := t.compileComputedUserSetIdentifier(ident.Idents[0].Literal)
377
			if err != nil {
378
				return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
379
			}
380
381
			// Set the Type of the Child to the compiled Leaf
382
			child.Type = &base.Child_Leaf{Leaf: leaf}
383
			return child, nil
384
		}
385
	}
386
387
	// If the identifier has two segments
388
	if len(ident.Idents) == 2 {
389
		// If reference validation is enabled, validate the tuple to user set reference
390
		if t.withReferenceValidation {
391
			err := t.validateTupleToUserSetReference(entityName, ident)
392
			if err != nil {
393
				return nil, err
394
			}
395
		}
396
397
		// Compile the identifier into a TupleToUserSetIdentifier
398
		leaf, err := t.compileTupleToUserSetIdentifier(ident.Idents[0].Literal, ident.Idents[1].Literal)
399
		if err != nil {
400
			return nil, compileError(ident.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
401
		}
402
403
		// Set the Type of the Child to the compiled Leaf
404
		child.Type = &base.Child_Leaf{Leaf: leaf}
405
		return child, nil
406
	}
407
408
	// If the identifier has more than two segments, return an error
409
	return nil, compileError(ident.Idents[2].PositionInfo, base.ErrorCode_ERROR_CODE_NOT_SUPPORTED_RELATION_WALK.String())
410
}
411
412
// compileCall compiles a function call within the Compiler.
413
// It takes the entityName and a pointer to an ast.Call object representing the function call.
414
// It returns a pointer to a base.Child object and an error, if any.
415
func (t *Compiler) compileCall(entityName string, call *ast.Call) (*base.Child, error) {
416
	// Create a new base.Child to store the compiled information for the call.
417
	child := &base.Child{}
418
419
	// Create a slice to store the call arguments.
420
	var arguments []*base.Argument
421
422
	// Create a map to store the types of the rule arguments, only if reference validation is enabled.
423
	var types map[string]string
424
425
	// If reference validation is enabled, try to get the rule argument types from the schema for the specific call.
426
	// If the call's rule does not exist in the schema, return an error.
427
	if t.withReferenceValidation {
428
		var exist bool
429
		types, exist = t.schema.GetReferences().GetRuleArgumentTypesIfRuleExist(call.Name.Literal)
430
		if !exist {
431
			return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_RULE_REFERENCE.String())
432
		}
433
434
		if len(types) != len(call.Arguments) {
435
			return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_MISSING_ARGUMENT.String())
436
		}
437
	}
438
439
	if len(call.Arguments) == 0 {
440
		return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_MISSING_ARGUMENT.String())
441
	}
442
443
	// Loop through each argument in the call.
444
	for _, argument := range call.Arguments {
445
446
		// Check if the argument has no identifiers, which is not allowed.
447
		// Return an error if this is the case.
448
		if len(argument.Idents) == 0 {
449
			return nil, compileError(token.PositionInfo{
450
				LinePosition:   1,
451
				ColumnPosition: 1,
452
			}, base.ErrorCode_ERROR_CODE_SCHEMA_COMPILE.String())
453
		}
454
455
		// If the argument has only one identifier, it is a computed attribute.
456
		if len(argument.Idents) == 1 {
457
458
			// If reference validation is enabled, check if the attribute reference exists and its type matches the rule's argument type.
459
			if t.withReferenceValidation {
460
				atyp, exist := t.schema.GetReferences().GetAttributeReferenceTypeIfExist(utils.Key(entityName, argument.Idents[0].Literal))
461
				if !exist {
462
					return nil, compileError(call.Name.PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_RULE_REFERENCE.String())
463
				}
464
465
				// Get the type of the rule argument from the types map and compare it with the attribute reference type.
466
				typeInfo, exist := types[argument.Idents[0].Literal]
467
				if !exist {
468
					return nil, compileError(argument.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String())
469
				}
470
471
				if typeInfo != atyp.String() {
472
					return nil, compileError(argument.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String())
473
				}
474
			}
475
476
			// Append the computed attribute to the arguments slice.
477
			arguments = append(arguments, &base.Argument{
478
				Type: &base.Argument_ComputedAttribute{
479
					ComputedAttribute: &base.ComputedAttribute{
480
						Name: argument.Idents[0].Literal,
481
					},
482
				},
483
			})
484
			continue
485
		}
486
487
		// If the argument has more than two identifiers, it indicates an unsupported relation walk.
488
		// Return an error in this case.
489
		return nil, compileError(argument.Idents[2].PositionInfo, base.ErrorCode_ERROR_CODE_NOT_SUPPORTED_WALK.String())
490
	}
491
492
	// Set the child's type to be a leaf with the compiled call information.
493
	child.Type = &base.Child_Leaf{Leaf: &base.Leaf{
494
		Type: &base.Leaf_Call{Call: &base.Call{
495
			RuleName:  call.Name.Literal,
496
			Arguments: arguments,
497
		}},
498
	}}
499
500
	// Return the compiled child and nil error to indicate success.
501
	return child, nil
502
}
503
504
// compileComputedUserSetIdentifier takes a string that represents a user set relation
505
// and compiles it into a base.Leaf object containing that relation. It returns the resulting Leaf and no error.
506
func (t *Compiler) compileComputedUserSetIdentifier(r string) (l *base.Leaf, err error) {
507
	// Initialize a new base.Leaf
508
	leaf := &base.Leaf{}
509
510
	// Initialize a new base.ComputedUserSet with the provided relation
511
	computedUserSet := &base.ComputedUserSet{
512
		Relation: r,
513
	}
514
515
	// Set the Type of the Leaf to the newly created ComputedUserSet
516
	leaf.Type = &base.Leaf_ComputedUserSet{ComputedUserSet: computedUserSet}
517
518
	// Return the Leaf and no error
519
	return leaf, nil
520
}
521
522
// compileComputedAttributeIdentifier compiles a string that represents a computed attribute
523
// into a base.Leaf object containing that attribute. It returns the resulting Leaf and no error.
524
func (t *Compiler) compileComputedAttributeIdentifier(r string) (l *base.Leaf, err error) {
525
	// Initialize a new base.Leaf
526
	leaf := &base.Leaf{}
527
528
	// Initialize a new base.ComputedAttribute with the provided name
529
	computedAttribute := &base.ComputedAttribute{
530
		Name: r,
531
	}
532
533
	// Set the Type of the Leaf to the newly created ComputedAttribute
534
	leaf.Type = &base.Leaf_ComputedAttribute{ComputedAttribute: computedAttribute}
535
536
	// Return the Leaf and no error
537
	return leaf, nil
538
}
539
540
// compileTupleToUserSetIdentifier compiles a tuple to user set identifier to a leaf node in the IR tree.
541
// The resulting leaf node is used in the child node of an permission definition in the final compiled schema.
542
// It takes in the parameters p and r, which represent the parent and relation of the tuple, respectively.
543
// It returns a pointer to a leaf node and an error.
544
func (t *Compiler) compileTupleToUserSetIdentifier(p, r string) (l *base.Leaf, err error) {
545
	leaf := &base.Leaf{}
546
	computedUserSet := &base.ComputedUserSet{
547
		Relation: r,
548
	}
549
	tupleToUserSet := &base.TupleToUserSet{
550
		TupleSet: &base.TupleSet{
551
			Relation: p,
552
		},
553
		Computed: computedUserSet,
554
	}
555
	leaf.Type = &base.Leaf_TupleToUserSet{TupleToUserSet: tupleToUserSet}
556
	return leaf, nil
557
}
558
559
// validateReference checks if the provided identifier refers to a valid relation in the schema.
560
func (t *Compiler) validateTupleToUserSetReference(entityName string, identifier *ast.Identifier) error {
561
	// Stack to hold the types to be checked.
562
	typeCheckStack := make([]ast.RelationTypeStatement, 0)
563
564
	// Get initial relation types for the given entity.
565
	initialRelationTypes, doesExist := t.schema.GetReferences().GetRelationReferenceTypesIfExist(utils.Key(entityName, identifier.Idents[0].Literal))
566
	if !doesExist {
567
		// If initial relation does not exist, return an error.
568
		return compileError(identifier.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
569
	}
570
571
	// Add the initial relation types to the stack.
572
	typeCheckStack = append(typeCheckStack, initialRelationTypes...)
573
574
	// While there are types to be checked in the stack...
575
	for len(typeCheckStack) > 0 {
576
		// Pop the last type from the stack.
577
		stackSize := len(typeCheckStack) - 1
578
		currentType := typeCheckStack[stackSize]
579
		typeCheckStack = typeCheckStack[:stackSize]
580
581
		if currentType.Relation.Literal == "" {
582
			typ, exist := t.schema.GetReferences().GetReferenceType(utils.Key(currentType.Type.Literal, identifier.Idents[1].Literal))
583
			// If the relation type does not exist, check if it is a valid relational reference.
584
			if !exist || typ == ast.ATTRIBUTE {
585
				// If not, return an error.
586
				return compileError(identifier.Idents[1].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
587
			}
588
		} else {
589
			// If the relation type does exist, get the corresponding relation types.
590
			relationTypes, doesExist := t.schema.GetReferences().GetRelationReferenceTypesIfExist(utils.Key(currentType.Type.Literal, currentType.Relation.Literal))
591
592
			if !doesExist {
593
				// If these types do not exist, return an error.
594
				return compileError(identifier.Idents[0].PositionInfo, base.ErrorCode_ERROR_CODE_UNDEFINED_RELATION_REFERENCE.String())
595
			}
596
597
			// Add the newly found relation types to the stack.
598
			typeCheckStack = append(typeCheckStack, relationTypes...)
599
		}
600
	}
601
602
	// If the function didn't return until now, the reference is valid.
603
	return nil
604
}
605
606
// compileError creates an error with the given message and position information.
607
func compileError(info token.PositionInfo, message string) error {
608
	msg := fmt.Sprintf("%v:%v: %s", info.LinePosition, info.ColumnPosition, strings.ToLower(strings.Replace(strings.Replace(message, "ERROR_CODE_", "", -1), "_", " ", -1)))
609
	return errors.New(msg)
610
}
611
612
// getArgumentTypeIfExist takes a token and checks its literal value against
613
// the known attribute types ("string", "boolean", "integer", "float").
614
// If the literal value matches one of these types, it returns the corresponding base.AttributeType and no error.
615
// If the literal value does not match any of the known types, it returns an ATTRIBUTE_TYPE_UNSPECIFIED
616
// and an error indicating an invalid argument type.
617
func getArgumentTypeIfExist(tkn ast.AttributeTypeStatement) (base.AttributeType, error) {
618
	var attrType base.AttributeType
619
620
	switch tkn.Type.Literal {
621
	case "string":
622
		attrType = base.AttributeType_ATTRIBUTE_TYPE_STRING
623
	case "boolean":
624
		attrType = base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN
625
	case "integer":
626
		attrType = base.AttributeType_ATTRIBUTE_TYPE_INTEGER
627
	case "double":
628
		attrType = base.AttributeType_ATTRIBUTE_TYPE_DOUBLE
629
	default:
630
		return base.AttributeType_ATTRIBUTE_TYPE_UNSPECIFIED, compileError(tkn.Type.PositionInfo, base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String())
631
	}
632
633
	if tkn.IsArray {
634
		return attrType + 1, nil
635
	}
636
637
	return attrType, nil
638
}
639