@ -1,173 +1,46 @@ | |||
package core | |||
import ( | |||
data "github.com/tendermint/go-wire/data" | |||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||
rpc "github.com/tendermint/tendermint/rpc/lib/server" | |||
"github.com/tendermint/tendermint/rpc/lib/types" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// TODO: better system than "unsafe" prefix | |||
var Routes = map[string]*rpc.RPCFunc{ | |||
// subscribe/unsubscribe are reserved for websocket events. | |||
"subscribe": rpc.NewWSRPCFunc(SubscribeResult, "event"), | |||
"unsubscribe": rpc.NewWSRPCFunc(UnsubscribeResult, "event"), | |||
"subscribe": rpc.NewWSRPCFunc(Subscribe, "event"), | |||
"unsubscribe": rpc.NewWSRPCFunc(Unsubscribe, "event"), | |||
// info API | |||
"status": rpc.NewRPCFunc(StatusResult, ""), | |||
"net_info": rpc.NewRPCFunc(NetInfoResult, ""), | |||
"blockchain": rpc.NewRPCFunc(BlockchainInfoResult, "minHeight,maxHeight"), | |||
"genesis": rpc.NewRPCFunc(GenesisResult, ""), | |||
"block": rpc.NewRPCFunc(BlockResult, "height"), | |||
"commit": rpc.NewRPCFunc(CommitResult, "height"), | |||
"tx": rpc.NewRPCFunc(TxResult, "hash,prove"), | |||
"validators": rpc.NewRPCFunc(ValidatorsResult, ""), | |||
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusStateResult, ""), | |||
"unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxsResult, ""), | |||
"num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxsResult, ""), | |||
"status": rpc.NewRPCFunc(Status, ""), | |||
"net_info": rpc.NewRPCFunc(NetInfo, ""), | |||
"blockchain": rpc.NewRPCFunc(BlockchainInfo, "minHeight,maxHeight"), | |||
"genesis": rpc.NewRPCFunc(Genesis, ""), | |||
"block": rpc.NewRPCFunc(Block, "height"), | |||
"commit": rpc.NewRPCFunc(Commit, "height"), | |||
"tx": rpc.NewRPCFunc(Tx, "hash,prove"), | |||
"validators": rpc.NewRPCFunc(Validators, ""), | |||
"dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""), | |||
"unconfirmed_txs": rpc.NewRPCFunc(UnconfirmedTxs, ""), | |||
"num_unconfirmed_txs": rpc.NewRPCFunc(NumUnconfirmedTxs, ""), | |||
// broadcast API | |||
"broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommitResult, "tx"), | |||
"broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSyncResult, "tx"), | |||
"broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsyncResult, "tx"), | |||
"broadcast_tx_commit": rpc.NewRPCFunc(BroadcastTxCommit, "tx"), | |||
"broadcast_tx_sync": rpc.NewRPCFunc(BroadcastTxSync, "tx"), | |||
"broadcast_tx_async": rpc.NewRPCFunc(BroadcastTxAsync, "tx"), | |||
// abci API | |||
"abci_query": rpc.NewRPCFunc(ABCIQueryResult, "path,data,prove"), | |||
"abci_info": rpc.NewRPCFunc(ABCIInfoResult, ""), | |||
"abci_query": rpc.NewRPCFunc(ABCIQuery, "path,data,prove"), | |||
"abci_info": rpc.NewRPCFunc(ABCIInfo, ""), | |||
// control API | |||
"dial_seeds": rpc.NewRPCFunc(UnsafeDialSeedsResult, "seeds"), | |||
"dial_seeds": rpc.NewRPCFunc(UnsafeDialSeeds, "seeds"), | |||
"unsafe_flush_mempool": rpc.NewRPCFunc(UnsafeFlushMempool, ""), | |||
// config is not in general thread safe. expose specifics if you need em | |||
// "unsafe_set_config": rpc.NewRPCFunc(UnsafeSetConfigResult, "type,key,value"), | |||
// "unsafe_set_config": rpc.NewRPCFunc(UnsafeSetConfig, "type,key,value"), | |||
// profiler API | |||
"unsafe_start_cpu_profiler": rpc.NewRPCFunc(UnsafeStartCPUProfilerResult, "filename"), | |||
"unsafe_stop_cpu_profiler": rpc.NewRPCFunc(UnsafeStopCPUProfilerResult, ""), | |||
"unsafe_write_heap_profile": rpc.NewRPCFunc(UnsafeWriteHeapProfileResult, "filename"), | |||
} | |||
func SubscribeResult(wsCtx rpctypes.WSRPCContext, event string) (ctypes.TMResult, error) { | |||
res, err := Subscribe(wsCtx, event) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnsubscribeResult(wsCtx rpctypes.WSRPCContext, event string) (ctypes.TMResult, error) { | |||
res, err := Unsubscribe(wsCtx, event) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func StatusResult() (ctypes.TMResult, error) { | |||
res, err := Status() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func NetInfoResult() (ctypes.TMResult, error) { | |||
res, err := NetInfo() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnsafeDialSeedsResult(seeds []string) (ctypes.TMResult, error) { | |||
res, err := UnsafeDialSeeds(seeds) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func BlockchainInfoResult(min, max int) (ctypes.TMResult, error) { | |||
res, err := BlockchainInfo(min, max) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func GenesisResult() (ctypes.TMResult, error) { | |||
res, err := Genesis() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func BlockResult(height int) (ctypes.TMResult, error) { | |||
res, err := Block(height) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func CommitResult(height int) (ctypes.TMResult, error) { | |||
res, err := Commit(height) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func ValidatorsResult() (ctypes.TMResult, error) { | |||
res, err := Validators() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func DumpConsensusStateResult() (ctypes.TMResult, error) { | |||
res, err := DumpConsensusState() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnconfirmedTxsResult() (ctypes.TMResult, error) { | |||
res, err := UnconfirmedTxs() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func NumUnconfirmedTxsResult() (ctypes.TMResult, error) { | |||
res, err := NumUnconfirmedTxs() | |||
return ctypes.TMResult{res}, err | |||
} | |||
// Tx allow user to query the transaction results. `nil` could mean the | |||
// transaction is in the mempool, invalidated, or was not send in the first | |||
// place. | |||
func TxResult(hash []byte, prove bool) (ctypes.TMResult, error) { | |||
res, err := Tx(hash, prove) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func BroadcastTxCommitResult(tx types.Tx) (ctypes.TMResult, error) { | |||
res, err := BroadcastTxCommit(tx) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func BroadcastTxSyncResult(tx types.Tx) (ctypes.TMResult, error) { | |||
res, err := BroadcastTxSync(tx) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func BroadcastTxAsyncResult(tx types.Tx) (ctypes.TMResult, error) { | |||
res, err := BroadcastTxAsync(tx) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func ABCIQueryResult(path string, data data.Bytes, prove bool) (ctypes.TMResult, error) { | |||
res, err := ABCIQuery(path, data, prove) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func ABCIInfoResult() (ctypes.TMResult, error) { | |||
res, err := ABCIInfo() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnsafeFlushMempoolResult() (ctypes.TMResult, error) { | |||
res, err := UnsafeFlushMempool() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnsafeSetConfigResult(typ, key, value string) (ctypes.TMResult, error) { | |||
res, err := UnsafeSetConfig(typ, key, value) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnsafeStartCPUProfilerResult(filename string) (ctypes.TMResult, error) { | |||
res, err := UnsafeStartCPUProfiler(filename) | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnsafeStopCPUProfilerResult() (ctypes.TMResult, error) { | |||
res, err := UnsafeStopCPUProfiler() | |||
return ctypes.TMResult{res}, err | |||
} | |||
func UnsafeWriteHeapProfileResult(filename string) (ctypes.TMResult, error) { | |||
res, err := UnsafeWriteHeapProfile(filename) | |||
return ctypes.TMResult{res}, err | |||
"unsafe_start_cpu_profiler": rpc.NewRPCFunc(UnsafeStartCPUProfiler, "filename"), | |||
"unsafe_stop_cpu_profiler": rpc.NewRPCFunc(UnsafeStopCPUProfiler, ""), | |||
"unsafe_write_heap_profile": rpc.NewRPCFunc(UnsafeWriteHeapProfile, "filename"), | |||
} |
@ -0,0 +1,133 @@ | |||
package rpcserver | |||
import ( | |||
"encoding/json" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/tendermint/go-wire/data" | |||
) | |||
func TestParseJSONMap(t *testing.T) { | |||
assert := assert.New(t) | |||
input := []byte(`{"value":"1234","height":22}`) | |||
// naive is float,string | |||
var p1 map[string]interface{} | |||
err := json.Unmarshal(input, &p1) | |||
if assert.Nil(err) { | |||
h, ok := p1["height"].(float64) | |||
if assert.True(ok, "%#v", p1["height"]) { | |||
assert.EqualValues(22, h) | |||
} | |||
v, ok := p1["value"].(string) | |||
if assert.True(ok, "%#v", p1["value"]) { | |||
assert.EqualValues("1234", v) | |||
} | |||
} | |||
// preloading map with values doesn't help | |||
tmp := 0 | |||
p2 := map[string]interface{}{ | |||
"value": &data.Bytes{}, | |||
"height": &tmp, | |||
} | |||
err = json.Unmarshal(input, &p2) | |||
if assert.Nil(err) { | |||
h, ok := p2["height"].(float64) | |||
if assert.True(ok, "%#v", p2["height"]) { | |||
assert.EqualValues(22, h) | |||
} | |||
v, ok := p2["value"].(string) | |||
if assert.True(ok, "%#v", p2["value"]) { | |||
assert.EqualValues("1234", v) | |||
} | |||
} | |||
// preload here with *pointers* to the desired types | |||
// struct has unknown types, but hard-coded keys | |||
tmp = 0 | |||
p3 := struct { | |||
Value interface{} `json:"value"` | |||
Height interface{} `json:"height"` | |||
}{ | |||
Height: &tmp, | |||
Value: &data.Bytes{}, | |||
} | |||
err = json.Unmarshal(input, &p3) | |||
if assert.Nil(err) { | |||
h, ok := p3.Height.(*int) | |||
if assert.True(ok, "%#v", p3.Height) { | |||
assert.Equal(22, *h) | |||
} | |||
v, ok := p3.Value.(*data.Bytes) | |||
if assert.True(ok, "%#v", p3.Value) { | |||
assert.EqualValues([]byte{0x12, 0x34}, *v) | |||
} | |||
} | |||
// simplest solution, but hard-coded | |||
p4 := struct { | |||
Value data.Bytes `json:"value"` | |||
Height int `json:"height"` | |||
}{} | |||
err = json.Unmarshal(input, &p4) | |||
if assert.Nil(err) { | |||
assert.EqualValues(22, p4.Height) | |||
assert.EqualValues([]byte{0x12, 0x34}, p4.Value) | |||
} | |||
// so, let's use this trick... | |||
// dynamic keys on map, and we can deserialize to the desired types | |||
var p5 map[string]*json.RawMessage | |||
err = json.Unmarshal(input, &p5) | |||
if assert.Nil(err) { | |||
var h int | |||
err = json.Unmarshal(*p5["height"], &h) | |||
if assert.Nil(err) { | |||
assert.Equal(22, h) | |||
} | |||
var v data.Bytes | |||
err = json.Unmarshal(*p5["value"], &v) | |||
if assert.Nil(err) { | |||
assert.Equal(data.Bytes{0x12, 0x34}, v) | |||
} | |||
} | |||
} | |||
func TestParseJSONArray(t *testing.T) { | |||
assert := assert.New(t) | |||
input := []byte(`["1234",22]`) | |||
// naive is float,string | |||
var p1 []interface{} | |||
err := json.Unmarshal(input, &p1) | |||
if assert.Nil(err) { | |||
v, ok := p1[0].(string) | |||
if assert.True(ok, "%#v", p1[0]) { | |||
assert.EqualValues("1234", v) | |||
} | |||
h, ok := p1[1].(float64) | |||
if assert.True(ok, "%#v", p1[1]) { | |||
assert.EqualValues(22, h) | |||
} | |||
} | |||
// preloading map with values helps here (unlike map - p2 above) | |||
tmp := 0 | |||
p2 := []interface{}{&data.Bytes{}, &tmp} | |||
err = json.Unmarshal(input, &p2) | |||
if assert.Nil(err) { | |||
v, ok := p2[0].(*data.Bytes) | |||
if assert.True(ok, "%#v", p2[0]) { | |||
assert.EqualValues([]byte{0x12, 0x34}, *v) | |||
} | |||
h, ok := p2[1].(*int) | |||
if assert.True(ok, "%#v", p2[1]) { | |||
assert.EqualValues(22, *h) | |||
} | |||
} | |||
} |
@ -0,0 +1,290 @@ | |||
package rpcserver | |||
import ( | |||
"encoding/base64" | |||
"encoding/hex" | |||
"reflect" | |||
"time" | |||
"github.com/pkg/errors" | |||
"github.com/tendermint/go-wire" | |||
"github.com/tendermint/go-wire/data" | |||
cmn "github.com/tendermint/tmlibs/common" | |||
) | |||
var ( | |||
timeType = wire.GetTypeFromStructDeclaration(struct{ time.Time }{}) | |||
) | |||
func readJSONObjectPtr(o interface{}, object interface{}, err *error) interface{} { | |||
rv, rt := reflect.ValueOf(o), reflect.TypeOf(o) | |||
if rv.Kind() == reflect.Ptr { | |||
readReflectJSON(rv.Elem(), rt.Elem(), wire.Options{}, object, err) | |||
} else { | |||
cmn.PanicSanity("ReadJSON(Object)Ptr expects o to be a pointer") | |||
} | |||
return o | |||
} | |||
func readByteJSON(o interface{}) (typeByte byte, rest interface{}, err error) { | |||
oSlice, ok := o.([]interface{}) | |||
if !ok { | |||
err = errors.New(cmn.Fmt("Expected type [Byte,?] but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
if len(oSlice) != 2 { | |||
err = errors.New(cmn.Fmt("Expected [Byte,?] len 2 but got len %v", len(oSlice))) | |||
return | |||
} | |||
typeByte_, ok := oSlice[0].(float64) | |||
typeByte = byte(typeByte_) | |||
rest = oSlice[1] | |||
return | |||
} | |||
// Contract: Caller must ensure that rt is supported | |||
// (e.g. is recursively composed of supported native types, and structs and slices.) | |||
// rv and rt refer to the object we're unmarhsaling into, whereas o is the result of naiive json unmarshal (map[string]interface{}) | |||
func readReflectJSON(rv reflect.Value, rt reflect.Type, opts wire.Options, o interface{}, err *error) { | |||
// Get typeInfo | |||
typeInfo := wire.GetTypeInfo(rt) | |||
if rt.Kind() == reflect.Interface { | |||
if !typeInfo.IsRegisteredInterface { | |||
// There's no way we can read such a thing. | |||
*err = errors.New(cmn.Fmt("Cannot read unregistered interface type %v", rt)) | |||
return | |||
} | |||
if o == nil { | |||
return // nil | |||
} | |||
typeByte, rest, err_ := readByteJSON(o) | |||
if err_ != nil { | |||
*err = err_ | |||
return | |||
} | |||
crt, ok := typeInfo.ByteToType[typeByte] | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Byte %X not registered for interface %v", typeByte, rt)) | |||
return | |||
} | |||
if crt.Kind() == reflect.Ptr { | |||
crt = crt.Elem() | |||
crv := reflect.New(crt) | |||
readReflectJSON(crv.Elem(), crt, opts, rest, err) | |||
rv.Set(crv) // NOTE: orig rv is ignored. | |||
} else { | |||
crv := reflect.New(crt).Elem() | |||
readReflectJSON(crv, crt, opts, rest, err) | |||
rv.Set(crv) // NOTE: orig rv is ignored. | |||
} | |||
return | |||
} | |||
if rt.Kind() == reflect.Ptr { | |||
if o == nil { | |||
return // nil | |||
} | |||
// Create new struct if rv is nil. | |||
if rv.IsNil() { | |||
newRv := reflect.New(rt.Elem()) | |||
rv.Set(newRv) | |||
rv = newRv | |||
} | |||
// Dereference pointer | |||
rv, rt = rv.Elem(), rt.Elem() | |||
typeInfo = wire.GetTypeInfo(rt) | |||
// continue... | |||
} | |||
switch rt.Kind() { | |||
case reflect.Array: | |||
elemRt := rt.Elem() | |||
length := rt.Len() | |||
if elemRt.Kind() == reflect.Uint8 { | |||
// Special case: Bytearrays | |||
oString, ok := o.(string) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected string but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
// if its data.Bytes, use hex; else use base64 | |||
dbty := reflect.TypeOf(data.Bytes{}) | |||
var buf []byte | |||
var err_ error | |||
if rt == dbty { | |||
buf, err_ = hex.DecodeString(oString) | |||
} else { | |||
buf, err_ = base64.StdEncoding.DecodeString(oString) | |||
} | |||
if err_ != nil { | |||
*err = err_ | |||
return | |||
} | |||
if len(buf) != length { | |||
*err = errors.New(cmn.Fmt("Expected bytearray of length %v but got %v", length, len(buf))) | |||
return | |||
} | |||
//log.Info("Read bytearray", "bytes", buf) | |||
reflect.Copy(rv, reflect.ValueOf(buf)) | |||
} else { | |||
oSlice, ok := o.([]interface{}) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected array of %v but got type %v", rt, reflect.TypeOf(o))) | |||
return | |||
} | |||
if len(oSlice) != length { | |||
*err = errors.New(cmn.Fmt("Expected array of length %v but got %v", length, len(oSlice))) | |||
return | |||
} | |||
for i := 0; i < length; i++ { | |||
elemRv := rv.Index(i) | |||
readReflectJSON(elemRv, elemRt, opts, oSlice[i], err) | |||
} | |||
//log.Info("Read x-array", "x", elemRt, "length", length) | |||
} | |||
case reflect.Slice: | |||
elemRt := rt.Elem() | |||
if elemRt.Kind() == reflect.Uint8 { | |||
// Special case: Byteslices | |||
oString, ok := o.(string) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected string but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
// if its data.Bytes, use hex; else use base64 | |||
dbty := reflect.TypeOf(data.Bytes{}) | |||
var buf []byte | |||
var err_ error | |||
if rt == dbty { | |||
buf, err_ = hex.DecodeString(oString) | |||
} else { | |||
buf, err_ = base64.StdEncoding.DecodeString(oString) | |||
} | |||
if err_ != nil { | |||
*err = err_ | |||
return | |||
} | |||
//log.Info("Read byteslice", "bytes", byteslice) | |||
rv.Set(reflect.ValueOf(buf)) | |||
} else { | |||
// Read length | |||
oSlice, ok := o.([]interface{}) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected array of %v but got type %v", rt, reflect.TypeOf(o))) | |||
return | |||
} | |||
length := len(oSlice) | |||
//log.Info("Read slice", "length", length) | |||
sliceRv := reflect.MakeSlice(rt, length, length) | |||
// Read elems | |||
for i := 0; i < length; i++ { | |||
elemRv := sliceRv.Index(i) | |||
readReflectJSON(elemRv, elemRt, opts, oSlice[i], err) | |||
} | |||
rv.Set(sliceRv) | |||
} | |||
case reflect.Struct: | |||
if rt == timeType { | |||
// Special case: time.Time | |||
str, ok := o.(string) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected string but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
// try three ways, seconds, milliseconds, or microseconds... | |||
t, err_ := time.Parse(time.RFC3339Nano, str) | |||
if err_ != nil { | |||
*err = err_ | |||
return | |||
} | |||
rv.Set(reflect.ValueOf(t)) | |||
} else { | |||
if typeInfo.Unwrap { | |||
f := typeInfo.Fields[0] | |||
fieldIdx, fieldType, opts := f.Index, f.Type, f.Options | |||
fieldRv := rv.Field(fieldIdx) | |||
readReflectJSON(fieldRv, fieldType, opts, o, err) | |||
} else { | |||
oMap, ok := o.(map[string]interface{}) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected map but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
// TODO: ensure that all fields are set? | |||
// TODO: disallow unknown oMap fields? | |||
for _, fieldInfo := range typeInfo.Fields { | |||
f := fieldInfo | |||
fieldIdx, fieldType, opts := f.Index, f.Type, f.Options | |||
value, ok := oMap[opts.JSONName] | |||
if !ok { | |||
continue // Skip missing fields. | |||
} | |||
fieldRv := rv.Field(fieldIdx) | |||
readReflectJSON(fieldRv, fieldType, opts, value, err) | |||
} | |||
} | |||
} | |||
case reflect.String: | |||
str, ok := o.(string) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected string but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
//log.Info("Read string", "str", str) | |||
rv.SetString(str) | |||
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: | |||
num, ok := o.(float64) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected numeric but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
//log.Info("Read num", "num", num) | |||
rv.SetInt(int64(num)) | |||
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: | |||
num, ok := o.(float64) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected numeric but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
if num < 0 { | |||
*err = errors.New(cmn.Fmt("Expected unsigned numeric but got %v", num)) | |||
return | |||
} | |||
//log.Info("Read num", "num", num) | |||
rv.SetUint(uint64(num)) | |||
case reflect.Float64, reflect.Float32: | |||
if !opts.Unsafe { | |||
*err = errors.New("Wire float* support requires `wire:\"unsafe\"`") | |||
return | |||
} | |||
num, ok := o.(float64) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected numeric but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
//log.Info("Read num", "num", num) | |||
rv.SetFloat(num) | |||
case reflect.Bool: | |||
bl, ok := o.(bool) | |||
if !ok { | |||
*err = errors.New(cmn.Fmt("Expected boolean but got type %v", reflect.TypeOf(o))) | |||
return | |||
} | |||
//log.Info("Read boolean", "boolean", bl) | |||
rv.SetBool(bl) | |||
default: | |||
cmn.PanicSanity(cmn.Fmt("Unknown field type %v", rt.Kind())) | |||
} | |||
} |