128 lines
3.2 KiB
Go
128 lines
3.2 KiB
Go
package rest
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Validation errors
|
|
var (
|
|
ErrInvalidAddress = fmt.Errorf("invalid address format")
|
|
ErrInvalidHash = fmt.Errorf("invalid hash format")
|
|
ErrInvalidBlockNumber = fmt.Errorf("invalid block number")
|
|
)
|
|
|
|
// isValidHash validates if a string is a valid hex hash (0x + 64 hex chars)
|
|
func isValidHash(hash string) bool {
|
|
if !strings.HasPrefix(hash, "0x") {
|
|
return false
|
|
}
|
|
if len(hash) != 66 {
|
|
return false
|
|
}
|
|
_, err := hex.DecodeString(hash[2:])
|
|
return err == nil
|
|
}
|
|
|
|
// isValidAddress validates if a string is a valid Ethereum address (0x + 40 hex chars)
|
|
func isValidAddress(address string) bool {
|
|
if !strings.HasPrefix(address, "0x") {
|
|
return false
|
|
}
|
|
if len(address) != 42 {
|
|
return false
|
|
}
|
|
_, err := hex.DecodeString(address[2:])
|
|
return err == nil
|
|
}
|
|
|
|
// validateBlockNumber validates and parses block number
|
|
func validateBlockNumber(blockStr string) (int64, error) {
|
|
blockNumber, err := strconv.ParseInt(blockStr, 10, 64)
|
|
if err != nil {
|
|
return 0, ErrInvalidBlockNumber
|
|
}
|
|
if blockNumber < 0 {
|
|
return 0, ErrInvalidBlockNumber
|
|
}
|
|
return blockNumber, nil
|
|
}
|
|
|
|
// validateChainID validates chain ID matches expected
|
|
func validateChainID(chainIDStr string, expectedChainID int) error {
|
|
chainID, err := strconv.Atoi(chainIDStr)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid chain ID format")
|
|
}
|
|
if chainID != expectedChainID {
|
|
return fmt.Errorf("chain ID mismatch: expected %d, got %d", expectedChainID, chainID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validatePagination validates and normalizes pagination parameters
|
|
func validatePagination(pageStr, pageSizeStr string) (page, pageSize int, err error) {
|
|
page = 1
|
|
if pageStr != "" {
|
|
page, err = strconv.Atoi(pageStr)
|
|
if err != nil || page < 1 {
|
|
return 0, 0, fmt.Errorf("invalid page number")
|
|
}
|
|
}
|
|
|
|
pageSize = 20
|
|
if pageSizeStr != "" {
|
|
pageSize, err = strconv.Atoi(pageSizeStr)
|
|
if err != nil || pageSize < 1 {
|
|
return 0, 0, fmt.Errorf("invalid page size")
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100 // Max page size
|
|
}
|
|
}
|
|
|
|
return page, pageSize, nil
|
|
}
|
|
|
|
// writeValidationError writes a validation error response
|
|
func writeValidationError(w http.ResponseWriter, err error) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"error": map[string]interface{}{
|
|
"code": "VALIDATION_ERROR",
|
|
"message": err.Error(),
|
|
},
|
|
})
|
|
}
|
|
|
|
// validateSearchQuery validates search query format
|
|
func validateSearchQuery(query string) (searchType string, value string, err error) {
|
|
query = strings.TrimSpace(query)
|
|
if query == "" {
|
|
return "", "", fmt.Errorf("search query cannot be empty")
|
|
}
|
|
|
|
// Block number (numeric)
|
|
if matched, _ := regexp.MatchString(`^\d+$`, query); matched {
|
|
return "block", query, nil
|
|
}
|
|
|
|
// Address (0x + 40 hex chars)
|
|
if matched, _ := regexp.MatchString(`^0x[a-fA-F0-9]{40}$`, query); matched {
|
|
return "address", query, nil
|
|
}
|
|
|
|
// Transaction hash (0x + 64 hex chars)
|
|
if matched, _ := regexp.MatchString(`^0x[a-fA-F0-9]{64}$`, query); matched {
|
|
return "transaction", query, nil
|
|
}
|
|
|
|
return "", "", fmt.Errorf("invalid search query format")
|
|
}
|