kaspad/apiserver/server/routes.go
Ori Newman e81ac5f19e [NOD-307] Implement get blocks for api server (#405)
* [NOD-307] Implement API-Server GET /blocks

* [NOD-307] Implement API-Server GET /blocks

* [NOD-307] Add comments to exported constants

* [NOD-307] Flatten GET query values and check that 'order' value is valid

* [NOD-307] Validate order values in GetBlocksHandler

* [NOD-307] Add convertQueryParamToInt function
2019-09-16 16:53:57 +03:00

202 lines
6.4 KiB
Go

package server
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/daglabs/btcd/apiserver/controllers"
"github.com/daglabs/btcd/apiserver/utils"
"github.com/gorilla/mux"
)
const (
routeParamTxID = "txID"
routeParamTxHash = "txHash"
routeParamAddress = "address"
routeParamBlockHash = "blockHash"
)
const (
queryParamSkip = "skip"
queryParamLimit = "limit"
queryParamOrder = "order"
)
const (
defaultGetTransactionsLimit = 100
defaultGetBlocksLimit = 25
defaultGetBlocksOrder = controllers.OrderAscending
)
func makeHandler(
handler func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) (
interface{}, *utils.HandlerError)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ctx := utils.ToAPIServerContext(r.Context())
flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query())
if hErr != nil {
sendErr(ctx, w, hErr)
return
}
response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams)
if hErr != nil {
sendErr(ctx, w, hErr)
return
}
sendJSONResponse(w, response)
}
}
func flattenQueryParams(queryParams map[string][]string) (map[string]string, *utils.HandlerError) {
flattenedMap := make(map[string]string)
for param, valuesSlice := range queryParams {
if len(valuesSlice) > 1 {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+
" expected a single value but got multiple values", param))
}
flattenedMap[param] = valuesSlice[0]
}
return flattenedMap, nil
}
type clientError struct {
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
}
func sendErr(ctx *utils.APIServerContext, w http.ResponseWriter, hErr *utils.HandlerError) {
errMsg := fmt.Sprintf("got error: %s", hErr)
ctx.Warnf(errMsg)
w.WriteHeader(hErr.Code)
sendJSONResponse(w, &clientError{
ErrorCode: hErr.Code,
ErrorMessage: hErr.ClientMessage,
})
}
func sendJSONResponse(w http.ResponseWriter, response interface{}) {
b, err := json.Marshal(response)
if err != nil {
panic(err)
}
_, err = fmt.Fprintf(w, string(b))
if err != nil {
panic(err)
}
}
func mainHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return "API server is running", nil
}
func addRoutes(router *mux.Router) {
router.HandleFunc("/", makeHandler(mainHandler))
router.HandleFunc(
fmt.Sprintf("/transaction/id/{%s}", routeParamTxID),
makeHandler(getTransactionByIDHandler)).
Methods("GET")
router.HandleFunc(
fmt.Sprintf("/transaction/hash/{%s}", routeParamTxHash),
makeHandler(getTransactionByHashHandler)).
Methods("GET")
router.HandleFunc(
fmt.Sprintf("/transactions/address/{%s}", routeParamAddress),
makeHandler(getTransactionsByAddressHandler)).
Methods("GET")
router.HandleFunc(
fmt.Sprintf("/utxos/address/{%s}", routeParamAddress),
makeHandler(getUTXOsByAddressHandler)).
Methods("GET")
router.HandleFunc(
fmt.Sprintf("/block/{%s}", routeParamBlockHash),
makeHandler(getBlockByHashHandler)).
Methods("GET")
router.HandleFunc(
"/blocks",
makeHandler(getBlocksHandler)).
Methods("GET")
router.HandleFunc(
"/fee-estimates",
makeHandler(getFeeEstimatesHandler)).
Methods("GET")
}
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *utils.HandlerError) {
if _, ok := queryParams[param]; ok {
intValue, err := strconv.Atoi(queryParams[param])
if err != nil {
return 0, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", param, err))
}
return intValue, nil
}
return defaultValue, nil
}
func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID])
}
func getTransactionByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash])
}
func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil {
return nil, hErr
}
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit)
if hErr != nil {
return nil, hErr
}
if _, ok := queryParams[queryParamLimit]; ok {
var err error
skip, err = strconv.Atoi(queryParams[queryParamLimit])
if err != nil {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err))
}
}
return controllers.GetTransactionsByAddressHandler(routeParams[routeParamAddress], uint64(skip), uint64(limit))
}
func getUTXOsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetUTXOsByAddressHandler(routeParams[routeParamAddress])
}
func getBlockByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetBlockByHashHandler(routeParams[routeParamBlockHash])
}
func getFeeEstimatesHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetFeeEstimatesHandler()
}
func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil {
return nil, hErr
}
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit)
if hErr != nil {
return nil, hErr
}
order := defaultGetBlocksOrder
if orderParamValue, ok := queryParams[queryParamOrder]; ok {
if orderParamValue != controllers.OrderAscending && orderParamValue != controllers.OrderDescending {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit))
}
order = orderParamValue
}
return controllers.GetBlocksHandler(order, uint64(skip), uint64(limit))
}