pkg/dsl/ast/schema.go   B
last analyzed

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 49
eloc 131
dl 0
loc 257
rs 8.48
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
D ast.*Schema.AddStatement 0 60 12
F ast.*Schema.UpdateStatement 0 75 18
A ast.*Schema.SetReferences 0 2 1
A ast.*Schema.GetReferences 0 2 1
A ast.NewSchema 0 4 1
F ast.*Schema.DeleteStatement 0 63 14
A ast.*Schema.String 0 6 2
1
package ast
2
3
import (
4
	"errors"
5
	"fmt"
6
	"strings"
7
8
	base "github.com/Permify/permify/pkg/pb/base/v1"
9
)
10
11
// Schema represents the parsed schema, which contains all the statements
12
// and extracted entity and relational references used by the schema. It
13
// is used as an intermediate representation before generating the
14
// corresponding metadata.
15
type Schema struct {
16
	// The list of statements in the schema
17
	Statements []Statement
18
19
	// references - Map of all relational references extracted from the schema
20
	references *References
21
}
22
23
func NewSchema() *Schema {
24
	return &Schema{
25
		Statements: []Statement{},
26
		references: NewReferences(),
27
	}
28
}
29
30
// String -
31
func (sch *Schema) String() string {
32
	stmts := make([]string, 0, len(sch.Statements))
33
	for _, stmt := range sch.Statements {
34
		stmts = append(stmts, stmt.String())
35
	}
36
	return strings.Join(stmts, "\n")
37
}
38
39
// GetReferences -
40
func (sch *Schema) GetReferences() *References {
41
	return sch.references
42
}
43
44
// SetReferences -
45
func (sch *Schema) SetReferences(refs *References) {
46
	sch.references = refs
47
}
48
49
// AddStatement adds a new statement to an entity within the schema. It also updates the schema's references.
50
func (sch *Schema) AddStatement(entityName string, stmt Statement) error {
51
	// Loop through all statements in the schema to find the one matching the entity name.
52
	var entityStmt *EntityStatement
53
	for _, statement := range sch.Statements {
54
		if statement.GetName() == entityName {
55
			// Attempt to cast the found statement to an EntityStatement.
56
			es, ok := statement.(*EntityStatement)
57
			if ok {
58
				entityStmt = es // Successfully found and cast the EntityStatement.
59
				break
60
			} else {
61
				return errors.New(base.ErrorCode_ERROR_CODE_CANNOT_CONVERT_TO_ENTITY_STATEMENT.String()) // Casting failed.
62
			}
63
		}
64
	}
65
66
	// If no matching entity was found, return an error.
67
	if entityStmt == nil {
68
		return errors.New(base.ErrorCode_ERROR_CODE_ENTITY_STATEMENT_NOT_FOUND.String())
69
	}
70
71
	// Construct a unique key for the new statement.
72
	refKey := fmt.Sprintf("%s#%s", entityName, stmt.GetName())
73
74
	// Check if a reference for this statement already exists in the schema.
75
	if sch.GetReferences().IsReferenceExist(refKey) {
76
		return errors.New(base.ErrorCode_ERROR_CODE_ALREADY_EXIST.String()) // Avoid duplicating references.
77
	}
78
79
	// Add the new statement to the appropriate list in the EntityStatement, based on its type.
80
	switch stmt.StatementType() {
81
	case PERMISSION_STATEMENT:
82
		// Append the new permission statement to the list of permission statements for the entity.
83
		entityStmt.PermissionStatements = append(entityStmt.PermissionStatements, stmt)
84
		// Add a new permission reference to the schema's references.
85
		return sch.GetReferences().AddPermissionReference(refKey)
86
87
	case RELATION_STATEMENT:
88
		// Append the new relation statement to the list of relation statements for the entity.
89
		entityStmt.RelationStatements = append(entityStmt.RelationStatements, stmt)
90
		// Check if the statement can be cast to a RelationStatement and add its references.
91
		if rs, ok := stmt.(*RelationStatement); ok {
92
			return sch.GetReferences().AddRelationReferences(refKey, rs.RelationTypes)
93
		} else {
94
			return errors.New(base.ErrorCode_ERROR_CODE_CANNOT_CONVERT_TO_RELATION_STATEMENT.String())
95
		}
96
97
	case ATTRIBUTE_STATEMENT:
98
		// Append the new attribute statement to the list of attribute statements for the entity.
99
		entityStmt.AttributeStatements = append(entityStmt.AttributeStatements, stmt)
100
		// Check if the statement can be cast to an AttributeStatement and add its references.
101
		if as, ok := stmt.(*AttributeStatement); ok {
102
			return sch.GetReferences().AddAttributeReferences(refKey, as.AttributeType)
103
		} else {
104
			return errors.New(base.ErrorCode_ERROR_CODE_CANNOT_CONVERT_TO_ATTRIBUTE_STATEMENT.String())
105
		}
106
107
	default:
108
		// The statement type is unknown, return an error.
109
		return errors.New(base.ErrorCode_ERROR_CODE_UNKNOWN_STATEMENT_TYPE.String())
110
	}
111
}
112
113
// UpdateStatement updates a statement of a specific type (permission, relation, or attribute)
114
// for a given entity within the schema. It either updates an existing statement or appends a new one if not found.
115
func (sch *Schema) UpdateStatement(entityName string, newStmt Statement) error {
116
	// Iterate through all statements in the schema to find the one matching the entity name.
117
	for _, statement := range sch.Statements {
118
		if statement.GetName() == entityName {
119
			// Convert the generic statement interface to a specific EntityStatement type.
120
			entityStmt, ok := statement.(*EntityStatement)
121
			if !ok {
122
				return errors.New(base.ErrorCode_ERROR_CODE_CANNOT_CONVERT_TO_ENTITY_STATEMENT.String())
123
			}
124
125
			// Construct a unique reference key for the new statement.
126
			referenceKey := fmt.Sprintf("%s#%s", entityName, newStmt.GetName())
127
128
			// Check if a reference for the statement already exists within the schema.
129
			if !sch.GetReferences().IsReferenceExist(referenceKey) {
130
				return errors.New(base.ErrorCode_ERROR_CODE_REFERENCE_NOT_FOUND.String())
131
			}
132
133
			// Based on the statement type, update the corresponding list in the EntityStatement.
134
			switch newStmt.StatementType() {
135
			case PERMISSION_STATEMENT, RELATION_STATEMENT, ATTRIBUTE_STATEMENT:
136
				var stmts *[]Statement // Pointer to the slice of statements to update.
137
138
				// Assign the correct slice based on the type of the new statement.
139
				switch newStmt.StatementType() {
140
				case PERMISSION_STATEMENT:
141
					stmts = &entityStmt.PermissionStatements
142
				case RELATION_STATEMENT:
143
					stmts = &entityStmt.RelationStatements
144
				case ATTRIBUTE_STATEMENT:
145
					stmts = &entityStmt.AttributeStatements
146
				}
147
148
				// Flag to check if the statement has been updated.
149
				updated := false
150
151
				// Iterate over the statements to find and update the one with the matching name.
152
				for i, stmt := range *stmts {
153
					if stmt.GetName() == newStmt.GetName() {
154
						(*stmts)[i] = newStmt
155
						updated = true
156
						break // Stop iterating once the statement is updated.
157
					}
158
				}
159
160
				// If the statement was not found and updated, append it to the slice.
161
				if !updated {
162
					*stmts = append(*stmts, newStmt)
163
				}
164
165
				// Update the reference in the schema based on the statement type.
166
				switch newStmt.StatementType() {
167
				case PERMISSION_STATEMENT:
168
					return sch.GetReferences().UpdatePermissionReference(referenceKey)
169
				case RELATION_STATEMENT:
170
					if rs, ok := newStmt.(*RelationStatement); ok {
171
						return sch.GetReferences().UpdateRelationReferences(referenceKey, rs.RelationTypes)
172
					}
173
					return errors.New(base.ErrorCode_ERROR_CODE_CANNOT_CONVERT_TO_RELATION_STATEMENT.String())
174
				case ATTRIBUTE_STATEMENT:
175
					if as, ok := newStmt.(*AttributeStatement); ok {
176
						return sch.GetReferences().UpdateAttributeReferences(referenceKey, as.AttributeType)
177
					}
178
					return errors.New(base.ErrorCode_ERROR_CODE_CANNOT_CONVERT_TO_ATTRIBUTE_STATEMENT.String())
179
				}
180
			default:
181
				return errors.New(base.ErrorCode_ERROR_CODE_UNKNOWN_STATEMENT_TYPE.String())
182
			}
183
			// Return nil to indicate successful update.
184
			return nil
185
		}
186
	}
187
188
	// If no matching entity statement is found, return an error.
189
	return errors.New(base.ErrorCode_ERROR_CODE_ENTITY_STATEMENT_NOT_FOUND.String())
190
}
191
192
// DeleteStatement removes a specific statement from an entity within the schema.
193
// It identifies the statement by its name and the name of the entity it belongs to.
194
// If successful, it also removes the corresponding reference from the schema.
195
func (sch *Schema) DeleteStatement(entityName, name string) error {
196
	// Iterate over all statements in the schema to find the entity by name.
197
	for _, statement := range sch.Statements {
198
		if statement.GetName() == entityName {
199
			// Try to convert the found statement into an EntityStatement.
200
			entityStmt, ok := statement.(*EntityStatement)
201
			if !ok {
202
				// If conversion fails, return an error indicating wrong type.
203
				return errors.New(base.ErrorCode_ERROR_CODE_CANNOT_CONVERT_TO_ENTITY_STATEMENT.String())
204
			}
205
206
			// Construct the reference key from the entity and statement names.
207
			referenceKey := fmt.Sprintf("%s#%s", entityName, name)
208
			// Retrieve the type of reference associated with the key.
209
			refType, ok := sch.GetReferences().GetReferenceType(referenceKey)
210
			if !ok {
211
				// If no reference is found, return an error.
212
				return errors.New(base.ErrorCode_ERROR_CODE_REFERENCE_NOT_FOUND.String())
213
			}
214
215
			// Declare a variable to hold the target list of statements for modification.
216
			var targetStatements *[]Statement
217
			// Assign the targetStatements pointer based on the reference type.
218
			switch refType {
219
			case PERMISSION:
220
				targetStatements = &entityStmt.PermissionStatements
221
			case RELATION:
222
				targetStatements = &entityStmt.RelationStatements
223
			case ATTRIBUTE:
224
				targetStatements = &entityStmt.AttributeStatements
225
			default:
226
				// If the reference type is unknown, return an error.
227
				return errors.New(base.ErrorCode_ERROR_CODE_UNKNOWN_REFERENCE_TYPE.String())
228
			}
229
230
			// Create a new slice to hold all statements except the one to be deleted.
231
			var newStatements []Statement
232
			for _, stmt := range *targetStatements {
233
				if stmt.GetName() != name {
234
					// If the statement is not the one to delete, add it to the new slice.
235
					newStatements = append(newStatements, stmt)
236
				}
237
			}
238
			// Replace the old slice with the new slice, effectively deleting the statement.
239
			*targetStatements = newStatements
240
241
			// Remove the corresponding reference based on the reference type.
242
			switch refType {
243
			case PERMISSION:
244
				return sch.GetReferences().RemovePermissionReference(referenceKey)
245
			case RELATION:
246
				return sch.GetReferences().RemoveRelationReferences(referenceKey)
247
			case ATTRIBUTE:
248
				return sch.GetReferences().RemoveAttributeReferences(referenceKey)
249
			}
250
251
			// If the statement was successfully removed, return nil to indicate success.
252
			return nil
253
		}
254
	}
255
256
	// If no matching entity is found in the schema, return an error.
257
	return errors.New(base.ErrorCode_ERROR_CODE_ENTITY_STATEMENT_NOT_FOUND.String())
258
}
259