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") }