summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/spicedb/pkg/tuple/strings.go
blob: 7b5a378a5d3de2de7cb35a38081f60341b3c43ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package tuple

import (
	"sort"
	"strings"
	"time"

	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/types/known/structpb"

	core "github.com/authzed/spicedb/pkg/proto/core/v1"
	"github.com/authzed/spicedb/pkg/spiceerrors"
)

var expirationFormat = time.RFC3339Nano

// JoinRelRef joins the namespace and relation together into the same
// format as `StringRR()`.
func JoinRelRef(namespace, relation string) string { return namespace + "#" + relation }

// MustSplitRelRef splits a string produced by `JoinRelRef()` and panics if
// it fails.
func MustSplitRelRef(relRef string) (namespace, relation string) {
	var ok bool
	namespace, relation, ok = strings.Cut(relRef, "#")
	if !ok {
		panic("improperly formatted relation reference")
	}
	return
}

// StringRR converts a RR object to a string.
func StringRR(rr RelationReference) string {
	return JoinRelRef(rr.ObjectType, rr.Relation)
}

// StringONR converts an ONR object to a string.
func StringONR(onr ObjectAndRelation) string {
	return StringONRStrings(onr.ObjectType, onr.ObjectID, onr.Relation)
}

func StringCoreRR(rr *core.RelationReference) string {
	if rr == nil {
		return ""
	}

	return JoinRelRef(rr.Namespace, rr.Relation)
}

// StringCoreONR converts a core ONR object to a string.
func StringCoreONR(onr *core.ObjectAndRelation) string {
	if onr == nil {
		return ""
	}

	return StringONRStrings(onr.Namespace, onr.ObjectId, onr.Relation)
}

// StringONRStrings converts ONR strings to a string.
func StringONRStrings(namespace, objectID, relation string) string {
	if relation == Ellipsis {
		return JoinObjectRef(namespace, objectID)
	}
	return JoinRelRef(JoinObjectRef(namespace, objectID), relation)
}

// StringsONRs converts ONR objects to a string slice, sorted.
func StringsONRs(onrs []ObjectAndRelation) []string {
	onrstrings := make([]string, 0, len(onrs))
	for _, onr := range onrs {
		onrstrings = append(onrstrings, StringONR(onr))
	}

	sort.Strings(onrstrings)
	return onrstrings
}

// MustString converts a relationship to a string.
func MustString(rel Relationship) string {
	tplString, err := String(rel)
	if err != nil {
		panic(err)
	}
	return tplString
}

// String converts a relationship to a string.
func String(rel Relationship) (string, error) {
	spiceerrors.DebugAssert(rel.ValidateNotEmpty, "relationship must not be empty")

	caveatString, err := StringCaveat(rel.OptionalCaveat)
	if err != nil {
		return "", err
	}

	expirationString, err := StringExpiration(rel.OptionalExpiration)
	if err != nil {
		return "", err
	}

	return StringONR(rel.Resource) + "@" + StringONR(rel.Subject) + caveatString + expirationString, nil
}

func StringExpiration(expiration *time.Time) (string, error) {
	if expiration == nil {
		return "", nil
	}

	return "[expiration:" + expiration.Format(expirationFormat) + "]", nil
}

// StringWithoutCaveatOrExpiration converts a relationship to a string, without its caveat or expiration included.
func StringWithoutCaveatOrExpiration(rel Relationship) string {
	spiceerrors.DebugAssert(rel.ValidateNotEmpty, "relationship must not be empty")

	return StringONR(rel.Resource) + "@" + StringONR(rel.Subject)
}

func MustStringCaveat(caveat *core.ContextualizedCaveat) string {
	caveatString, err := StringCaveat(caveat)
	if err != nil {
		panic(err)
	}
	return caveatString
}

// StringCaveat converts a contextualized caveat to a string. If the caveat is nil or empty, returns empty string.
func StringCaveat(caveat *core.ContextualizedCaveat) (string, error) {
	if caveat == nil || caveat.CaveatName == "" {
		return "", nil
	}

	contextString, err := StringCaveatContext(caveat.Context)
	if err != nil {
		return "", err
	}

	if len(contextString) > 0 {
		contextString = ":" + contextString
	}

	return "[" + caveat.CaveatName + contextString + "]", nil
}

// StringCaveatContext converts the context of a caveat to a string. If the context is nil or empty, returns an empty string.
func StringCaveatContext(context *structpb.Struct) (string, error) {
	if context == nil || len(context.Fields) == 0 {
		return "", nil
	}

	contextBytes, err := protojson.MarshalOptions{
		Multiline: false,
		Indent:    "",
	}.Marshal(context)
	if err != nil {
		return "", err
	}
	return string(contextBytes), nil
}

// JoinObjectRef joins the namespace and the objectId together into the standard
// format.
//
// This function assumes that the provided values have already been validated.
func JoinObjectRef(namespace, objectID string) string { return namespace + ":" + objectID }