153 lines
5.2 KiB
Go
153 lines
5.2 KiB
Go
package collections
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
"cosmossdk.io/collections/codec"
|
|
)
|
|
|
|
// WithKeySetUncheckedValue changes the behavior of the KeySet when it encounters
|
|
// a value different from '[]byte{}', by default the KeySet errors when this happens.
|
|
// This option allows to ignore the value and continue with the operation, in turn
|
|
// the value will be cleared out and set to '[]byte{}'.
|
|
// You should never use this option if you're creating a new state object from scratch.
|
|
// This should be used only to behave nicely in case you have used values different
|
|
// from '[]byte{}' in your storage before migrating to collections.
|
|
func WithKeySetUncheckedValue() func(opt *keySetOptions) {
|
|
return func(opt *keySetOptions) {
|
|
opt.uncheckedValue = true
|
|
}
|
|
}
|
|
|
|
// WithKeySetSecondaryIndex changes the behavior of the KeySet to be a secondary index.
|
|
func WithKeySetSecondaryIndex() func(opt *keySetOptions) {
|
|
return func(opt *keySetOptions) {
|
|
opt.isSecondaryIndex = true
|
|
}
|
|
}
|
|
|
|
type keySetOptions struct {
|
|
uncheckedValue bool
|
|
isSecondaryIndex bool
|
|
}
|
|
|
|
// KeySet builds on top of a Map and represents a collection retaining only a set
|
|
// of keys and no value. It can be used, for example, in an allow list.
|
|
type KeySet[K any] Map[K, NoValue]
|
|
|
|
// NewKeySet returns a KeySet given a Schema, Prefix a human name for the collection
|
|
// and a KeyCodec for the key K.
|
|
func NewKeySet[K any](
|
|
schema *SchemaBuilder,
|
|
prefix Prefix,
|
|
name string,
|
|
keyCodec codec.KeyCodec[K],
|
|
options ...func(opt *keySetOptions),
|
|
) KeySet[K] {
|
|
o := new(keySetOptions)
|
|
for _, opt := range options {
|
|
opt(o)
|
|
}
|
|
vc := noValueCodec
|
|
if o.uncheckedValue {
|
|
vc = codec.NewAltValueCodec(vc, func(_ []byte) (NoValue, error) { return NoValue{}, nil })
|
|
}
|
|
return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, vc, withMapSecondaryIndex(o.isSecondaryIndex)))
|
|
}
|
|
|
|
// Set adds the key to the KeySet. Errors on encoding problems.
|
|
func (k KeySet[K]) Set(ctx context.Context, key K) error {
|
|
return (Map[K, NoValue])(k).Set(ctx, key, NoValue{})
|
|
}
|
|
|
|
// Has returns if the key is present in the KeySet.
|
|
// An error is returned only in case of encoding problems.
|
|
func (k KeySet[K]) Has(ctx context.Context, key K) (bool, error) {
|
|
return (Map[K, NoValue])(k).Has(ctx, key)
|
|
}
|
|
|
|
// Remove removes the key for the KeySet. An error is returned in case of
|
|
// encoding error, it won't report through the error if the key was
|
|
// removed or not.
|
|
func (k KeySet[K]) Remove(ctx context.Context, key K) error {
|
|
return (Map[K, NoValue])(k).Remove(ctx, key)
|
|
}
|
|
|
|
// Iterate iterates over the keys given the provided Ranger. If ranger is nil,
|
|
// the KeySetIterator will include all the existing keys within the KeySet.
|
|
func (k KeySet[K]) Iterate(ctx context.Context, ranger Ranger[K]) (KeySetIterator[K], error) {
|
|
iter, err := (Map[K, NoValue])(k).Iterate(ctx, ranger)
|
|
if err != nil {
|
|
return KeySetIterator[K]{}, err
|
|
}
|
|
|
|
return (KeySetIterator[K])(iter), nil
|
|
}
|
|
|
|
func (k KeySet[K]) IterateRaw(ctx context.Context, start, end []byte, order Order) (Iterator[K, NoValue], error) {
|
|
return (Map[K, NoValue])(k).IterateRaw(ctx, start, end, order)
|
|
}
|
|
|
|
// Walk provides the same functionality as Map.Walk, but callbacks the walk
|
|
// function only with the key.
|
|
func (k KeySet[K]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key K) (stop bool, err error)) error {
|
|
return (Map[K, NoValue])(k).Walk(ctx, ranger, func(key K, value NoValue) (bool, error) { return walkFunc(key) })
|
|
}
|
|
|
|
// Clear clears the KeySet using the provided Ranger. Refer to Map.Clear for
|
|
// behavioral documentation.
|
|
func (k KeySet[K]) Clear(ctx context.Context, ranger Ranger[K]) error {
|
|
return (Map[K, NoValue])(k).Clear(ctx, ranger)
|
|
}
|
|
|
|
func (k KeySet[K]) KeyCodec() codec.KeyCodec[K] { return (Map[K, NoValue])(k).KeyCodec() }
|
|
func (k KeySet[K]) ValueCodec() codec.ValueCodec[NoValue] { return (Map[K, NoValue])(k).ValueCodec() }
|
|
|
|
// KeySetIterator works like an Iterator, but it does not expose any API to deal with values.
|
|
type KeySetIterator[K any] Iterator[K, NoValue]
|
|
|
|
func (i KeySetIterator[K]) Key() (K, error) { return (Iterator[K, NoValue])(i).Key() }
|
|
func (i KeySetIterator[K]) Keys() ([]K, error) { return (Iterator[K, NoValue])(i).Keys() }
|
|
func (i KeySetIterator[K]) Next() { (Iterator[K, NoValue])(i).Next() }
|
|
func (i KeySetIterator[K]) Valid() bool { return (Iterator[K, NoValue])(i).Valid() }
|
|
func (i KeySetIterator[K]) Close() error { return (Iterator[K, NoValue])(i).Close() }
|
|
|
|
var noValueCodec codec.ValueCodec[NoValue] = NoValue{}
|
|
|
|
const noValueValueType = "no_value"
|
|
|
|
// NoValue is a type that can be used to represent a non-existing value.
|
|
type NoValue struct{}
|
|
|
|
func (n NoValue) EncodeJSON(_ NoValue) ([]byte, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (n NoValue) DecodeJSON(b []byte) (NoValue, error) {
|
|
if b != nil {
|
|
return NoValue{}, fmt.Errorf("%w: expected nil json bytes, got: %x", ErrEncoding, b)
|
|
}
|
|
return NoValue{}, nil
|
|
}
|
|
|
|
func (NoValue) Encode(_ NoValue) ([]byte, error) {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (NoValue) Decode(b []byte) (NoValue, error) {
|
|
if !bytes.Equal(b, []byte{}) {
|
|
return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice, got: %x", ErrEncoding, b)
|
|
}
|
|
return NoValue{}, nil
|
|
}
|
|
|
|
func (NoValue) Stringify(_ NoValue) string {
|
|
return noValueValueType
|
|
}
|
|
|
|
func (n NoValue) ValueType() string {
|
|
return noValueValueType
|
|
}
|