github.com/openfga/openfga@v1.5.4-rc1/internal/keys/hasher.go (about) 1 package keys 2 3 import ( 4 "fmt" 5 "sort" 6 "strconv" 7 8 openfgav1 "github.com/openfga/api/proto/openfga/v1" 9 "golang.org/x/exp/maps" 10 "google.golang.org/protobuf/types/known/structpb" 11 ) 12 13 type hasher interface { 14 WriteString(value string) error 15 } 16 17 type hashableValue interface { 18 Append(hasher) error 19 } 20 21 // strinHasher implements the hashableValue interface for string types. 22 type stringHasher string 23 24 var _ hashableValue = (*stringHasher)(nil) 25 26 func (s stringHasher) Append(h hasher) error { 27 return h.WriteString(string(s)) 28 } 29 30 // NewTupleKeysHasher returns a hasher for an array of *openfgav1.TupleKey. 31 // It sorts the tuples first to guarantee that two arrays that are identical except for the ordering 32 // return the same hash. 33 func NewTupleKeysHasher(tupleKeys ...*openfgav1.TupleKey) *tupleKeysHasher { 34 return &tupleKeysHasher{tupleKeys} 35 } 36 37 // tupleKeysHasher implements the hashableValue interface for TupleKey protobuf types. 38 type tupleKeysHasher struct { 39 tupleKeys []*openfgav1.TupleKey 40 } 41 42 var _ hashableValue = (*tupleKeysHasher)(nil) 43 44 func (t tupleKeysHasher) Append(h hasher) error { 45 sortedTupleKeys := append([]*openfgav1.TupleKey(nil), t.tupleKeys...) // Copy input to avoid mutating it 46 47 sort.SliceStable(sortedTupleKeys, func(i, j int) bool { 48 if sortedTupleKeys[i].GetObject() != sortedTupleKeys[j].GetObject() { 49 return sortedTupleKeys[i].GetObject() < sortedTupleKeys[j].GetObject() 50 } 51 52 if sortedTupleKeys[i].GetRelation() != sortedTupleKeys[j].GetRelation() { 53 return sortedTupleKeys[i].GetRelation() < sortedTupleKeys[j].GetRelation() 54 } 55 56 if sortedTupleKeys[i].GetUser() != sortedTupleKeys[j].GetUser() { 57 return sortedTupleKeys[i].GetUser() < sortedTupleKeys[j].GetUser() 58 } 59 60 return true 61 }) 62 63 // prefix to avoid overlap with previous strings written 64 if err := h.WriteString("/"); err != nil { 65 return err 66 } 67 68 n := 0 69 for _, tupleKey := range sortedTupleKeys { 70 key := fmt.Sprintf("%s#%s@%s", tupleKey.GetObject(), tupleKey.GetRelation(), tupleKey.GetUser()) 71 72 if err := h.WriteString(key); err != nil { 73 return err 74 } 75 76 if n < len(t.tupleKeys)-1 { 77 if err := h.WriteString(","); err != nil { 78 return err 79 } 80 } 81 82 n++ 83 } 84 85 return nil 86 } 87 88 // contextHasher represents a hashable protobuf Struct. 89 // 90 // The contextHasher can be used to generate a stable hash of a protobuf Struct. The fields 91 // of the struct are ordered to produce a stable hash, and the values for each struct key 92 // are produced using the structValueHasher, which produces a stable hash value for the Struct 93 // value. 94 type contextHasher struct { 95 *structpb.Struct 96 } 97 98 // NewContextHasher constructs a contextHasher which can be used to produce 99 // a stable hash of a protobuf Struct. 100 func NewContextHasher(s *structpb.Struct) *contextHasher { 101 return &contextHasher{s} 102 } 103 104 var _ hashableValue = (*contextHasher)(nil) 105 106 func (c contextHasher) Append(h hasher) error { 107 if c.Struct == nil { 108 return nil 109 } 110 111 fields := c.GetFields() 112 keys := maps.Keys(fields) 113 sort.Strings(keys) 114 115 for _, key := range keys { 116 if err := h.WriteString(fmt.Sprintf("'%s:'", key)); err != nil { 117 return err 118 } 119 120 valueHasher := structValueHasher{fields[key]} 121 if err := valueHasher.Append(h); err != nil { 122 return err 123 } 124 125 if err := h.WriteString(","); err != nil { 126 return err 127 } 128 } 129 130 return nil 131 } 132 133 // structValueHasher represents a hashable protobuf Struct value. 134 // 135 // The structValueHasher can be used to generate a stable hash of a protobuf Struct value. 136 type structValueHasher struct { 137 *structpb.Value 138 } 139 140 var _ hashableValue = (*structValueHasher)(nil) 141 142 func (s structValueHasher) Append(h hasher) error { 143 switch val := s.Kind.(type) { 144 case *structpb.Value_BoolValue: 145 return h.WriteString(fmt.Sprintf("%v", val.BoolValue)) 146 case *structpb.Value_NullValue: 147 return h.WriteString("null") 148 case *structpb.Value_StringValue: 149 return h.WriteString(val.StringValue) 150 case *structpb.Value_NumberValue: 151 return h.WriteString(strconv.FormatFloat(val.NumberValue, 'f', -1, 64)) // -1 precision ensures we represent the 64-bit value with the maximum precision needed to represent it, see strconv#FormatFloat for more info. 152 case *structpb.Value_ListValue: 153 n := 0 154 values := val.ListValue.GetValues() 155 156 for _, v := range values { 157 valueHasher := structValueHasher{v} 158 if err := valueHasher.Append(h); err != nil { 159 return err 160 } 161 162 if n < len(values)-1 { 163 if err := h.WriteString(","); err != nil { 164 return err 165 } 166 } 167 168 n++ 169 } 170 case *structpb.Value_StructValue: 171 return contextHasher{val.StructValue}.Append(h) 172 default: 173 panic("unexpected structpb value encountered") 174 } 175 176 return nil 177 }