Files
upstream-ory-keto/internal/relationtuple/definitions.go
upstream-mirror 9456a4e922
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:08:34 +05:00

275 lines
8.8 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package relationtuple
import (
"context"
"sync"
"github.com/gofrs/uuid"
keysetpagination "github.com/ory/x/pagination/keysetpagination_v2"
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
"github.com/ory/keto/ketoapi"
)
type (
ManagerProvider interface {
RelationTupleManager() Manager
}
// SubjectSetType identifies a subject-set pointer by its target namespace and
// relation, as declared in OPL. AllowsDirect indicates that OPL permits a
// direct subject match for this type.
SubjectSetType struct {
Namespace string
Relation string
AllowsDirect bool
}
Manager interface {
GetRelationTuples(ctx context.Context, query *RelationQuery, options ...keysetpagination.Option) ([]*RelationTuple, *keysetpagination.Paginator, error)
ExistsRelationTuples(ctx context.Context, query *RelationQuery) (bool, error)
WriteRelationTuples(ctx context.Context, rs ...*RelationTuple) error
DeleteRelationTuples(ctx context.Context, rs ...*RelationTuple) error
DeleteAllRelationTuples(ctx context.Context, query *RelationQuery) error
TransactRelationTuples(ctx context.Context, insert []*RelationTuple, delete []*RelationTuple) error
// TraverseSubjectSetExpansion gets all subject sets for the tuple's
// object#relation and checks whether the tuple's subject is a member of
// each. When allowedSubjectSets is non-empty, only subject-set pointers
// matching the declared (namespace, relation) pairs are considered.
TraverseSubjectSetExpansion(ctx context.Context, tuple *RelationTuple, allowedSubjectSets []SubjectSetType) ([]*TraversalResult, error)
// FindTupleWithRelations returns the first tuple that matches the given
// tuple on namespace, object, and subject and has any of the given
// relations, or nil if there is none.
FindTupleWithRelations(ctx context.Context, tuple *RelationTuple, relations []string) (*RelationTuple, error)
// Transaction runs f inside a database transaction. Nested calls reuse
// the outer transaction.
Transaction(ctx context.Context, f func(ctx context.Context) error) error
// WithLatestSnapshot runs f inside a snapshot-isolated read transaction,
// ensuring all SQL queries within f see a consistent database state.
WithLatestSnapshot(ctx context.Context, f func(ctx context.Context) error) error
}
SubjectID struct {
ID uuid.UUID `json:"id"`
}
RelationQuery struct {
Namespace *string `json:"namespace"`
Object *uuid.UUID `json:"object"`
Relation *string `json:"relation"`
Subject Subject `json:"subject_id,omitempty"`
}
TupleData interface {
GetSubject() *rts.Subject
GetObject() string
GetNamespace() string
GetRelation() string
}
Subject interface {
Equals(Subject) bool
UniqueID() uuid.UUID
String() string
}
RelationTuple struct {
Namespace string `json:"namespace"`
Object uuid.UUID `json:"object"`
Relation string `json:"relation"`
Subject Subject `json:"subject"`
}
InternalRelationTuples []*RelationTuple
SubjectSet struct {
Namespace string `json:"namespace"`
Object uuid.UUID `json:"object"`
Relation string `json:"relation"`
}
// TODO(hperl): Also use a ketoapi.Tree here.
Tree struct {
Type ketoapi.TreeNodeType `json:"type"`
Subject Subject `json:"subject"`
Children []*Tree `json:"children,omitempty"`
Truncation *Truncation `json:"truncation,omitempty"`
}
Truncation struct {
Reason TruncationReason `json:"reason,omitempty"`
Cursor *ExpandCursor `json:"cursor,omitempty"`
}
TruncationReason string
ExpandCursorKind string
ExpandCursor struct {
Kind ExpandCursorKind `json:"kind"`
SubjectSet *SubjectSet `json:"subject_set,omitempty"`
NextPageToken keysetpagination.PageToken `json:"next_page_token,omitempty"`
// TraverseRelation is the ComputedRelation in TupleToSubjectSet relation
TraverseRelation *string `json:"traverse_relation,omitempty"`
}
)
const (
ExpandCursorKindDirect ExpandCursorKind = "direct"
ExpandCursorKindTTU ExpandCursorKind = "ttu"
)
type (
TraversalResult struct {
From *RelationTuple
To *RelationTuple
Via Traversal
Found bool
}
Traversal string
)
const (
TraversalUnknown Traversal = "unknown"
TraversalSubjectSetExpand Traversal = "subject set expand"
TraversalComputedUserset Traversal = "computed userset"
TraversalTupleToUserset Traversal = "tuple to userset"
)
const (
TruncationReasonDepthLimit TruncationReason = "depth_limit"
TruncationReasonTupleLimit TruncationReason = "tuple_limit"
TruncationReasonCycle TruncationReason = "cycle"
)
var _, _ Subject = (*SubjectID)(nil), (*SubjectSet)(nil)
func (s *SubjectID) Equals(other Subject) bool {
uv, ok := other.(*SubjectID)
if !ok {
return false
}
return uv.ID == s.ID
}
func (s *SubjectID) UniqueID() uuid.UUID { return s.ID }
func (s *SubjectID) String() string { return s.ID.String() }
func (s *SubjectSet) Equals(other Subject) bool {
uv, ok := other.(*SubjectSet)
if !ok {
return false
}
return uv.Relation == s.Relation && uv.Object == s.Object && uv.Namespace == s.Namespace
}
func (s *SubjectSet) UniqueID() uuid.UUID {
return uuid.NewV5(s.Object, s.Namespace+"-"+s.Relation)
}
func (s *SubjectSet) String() string {
if s == nil {
return ""
}
return s.Namespace + ":" + s.Object.String() + "#" + s.Relation
}
func (t *RelationTuple) ToQuery() *RelationQuery {
return &RelationQuery{
Namespace: &t.Namespace,
Object: &t.Object,
Relation: &t.Relation,
Subject: t.Subject,
}
}
func (t *RelationTuple) String() string {
if t == nil {
return ""
}
subject := ""
if t.Subject != nil {
subject = t.Subject.String()
}
return t.Namespace + ":" + t.Object.String() + "#" + t.Relation + "@" + subject
}
func (t *RelationTuple) FromProto(proto *rts.RelationTuple) *RelationTuple {
// TODO(hperl)
return t
}
func (t *RelationTuple) ToProto() *rts.RelationTuple {
// TODO(hperl)
return &rts.RelationTuple{}
}
type ManagerWrapper struct {
Reg ManagerProvider
PageOpts []keysetpagination.Option
RequestedPages []keysetpagination.PageToken
// lock is necessary so that GetRelationTuples() is safe for concurrency.
requestedPagesLock sync.Mutex
}
var (
_ Manager = (*ManagerWrapper)(nil)
_ ManagerProvider = (*ManagerWrapper)(nil)
)
func NewManagerWrapper(_ any, reg ManagerProvider, options ...keysetpagination.Option) *ManagerWrapper {
return &ManagerWrapper{
Reg: reg,
PageOpts: options,
}
}
func (t *ManagerWrapper) GetRelationTuples(ctx context.Context, query *RelationQuery, options ...keysetpagination.Option) ([]*RelationTuple, *keysetpagination.Paginator, error) {
p, err := keysetpagination.NewPaginator(options...)
if err != nil {
return nil, nil, err
}
t.requestedPagesLock.Lock()
defer t.requestedPagesLock.Unlock()
t.RequestedPages = append(t.RequestedPages, p.PageToken())
return t.Reg.RelationTupleManager().GetRelationTuples(ctx, query, append(t.PageOpts, options...)...)
}
func (t *ManagerWrapper) ExistsRelationTuples(ctx context.Context, query *RelationQuery) (bool, error) {
return t.Reg.RelationTupleManager().ExistsRelationTuples(ctx, query)
}
func (t *ManagerWrapper) WriteRelationTuples(ctx context.Context, rs ...*RelationTuple) error {
return t.Reg.RelationTupleManager().WriteRelationTuples(ctx, rs...)
}
func (t *ManagerWrapper) DeleteRelationTuples(ctx context.Context, rs ...*RelationTuple) error {
return t.Reg.RelationTupleManager().DeleteRelationTuples(ctx, rs...)
}
func (t *ManagerWrapper) DeleteAllRelationTuples(ctx context.Context, query *RelationQuery) error {
return t.Reg.RelationTupleManager().DeleteAllRelationTuples(ctx, query)
}
func (t *ManagerWrapper) TransactRelationTuples(ctx context.Context, insert []*RelationTuple, delete []*RelationTuple) error {
return t.Reg.RelationTupleManager().TransactRelationTuples(ctx, insert, delete)
}
func (t *ManagerWrapper) TraverseSubjectSetExpansion(ctx context.Context, tuple *RelationTuple, allowedSubjectSets []SubjectSetType) ([]*TraversalResult, error) {
return t.Reg.RelationTupleManager().TraverseSubjectSetExpansion(ctx, tuple, allowedSubjectSets)
}
func (t *ManagerWrapper) FindTupleWithRelations(ctx context.Context, tuple *RelationTuple, relations []string) (*RelationTuple, error) {
return t.Reg.RelationTupleManager().FindTupleWithRelations(ctx, tuple, relations)
}
func (t *ManagerWrapper) Transaction(ctx context.Context, f func(ctx context.Context) error) error {
return t.Reg.RelationTupleManager().Transaction(ctx, f)
}
func (t *ManagerWrapper) WithLatestSnapshot(ctx context.Context, f func(ctx context.Context) error) error {
return t.Reg.RelationTupleManager().WithLatestSnapshot(ctx, f)
}
func (t *ManagerWrapper) RelationTupleManager() Manager {
return t
}