feat(orm): ModuleDB JSON import/export/default/validate methods (#11101)
* feat(orm): ModuleDB JSON methods * WIP * WIP on JSON * WIP * WIP * tests and docs * revert * tests and docs * docs * address review comments
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package ormdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/types/ormjson"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/errors"
|
||||
)
|
||||
|
||||
func (m moduleDB) DefaultJSON(target ormjson.WriteTarget) error {
|
||||
for name, table := range m.tablesByName {
|
||||
w, err := target.OpenWriter(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(table.DefaultJSON())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m moduleDB) ValidateJSON(source ormjson.ReadSource) error {
|
||||
errMap := map[protoreflect.FullName]error{}
|
||||
for name, table := range m.tablesByName {
|
||||
r, err := source.OpenReader(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = table.ValidateJSON(r)
|
||||
if err != nil {
|
||||
errMap[name] = err
|
||||
}
|
||||
|
||||
err = r.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(errMap) != 0 {
|
||||
var allErrors string
|
||||
for name, err := range errMap {
|
||||
allErrors += fmt.Sprintf("Error in JSON for table %s: %v\n", name, err)
|
||||
}
|
||||
return ormerrors.JSONValidationError.Wrap(allErrors)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m moduleDB) ImportJSON(ctx context.Context, source ormjson.ReadSource) error {
|
||||
var names []string
|
||||
for name := range m.tablesByName {
|
||||
names = append(names, string(name))
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
for _, name := range names {
|
||||
fullName := protoreflect.FullName(name)
|
||||
table := m.tablesByName[fullName]
|
||||
|
||||
r, err := source.OpenReader(fullName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "table %s", fullName)
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err = table.ImportJSON(ctx, r)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "table %s", fullName)
|
||||
}
|
||||
|
||||
err = r.Close()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "table %s", fullName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m moduleDB) ExportJSON(ctx context.Context, sink ormjson.WriteTarget) error {
|
||||
for name, table := range m.tablesByName {
|
||||
w, err := sink.OpenWriter(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = table.ExportJSON(ctx, w)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/types/ormjson"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/encoding/encodeutil"
|
||||
@@ -31,7 +33,22 @@ type ModuleSchema struct {
|
||||
}
|
||||
|
||||
// ModuleDB defines the ORM database type to be used by modules.
|
||||
type ModuleDB = ormtable.Schema
|
||||
type ModuleDB interface {
|
||||
ormtable.Schema
|
||||
|
||||
// DefaultJSON writes default JSON for each table in the module to the target.
|
||||
DefaultJSON(ormjson.WriteTarget) error
|
||||
|
||||
// ValidateJSON validates JSON for each table in the module.
|
||||
ValidateJSON(ormjson.ReadSource) error
|
||||
|
||||
// ImportJSON imports JSON for each table in the module which has JSON
|
||||
// defined in the read source.
|
||||
ImportJSON(context.Context, ormjson.ReadSource) error
|
||||
|
||||
// ExportJSON exports JSON for each table in the module.
|
||||
ExportJSON(context.Context, ormjson.WriteTarget) error
|
||||
}
|
||||
|
||||
type moduleDB struct {
|
||||
prefix []byte
|
||||
|
||||
@@ -3,10 +3,17 @@ package ormdb_test
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/types/ormerrors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/testing/ormtest"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/orm/types/ormjson"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@@ -157,14 +164,14 @@ func TestModuleDB(t *testing.T) {
|
||||
db, err := ormdb.NewModuleDB(TestBankSchema, ormdb.ModuleDBOptions{})
|
||||
assert.NilError(t, err)
|
||||
debugBuf := &strings.Builder{}
|
||||
store := testkv.NewDebugBackend(
|
||||
testkv.NewSharedMemBackend(),
|
||||
backend := ormtest.NewMemoryBackend()
|
||||
ctx := ormtable.WrapContextDefault(testkv.NewDebugBackend(
|
||||
backend,
|
||||
&testkv.EntryCodecDebugger{
|
||||
EntryCodec: db,
|
||||
Print: func(s string) { debugBuf.WriteString(s + "\n") },
|
||||
},
|
||||
)
|
||||
ctx := ormtable.WrapContextDefault(store)
|
||||
))
|
||||
|
||||
// create keeper
|
||||
k, err := newKeeper(db)
|
||||
@@ -205,7 +212,7 @@ func TestModuleDB(t *testing.T) {
|
||||
golden.Assert(t, debugBuf.String(), "bank_scenario.golden")
|
||||
|
||||
// check decode & encode
|
||||
it, err := store.CommitmentStore().Iterator(nil, nil)
|
||||
it, err := backend.CommitmentStore().Iterator(nil, nil)
|
||||
assert.NilError(t, err)
|
||||
for it.Valid() {
|
||||
entry, err := db.DecodeEntry(it.Key(), it.Value())
|
||||
@@ -216,4 +223,33 @@ func TestModuleDB(t *testing.T) {
|
||||
assert.Assert(t, bytes.Equal(v, it.Value()))
|
||||
it.Next()
|
||||
}
|
||||
|
||||
// check JSON
|
||||
target := ormjson.NewRawMessageTarget()
|
||||
assert.NilError(t, db.DefaultJSON(target))
|
||||
rawJson, err := target.JSON()
|
||||
assert.NilError(t, err)
|
||||
golden.Assert(t, string(rawJson), "default_json.golden")
|
||||
|
||||
target = ormjson.NewRawMessageTarget()
|
||||
assert.NilError(t, db.ExportJSON(ctx, target))
|
||||
rawJson, err = target.JSON()
|
||||
assert.NilError(t, err)
|
||||
|
||||
badJSON := `{
|
||||
"testpb.Balance": 5,
|
||||
"testpb.Supply": {}
|
||||
}
|
||||
`
|
||||
source, err := ormjson.NewRawMessageSource(json.RawMessage(badJSON))
|
||||
assert.NilError(t, err)
|
||||
assert.ErrorIs(t, db.ValidateJSON(source), ormerrors.JSONValidationError)
|
||||
|
||||
backend2 := ormtest.NewMemoryBackend()
|
||||
ctx2 := ormtable.WrapContextDefault(backend2)
|
||||
source, err = ormjson.NewRawMessageSource(rawJson)
|
||||
assert.NilError(t, err)
|
||||
assert.NilError(t, db.ValidateJSON(source))
|
||||
assert.NilError(t, db.ImportJSON(ctx2, source))
|
||||
testkv.AssertBackendsEqual(t, backend, backend2)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"testpb.Balance": [],
|
||||
"testpb.Supply": []
|
||||
}
|
||||
Reference in New Issue
Block a user