cosmos-sdk/collections/codec/indexing.go

103 lines
3.7 KiB
Go

package codec
import (
"encoding/json"
"fmt"
"cosmossdk.io/schema"
)
// HasSchemaCodec is an interface that all codec's should implement in order
// to properly support indexing. It is not required by KeyCodec or ValueCodec
// in order to preserve backwards compatibility, but a future version of collections
// may make it required and all codec's should aim to implement it. If it is not
// implemented, fallback defaults will be used for indexing that may be sub-optimal.
//
// Implementations of HasSchemaCodec should test that they are conformant using
// schema.ValidateObjectKey or schema.ValidateObjectValue depending on whether
// the codec is a KeyCodec or ValueCodec respectively.
type HasSchemaCodec[T any] interface {
// SchemaCodec returns the schema codec for the collections codec.
SchemaCodec() (SchemaCodec[T], error)
}
// SchemaCodec is a codec that supports converting collection codec values to and
// from schema codec values.
type SchemaCodec[T any] struct {
// Fields are the schema fields that the codec represents. If this is empty,
// it will be assumed that this codec represents no value (such as an item key
// or key set value).
Fields []schema.Field
// ToSchemaType converts a codec value of type T to a value corresponding to
// a schema object key or value (depending on whether this is a key or value
// codec). The returned value should pass validation with schema.ValidateObjectKey
// or schema.ValidateObjectValue with the fields specified in Fields.
// If this function is nil, it will be assumed that T already represents a
// value that conforms to a schema value without any further conversion.
ToSchemaType func(T) (any, error)
// FromSchemaType converts a schema object key or value to T.
// If this function is nil, it will be assumed that T already represents a
// value that conforms to a schema value without any further conversion.
FromSchemaType func(any) (T, error)
}
// KeySchemaCodec gets the schema codec for the provided KeyCodec either
// by casting to HasSchemaCodec or returning a fallback codec.
func KeySchemaCodec[K any](cdc KeyCodec[K]) (SchemaCodec[K], error) {
if indexable, ok := cdc.(HasSchemaCodec[K]); ok {
return indexable.SchemaCodec()
} else {
return FallbackSchemaCodec[K](), nil
}
}
// ValueSchemaCodec gets the schema codec for the provided ValueCodec either
// by casting to HasSchemaCodec or returning a fallback codec.
func ValueSchemaCodec[V any](cdc ValueCodec[V]) (SchemaCodec[V], error) {
if indexable, ok := cdc.(HasSchemaCodec[V]); ok {
return indexable.SchemaCodec()
} else {
return FallbackSchemaCodec[V](), nil
}
}
// FallbackSchemaCodec returns a fallback schema codec for T when one isn't explicitly
// specified with HasSchemaCodec. It maps all simple types directly to schema kinds
// and converts everything else to JSON String.
func FallbackSchemaCodec[T any]() SchemaCodec[T] {
var t T
kind := schema.KindForGoValue(t)
if err := kind.Validate(); err == nil {
return SchemaCodec[T]{
Fields: []schema.Field{{
// we don't set any name so that this can be set to a good default by the caller
Name: "",
Kind: kind,
}},
// these can be nil because T maps directly to a schema value for this kind
ToSchemaType: nil,
FromSchemaType: nil,
}
} else {
// we default to encoding everything to JSON String
return SchemaCodec[T]{
Fields: []schema.Field{{Kind: schema.StringKind}},
ToSchemaType: func(t T) (any, error) {
bz, err := json.Marshal(t)
return string(json.RawMessage(bz)), err
},
FromSchemaType: func(a any) (T, error) {
var t T
sz, ok := a.(string)
if !ok {
return t, fmt.Errorf("expected string, got %T", a)
}
err := json.Unmarshal([]byte(sz), &t)
return t, err
},
}
}
}