forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memstore.go
218 lines (193 loc) · 5.9 KB
/
memstore.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
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
// Package memstore contains a store which is just
// a collection of key-value entries with immutability capabilities.
//
// Developers can use that storage to their own apps if they like its behavior.
// It's fast and in the same time you get read-only access (safety) when you need it.
package memstore
import (
"reflect"
"strconv"
"github.com/kataras/iris/core/errors"
)
type (
// Entry is the entry of the context storage Store - .Values()
Entry struct {
Key string
ValueRaw interface{}
immutable bool // if true then it can't change by its caller.
}
// Store is a collection of key-value entries with immutability capabilities.
Store []Entry
)
// Value returns the value of the entry,
// respects the immutable.
func (e Entry) Value() interface{} {
if e.immutable {
// take its value, no pointer even if setted with a rreference.
vv := reflect.Indirect(reflect.ValueOf(e.ValueRaw))
// return copy of that slice
if vv.Type().Kind() == reflect.Slice {
newSlice := reflect.MakeSlice(vv.Type(), vv.Len(), vv.Cap())
reflect.Copy(newSlice, vv)
return newSlice.Interface()
}
// return a copy of that map
if vv.Type().Kind() == reflect.Map {
newMap := reflect.MakeMap(vv.Type())
for _, k := range vv.MapKeys() {
newMap.SetMapIndex(k, vv.MapIndex(k))
}
return newMap.Interface()
}
// if was *value it will return value{}.
return vv.Interface()
}
return e.ValueRaw
}
// Save same as `Set`
// However, if "immutable" is true then saves it as immutable (same as `SetImmutable`).
//
//
// Returns the entry and true if it was just inserted, meaning that
// it will return the entry and a false boolean if the entry exists and it has been updated.
func (r *Store) Save(key string, value interface{}, immutable bool) (Entry, bool) {
args := *r
n := len(args)
// replace if we can, else just return
for i := 0; i < n; i++ {
kv := &args[i]
if kv.Key == key {
if immutable && kv.immutable {
// if called by `SetImmutable`
// then allow the update, maybe it's a slice that user wants to update by SetImmutable method,
// we should allow this
kv.ValueRaw = value
kv.immutable = immutable
} else if kv.immutable == false {
// if it was not immutable then user can alt it via `Set` and `SetImmutable`
kv.ValueRaw = value
kv.immutable = immutable
}
// else it was immutable and called by `Set` then disallow the update
return *kv, false
}
}
// expand slice to add it
c := cap(args)
if c > n {
args = args[:n+1]
kv := &args[n]
kv.Key = key
kv.ValueRaw = value
kv.immutable = immutable
*r = args
return *kv, true
}
// add
kv := Entry{
Key: key,
ValueRaw: value,
immutable: immutable,
}
*r = append(args, kv)
return kv, true
}
// Set saves a value to the key-value storage.
// Returns the entry and true if it was just inserted, meaning that
// it will return the entry and a false boolean if the entry exists and it has been updated.
//
// See `SetImmutable` and `Get`.
func (r *Store) Set(key string, value interface{}) (Entry, bool) {
return r.Save(key, value, false)
}
// SetImmutable saves a value to the key-value storage.
// Unlike `Set`, the output value cannot be changed by the caller later on (when .Get OR .Set)
//
// An Immutable entry should be only changed with a `SetImmutable`, simple `Set` will not work
// if the entry was immutable, for your own safety.
//
// Returns the entry and true if it was just inserted, meaning that
// it will return the entry and a false boolean if the entry exists and it has been updated.
//
// Use it consistently, it's far slower than `Set`.
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
func (r *Store) SetImmutable(key string, value interface{}) (Entry, bool) {
return r.Save(key, value, true)
}
// Get returns the entry's value based on its key.
func (r *Store) Get(key string) interface{} {
args := *r
n := len(args)
for i := 0; i < n; i++ {
kv := &args[i]
if kv.Key == key {
return kv.Value()
}
}
return nil
}
// Visit accepts a visitor which will be filled
// by the key-value objects.
func (r *Store) Visit(visitor func(key string, value interface{})) {
args := *r
for i, n := 0, len(args); i < n; i++ {
kv := args[i]
visitor(kv.Key, kv.Value())
}
}
// GetString returns the entry's value as string, based on its key.
func (r *Store) GetString(key string) string {
if v, ok := r.Get(key).(string); ok {
return v
}
return ""
}
// ErrIntParse returns an error message when int parse failed
// it's not statical error, it depends on the failed value.
var ErrIntParse = errors.New("unable to find or parse the integer, found: %#v")
// GetInt returns the entry's value as int, based on its key.
func (r *Store) GetInt(key string) (int, error) {
v := r.Get(key)
if vint, ok := v.(int); ok {
return vint, nil
} else if vstring, sok := v.(string); sok {
return strconv.Atoi(vstring)
}
return -1, ErrIntParse.Format(v)
}
// GetInt64 returns the entry's value as int64, based on its key.
func (r *Store) GetInt64(key string) (int64, error) {
return strconv.ParseInt(r.GetString(key), 10, 64)
}
// Remove deletes an entry linked to that "key",
// returns true if an entry is actually removed.
func (r *Store) Remove(key string) bool {
args := *r
n := len(args)
for i := 0; i < n; i++ {
kv := &args[i]
if kv.Key == key {
// we found the index,
// let's remove the item by appending to the temp and
// after set the pointer of the slice to this temp args
args = append(args[:i], args[i+1:]...)
*r = args
return true
}
}
return false
}
// Reset clears all the request entries.
func (r *Store) Reset() {
*r = (*r)[0:0]
}
// Len returns the full length of the entries.
func (r *Store) Len() int {
args := *r
return len(args)
}
// Serialize returns the byte representation of the current Store.
func (r Store) Serialize() []byte { // note: no pointer here, ignore linters if shows up.
b, _ := GobSerialize(r)
return b
}