Files
explorer-monorepo/backend/api/rest/validation.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")
}