diff --git a/binary/reflect.go b/binary/reflect.go index 736ff4ede..5f89585ec 100644 --- a/binary/reflect.go +++ b/binary/reflect.go @@ -9,7 +9,7 @@ import ( "sync" "time" - . "github.com/tendermint/tendermint2/common" + . "github.com/tendermint/tendermint/common" ) type TypeInfo struct { @@ -26,6 +26,19 @@ type TypeInfo struct { // If Type is concrete HasTypeByte bool TypeByte byte + + // If Type is kind reflect.Struct + Fields []StructFieldInfo +} + +type StructFieldInfo struct { + Index int // Struct field index + JSONName string // Corresponding JSON field name. (override with `json=""`) + Type reflect.Type // Struct field type +} + +func (info StructFieldInfo) unpack() (int, string, reflect.Type) { + return info.Index, info.JSONName, info.Type } // e.g. If o is struct{Foo}{}, return is the Foo interface type. @@ -155,6 +168,28 @@ func RegisterType(info *TypeInfo) *TypeInfo { } } + // If struct, register field name options + if rt.Kind() == reflect.Struct { + numFields := rt.NumField() + structFields := []StructFieldInfo{} + for i := 0; i < numFields; i++ { + field := rt.Field(i) + if field.PkgPath != "" { + continue + } + jsonName := field.Tag.Get("json") + if jsonName == "" { + jsonName = field.Name + } + structFields = append(structFields, StructFieldInfo{ + Index: i, + JSONName: jsonName, + Type: field.Type, + }) + } + info.Fields = structFields + } + return info } @@ -532,19 +567,15 @@ func readReflectJSON(rv reflect.Value, rt reflect.Type, o interface{}, err *erro return } // TODO: ensure that all fields are set? - for name, value := range oMap { - field, ok := rt.FieldByName(name) + // TODO: disallow unknown oMap fields? + for _, fieldInfo := range typeInfo.Fields { + i, jsonName, fieldType := fieldInfo.unpack() + value, ok := oMap[jsonName] if !ok { - *err = errors.New(Fmt("Attempt to set unknown field %v", field.Name)) - return + continue // Skip missing fields. } - // JAE: I don't think golang reflect lets us set unexported fields, but just in case: - if field.PkgPath != "" { - *err = errors.New(Fmt("Attempt to set unexported field %v", field.Name)) - return - } - fieldRv := rv.FieldByName(name) - readReflectJSON(fieldRv, field.Type, value, err) + fieldRv := rv.Field(i) + readReflectJSON(fieldRv, fieldType, value, err) } } @@ -643,21 +674,17 @@ func writeReflectJSON(rv reflect.Value, rt reflect.Type, w io.Writer, n *int64, WriteTo(jsonBytes, w, n, err) } else { WriteTo([]byte("{"), w, n, err) - numFields := rt.NumField() wroteField := false - for i := 0; i < numFields; i++ { - field := rt.Field(i) - if field.PkgPath != "" { - continue - } + for _, fieldInfo := range typeInfo.Fields { + i, jsonName, fieldType := fieldInfo.unpack() fieldRv := rv.Field(i) if wroteField { WriteTo([]byte(","), w, n, err) } else { wroteField = true } - WriteTo([]byte(Fmt("\"%v\":", field.Name)), w, n, err) - writeReflectJSON(fieldRv, field.Type, w, n, err) + WriteTo([]byte(Fmt("\"%v\":", jsonName)), w, n, err) + writeReflectJSON(fieldRv, fieldType, w, n, err) } WriteTo([]byte("}"), w, n, err) } diff --git a/binary/reflect_test.go b/binary/reflect_test.go index ac5423685..9220d8fa6 100644 --- a/binary/reflect_test.go +++ b/binary/reflect_test.go @@ -401,3 +401,23 @@ func TestJSON(t *testing.T) { } } + +//------------------------------------------------------------------------------ + +type Foo struct { + FieldA string `json:"fieldA"` // json field name is "fieldA" + FieldB string // json field name is "FieldB" + fieldC string // not exported, not serialized. +} + +func TestJSONFieldNames(t *testing.T) { + for i := 0; i < 20; i++ { // Try to ensure deterministic success. + foo := Foo{"a", "b", "c"} + stringified := string(JSONBytes(foo)) + expected := `{"fieldA":"a","FieldB":"b"}` + if stringified != expected { + t.Fatalf("JSONFieldNames error: expected %v, got %v", + expected, stringified) + } + } +} diff --git a/daemon/daemon.go b/daemon/daemon.go index b691a48fe..e13ff036c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -199,6 +199,7 @@ func deboraBroadcast(n *Node) func([]byte) { func Daemon(deborable DeboraMode) { // Add to debora if deborable == DeboraPeerMode { + // TODO: support debora.logfile if err := debora.Add(PublicKey, SrcPath, AppName, config.App().GetString("Debora.LogFile")); err != nil { log.Info("Failed to add program to debora", "error", err) } diff --git a/rpc/handlers.go b/rpc/handlers.go index 703be74c4..1cf940803 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -97,22 +97,23 @@ func JSONRPCHandler(w http.ResponseWriter, r *http.Request) { var jrpc JSONRPC err := json.Unmarshal(b, &jrpc) if err != nil { - // TODO + WriteRPCResponse(w, NewRPCResponse(nil, err.Error())) + return } funcInfo := funcMap[jrpc.Method] args, err := jsonParamsToArgs(funcInfo, jrpc.Params) if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error()) + WriteRPCResponse(w, NewRPCResponse(nil, err.Error())) return } returns := funcInfo.f.Call(args) response, err := returnsToResponse(returns) if err != nil { - WriteAPIResponse(w, API_ERROR, nil, err.Error()) + WriteRPCResponse(w, NewRPCResponse(nil, err.Error())) return } - WriteAPIResponse(w, API_OK, response, "") + WriteRPCResponse(w, NewRPCResponse(response, "")) } // covert a list of interfaces to properly typed values @@ -149,16 +150,16 @@ func toHttpHandler(funcInfo *FuncWrapper) func(http.ResponseWriter, *http.Reques return func(w http.ResponseWriter, r *http.Request) { args, err := httpParamsToArgs(funcInfo, r) if err != nil { - WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error()) + WriteRPCResponse(w, NewRPCResponse(nil, err.Error())) return } returns := funcInfo.f.Call(args) response, err := returnsToResponse(returns) if err != nil { - WriteAPIResponse(w, API_ERROR, nil, err.Error()) + WriteRPCResponse(w, NewRPCResponse(nil, err.Error())) return } - WriteAPIResponse(w, API_OK, response, "") + WriteRPCResponse(w, NewRPCResponse(response, "")) } } diff --git a/rpc/http_params.go b/rpc/http_params.go index af7e23d4e..cf4dec220 100644 --- a/rpc/http_params.go +++ b/rpc/http_params.go @@ -23,8 +23,8 @@ var ( //RE_ID12 = regexp.MustCompile(`^[a-zA-Z0-9]{12}$`) ) -func panicAPI(err error) { - panic(APIResponse{API_INVALID_PARAM, nil, err.Error()}) +func panicRPC(err error) { + panic(NewRPCResponse(nil, err.Error())) } func GetParam(r *http.Request, param string) string { diff --git a/rpc/http_server.go b/rpc/http_server.go index 76e0caf0c..8551e2bb9 100644 --- a/rpc/http_server.go +++ b/rpc/http_server.go @@ -23,64 +23,39 @@ func StartHTTPServer() { }() } -//----------------------------------------------------------------------------- - -type APIStatus string - -const ( - API_OK APIStatus = "OK" - API_ERROR APIStatus = "ERROR" - API_INVALID_PARAM APIStatus = "INVALID_PARAM" - API_UNAUTHORIZED APIStatus = "UNAUTHORIZED" - API_REDIRECT APIStatus = "REDIRECT" -) - -type APIResponse struct { - Status APIStatus `json:"status"` - Data interface{} `json:"data"` - Error string `json:"error"` +type RPCResponse struct { + Result interface{} `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } -func (res APIResponse) StatusError() string { - return fmt.Sprintf("Status(%v) %v", res.Status, res.Error) -} - -func WriteAPIResponse(w http.ResponseWriter, status APIStatus, data interface{}, responseErr string) { - res := APIResponse{} - res.Status = status - if data == nil { - // so json doesn't vommit - data = struct{}{} +func NewRPCResponse(res interface{}, err string) RPCResponse { + if res == nil { + res = struct{}{} + } + return RPCResponse{ + Result: res, + Error: err, + Id: "", + JSONRPC: 2, } - res.Data = data - res.Error = responseErr +} +func WriteRPCResponse(w http.ResponseWriter, res RPCResponse) { buf, n, err := new(bytes.Buffer), new(int64), new(error) binary.WriteJSON(res, buf, n, err) if *err != nil { - log.Warn("Failed to write JSON APIResponse", "error", err) + log.Warn("Failed to write JSON RPCResponse", "error", err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - /* Bad idea: (e.g. hard to use with jQuery) - switch res.Status { - case API_OK: - w.WriteHeader(200) - case API_ERROR: - w.WriteHeader(400) - case API_UNAUTHORIZED: - w.WriteHeader(401) - case API_INVALID_PARAM: - w.WriteHeader(420) - case API_REDIRECT: - w.WriteHeader(430) - default: - w.WriteHeader(440) - }*/ w.Write(buf.Bytes()) } +//----------------------------------------------------------------------------- + // Wraps an HTTP handler, adding error logging. // // If the inner function panics, the outer function recovers, logs, sends an @@ -113,9 +88,9 @@ func RecoverAndLogHandler(handler http.Handler) http.Handler { // at least to my localhost. if e := recover(); e != nil { - // If APIResponse, - if res, ok := e.(APIResponse); ok { - WriteAPIResponse(rww, res.Status, nil, res.Error) + // If RPCResponse + if res, ok := e.(RPCResponse); ok { + WriteRPCResponse(rww, res) } else { // For the rest, rww.WriteHeader(http.StatusInternalServerError) diff --git a/rpc/test/http_rpc_test.go b/rpc/test/http_rpc_test.go index 6ed7f4e34..a98abba3c 100644 --- a/rpc/test/http_rpc_test.go +++ b/rpc/test/http_rpc_test.go @@ -3,11 +3,9 @@ package rpc import ( "bytes" "encoding/hex" - "encoding/json" "fmt" "github.com/tendermint/tendermint2/binary" . "github.com/tendermint/tendermint2/common" - "github.com/tendermint/tendermint2/config" "github.com/tendermint/tendermint2/merkle" "github.com/tendermint/tendermint2/rpc/core" "github.com/tendermint/tendermint2/state" @@ -28,19 +26,19 @@ func TestHTTPStatus(t *testing.T) { if err != nil { t.Fatal(err) } - var status struct { - Status string - Data core.ResponseStatus - Error string + var response struct { + Result core.ResponseStatus `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - err = json.Unmarshal(body, &status) + binary.ReadJSON(&response, body, &err) if err != nil { t.Fatal(err) } - data := status.Data - if data.Network != config.App().GetString("Network") { - t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", data.Network, config.App().Get("Network"))) - } + result := response.Result + fmt.Println(">>>", result) + return } func TestHTTPGenPriv(t *testing.T) { @@ -56,18 +54,17 @@ func TestHTTPGenPriv(t *testing.T) { if err != nil { t.Fatal(err) } - var status struct { - Status string - Data core.ResponseGenPrivAccount - Error string + var response struct { + Result core.ResponseGenPrivAccount `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - binary.ReadJSON(&status, body, &err) + binary.ReadJSON(&response, body, &err) if err != nil { t.Fatal(err) } - if len(status.Data.PrivAccount.Address) == 0 { - t.Fatal("Failed to generate an address") - } + fmt.Println(">>>", response) } func TestHTTPGetAccount(t *testing.T) { diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go index 7da00ccc3..9fe28a441 100644 --- a/rpc/test/json_rpc_test.go +++ b/rpc/test/json_rpc_test.go @@ -38,17 +38,20 @@ func TestJSONStatus(t *testing.T) { if err != nil { t.Fatal(err) } - status := new(struct { - Status string - Data core.ResponseStatus - Error string - }) - err = json.Unmarshal(body, status) + + var response struct { + Result core.ResponseStatus `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` + } + binary.ReadJSON(&response, body, &err) if err != nil { t.Fatal(err) } - if status.Data.Network != config.App().GetString("Network") { - t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", status.Data.Network, config.App().Get("Network"))) + if response.Result.Network != config.App().GetString("Network") { + t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", + response.Result.Network, config.App().Get("Network"))) } } @@ -76,16 +79,17 @@ func TestJSONGenPriv(t *testing.T) { if err != nil { t.Fatal(err) } - var status struct { - Status string - Data core.ResponseGenPrivAccount - Error string + var response struct { + Result core.ResponseGenPrivAccount `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - binary.ReadJSON(&status, body, &err) + binary.ReadJSON(&response, body, &err) if err != nil { t.Fatal(err) } - if len(status.Data.PrivAccount.Address) == 0 { + if len(response.Result.PrivAccount.Address) == 0 { t.Fatal("Failed to generate an address") } } @@ -138,16 +142,17 @@ func TestJSONBroadcastTx(t *testing.T) { } b := w.Bytes() - var status struct { - Status string - Data core.ResponseBroadcastTx - Error string + var response struct { + Result core.ResponseBroadcastTx `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status) - if status.Status == "ERROR" { - t.Fatal(status.Error) + requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &response) + if response.Error != "" { + t.Fatal(response.Error) } - receipt := status.Data.Receipt + receipt := response.Result.Receipt if receipt.CreatesContract > 0 { t.Fatal("This tx does not create a contract") } diff --git a/rpc/test/test.go b/rpc/test/test.go index 0923508bc..8d87fde33 100644 --- a/rpc/test/test.go +++ b/rpc/test/test.go @@ -96,16 +96,17 @@ func getAccount(t *testing.T, typ string, addr []byte) *account.Account { if err != nil { t.Fatal(err) } - var status struct { - Status string - Data core.ResponseGetAccount - Error string + var response struct { + Result core.ResponseGetAccount `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - binary.ReadJSON(&status, body, &err) + binary.ReadJSON(&response, body, &err) if err != nil { t.Fatal(err) } - return status.Data.Account + return response.Result.Account } func makeSendTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx { @@ -165,7 +166,7 @@ func makeCallTx(t *testing.T, typ string, from, to, data []byte, amt, gaslim, fe return tx } -func requestResponse(t *testing.T, method string, values url.Values, status interface{}) { +func requestResponse(t *testing.T, method string, values url.Values, response interface{}) { resp, err := http.PostForm(requestAddr+method, values) if err != nil { t.Fatal(err) @@ -175,7 +176,7 @@ func requestResponse(t *testing.T, method string, values url.Values, status inte if err != nil { t.Fatal(err) } - binary.ReadJSON(status, body, &err) + binary.ReadJSON(response, body, &err) if err != nil { t.Fatal(err) } @@ -207,18 +208,18 @@ func signTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [64]byt t.Fatal(err) } - var status struct { - Status string - Data core.ResponseSignTx - Error string + var response struct { + Result core.ResponseSignTx `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - requestResponse(t, "unsafe/sign_tx", url.Values{"tx": {string(b)}, "privAccounts": {string(w.Bytes())}}, &status) - if status.Status == "ERROR" { - t.Fatal(status.Error) + requestResponse(t, "unsafe/sign_tx", url.Values{"tx": {string(b)}, "privAccounts": {string(w.Bytes())}}, &response) + if response.Error != "" { + t.Fatal(response.Error) } - response := status.Data - //tx = response.Tx.(*types.SendTx) - return response.Tx, privAcc + result := response.Result + return result.Tx, privAcc } func broadcastTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [64]byte, amt, gaslim, fee uint64) (types.Tx, core.Receipt) { @@ -232,45 +233,48 @@ func broadcastTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [6 } b := w.Bytes() - var status struct { - Status string - Data core.ResponseBroadcastTx - Error string + var response struct { + Result core.ResponseBroadcastTx `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status) - if status.Status == "ERROR" { - t.Fatal(status.Error) + requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &response) + if response.Error != "" { + t.Fatal(response.Error) } - return tx, status.Data.Receipt + return tx, response.Result.Receipt } func dumpStorage(t *testing.T, addr []byte) core.ResponseDumpStorage { addrString := "\"" + hex.EncodeToString(addr) + "\"" - var status struct { - Status string - Data core.ResponseDumpStorage - Error string + var response struct { + Result core.ResponseDumpStorage `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - requestResponse(t, "dump_storage", url.Values{"address": {addrString}}, &status) - if status.Status != "OK" { - t.Fatal(status.Error) + requestResponse(t, "dump_storage", url.Values{"address": {addrString}}, &response) + if response.Error != "" { + t.Fatal(response.Error) } - return status.Data + return response.Result } func getStorage(t *testing.T, addr, slot []byte) []byte { addrString := "\"" + hex.EncodeToString(addr) + "\"" slotString := "\"" + hex.EncodeToString(slot) + "\"" - var status struct { - Status string - Data core.ResponseGetStorage - Error string + var response struct { + Result core.ResponseGetStorage `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC int `json:"jsonrpc"` } - requestResponse(t, "get_storage", url.Values{"address": {addrString}, "storage": {slotString}}, &status) - if status.Status != "OK" { - t.Fatal(status.Error) + requestResponse(t, "get_storage", url.Values{"address": {addrString}, "storage": {slotString}}, &response) + if response.Error != "" { + t.Fatal(response.Error) } - return status.Data.Value + return response.Result.Value } func checkTx(t *testing.T, fromAddr []byte, priv *account.PrivAccount, tx *types.SendTx) {