github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/tuple/onr.go (about) 1 package tuple 2 3 import ( 4 "slices" 5 "sort" 6 "strings" 7 8 core "github.com/authzed/spicedb/pkg/proto/core/v1" 9 ) 10 11 // ObjectAndRelation creates an ONR from string pieces. 12 func ObjectAndRelation(ns, oid, rel string) *core.ObjectAndRelation { 13 return &core.ObjectAndRelation{ 14 Namespace: ns, 15 ObjectId: oid, 16 Relation: rel, 17 } 18 } 19 20 // RelationReference creates a RelationReference from the string pieces. 21 func RelationReference(namespaceName string, relationName string) *core.RelationReference { 22 return &core.RelationReference{ 23 Namespace: namespaceName, 24 Relation: relationName, 25 } 26 } 27 28 // ParseSubjectONR converts a string representation of a Subject ONR to a proto object. Unlike 29 // ParseONR, this method allows for objects without relations. If an object without a relation 30 // is given, the relation will be set to ellipsis. 31 func ParseSubjectONR(subjectOnr string) *core.ObjectAndRelation { 32 groups := subjectRegex.FindStringSubmatch(subjectOnr) 33 34 if len(groups) == 0 { 35 return nil 36 } 37 38 relation := Ellipsis 39 subjectRelIndex := slices.Index(subjectRegex.SubexpNames(), "subjectRel") 40 if len(groups[subjectRelIndex]) > 0 { 41 relation = groups[subjectRelIndex] 42 } 43 44 return &core.ObjectAndRelation{ 45 Namespace: groups[slices.Index(subjectRegex.SubexpNames(), "subjectType")], 46 ObjectId: groups[slices.Index(subjectRegex.SubexpNames(), "subjectID")], 47 Relation: relation, 48 } 49 } 50 51 // ParseONR converts a string representation of an ONR to a proto object. 52 func ParseONR(onr string) *core.ObjectAndRelation { 53 groups := onrRegex.FindStringSubmatch(onr) 54 55 if len(groups) == 0 { 56 return nil 57 } 58 59 return &core.ObjectAndRelation{ 60 Namespace: groups[slices.Index(onrRegex.SubexpNames(), "resourceType")], 61 ObjectId: groups[slices.Index(onrRegex.SubexpNames(), "resourceID")], 62 Relation: groups[slices.Index(onrRegex.SubexpNames(), "resourceRel")], 63 } 64 } 65 66 // JoinRelRef joins the namespace and relation together into the same 67 // format as `StringRR()`. 68 func JoinRelRef(namespace, relation string) string { return namespace + "#" + relation } 69 70 // MustSplitRelRef splits a string produced by `JoinRelRef()` and panics if 71 // it fails. 72 func MustSplitRelRef(relRef string) (namespace, relation string) { 73 var ok bool 74 namespace, relation, ok = strings.Cut(relRef, "#") 75 if !ok { 76 panic("improperly formatted relation reference") 77 } 78 return 79 } 80 81 // StringRR converts a RR object to a string. 82 func StringRR(rr *core.RelationReference) string { 83 if rr == nil { 84 return "" 85 } 86 87 return JoinRelRef(rr.Namespace, rr.Relation) 88 } 89 90 // StringONR converts an ONR object to a string. 91 func StringONR(onr *core.ObjectAndRelation) string { 92 if onr == nil { 93 return "" 94 } 95 if onr.Relation == Ellipsis { 96 return JoinObjectRef(onr.Namespace, onr.ObjectId) 97 } 98 return JoinRelRef(JoinObjectRef(onr.Namespace, onr.ObjectId), onr.Relation) 99 } 100 101 // StringsONRs converts ONR objects to a string slice, sorted. 102 func StringsONRs(onrs []*core.ObjectAndRelation) []string { 103 onrstrings := make([]string, 0, len(onrs)) 104 for _, onr := range onrs { 105 onrstrings = append(onrstrings, StringONR(onr)) 106 } 107 108 sort.Strings(onrstrings) 109 return onrstrings 110 } 111 112 func OnrEqual(lhs, rhs *core.ObjectAndRelation) bool { 113 // Properties are sorted by highest to lowest cardinality to optimize for short-circuiting. 114 return lhs.ObjectId == rhs.ObjectId && lhs.Relation == rhs.Relation && lhs.Namespace == rhs.Namespace 115 } 116 117 func OnrEqualOrWildcard(tpl, target *core.ObjectAndRelation) bool { 118 return OnrEqual(tpl, target) || (tpl.ObjectId == PublicWildcard && tpl.Namespace == target.Namespace) 119 }