-
Notifications
You must be signed in to change notification settings - Fork 245
/
hasher_common.go
129 lines (100 loc) · 3.25 KB
/
hasher_common.go
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
package keys
import (
"fmt"
"net/url"
"sort"
"strconv"
"golang.org/x/exp/maps"
"google.golang.org/protobuf/types/known/structpb"
core "github.com/authzed/spicedb/pkg/proto/core/v1"
v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1"
"github.com/authzed/spicedb/pkg/tuple"
)
type hashableValue interface {
AppendToHash(hasher hasherInterface)
}
type hasherInterface interface {
WriteString(value string)
}
type hashableRelationReference struct {
*core.RelationReference
}
func (hrr hashableRelationReference) AppendToHash(hasher hasherInterface) {
hasher.WriteString(tuple.StringRR(hrr.RelationReference))
}
type hashableResultSetting v1.DispatchCheckRequest_ResultsSetting
func (hrs hashableResultSetting) AppendToHash(hasher hasherInterface) {
hasher.WriteString(string(hrs))
}
type hashableIds []string
func (hid hashableIds) AppendToHash(hasher hasherInterface) {
// Sort the IDs to canonicalize them. We have to clone to ensure that this does cause issues
// with others accessing the slice.
c := make([]string, len(hid))
copy(c, hid)
sort.Strings(c)
for _, id := range c {
hasher.WriteString(id)
hasher.WriteString(",")
}
}
type hashableOnr struct {
*core.ObjectAndRelation
}
func (hnr hashableOnr) AppendToHash(hasher hasherInterface) {
hasher.WriteString(hnr.Namespace)
hasher.WriteString(":")
hasher.WriteString(hnr.ObjectId)
hasher.WriteString("#")
hasher.WriteString(hnr.Relation)
}
type hashableString string
func (hs hashableString) AppendToHash(hasher hasherInterface) {
hasher.WriteString(string(hs))
}
type hashableContext struct{ *structpb.Struct }
func (hc hashableContext) AppendToHash(hasher hasherInterface) {
// NOTE: the order of keys in the Struct and its resulting JSON output are *unspecified*,
// as the go runtime randomizes iterator order to ensure that if relied upon, a sort is used.
// Therefore, we sort the keys here before adding them to the hash.
if hc.Struct == nil {
return
}
fields := hc.Struct.Fields
keys := maps.Keys(fields)
sort.Strings(keys)
for _, key := range keys {
hasher.WriteString("`")
hasher.WriteString(key)
hasher.WriteString("`:")
hashableStructValue{fields[key]}.AppendToHash(hasher)
hasher.WriteString(",\n")
}
}
type hashableStructValue struct{ *structpb.Value }
func (hsv hashableStructValue) AppendToHash(hasher hasherInterface) {
switch t := hsv.Kind.(type) {
case *structpb.Value_BoolValue:
hasher.WriteString(strconv.FormatBool(t.BoolValue))
case *structpb.Value_ListValue:
for _, value := range t.ListValue.Values {
hashableStructValue{value}.AppendToHash(hasher)
hasher.WriteString(",")
}
case *structpb.Value_NullValue:
hasher.WriteString("null")
case *structpb.Value_NumberValue:
// AFAICT, this is how Sprintf-style formats float64s
hasher.WriteString(strconv.FormatFloat(t.NumberValue, 'f', 6, 64))
case *structpb.Value_StringValue:
// NOTE: we escape the string value here to prevent accidental overlap in keys for string
// values that may themselves contain backticks.
hasher.WriteString("`" + url.PathEscape(t.StringValue) + "`")
case *structpb.Value_StructValue:
hasher.WriteString("{")
hashableContext{t.StructValue}.AppendToHash(hasher)
hasher.WriteString("}")
default:
panic(fmt.Sprintf("unknown struct value type: %T", t))
}
}