[NOD-398] Change API server type HandlerError to work with errors instead of error strings (#454)

* [NOD-398] Change API server type HandlerError to work with errors instead of error strings

* [NOD-398] Rename OriginalError -> Cause and isHandleError -> ok
This commit is contained in:
Ori Newman 2019-11-06 16:58:58 +02:00 committed by Svarog
parent 3cc6f2d648
commit a9ff9b0e70
8 changed files with 120 additions and 113 deletions

View File

@ -2,10 +2,10 @@ package controllers
import (
"encoding/hex"
"fmt"
"github.com/daglabs/btcd/apiserver/apimodels"
"github.com/daglabs/btcd/apiserver/dbmodels"
"github.com/daglabs/btcd/httpserverutils"
"github.com/pkg/errors"
"net/http"
"github.com/daglabs/btcd/apiserver/database"
@ -27,38 +27,38 @@ const (
const maxGetBlocksLimit = 100
// GetBlockByHashHandler returns a block by a given hash.
func GetBlockByHashHandler(blockHash string) (interface{}, *httpserverutils.HandlerError) {
func GetBlockByHashHandler(blockHash string) (interface{}, error) {
if bytes, err := hex.DecodeString(blockHash); err != nil || len(bytes) != daghash.HashSize {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The given block hash is not a hex-encoded %d-byte hash.", daghash.HashSize))
errors.Errorf("The given block hash is not a hex-encoded %d-byte hash.", daghash.HashSize))
}
db, err := database.DB()
if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error())
return nil, err
}
block := &dbmodels.Block{}
dbResult := db.Where(&dbmodels.Block{BlockHash: blockHash}).Preload("AcceptingBlock").First(block)
dbErrors := dbResult.GetErrors()
if httpserverutils.IsDBRecordNotFoundError(dbErrors) {
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, "No block with the given block hash was found.")
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, errors.New("No block with the given block hash was found"))
}
if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbResult.GetErrors())
return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbResult.GetErrors())
}
return convertBlockModelToBlockResponse(block), nil
}
// GetBlocksHandler searches for all blocks
func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *httpserverutils.HandlerError) {
func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, error) {
if limit > maxGetBlocksLimit {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
}
blocks := []*dbmodels.Block{}
db, err := database.DB()
if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return nil, err
}
query := db.
Limit(limit).
@ -69,7 +69,7 @@ func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *ht
} else if order == OrderDescending {
query = query.Order("`id` DESC")
} else {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid order", order))
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("'%s' is not a valid order", order))
}
query.Find(&blocks)
blockResponses := make([]*apimodels.BlockResponse, len(blocks))

View File

@ -10,6 +10,7 @@ import (
"github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/httpserverutils"
"github.com/daglabs/btcd/util/subnetworkid"
"github.com/pkg/errors"
"net/http"
"github.com/daglabs/btcd/apiserver/database"
@ -23,15 +24,15 @@ import (
const maxGetTransactionsLimit = 1000
// GetTransactionByIDHandler returns a transaction by a given transaction ID.
func GetTransactionByIDHandler(txID string) (interface{}, *httpserverutils.HandlerError) {
func GetTransactionByIDHandler(txID string) (interface{}, error) {
if bytes, err := hex.DecodeString(txID); err != nil || len(bytes) != daghash.TxIDSize {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The given txid is not a hex-encoded %d-byte hash.", daghash.TxIDSize))
errors.Errorf("The given txid is not a hex-encoded %d-byte hash.", daghash.TxIDSize))
}
db, err := database.DB()
if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error())
return nil, err
}
tx := &dbmodels.Transaction{}
@ -39,24 +40,24 @@ func GetTransactionByIDHandler(txID string) (interface{}, *httpserverutils.Handl
dbResult := addTxPreloadedFields(query).First(&tx)
dbErrors := dbResult.GetErrors()
if httpserverutils.IsDBRecordNotFoundError(dbErrors) {
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, "No transaction with the given txid was found.")
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, errors.New("No transaction with the given txid was found"))
}
if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors)
return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors)
}
return convertTxDBModelToTxResponse(tx), nil
}
// GetTransactionByHashHandler returns a transaction by a given transaction hash.
func GetTransactionByHashHandler(txHash string) (interface{}, *httpserverutils.HandlerError) {
func GetTransactionByHashHandler(txHash string) (interface{}, error) {
if bytes, err := hex.DecodeString(txHash); err != nil || len(bytes) != daghash.HashSize {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The given txhash is not a hex-encoded %d-byte hash.", daghash.HashSize))
errors.Errorf("The given txhash is not a hex-encoded %d-byte hash.", daghash.HashSize))
}
db, err := database.DB()
if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return nil, err
}
tx := &dbmodels.Transaction{}
@ -64,25 +65,25 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *httpserverutils.H
dbResult := addTxPreloadedFields(query).First(&tx)
dbErrors := dbResult.GetErrors()
if httpserverutils.IsDBRecordNotFoundError(dbErrors) {
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, "No transaction with the given txhash was found.")
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, errors.Errorf("No transaction with the given txhash was found."))
}
if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors)
return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors)
}
return convertTxDBModelToTxResponse(tx), nil
}
// GetTransactionsByAddressHandler searches for all transactions
// where the given address is either an input or an output.
func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, *httpserverutils.HandlerError) {
func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, error) {
if limit > maxGetTransactionsLimit {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
errors.Errorf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
}
db, err := database.DB()
if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return nil, err
}
txs := []*dbmodels.Transaction{}
@ -90,7 +91,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
Joins("LEFT JOIN `transaction_outputs` ON `transaction_outputs`.`transaction_id` = `transactions`.`id`").
Joins("LEFT JOIN `addresses` AS `out_addresses` ON `out_addresses`.`id` = `transaction_outputs`.`address_id`").
Joins("LEFT JOIN `transaction_inputs` ON `transaction_inputs`.`transaction_id` = `transactions`.`id`").
Joins("LEFT JOIN `transaction_outputs` AS `inputs_outs` ON `inputs_outs`.`id` = `transaction_inputs`.`transaction_output_id`").
Joins("LEFT JOIN `transaction_outputs` AS `inputs_outs` ON `inputs_outs`.`id` = `transaction_inputs`.`previous_transaction_output_id`").
Joins("LEFT JOIN `addresses` AS `in_addresses` ON `in_addresses`.`id` = `inputs_outs`.`address_id`").
Where("`out_addresses`.`address` = ?", address).
Or("`in_addresses`.`address` = ?", address).
@ -100,7 +101,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
dbResult := addTxPreloadedFields(query).Find(&txs)
dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors)
return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors)
}
txResponses := make([]*apimodels.TransactionResponse, len(txs))
for i, tx := range txs {
@ -109,10 +110,10 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
return txResponses, nil
}
func fetchSelectedTipBlueScore() (uint64, *httpserverutils.HandlerError) {
func fetchSelectedTipBlueScore() (uint64, error) {
db, err := database.DB()
if err != nil {
return 0, httpserverutils.NewInternalServerHandlerError(err.Error())
return 0, err
}
block := &dbmodels.Block{}
dbResult := db.Order("blue_score DESC").
@ -121,16 +122,16 @@ func fetchSelectedTipBlueScore() (uint64, *httpserverutils.HandlerError) {
First(block)
dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) {
return 0, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors)
return 0, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors)
}
return block.BlueScore, nil
}
// GetUTXOsByAddressHandler searches for all UTXOs that belong to a certain address.
func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.HandlerError) {
func GetUTXOsByAddressHandler(address string) (interface{}, error) {
db, err := database.DB()
if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error())
return nil, err
}
var transactionOutputs []*dbmodels.TransactionOutput
@ -141,12 +142,12 @@ func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.Han
Preload("Transaction.Subnetwork").
Find(&transactionOutputs).GetErrors()
if len(dbErrors) > 0 {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading UTXOs from the database:", dbErrors)
return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading UTXOs from the database:", dbErrors)
}
selectedTipBlueScore, hErr := fetchSelectedTipBlueScore()
if hErr != nil {
return nil, hErr
selectedTipBlueScore, err := fetchSelectedTipBlueScore()
if err != nil {
return nil, err
}
UTXOsResponses := make([]*apimodels.TransactionOutputResponse, len(transactionOutputs))
@ -154,7 +155,7 @@ func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.Han
subnetworkID := &subnetworkid.SubnetworkID{}
err := subnetworkid.Decode(subnetworkID, transactionOutput.Transaction.Subnetwork.SubnetworkID)
if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(fmt.Sprintf("Couldn't decode subnetwork id %s: %s", transactionOutput.Transaction.Subnetwork.SubnetworkID, err))
return nil, errors.Wrap(err, fmt.Sprintf("Couldn't decode subnetwork id %s", transactionOutput.Transaction.Subnetwork.SubnetworkID))
}
var acceptingBlockHash *string
var confirmations uint64
@ -188,24 +189,24 @@ func addTxPreloadedFields(query *gorm.DB) *gorm.DB {
}
// PostTransaction forwards a raw transaction to the JSON-RPC API server
func PostTransaction(requestBody []byte) *httpserverutils.HandlerError {
func PostTransaction(requestBody []byte) error {
client, err := jsonrpc.GetClient()
if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error())
return err
}
rawTx := &apimodels.RawTransaction{}
err = json.Unmarshal(requestBody, rawTx)
if err != nil {
return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error unmarshalling request body: %s", err),
errors.Wrap(err, "Error unmarshalling request body"),
"The request body is not json-formatted")
}
txBytes, err := hex.DecodeString(rawTx.RawTransaction)
if err != nil {
return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error decoding hex raw transaction: %s", err),
errors.Wrap(err, "Error decoding hex raw transaction"),
"The raw transaction is not a hex-encoded transaction")
}
@ -214,16 +215,16 @@ func PostTransaction(requestBody []byte) *httpserverutils.HandlerError {
err = tx.BtcDecode(txReader, 0)
if err != nil {
return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error decoding raw transaction: %s", err),
errors.Wrap(err, "Error decoding raw transaction"),
"Error decoding raw transaction")
}
_, err = client.SendRawTransaction(tx, true)
if err != nil {
if rpcErr, ok := err.(*btcjson.RPCError); ok && rpcErr.Code == btcjson.ErrRPCVerify {
return httpserverutils.NewHandlerError(http.StatusInternalServerError, rpcErr.Message)
return httpserverutils.NewHandlerError(http.StatusInternalServerError, err)
}
return httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
return err
}
return nil

View File

@ -3,6 +3,7 @@ package server
import (
"fmt"
"github.com/daglabs/btcd/httpserverutils"
"github.com/pkg/errors"
"net/http"
"strconv"
@ -29,7 +30,7 @@ const (
defaultGetBlocksOrder = controllers.OrderAscending
)
func mainHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string, _ []byte) (interface{}, *httpserverutils.HandlerError) {
func mainHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string, _ []byte) (interface{}, error) {
return struct {
Message string `json:"message"`
}{
@ -81,11 +82,11 @@ func addRoutes(router *mux.Router) {
Methods("POST")
}
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *httpserverutils.HandlerError) {
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, error) {
if _, ok := queryParams[param]; ok {
intValue, err := strconv.Atoi(queryParams[param])
if err != nil {
return 0, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", param, err))
return 0, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Wrap(err, fmt.Sprintf("Couldn't parse the '%s' query parameter", param)))
}
return intValue, nil
}
@ -93,72 +94,72 @@ func convertQueryParamToInt(queryParams map[string]string, param string, default
}
func getTransactionByIDHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) {
_ []byte) (interface{}, error) {
return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID])
}
func getTransactionByHashHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) {
_ []byte) (interface{}, error) {
return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash])
}
func getTransactionsByAddressHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, queryParams map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) {
_ []byte) (interface{}, error) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil {
return nil, hErr
skip, err := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if err != nil {
return nil, err
}
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit)
if hErr != nil {
return nil, hErr
limit, err := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit)
if err != nil {
return nil, err
}
if _, ok := queryParams[queryParamLimit]; ok {
var err error
skip, err = strconv.Atoi(queryParams[queryParamLimit])
if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err))
errors.Wrap(err, fmt.Sprintf("Couldn't parse the '%s' query parameter", queryParamLimit)))
}
}
return controllers.GetTransactionsByAddressHandler(routeParams[routeParamAddress], uint64(skip), uint64(limit))
}
func getUTXOsByAddressHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) {
_ []byte) (interface{}, error) {
return controllers.GetUTXOsByAddressHandler(routeParams[routeParamAddress])
}
func getBlockByHashHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) {
_ []byte) (interface{}, error) {
return controllers.GetBlockByHashHandler(routeParams[routeParamBlockHash])
}
func getFeeEstimatesHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) {
_ []byte) (interface{}, error) {
return controllers.GetFeeEstimatesHandler()
}
func getBlocksHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, queryParams map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) {
_ []byte) (interface{}, error) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil {
return nil, hErr
skip, err := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if err != nil {
return nil, err
}
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit)
if hErr != nil {
return nil, hErr
limit, err := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit)
if err != nil {
return nil, err
}
order := defaultGetBlocksOrder
if orderParamValue, ok := queryParams[queryParamOrder]; ok {
if orderParamValue != controllers.OrderAscending && orderParamValue != controllers.OrderDescending {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit))
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit))
}
order = orderParamValue
}
@ -166,6 +167,6 @@ func getBlocksHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[s
}
func postTransactionHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string,
requestBody []byte) (interface{}, *httpserverutils.HandlerError) {
requestBody []byte) (interface{}, error) {
return nil, controllers.PostTransaction(requestBody)
}

View File

@ -3,6 +3,7 @@ package main
import (
"github.com/daglabs/btcd/faucet/database"
"github.com/daglabs/btcd/httpserverutils"
"github.com/pkg/errors"
"net"
"net/http"
"time"
@ -23,43 +24,43 @@ func ipFromRequest(r *http.Request) (string, error) {
return ip, nil
}
func validateIPUsage(r *http.Request) *httpserverutils.HandlerError {
func validateIPUsage(r *http.Request) error {
db, err := database.DB()
if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error())
return err
}
now := time.Now()
timeBeforeMinRequestInterval := now.Add(-minRequestInterval)
var count int
ip, err := ipFromRequest(r)
if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error())
return err
}
dbResult := db.Model(&ipUse{}).Where(&ipUse{IP: ip}).Where("last_use BETWEEN ? AND ?", timeBeforeMinRequestInterval, now).Count(&count)
dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) {
return httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when checking the last use of an IP:", dbResult.GetErrors())
return httpserverutils.NewErrorFromDBErrors("Some errors were encountered when checking the last use of an IP:", dbResult.GetErrors())
}
if count != 0 {
return httpserverutils.NewHandlerError(http.StatusForbidden, "A user is allowed to to have one request from the faucet every 24 hours.")
return httpserverutils.NewHandlerError(http.StatusForbidden, errors.New("A user is allowed to to have one request from the faucet every 24 hours"))
}
return nil
}
func updateIPUsage(r *http.Request) *httpserverutils.HandlerError {
func updateIPUsage(r *http.Request) error {
db, err := database.DB()
if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error())
return err
}
ip, err := ipFromRequest(r)
if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error())
return err
}
dbResult := db.Where(&ipUse{IP: ip}).Assign(&ipUse{LastUse: time.Now()}).FirstOrCreate(&ipUse{})
dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) {
return httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when upserting the IP to the new date:", dbResult.GetErrors())
return httpserverutils.NewErrorFromDBErrors("Some errors were encountered when upserting the IP to the new date:", dbResult.GetErrors())
}
return nil
}

View File

@ -3,10 +3,10 @@ package main
import (
"context"
"encoding/json"
"fmt"
"github.com/daglabs/btcd/faucet/config"
"github.com/daglabs/btcd/httpserverutils"
"github.com/daglabs/btcd/util"
"github.com/pkg/errors"
"net/http"
"time"
@ -51,7 +51,7 @@ type requestMoneyData struct {
}
func requestMoneyHandler(_ *httpserverutils.ServerContext, r *http.Request, _ map[string]string, _ map[string]string,
requestBody []byte) (interface{}, *httpserverutils.HandlerError) {
requestBody []byte) (interface{}, error) {
hErr := validateIPUsage(r)
if hErr != nil {
return nil, hErr
@ -60,18 +60,18 @@ func requestMoneyHandler(_ *httpserverutils.ServerContext, r *http.Request, _ ma
err := json.Unmarshal(requestBody, requestData)
if err != nil {
return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error unmarshalling request body: %s", err),
errors.Wrap(err, "Error unmarshalling request body"),
"The request body is not json-formatted")
}
address, err := util.DecodeAddress(requestData.Address, config.ActiveNetParams().Prefix)
if err != nil {
return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error decoding address: %s", err),
errors.Wrap(err, "Error decoding address"),
"Error decoding address")
}
tx, err := sendToAddress(address)
if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error())
return nil, err
}
hErr = updateIPUsage(r)
if hErr != nil {

View File

@ -13,29 +13,29 @@ import (
// a rest route handler or a middleware.
type HandlerError struct {
Code int
Message string
Cause error
ClientMessage string
}
func (hErr *HandlerError) Error() string {
return hErr.Message
return hErr.Cause.Error()
}
// NewHandlerError returns a HandlerError with the given code and message.
func NewHandlerError(code int, message string) *HandlerError {
func NewHandlerError(code int, err error) error {
return &HandlerError{
Code: code,
Message: message,
ClientMessage: message,
Cause: err,
ClientMessage: err.Error(),
}
}
// NewHandlerErrorWithCustomClientMessage returns a HandlerError with
// the given code, message and client error message.
func NewHandlerErrorWithCustomClientMessage(code int, message, clientMessage string) *HandlerError {
func NewHandlerErrorWithCustomClientMessage(code int, err error, clientMessage string) error {
return &HandlerError{
Code: code,
Message: message,
Cause: err,
ClientMessage: clientMessage,
}
}
@ -43,8 +43,8 @@ func NewHandlerErrorWithCustomClientMessage(code int, message, clientMessage str
// NewInternalServerHandlerError returns a HandlerError with
// the given message, and the http.StatusInternalServerError
// status text as client message.
func NewInternalServerHandlerError(message string) *HandlerError {
return NewHandlerErrorWithCustomClientMessage(http.StatusInternalServerError, message, http.StatusText(http.StatusInternalServerError))
func NewInternalServerHandlerError(err error) error {
return NewHandlerErrorWithCustomClientMessage(http.StatusInternalServerError, err, http.StatusText(http.StatusInternalServerError))
}
// NewErrorFromDBErrors takes a slice of database errors and a prefix, and
@ -58,13 +58,6 @@ func NewErrorFromDBErrors(prefix string, dbErrors []error) error {
return errors.Errorf("%s [%s]", prefix, strings.Join(dbErrorsStrings, ","))
}
// NewHandlerErrorFromDBErrors takes a slice of database errors and a prefix, and
// returns an HandlerError with error code http.StatusInternalServerError with
// all of the database errors formatted to one string with the given prefix
func NewHandlerErrorFromDBErrors(prefix string, dbErrors []error) *HandlerError {
return NewInternalServerHandlerError(NewErrorFromDBErrors(prefix, dbErrors).Error())
}
// IsDBRecordNotFoundError returns true if the given dbErrors contains only a RecordNotFound error
func IsDBRecordNotFoundError(dbErrors []error) bool {
return len(dbErrors) == 1 && gorm.IsRecordNotFoundError(dbErrors[0])
@ -88,9 +81,13 @@ func (err *ClientError) Error() string {
// SendErr takes a HandlerError and create a ClientError out of it that is sent
// to the http client.
func SendErr(ctx *ServerContext, w http.ResponseWriter, hErr *HandlerError) {
errMsg := fmt.Sprintf("got error: %s", hErr)
ctx.Warnf(errMsg)
func SendErr(ctx *ServerContext, w http.ResponseWriter, err error) {
var hErr *HandlerError
var ok bool
if hErr, ok = err.(*HandlerError); !ok {
hErr = NewInternalServerHandlerError(err).(*HandlerError)
}
ctx.Warnf("got error: %s", err)
w.WriteHeader(hErr.Code)
SendJSONResponse(w, &ClientError{
ErrorCode: hErr.Code,

View File

@ -2,6 +2,7 @@ package httpserverutils
import (
"fmt"
"github.com/pkg/errors"
"net/http"
"runtime/debug"
)
@ -38,10 +39,16 @@ func RecoveryMiddleware(h http.Handler) http.Handler {
defer func() {
recoveryErr := recover()
if recoveryErr != nil {
var recoveryErrAsError error
if rErr, ok := recoveryErr.(error); ok {
recoveryErrAsError = rErr
} else {
recoveryErrAsError = errors.Errorf("%s", recoveryErr)
}
recoveryErrStr := fmt.Sprintf("%s", recoveryErr)
log.Criticalf("Fatal error: %s", recoveryErrStr)
log.Criticalf("Fatal error: %+v", recoveryErrStr)
log.Criticalf("Stack trace: %s", debug.Stack())
SendErr(ctx, w, NewInternalServerHandlerError(recoveryErrStr))
SendErr(ctx, w, recoveryErrAsError)
}
}()
h.ServeHTTP(w, r)

View File

@ -1,8 +1,8 @@
package httpserverutils
import (
"fmt"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
)
@ -11,7 +11,7 @@ import (
// MakeHandler wrapper and gets the relevant request fields
// from it.
type HandlerFunc func(ctx *ServerContext, r *http.Request, routeParams map[string]string, queryParams map[string]string, requestBody []byte) (
interface{}, *HandlerError)
interface{}, error)
// MakeHandler is a wrapper function that takes a handler in the form of HandlerFunc
// and returns a function that can be used as a handler in mux.Router.HandleFunc.
@ -24,19 +24,19 @@ func MakeHandler(handler HandlerFunc) func(http.ResponseWriter, *http.Request) {
var err error
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
SendErr(ctx, w, NewInternalServerHandlerError("Error reading POST data"))
SendErr(ctx, w, errors.New("Error reading POST data"))
}
}
flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query())
if hErr != nil {
SendErr(ctx, w, hErr)
flattenedQueryParams, err := flattenQueryParams(r.URL.Query())
if err != nil {
SendErr(ctx, w, err)
return
}
response, hErr := handler(ctx, r, mux.Vars(r), flattenedQueryParams, requestBody)
if hErr != nil {
SendErr(ctx, w, hErr)
response, err := handler(ctx, r, mux.Vars(r), flattenedQueryParams, requestBody)
if err != nil {
SendErr(ctx, w, err)
return
}
if response != nil {
@ -45,11 +45,11 @@ func MakeHandler(handler HandlerFunc) func(http.ResponseWriter, *http.Request) {
}
}
func flattenQueryParams(queryParams map[string][]string) (map[string]string, *HandlerError) {
func flattenQueryParams(queryParams map[string][]string) (map[string]string, error) {
flattenedMap := make(map[string]string)
for param, valuesSlice := range queryParams {
if len(valuesSlice) > 1 {
return nil, NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+
return nil, NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("Couldn't parse the '%s' query parameter:"+
" expected a single value but got multiple values", param))
}
flattenedMap[param] = valuesSlice[0]