-
Notifications
You must be signed in to change notification settings - Fork 0
/
new.go
348 lines (304 loc) · 11.7 KB
/
new.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
package store
import (
"context"
stderr "errors"
"io"
"gopkg.in/hedzr/errors.v3"
"github.com/hedzr/store/radix"
)
// New makes a new instance of storeS and returns it.
//
// A storeS is a key-value container in memory with hierarchical
// tree data. A leaf or branch node can hold data. The dotted
// path
func New(opts ...Opt) Store { return newStore(opts...) }
// NewStoreT allows reimplementing your own Store.
//
// Any suggestions are welcome, please issue me.
func NewStoreT[T any]() MinimalStoreT[T] {
return radix.NewTrie[T]()
}
// type storeS interface {
// Get (path string) (data any, found bool)
// Set (path string, data any)
// Has (path string) (found bool)
// }
//
// type entryS struct {
// name string
// value Value
// }
//
// type storeSs struct {
// root *entryS
// rootM map[string]any
//
// items *itemS
// }
//
// type itemS struct { //nolint:unused
// leaves map[string]any
// children map[string]*direntS
// }
//
// type direntS struct { //nolint:unused
// items map[string]*itemS
// }
// MinimalStoreT holds a minimal typed Store interface.
type MinimalStoreT[T any] interface {
MustGet(path string) (data T)
Get(path string) (data T, found bool)
Set(path string, data T) (node radix.Node[T], oldData any)
Has(path string) (found bool)
}
// Store holds a standard Store interface.
type Store interface {
// Close cleanup the internal resources.
// See [basics.Peripheral] for more information.
Close()
// MustGet is the shortcut version of Get without
// returning any error.
MustGet(path string) (data any)
// Get the value at path point 'path'.
Get(path string) (data any, found bool)
// Set sets key('path') and value pair into storeS.
Set(path string, data any) (node radix.Node[any], oldData any)
// Remove a key and its children
Remove(path string) (removed bool)
// Merge a map at path point 'pathAt'.
Merge(pathAt string, data map[string]any) (err error)
// Has tests if the given path exists
Has(path string) (found bool)
// Locate provides an advanced interface for locating a path.
//
// RETURNs:
// node: the matched node for retrieving node data later
// branch: true means a branch node matched (generally partialMatched is true)
// patialMatched: true means only a part of the node key was matched.
// found: any (fully or partially) found.
//
// When querying "app.logging.f" on a tree holding "app.logging.file",
// Locate will return
// found = true, partialMatched = true,
// branch = false, and
// node is pointed to "app.logging.file".
//
// These high order apis (Has, Get(Xxx), Set(Xxx), Must(Xxx)) covers the
// Locate's results and provides a dotted-key-path-based behaviors.
// Which means, Has("app.logging.f") gets false and
// Has("app.logging.file") is true.
Locate(path string, kvpair radix.KVPair) (node radix.Node[any], branch, partialMatched, found bool)
radix.TypedGetters[any] // getters
SetComment(path, description, comment string) (ok bool) // set extra meta-info bound to a key
SetTag(path string, tags any) (ok bool) // set extra notable data bound to a key
// Dump prints internal data tree for debugging
Dump() (text string)
// Clone makes a clone copy for this store
Clone() (newStore Store)
// Dup is a native Clone tool.
//
// After Dup, a copy of the original store will be created,
// but closers not.
// Most of the closers are cleanup code fragments coming
// from Load(WithProvider()), some of them needs to shut down the
// remote connection such as what want to do by consul provider.
//
// At this scene, the parent store still holds the cleanup closers.
Dup() (newStore Store)
// Walk does iterate the whole Store.
//
// Walk("") walks from top-level root node.
// Walk("app") walks from the parent of "app" node.
// Walk("app.") walks from the "app." node.
Walk(path string, cb func(path, fragment string, node radix.Node[any]))
// WithPrefix makes a lightweight copy from current storeS.
//
// The new copy is enough light so that you can always use
// it with quite a low price.
//
// WithPrefix appends an extra prefix at the end of the current
// prefix.
//
// For example, on a store with old prefix "app",
// WithPrefix("store") will return a new store 'NS' with prefix
// "app.server". And NS.MustGet("type") retrieve value at key path
// "app.server.type" now.
//
// conf := store.New()
// s1 := conf.WithPrefix("app")
// ns := s1.WithPrefix("server")
// println(ns.MustGet("type")) # print conf["app.server.type"]
//
// It simplify biz-logic codes sometimes.
//
// The arg 'prefix' can be an array, which will be joint
// with the [Delimiter].
WithPrefix(prefix ...string) (newStore Store)
// WithPrefixReplaced is similar with WithPrefix, but it replaces old
// prefix with new one instead of appending it.
//
// conf := store.New()
// s1 := conf.WithPrefix("app")
// ns := s1.WithPrefixReplaced("app.server")
// println(ns.MustGet("type")) # print conf["app.server.type"]
//
// The arg 'prefix' can be an array, which will be joint
// with the [Delimiter].
//
// todo need a balance on returning *storeS or Store, for WithPrefixReplaced.
WithPrefixReplaced(newPrefix ...string) (newStore Store)
// SetPrefix updates the prefix in current storeS.
//
// The arg 'prefix' can be an array, which will be joint
// with the [Delimiter].
SetPrefix(newPrefix ...string)
Prefix() string // return current prefix string
Delimiter() rune // return current delimiter, generally it's dot ('.')
SetDelimiter(delimiter rune) // setter. Change it at runtime doesn't update old delimiter inside tree nodes.
// Load loads k-v pairs from external provider(s) with specified codec decoder(s).
//
// For those provider which run some service at background, such
// as watching service, ctx gives a change to shut them down
// gracefully. So you need pass a cancellable context into it.
//
// Or you know nothing or you don't care the terminating security,
// simply passing context.TODO() is okay.
Load(ctx context.Context, opts ...LoadOpt) (wr Writeable, err error)
// WithinLoading executes a functor with loading state.
//
// About the Store's loading state:
// If it's in loading, the k-v pairs will be put into store with a clean
// modified flag.
WithinLoading(fn func())
}
// Dumpable interface identify an object can be represented as a string for debugging.
type Dumpable interface {
Dump() string
}
// ErrNotImplemented is used to identify unimplemented API.
var ErrNotImplemented = stderr.New("not implemented")
// The Provider gives a minimal set of interface to identify a data source.
//
// The typical data sources are: consul, etcd, file, OS environ, ....
//
// The interfaces are split to several groups: Streamable, Reader,
// Read, ReadBytes and Write.
//
// A provider can implement just one of the above groups.
// At this time, the other interfaces should return ErrNotImplemented.
//
// The Streamable API includes these: Keys, Count, Has, Next, Value
// and "MustValue".
// If you are implementing it, Keys, Value and Next are Must-Have.
// Because our kernel uses Keys to confirm the provider is Streamable,
// and invokes Next to iterate the key one by one.
// Once a key got, Value to get its associated value.
//
// If the dataset is not very large scale, implementing Read is
// recommended to you.
// Read returns hierarchical data set as a nested `map[string]any`
// at once.
// Our kernel (loader) likes its simple logics.
//
// Some providers may support Watchable API.
//
// All providers should always accept Codec and Position and store them.
// When a provider monitored changes, storeS will request a reload
// action and these two Properties shall be usable.
//
// Implementing OnceProvider.Write allows the provider to support
// Write-back mechanism.
type Provider interface {
Read() (m map[string]ValPkg, err error) // return ErrNotImplemented as an identifier if it wants to be skipped
ProviderSupports
}
// ValPkg is a value pack, It will be inserted into trie-tree as a data field.
// A node is commentable by Desc and Comment field.
type ValPkg struct {
Value any // node's value
Desc string // description of a node
Comment string // comment of a node
Tag any // any extra data of a node
}
// OnceProvider is fit for a small-scale provider.
//
// The kv data will be all loaded into memory.
type OnceProvider interface {
ReadBytes() (data []byte, err error) // return ErrNotImplemented as an identifier if it wants to be skipped
Write(data []byte) (err error) // return ErrNotImplemented as an identifier if it wants to be skipped
ProviderSupports
}
// StreamProvider is fit for a large-scale provider and load data on-demand.
type StreamProvider interface {
Keys() (keys []string, err error) // return ErrNotImplemented as an identifier if it wants to be skipped
Count() int // count of keys and/or key-value pairs
Has(key string) bool // test if the key exists
Next() (key string, eol bool) // return next usable key
Value(key string) (value any, ok bool) // return the associated value
MustValue(key string) (value any) // return the value, or nil for a non-existence key
ProviderSupports
}
// FallbackProvider reserved for future.
type FallbackProvider interface {
Reader() (r Reader, err error) // return ErrNotImplemented as an identifier if it wants to be skipped
ProviderSupports
}
// ProviderSupports means which ability is supported by a Provider.
type ProviderSupports interface {
GetCodec() (codec Codec) // return the bound codec decoder
GetPosition() (pos string) // return a position pointed to a Trie-node path
WithCodec(codec Codec)
WithPosition(pos string)
}
// Reader reserved for future purpose.
type Reader interface {
Len() int // Len returns the number of bytes of the unread portion of the slice.
// Size returns the original length of the underlying byte slice.
// Size is the number of bytes available for reading via ReadAt.
// The result is unaffected by any method calls except Reset.
Size() int64
// Read implements the io.Reader interface.
Read(b []byte) (n int, err error)
// ReadAt implements the io.ReaderAt interface.
ReadAt(b []byte, off int64) (n int, err error)
// ReadByte implements the io.ByteReader interface.
ReadByte() (byte, error)
// UnreadByte complements ReadByte in implementing the io.ByteScanner interface.
UnreadByte() error
// ReadRune implements the io.RuneReader interface.
ReadRune() (ch rune, size int, err error)
// UnreadRune complements ReadRune in implementing the io.RuneScanner interface.
UnreadRune() error
// Seek implements the io.Seeker interface.
Seek(offset int64, whence int) (int64, error)
// WriteTo implements the io.WriterTo interface.
WriteTo(w io.Writer) (n int64, err error)
// Reset resets the Reader to be reading from b.
Reset(b []byte)
}
// Codec is decoder and/or encoder for text format.
//
// For example, a file can be encoded with JSON format.
// So you need a JSON codec parser here.
//
// Well-known codec parsers can be JSON, YAML, TOML, ....
type Codec interface {
Marshal(m map[string]any) (data []byte, err error)
Unmarshal(b []byte) (data map[string]any, err error)
}
// CodecEx reserved.
type CodecEx interface {
MarshalEx(m map[string]ValPkg) (data []byte, err error)
UnmarshalEx(b []byte) (data map[string]ValPkg, err error)
}
// Writeable interface
type Writeable interface {
Save(ctx context.Context) (err error)
}
// ErrorIsNotFound checks if TypedGetters returning a NotFound error.
//
// _, err := trie.GetFloat64("app.dump.")
// println(store.ErrorIsNotFound(err)) # this should be 'true'
//
// If you don't care about these errors, use MustXXX such as [radix.Trie.MustFloat64].
func ErrorIsNotFound(err error) bool { return errors.Is(err, errors.NotFound) }