Passed
Pull Request — master (#1470)
by Tolga
02:39
created

context.isTupleAfterCursor   A

Complexity

Conditions 4

Size

Total Lines 9
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nop 3
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
package context
2
3
import (
4
	"sort"
5
6
	"golang.org/x/exp/slices"
7
8
	"github.com/Permify/permify/internal/storage/context/utils"
9
	"github.com/Permify/permify/pkg/database"
10
	base "github.com/Permify/permify/pkg/pb/base/v1"
11
)
12
13
// ContextualTuples - A collection of tuples with context.
14
type ContextualTuples struct {
15
	Tuples []*base.Tuple
16
}
17
18
// NewContextualTuples - Creates a new collection of tuples with context.
19
func NewContextualTuples(tuples ...*base.Tuple) *ContextualTuples {
20
	return &ContextualTuples{
21
		Tuples: tuples,
22
	}
23
}
24
25
// QueryRelationships filters the ContextualTuples based on the provided TupleFilter
26
// and returns a TupleIterator for the filtered tuples.
27
// QueryRelationships filters the ContextualTuples based on the provided TupleFilter, applies cursor-based pagination, and returns a TupleIterator for the filtered tuples.
28
func (c *ContextualTuples) QueryRelationships(filter *base.TupleFilter, pagination database.CursorPagination) (*database.TupleIterator, error) {
29
	// Sort tuples based on the provided order field
30
	sort.Slice(c.Tuples, func(i, j int) bool {
31
		switch pagination.Sort() {
32
		case "entity_id":
33
			return c.Tuples[i].GetEntity().GetId() < c.Tuples[j].GetEntity().GetId()
34
		case "subject_id":
35
			return c.Tuples[i].GetSubject().GetId() < c.Tuples[j].GetSubject().GetId()
36
		default:
37
			return false // If no valid order is provided, no sorting is applied
38
		}
39
	})
40
41
	cursor := ""
42
	if pagination.Cursor() != "" {
43
		t, err := utils.EncodedContinuousToken{Value: pagination.Cursor()}.Decode()
44
		if err != nil {
45
			return nil, err
46
		}
47
		cursor = t.(utils.ContinuousToken).Value
48
	}
49
50
	// Filter the tuples based on the provided filter and cursor
51
	filtered := c.filterTuples(filter, cursor, pagination.Sort())
52
53
	// Return a new TupleIterator for the filtered tuples
54
	return database.NewTupleIterator(filtered...), nil
55
}
56
57
// filterTuples applies the provided filter to c's Tuples and returns a slice of Tuples that match the filter.
58
func (c *ContextualTuples) filterTuples(filter *base.TupleFilter, cursor, order string) []*base.Tuple {
59
	var filtered []*base.Tuple // Initialize a slice to hold the filtered tuples
60
61
	// Iterate over the tuples
62
	for _, tup := range c.Tuples {
63
		// Skip tuples that come before the cursor based on the specified order field
64
		if cursor != "" && !isTupleAfterCursor(tup, cursor, order) {
65
			continue
66
		}
67
68
		// If a tuple matches the Entity, Relation, and Subject filters, add it to the filtered slice
69
		if matchesEntityFilterForTuples(tup, filter.GetEntity()) &&
70
			matchesRelationFilter(tup, filter.GetRelation()) &&
71
			matchesSubjectFilter(tup, filter.GetSubject()) {
72
			filtered = append(filtered, tup)
73
		}
74
	}
75
76
	return filtered // Return the filtered tuples
77
}
78
79
// isAfterCursor checks if the tuple's ID (based on the order field) comes after the cursor.
80
func isTupleAfterCursor(tup *base.Tuple, cursor, order string) bool {
81
	switch order {
82
	case "entity_id":
83
		return tup.GetEntity().GetId() >= cursor
84
	case "subject_id":
85
		return tup.GetSubject().GetId() >= cursor
86
	default:
87
		// If the order field is not recognized, default to not skipping any tuples
88
		return true
89
	}
90
}
91
92
// matchesEntityFilterForTuples checks if a Tuple matches the conditions in an EntityFilter.
93
func matchesEntityFilterForTuples(tup *base.Tuple, filter *base.EntityFilter) bool {
94
	// Return true if the filter is empty or the tuple's entity matches the filter
95
	return (filter.GetType() == "" || tup.GetEntity().GetType() == filter.GetType()) &&
96
		(len(filter.GetIds()) == 0 || slices.Contains(filter.GetIds(), tup.GetEntity().GetId()))
97
}
98
99
// matchesRelationFilter checks if a Tuple matches the condition in a RelationFilter.
100
func matchesRelationFilter(tup *base.Tuple, filter string) bool {
101
	// Return true if the filter is empty or the tuple's relation matches the filter
102
	return filter == "" || tup.GetRelation() == filter
103
}
104
105
// matchesSubjectFilter checks if a Tuple matches the conditions in a SubjectFilter.
106
func matchesSubjectFilter(tup *base.Tuple, filter *base.SubjectFilter) bool {
107
	// Return true if the filter is empty or the tuple's subject matches the filter
108
	return (filter.GetType() == "" || tup.GetSubject().GetType() == filter.GetType()) &&
109
		(len(filter.GetIds()) == 0 || slices.Contains(filter.GetIds(), tup.GetSubject().GetId())) &&
110
		(filter.GetRelation() == "" || tup.GetSubject().GetRelation() == filter.GetRelation())
111
}
112