From a312bd91e76faf4983b81b80ccf4e5072b74daf9 Mon Sep 17 00:00:00 2001 From: Matthew Dargan Date: Fri, 22 Dec 2023 13:19:52 -0600 Subject: [PATCH] refactor: simplify API (#3) --- README.md | 2 +- doc.go | 2 +- example_test.go | 26 +- filter.go | 683 ------- finding.go | 259 ++- finding_test.go | 5192 ++++------------------------------------------- params.go | 1311 ------------ response.go | 49 +- 8 files changed, 500 insertions(+), 7024 deletions(-) delete mode 100644 filter.go delete mode 100644 params.go diff --git a/README.md b/README.md index 866d307..61c28c2 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ params := map[string]string{ c := &http.Client{Timeout: time.Second * 5} appID := "your_app_id" client := ebay.NewFindingClient(c, appID) -resp, err := client.FindItemsByCategories(context.Background(), params) +resp, err := client.FindItemsByCategory(context.Background(), params) if err != nil { // handle error } diff --git a/doc.go b/doc.go index 6cce529..66496fe 100644 --- a/doc.go +++ b/doc.go @@ -18,7 +18,7 @@ To interact with the eBay Finding API, create a [FindingClient]: c := &http.Client{Timeout: time.Second * 5} appID := "your_app_id" client := ebay.NewFindingClient(c, appID) - resp, err := client.FindItemsByCategories(context.Background(), params) + resp, err := client.FindItemsByCategory(context.Background(), params) if err != nil { // handle error } diff --git a/example_test.go b/example_test.go index 7e74858..3097a69 100644 --- a/example_test.go +++ b/example_test.go @@ -12,9 +12,10 @@ import ( "github.com/matthewdargan/ebay" ) -func ExampleFindingClient_FindItemsByCategories() { +func ExampleFindingClient_FindItemsAdvanced() { params := map[string]string{ "categoryId": "9355", + "keywords": "iphone", "itemFilter.name": "MaxPrice", "itemFilter.value": "500.0", "itemFilter.paramName": "Currency", @@ -23,19 +24,19 @@ func ExampleFindingClient_FindItemsByCategories() { c := &http.Client{Timeout: time.Second * 5} appID := "your_app_id" client := ebay.NewFindingClient(c, appID) - resp, err := client.FindItemsByCategories(context.Background(), params) + resp, err := client.FindItemsAdvanced(context.Background(), params) if err != nil { fmt.Println(err) } else { fmt.Println(resp) } // Output: - // ebay: failed to perform eBay Finding API request with status code 500 + // ebay: failed to perform eBay Finding API request with status code: 500 } -func ExampleFindingClient_FindItemsByKeywords() { +func ExampleFindingClient_FindItemsByCategory() { params := map[string]string{ - "keywords": "iphone", + "categoryId": "9355", "itemFilter.name": "MaxPrice", "itemFilter.value": "500.0", "itemFilter.paramName": "Currency", @@ -44,19 +45,18 @@ func ExampleFindingClient_FindItemsByKeywords() { c := &http.Client{Timeout: time.Second * 5} appID := "your_app_id" client := ebay.NewFindingClient(c, appID) - resp, err := client.FindItemsByKeywords(context.Background(), params) + resp, err := client.FindItemsByCategory(context.Background(), params) if err != nil { fmt.Println(err) } else { fmt.Println(resp) } // Output: - // ebay: failed to perform eBay Finding API request with status code 500 + // ebay: failed to perform eBay Finding API request with status code: 500 } -func ExampleFindingClient_FindItemsAdvanced() { +func ExampleFindingClient_FindItemsByKeywords() { params := map[string]string{ - "categoryId": "9355", "keywords": "iphone", "itemFilter.name": "MaxPrice", "itemFilter.value": "500.0", @@ -66,14 +66,14 @@ func ExampleFindingClient_FindItemsAdvanced() { c := &http.Client{Timeout: time.Second * 5} appID := "your_app_id" client := ebay.NewFindingClient(c, appID) - resp, err := client.FindItemsAdvanced(context.Background(), params) + resp, err := client.FindItemsByKeywords(context.Background(), params) if err != nil { fmt.Println(err) } else { fmt.Println(resp) } // Output: - // ebay: failed to perform eBay Finding API request with status code 500 + // ebay: failed to perform eBay Finding API request with status code: 500 } func ExampleFindingClient_FindItemsByProduct() { @@ -95,7 +95,7 @@ func ExampleFindingClient_FindItemsByProduct() { fmt.Println(resp) } // Output: - // ebay: failed to perform eBay Finding API request with status code 500 + // ebay: failed to perform eBay Finding API request with status code: 500 } func ExampleFindingClient_FindItemsInEBayStores() { @@ -116,5 +116,5 @@ func ExampleFindingClient_FindItemsInEBayStores() { fmt.Println(resp) } // Output: - // ebay: failed to perform eBay Finding API request with status code 500 + // ebay: failed to perform eBay Finding API request with status code: 500 } diff --git a/filter.go b/filter.go deleted file mode 100644 index 7d455af..0000000 --- a/filter.go +++ /dev/null @@ -1,683 +0,0 @@ -// Copyright 2023 Matthew P. Dargan. -// SPDX-License-Identifier: Apache-2.0 - -package ebay - -import ( - "errors" - "fmt" - "slices" - "strconv" - "time" - "unicode" -) - -var ( - // ErrIncompleteFilterNameOnly is returned when a filter is missing the 'value' parameter. - ErrIncompleteFilterNameOnly = errors.New("incomplete item filter: missing") - - // ErrIncompleteItemFilterParam is returned when an item filter is missing - // either the 'paramName' or 'paramValue' parameter, as both 'paramName' and 'paramValue' - // are required when either one is specified. - ErrIncompleteItemFilterParam = errors.New("incomplete item filter: both paramName and paramValue must be specified together") - - // ErrUnsupportedItemFilterType is returned when an item filter 'name' parameter has an unsupported type. - ErrUnsupportedItemFilterType = errors.New("unsupported item filter type") - - // ErrInvalidCountryCode is returned when an item filter 'values' parameter contains an invalid country code. - ErrInvalidCountryCode = errors.New("invalid country code") - - // ErrInvalidCondition is returned when an item filter 'values' parameter contains an invalid condition ID or name. - ErrInvalidCondition = errors.New("invalid condition") - - // ErrInvalidCurrencyID is returned when an item filter 'values' parameter contains an invalid currency ID. - ErrInvalidCurrencyID = errors.New("invalid currency ID") - - // ErrInvalidDateTime is returned when an item filter 'values' parameter contains an invalid date time. - ErrInvalidDateTime = errors.New("invalid date time value") - - maxExcludeCategories = 25 - - // ErrMaxExcludeCategories is returned when an item filter 'values' parameter - // contains more categories to exclude than the maximum allowed. - ErrMaxExcludeCategories = fmt.Errorf("maximum categories to exclude is %d", maxExcludeCategories) - - maxExcludeSellers = 100 - - // ErrMaxExcludeSellers is returned when an item filter 'values' parameter - // contains more categories to exclude than the maximum allowed. - ErrMaxExcludeSellers = fmt.Errorf("maximum sellers to exclude is %d", maxExcludeSellers) - - // ErrExcludeSellerCannotBeUsedWithSellers is returned when there is an attempt to use - // the ExcludeSeller item filter together with either the Seller or TopRatedSellerOnly item filters. - ErrExcludeSellerCannotBeUsedWithSellers = errors.New( - "'ExcludeSeller' item filter cannot be used together with either the Seller or TopRatedSellerOnly item filters") - - // ErrInvalidInteger is returned when an item filter 'values' parameter contains an invalid integer. - ErrInvalidInteger = errors.New("invalid integer") - - // ErrInvalidNumericFilter is returned when a numeric item filter is invalid. - ErrInvalidNumericFilter = errors.New("invalid numeric item filter") - - // ErrInvalidExpeditedShippingType is returned when an item filter 'values' parameter - // contains an invalid expedited shipping type. - ErrInvalidExpeditedShippingType = errors.New("invalid expedited shipping type") - - // ErrInvalidAllListingType is returned when an item filter 'values' parameter - // contains the 'All' listing type and other listing types. - ErrInvalidAllListingType = errors.New("'All' listing type cannot be combined with other listing types") - - // ErrInvalidListingType is returned when an item filter 'values' parameter contains an invalid listing type. - ErrInvalidListingType = errors.New("invalid listing type") - - // ErrDuplicateListingType is returned when an item filter 'values' parameter contains duplicate listing types. - ErrDuplicateListingType = errors.New("duplicate listing type") - - // ErrInvalidAuctionListingTypes is returned when an item filter 'values' parameter - // contains both 'Auction' and 'AuctionWithBIN' listing types. - ErrInvalidAuctionListingTypes = errors.New("'Auction' and 'AuctionWithBIN' listing types cannot be combined") - - // ErrMaxDistanceMissing is returned when the LocalSearchOnly item filter is used, - // but the MaxDistance item filter is missing in the request. - ErrMaxDistanceMissing = errors.New("MaxDistance item filter is missing when using LocalSearchOnly item filter") - - maxLocatedIns = 25 - - // ErrMaxLocatedIns is returned when an item filter 'values' parameter - // contains more countries to locate items in than the maximum allowed. - ErrMaxLocatedIns = fmt.Errorf("maximum countries to locate items in is %d", maxLocatedIns) - - // ErrInvalidPrice is returned when an item filter 'values' parameter contains an invalid price. - ErrInvalidPrice = errors.New("invalid price") - - // ErrInvalidPriceParamName is returned when an item filter 'paramName' parameter - // contains anything other than "Currency". - ErrInvalidPriceParamName = errors.New(`invalid price parameter name, must be "Currency"`) - - // ErrInvalidMaxPrice is returned when an item filter 'values' parameter - // contains a maximum price less than a minimum price. - ErrInvalidMaxPrice = errors.New("maximum price must be greater than or equal to minimum price") - - maxSellers = 100 - - // ErrMaxSellers is returned when an item filter 'values' parameter - // contains more categories to include than the maximum allowed. - ErrMaxSellers = fmt.Errorf("maximum sellers to include is %d", maxExcludeSellers) - - // ErrSellerCannotBeUsedWithOtherSellers is returned when there is an attempt to use - // the Seller item filter together with either the ExcludeSeller or TopRatedSellerOnly item filters. - ErrSellerCannotBeUsedWithOtherSellers = errors.New( - "'Seller' item filter cannot be used together with either the ExcludeSeller or TopRatedSellerOnly item filters") - - // ErrMultipleSellerBusinessTypes is returned when an item filter 'values' parameter - // contains multiple seller business types. - ErrMultipleSellerBusinessTypes = errors.New("multiple seller business types found") - - // ErrInvalidSellerBusinessType is returned when an item filter 'values' parameter - // contains an invalid seller business type. - ErrInvalidSellerBusinessType = errors.New("invalid seller business type") - - // ErrTopRatedSellerCannotBeUsedWithSellers is returned when there is an attempt to use - // the TopRatedSellerOnly item filter together with either the Seller or ExcludeSeller item filters. - ErrTopRatedSellerCannotBeUsedWithSellers = errors.New( - "'TopRatedSellerOnly' item filter cannot be used together with either the Seller or ExcludeSeller item filters") - - // ErrInvalidValueBoxInventory is returned when an item filter 'values' parameter - // contains an invalid value box inventory. - ErrInvalidValueBoxInventory = errors.New("invalid value box inventory") -) - -func processAspectFilters(params map[string]string) ([]aspectFilter, error) { - _, nonNumberedExists := params["aspectFilter.aspectName"] - _, numberedExists := params["aspectFilter(0).aspectName"] - if nonNumberedExists && numberedExists { - return nil, ErrInvalidIndexSyntax - } - if nonNumberedExists { - return processNonNumberedAspectFilter(params) - } - return processNumberedAspectFilters(params) -} - -func processNonNumberedAspectFilter(params map[string]string) ([]aspectFilter, error) { - filterValues, err := parseFilterValues(params, "aspectFilter.aspectValueName") - if err != nil { - return nil, err - } - flt := aspectFilter{ - aspectName: params["aspectFilter.aspectName"], - aspectValueNames: filterValues, - } - return []aspectFilter{flt}, nil -} - -func processNumberedAspectFilters(params map[string]string) ([]aspectFilter, error) { - var aspectFilters []aspectFilter - for i := 0; ; i++ { - name, ok := params[fmt.Sprintf("aspectFilter(%d).aspectName", i)] - if !ok { - break - } - filterValues, err := parseFilterValues(params, fmt.Sprintf("aspectFilter(%d).aspectValueName", i)) - if err != nil { - return nil, err - } - flt := aspectFilter{ - aspectName: name, - aspectValueNames: filterValues, - } - aspectFilters = append(aspectFilters, flt) - } - return aspectFilters, nil -} - -func processItemFilters(params map[string]string) ([]itemFilter, error) { - _, nonNumberedExists := params["itemFilter.name"] - _, numberedExists := params["itemFilter(0).name"] - if nonNumberedExists && numberedExists { - return nil, ErrInvalidIndexSyntax - } - if nonNumberedExists { - return processNonNumberedItemFilter(params) - } - return processNumberedItemFilters(params) -} - -func processNonNumberedItemFilter(params map[string]string) ([]itemFilter, error) { - filterValues, err := parseFilterValues(params, "itemFilter.value") - if err != nil { - return nil, err - } - flt := itemFilter{ - name: params["itemFilter.name"], - values: filterValues, - } - pn, pnOk := params["itemFilter.paramName"] - pv, pvOk := params["itemFilter.paramValue"] - if pnOk != pvOk { - return nil, ErrIncompleteItemFilterParam - } - if pnOk && pvOk { - flt.paramName = &pn - flt.paramValue = &pv - } - if err = handleItemFilterType(&flt, nil, params); err != nil { - return nil, err - } - return []itemFilter{flt}, nil -} - -func processNumberedItemFilters(params map[string]string) ([]itemFilter, error) { - var itemFilters []itemFilter - for i := 0; ; i++ { - name, ok := params[fmt.Sprintf("itemFilter(%d).name", i)] - if !ok { - break - } - filterValues, err := parseFilterValues(params, fmt.Sprintf("itemFilter(%d).value", i)) - if err != nil { - return nil, err - } - flt := itemFilter{ - name: name, - values: filterValues, - } - pn, pnOk := params[fmt.Sprintf("itemFilter(%d).paramName", i)] - pv, pvOk := params[fmt.Sprintf("itemFilter(%d).paramValue", i)] - if pnOk != pvOk { - return nil, ErrIncompleteItemFilterParam - } - if pnOk && pvOk { - flt.paramName = &pn - flt.paramValue = &pv - } - itemFilters = append(itemFilters, flt) - } - for i := range itemFilters { - if err := handleItemFilterType(&itemFilters[i], itemFilters, params); err != nil { - return nil, err - } - } - return itemFilters, nil -} - -func parseFilterValues(params map[string]string, filterAttr string) ([]string, error) { - var filterValues []string - for i := 0; ; i++ { - k := fmt.Sprintf("%s(%d)", filterAttr, i) - if v, ok := params[k]; ok { - filterValues = append(filterValues, v) - } else { - break - } - } - if v, ok := params[filterAttr]; ok { - filterValues = append(filterValues, v) - } - if len(filterValues) == 0 { - return nil, fmt.Errorf("%w %q", ErrIncompleteFilterNameOnly, filterAttr) - } - _, nonNumberedExists := params[filterAttr] - _, numberedExists := params[filterAttr+"(0)"] - if nonNumberedExists && numberedExists { - return nil, ErrInvalidIndexSyntax - } - return filterValues, nil -} - -const ( - // ItemFilterType enumeration values from the eBay documentation. - // See https://developer.ebay.com/devzone/finding/CallRef/types/ItemFilterType.html. - authorizedSellerOnly = "AuthorizedSellerOnly" - availableTo = "AvailableTo" - bestOfferOnly = "BestOfferOnly" - charityOnly = "CharityOnly" - condition = "Condition" - currency = "Currency" - endTimeFrom = "EndTimeFrom" - endTimeTo = "EndTimeTo" - excludeAutoPay = "ExcludeAutoPay" - excludeCategory = "ExcludeCategory" - excludeSeller = "ExcludeSeller" - expeditedShippingType = "ExpeditedShippingType" - feedbackScoreMax = "FeedbackScoreMax" - feedbackScoreMin = "FeedbackScoreMin" - freeShippingOnly = "FreeShippingOnly" - hideDuplicateItems = "HideDuplicateItems" - listedIn = "ListedIn" - listingType = "ListingType" - localPickupOnly = "LocalPickupOnly" - localSearchOnly = "LocalSearchOnly" - locatedIn = "LocatedIn" - lotsOnly = "LotsOnly" - maxBids = "MaxBids" - maxDistance = "MaxDistance" - maxHandlingTime = "MaxHandlingTime" - maxPrice = "MaxPrice" - maxQuantity = "MaxQuantity" - minBids = "MinBids" - minPrice = "MinPrice" - minQuantity = "MinQuantity" - modTimeFrom = "ModTimeFrom" - returnsAcceptedOnly = "ReturnsAcceptedOnly" - seller = "Seller" - sellerBusinessType = "SellerBusinessType" - soldItemsOnly = "SoldItemsOnly" - startTimeFrom = "StartTimeFrom" - startTimeTo = "StartTimeTo" - topRatedSellerOnly = "TopRatedSellerOnly" - valueBoxInventory = "ValueBoxInventory" - - trueValue = "true" - falseValue = "false" - trueNum = "1" - falseNum = "0" - smallestMaxDistance = 5 -) - -// Valid Currency ID values from the eBay documentation. -// See https://developer.ebay.com/devzone/finding/CallRef/Enums/currencyIdList.html. -var validCurrencyIDs = []string{ - "AUD", "CAD", "CHF", "CNY", "EUR", "GBP", "HKD", "INR", "MYR", "PHP", "PLN", "SEK", "SGD", "TWD", "USD", -} - -func handleItemFilterType(filter *itemFilter, itemFilters []itemFilter, params map[string]string) error { - switch filter.name { - case authorizedSellerOnly, bestOfferOnly, charityOnly, excludeAutoPay, freeShippingOnly, hideDuplicateItems, - localPickupOnly, lotsOnly, returnsAcceptedOnly, soldItemsOnly: - if filter.values[0] != trueValue && filter.values[0] != falseValue { - return fmt.Errorf("%w: %q", ErrInvalidBooleanValue, filter.values[0]) - } - case availableTo: - if !isValidCountryCode(filter.values[0]) { - return fmt.Errorf("%w: %q", ErrInvalidCountryCode, filter.values[0]) - } - case condition: - if !isValidCondition(filter.values[0]) { - return fmt.Errorf("%w: %q", ErrInvalidCondition, filter.values[0]) - } - case currency: - if !slices.Contains(validCurrencyIDs, filter.values[0]) { - return fmt.Errorf("%w: %q", ErrInvalidCurrencyID, filter.values[0]) - } - case endTimeFrom, endTimeTo, startTimeFrom, startTimeTo: - if !isValidDateTime(filter.values[0], true) { - return fmt.Errorf("%w: %q", ErrInvalidDateTime, filter.values[0]) - } - case excludeCategory: - if err := validateExcludeCategories(filter.values); err != nil { - return err - } - case excludeSeller: - if err := validateExcludeSellers(filter.values, itemFilters); err != nil { - return err - } - case expeditedShippingType: - if filter.values[0] != "Expedited" && filter.values[0] != "OneDayShipping" { - return fmt.Errorf("%w: %q", ErrInvalidExpeditedShippingType, filter.values[0]) - } - case feedbackScoreMax, feedbackScoreMin: - if err := validateNumericFilter(filter, itemFilters, 0, feedbackScoreMax, feedbackScoreMin); err != nil { - return err - } - case listedIn: - if err := validateGlobalID(filter.values[0]); err != nil { - return err - } - case listingType: - if err := validateListingTypes(filter.values); err != nil { - return err - } - case localSearchOnly: - if err := validateLocalSearchOnly(filter.values, itemFilters, params); err != nil { - return err - } - case locatedIn: - if err := validateLocatedIns(filter.values); err != nil { - return err - } - case maxBids, minBids: - if err := validateNumericFilter(filter, itemFilters, 0, maxBids, minBids); err != nil { - return err - } - case maxDistance: - if _, ok := params["buyerPostalCode"]; !ok { - return ErrBuyerPostalCodeMissing - } - if !isValidIntegerInRange(filter.values[0], smallestMaxDistance) { - return invalidIntegerError(filter.values[0], smallestMaxDistance) - } - case maxHandlingTime: - if !isValidIntegerInRange(filter.values[0], 1) { - return invalidIntegerError(filter.values[0], 1) - } - case maxPrice, minPrice: - if err := validatePriceRange(filter, itemFilters); err != nil { - return err - } - case maxQuantity, minQuantity: - if err := validateNumericFilter(filter, itemFilters, 1, maxQuantity, minQuantity); err != nil { - return err - } - case modTimeFrom: - if !isValidDateTime(filter.values[0], false) { - return fmt.Errorf("%w: %q", ErrInvalidDateTime, filter.values[0]) - } - case seller: - if err := validateSellers(filter.values, itemFilters); err != nil { - return err - } - case sellerBusinessType: - if err := validateSellerBusinessType(filter.values); err != nil { - return err - } - case topRatedSellerOnly: - if err := validateTopRatedSellerOnly(filter.values[0], itemFilters); err != nil { - return err - } - case valueBoxInventory: - if filter.values[0] != trueNum && filter.values[0] != falseNum { - return fmt.Errorf("%w: %q", ErrInvalidValueBoxInventory, filter.values[0]) - } - default: - return fmt.Errorf("%w: %q", ErrUnsupportedItemFilterType, filter.name) - } - return nil -} - -func isValidCountryCode(value string) bool { - const countryCodeLen = 2 - if len(value) != countryCodeLen { - return false - } - for _, r := range value { - if !unicode.IsUpper(r) { - return false - } - } - return true -} - -// Valid Condition IDs from the eBay documentation. -// See https://developer.ebay.com/Devzone/finding/CallRef/Enums/conditionIdList.html#ConditionDefinitions. -var validConditionIDs = []int{1000, 1500, 1750, 2000, 2010, 2020, 2030, 2500, 2750, 3000, 4000, 5000, 6000, 7000} - -func isValidCondition(value string) bool { - cID, err := strconv.Atoi(value) - if err == nil { - return slices.Contains(validConditionIDs, cID) - } - // Value is a condition name, refer to the eBay documentation for condition name definitions. - // See https://developer.ebay.com/Devzone/finding/CallRef/Enums/conditionIdList.html. - return true -} - -func isValidDateTime(value string, future bool) bool { - dateTime, err := time.Parse(time.RFC3339, value) - if err != nil { - return false - } - if dateTime.Location() != time.UTC { - return false - } - now := time.Now().UTC() - if future && dateTime.Before(now) { - return false - } - if !future && dateTime.After(now) { - return false - } - return true -} - -func validateExcludeCategories(values []string) error { - if len(values) > maxExcludeCategories { - return ErrMaxExcludeCategories - } - for _, v := range values { - if !isValidIntegerInRange(v, 0) { - return invalidIntegerError(v, 0) - } - } - return nil -} - -func validateExcludeSellers(values []string, itemFilters []itemFilter) error { - if len(values) > maxExcludeSellers { - return ErrMaxExcludeSellers - } - for _, f := range itemFilters { - if f.name == seller || f.name == topRatedSellerOnly { - return ErrExcludeSellerCannotBeUsedWithSellers - } - } - return nil -} - -func validateNumericFilter( - filter *itemFilter, itemFilters []itemFilter, minAllowedValue int, filterA, filterB string, -) error { - v, err := strconv.Atoi(filter.values[0]) - if err != nil { - return fmt.Errorf("%w: %w", ErrInvalidInteger, err) - } - if minAllowedValue > v { - return invalidIntegerError(filter.values[0], minAllowedValue) - } - var filterAValue, filterBValue *int - for _, f := range itemFilters { - if f.name == filterA { - val, err := strconv.Atoi(f.values[0]) - if err != nil { - return fmt.Errorf("%w: %w", ErrInvalidInteger, err) - } - filterAValue = &val - } else if f.name == filterB { - val, err := strconv.Atoi(f.values[0]) - if err != nil { - return fmt.Errorf("%w: %w", ErrInvalidInteger, err) - } - filterBValue = &val - } - } - if filterAValue != nil && filterBValue != nil && *filterBValue > *filterAValue { - return fmt.Errorf("%w: %q must be greater than or equal to %q", ErrInvalidNumericFilter, filterA, filterB) - } - return nil -} - -func invalidIntegerError(value string, min int) error { - return fmt.Errorf("%w: %q (minimum value: %d)", ErrInvalidInteger, value, min) -} - -func isValidIntegerInRange(value string, min int) bool { - n, err := strconv.Atoi(value) - if err != nil { - return false - } - return n >= min -} - -// Valid Listing Type values from the eBay documentation. -// See https://developer.ebay.com/devzone/finding/CallRef/types/ItemFilterType.html#ListingType. -var validListingTypes = []string{"Auction", "AuctionWithBIN", "Classified", "FixedPrice", "StoreInventory", "All"} - -func validateListingTypes(values []string) error { - seenTypes := make(map[string]bool) - hasAuction, hasAuctionWithBIN := false, false - for _, v := range values { - if v == "All" && len(values) > 1 { - return ErrInvalidAllListingType - } - found := false - for _, lt := range validListingTypes { - if v == lt { - found = true - if v == "Auction" { - hasAuction = true - } else if v == "AuctionWithBIN" { - hasAuctionWithBIN = true - } - - break - } - } - if !found { - return fmt.Errorf("%w: %q", ErrInvalidListingType, v) - } - if seenTypes[v] { - return fmt.Errorf("%w: %q", ErrDuplicateListingType, v) - } - if hasAuction && hasAuctionWithBIN { - return ErrInvalidAuctionListingTypes - } - seenTypes[v] = true - } - return nil -} - -func validateLocalSearchOnly(values []string, itemFilters []itemFilter, params map[string]string) error { - if _, ok := params["buyerPostalCode"]; !ok { - return ErrBuyerPostalCodeMissing - } - foundMaxDistance := slices.ContainsFunc(itemFilters, func(f itemFilter) bool { - return f.name == maxDistance - }) - if !foundMaxDistance { - return ErrMaxDistanceMissing - } - if values[0] != trueValue && values[0] != falseValue { - return fmt.Errorf("%w: %q", ErrInvalidBooleanValue, values[0]) - } - return nil -} - -func validateLocatedIns(values []string) error { - if len(values) > maxLocatedIns { - return ErrMaxLocatedIns - } - for _, v := range values { - if !isValidCountryCode(v) { - return fmt.Errorf("%w: %q", ErrInvalidCountryCode, v) - } - } - return nil -} - -func validatePriceRange(filter *itemFilter, itemFilters []itemFilter) error { - price, err := parsePrice(filter) - if err != nil { - return err - } - var relatedFilterName string - if filter.name == maxPrice { - relatedFilterName = minPrice - } else if filter.name == minPrice { - relatedFilterName = maxPrice - } - for i := range itemFilters { - if itemFilters[i].name == relatedFilterName { - relatedPrice, err := parsePrice(&itemFilters[i]) - if err != nil { - return err - } - if (filter.name == maxPrice && price < relatedPrice) || - (filter.name == minPrice && price > relatedPrice) { - return ErrInvalidMaxPrice - } - } - } - return nil -} - -func parsePrice(filter *itemFilter) (float64, error) { - const minAllowedPrice float64 = 0.0 - price, err := strconv.ParseFloat(filter.values[0], 64) - if err != nil { - return 0, fmt.Errorf("%w: %w", ErrInvalidPrice, err) - } - if minAllowedPrice > price { - return 0, fmt.Errorf("%w: %f (minimum value: %f)", ErrInvalidPrice, price, minAllowedPrice) - } - if filter.paramName != nil && *filter.paramName != currency { - return 0, fmt.Errorf("%w: %q", ErrInvalidPriceParamName, *filter.paramName) - } - if filter.paramValue != nil && !slices.Contains(validCurrencyIDs, *filter.paramValue) { - return 0, fmt.Errorf("%w: %q", ErrInvalidCurrencyID, *filter.paramValue) - } - return price, nil -} - -func validateSellers(values []string, itemFilters []itemFilter) error { - if len(values) > maxSellers { - return ErrMaxSellers - } - for _, f := range itemFilters { - if f.name == excludeSeller || f.name == topRatedSellerOnly { - return ErrSellerCannotBeUsedWithOtherSellers - } - } - return nil -} - -func validateSellerBusinessType(values []string) error { - if len(values) > 1 { - return fmt.Errorf("%w", ErrMultipleSellerBusinessTypes) - } - if values[0] != "Business" && values[0] != "Private" { - return fmt.Errorf("%w: %q", ErrInvalidSellerBusinessType, values[0]) - } - return nil -} - -func validateTopRatedSellerOnly(value string, itemFilters []itemFilter) error { - if value != trueValue && value != falseValue { - return fmt.Errorf("%w: %q", ErrInvalidBooleanValue, value) - } - for _, f := range itemFilters { - if f.name == seller || f.name == excludeSeller { - return ErrTopRatedSellerCannotBeUsedWithSellers - } - } - return nil -} diff --git a/finding.go b/finding.go index 5fb0ac1..fc3e90c 100644 --- a/finding.go +++ b/finding.go @@ -11,6 +11,18 @@ import ( "net/http" ) +const ( + findingURL = "https://svcs.ebay.com/services/search/FindingService/v1" + operationAdvanced = "findItemsAdvanced" + operationCategory = "findItemsByCategory" + operationKeywords = "findItemsByKeywords" + operationProduct = "findItemsByProduct" + operationStores = "findItemsIneBayStores" + serviceVersion = "1.0.0" + responseFormat = "JSON" + restPayload = "" +) + // A FindingClient is a client that interacts with the eBay Finding API. type FindingClient struct { // Client is the HTTP client used to make requests to the eBay Finding API. @@ -31,123 +43,121 @@ type FindingClient struct { URL string } -const findingURL = "https://svcs.ebay.com/services/search/FindingService/v1?REST-PAYLOAD" - // NewFindingClient creates a new FindingClient with the given HTTP client and valid eBay application ID. func NewFindingClient(client *http.Client, appID string) *FindingClient { return &FindingClient{Client: client, AppID: appID, URL: findingURL} } -// APIError represents an eBay Finding API call error. -type APIError struct { - // Err is the error that occurred during the call. - Err error +var ( + // ErrNewRequest is returned when creating an HTTP request fails. + ErrNewRequest = errors.New("ebay: failed to create HTTP request") - // StatusCode is the HTTP status code indicating why the call was bad. - StatusCode int -} + // ErrFailedRequest is returned when the eBay Finding API request fails. + ErrFailedRequest = errors.New("ebay: failed to perform eBay Finding API request") -func (e *APIError) Error() string { - if e.Err != nil { - return fmt.Sprintf("ebay: %v", e.Err) + // ErrInvalidStatus is returned when the eBay Finding API request returns an invalid status code. + ErrInvalidStatus = errors.New("ebay: failed to perform eBay Finding API request with status code") + + // ErrDecodeAPIResponse is returned when there is an error decoding the eBay Finding API response body. + ErrDecodeAPIResponse = errors.New("ebay: failed to decode eBay Finding API response body") +) + +// FindItemsAdvanced searches for items on eBay by category and/or keyword. +// See [Searching and Browsing By Category] for searching by category +// and [Searching by Keywords] for searching by keywords. +// +// [Searching and Browsing By Category]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-browsing-by-category.html +// [Searching by Keywords]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-by-keywords.html +func (c *FindingClient) FindItemsAdvanced(ctx context.Context, params map[string]string) (*FindItemsAdvancedResponse, error) { + req, err := c.newRequest(ctx, operationAdvanced, params) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrNewRequest, err) + } + resp, err := c.Do(req) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedRequest, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%w: %d", ErrInvalidStatus, resp.StatusCode) } - return "ebay: API error occurred" + var res FindItemsAdvancedResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return nil, fmt.Errorf("%w: %w", ErrDecodeAPIResponse, err) + } + return &res, nil } -// FindItemsByCategories searches for items on eBay using specific eBay category ID numbers. +// FindItemsByCategory searches for items on eBay using specific eBay category ID numbers. // See [Searching and Browsing By Category] for searching by category. // -// The category IDs narrow down the search results. The provided parameters -// contain additional query parameters for the search. If the FindingClient is configured with an invalid -// AppID, the search call will fail to authenticate. -// -// An error of type [*APIError] is returned if the category IDs and/or additional parameters were not valid, -// the request could not be created, the request or response could not be completed, or the response could not -// be parsed into type [FindItemsByCategoriesResponse]. -// -// If the returned error is nil, the [FindItemsByCategoriesResponse] will contain a non-nil ItemsResponse -// containing search results. -// See https://developer.ebay.com/devzone/finding/CallRef/findItemsByCategory.html. -// // [Searching and Browsing By Category]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-browsing-by-category.html -func (c *FindingClient) FindItemsByCategories(ctx context.Context, params map[string]string) (FindItemsByCategoriesResponse, error) { - var findItems FindItemsByCategoriesResponse - if err := c.findItems(ctx, params, &findItemsByCategoryParams{appID: c.AppID}, &findItems); err != nil { - return findItems, err +func (c *FindingClient) FindItemsByCategory(ctx context.Context, params map[string]string) (*FindItemsByCategoryResponse, error) { + req, err := c.newRequest(ctx, operationCategory, params) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrNewRequest, err) + } + resp, err := c.Do(req) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedRequest, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%w: %d", ErrInvalidStatus, resp.StatusCode) } - return findItems, nil + var res FindItemsByCategoryResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return nil, fmt.Errorf("%w: %w", ErrDecodeAPIResponse, err) + } + return &res, nil } // FindItemsByKeywords searches for items on eBay by a keyword query. // See [Searching by Keywords] for searching by keywords. // -// The keywords narrow down the search results. The provided parameters contain additional query parameters -// for the search. If the FindingClient is configured with an invalid AppID, the search call will fail to authenticate. -// -// An error of type [*APIError] is returned if the keywords and/or additional parameters were not valid, -// the request could not be created, the request or response could not be completed, or the response could not -// be parsed into type [FindItemsByKeywordsResponse]. -// -// If the returned error is nil, the [FindItemsByKeywordsResponse] will contain a non-nil ItemsResponse -// containing search results. -// See https://developer.ebay.com/devzone/finding/CallRef/findItemsByKeywords.html. -// // [Searching by Keywords]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-by-keywords.html -func (c *FindingClient) FindItemsByKeywords(ctx context.Context, params map[string]string) (FindItemsByKeywordsResponse, error) { - var findItems FindItemsByKeywordsResponse - if err := c.findItems(ctx, params, &findItemsByKeywordsParams{appID: c.AppID}, &findItems); err != nil { - return findItems, err +func (c *FindingClient) FindItemsByKeywords(ctx context.Context, params map[string]string) (*FindItemsByKeywordsResponse, error) { + req, err := c.newRequest(ctx, operationKeywords, params) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrNewRequest, err) } - return findItems, nil -} - -// FindItemsAdvanced searches for items on eBay by category and/or keyword. -// See [Searching and Browsing By Category] for searching by category -// and [Searching by Keywords] for searching by keywords. -// -// The category IDs and keywords narrow down the search results. The provided parameters contain additional -// query parameters for the search. If the FindingClient is configured with an invalid AppID, -// the search call will fail to authenticate. -// -// An error of type [*APIError] is returned if the category IDs, keywords, and/or additional parameters were not valid, -// the request could not be created, the request or response could not be completed, or the response could not -// be parsed into type [FindItemsAdvancedResponse]. -// -// If the returned error is nil, the [FindItemsAdvancedResponse] will contain a non-nil ItemsResponse -// containing search results. -// See https://developer.ebay.com/Devzone/finding/CallRef/findItemsAdvanced.html. -// -// [Searching and Browsing By Category]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-browsing-by-category.html -// [Searching by Keywords]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-by-keywords.html -func (c *FindingClient) FindItemsAdvanced(ctx context.Context, params map[string]string) (FindItemsAdvancedResponse, error) { - var findItems FindItemsAdvancedResponse - if err := c.findItems(ctx, params, &findItemsAdvancedParams{appID: c.AppID}, &findItems); err != nil { - return findItems, err + resp, err := c.Do(req) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedRequest, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%w: %d", ErrInvalidStatus, resp.StatusCode) } - return findItems, nil + var res FindItemsByKeywordsResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return nil, fmt.Errorf("%w: %w", ErrDecodeAPIResponse, err) + } + return &res, nil } // FindItemsByProduct searches for items on eBay using specific eBay product values. // See [Searching by Product] for searching by product. // -// The product ID narrows down the search results. The provided parameters contain additional query parameters -// for the search. If the FindingClient is configured with an invalid AppID, the search call will fail to authenticate. -// -// An error of type [*APIError] is returned if the product ID and/or additional parameters were not valid, -// the request could not be created, the request or response could not be completed, or the response could not -// be parsed into type [FindItemsByProductResponse]. -// -// If the returned error is nil, the [FindItemsByProductResponse] will contain a non-nil ItemsResponse -// containing search results. -// See https://developer.ebay.com/Devzone/finding/CallRef/findItemsByProduct.html. -// // [Searching by Product]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-by-product.html -func (c *FindingClient) FindItemsByProduct(ctx context.Context, params map[string]string) (FindItemsByProductResponse, error) { - var findItems FindItemsByProductResponse - if err := c.findItems(ctx, params, &findItemsByProductParams{appID: c.AppID}, &findItems); err != nil { - return findItems, err +func (c *FindingClient) FindItemsByProduct(ctx context.Context, params map[string]string) (*FindItemsByProductResponse, error) { + req, err := c.newRequest(ctx, operationProduct, params) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrNewRequest, err) + } + resp, err := c.Do(req) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrFailedRequest, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%w: %d", ErrInvalidStatus, resp.StatusCode) } - return findItems, nil + var res FindItemsByProductResponse + if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { + return nil, fmt.Errorf("%w: %w", ErrDecodeAPIResponse, err) + } + return &res, nil } // FindItemsInEBayStores searches for items in the eBay store inventories. The search can utilize a combination of @@ -156,63 +166,44 @@ func (c *FindingClient) FindItemsByProduct(ctx context.Context, params map[strin // See [Searching and Browsing By Category] for searching by category // and [Searching by Keywords] for searching by keywords. // -// The store name, category IDs, and keywords narrow down the search results. The provided parameters contain -// additional query parameters for the search. If the FindingClient is configured with an invalid AppID, -// the search call will fail to authenticate. -// -// An error of type [*APIError] is returned if the store name, category IDs, keywords, and/or additional parameters -// were not valid, the request could not be created, the request or response could not be completed, -// or the response could not be parsed into type [FindItemsInEBayStoresResponse]. -// -// If the returned error is nil, the [FindItemsInEBayStoresResponse] will contain a non-nil ItemsResponse -// containing search results. -// See https://developer.ebay.com/Devzone/finding/CallRef/findItemsIneBayStores.html. -// // [Searching and Browsing By Category]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-browsing-by-category.html // [Searching by Keywords]: https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-by-keywords.html -func (c *FindingClient) FindItemsInEBayStores(ctx context.Context, params map[string]string) (FindItemsInEBayStoresResponse, error) { - var findItems FindItemsInEBayStoresResponse - if err := c.findItems(ctx, params, &findItemsInEBayStoresParams{appID: c.AppID}, &findItems); err != nil { - return findItems, err - } - return findItems, nil -} - -var ( - // ErrFailedRequest is returned when the eBay Finding API request fails. - ErrFailedRequest = errors.New("failed to perform eBay Finding API request") - - // ErrInvalidStatus is returned when the eBay Finding API request returns an invalid status code. - ErrInvalidStatus = errors.New("failed to perform eBay Finding API request with status code") - - // ErrDecodeAPIResponse is returned when there is an error decoding the eBay Finding API response body. - ErrDecodeAPIResponse = errors.New("failed to decode eBay Finding API response body") -) - -func (c *FindingClient) findItems(ctx context.Context, params map[string]string, v findParamsValidator, res ResultProvider) error { - if err := v.validate(params); err != nil { - return &APIError{Err: err, StatusCode: http.StatusBadRequest} - } - req, err := v.newRequest(ctx, c.URL) +func (c *FindingClient) FindItemsInEBayStores(ctx context.Context, params map[string]string) (*FindItemsInEBayStoresResponse, error) { + req, err := c.newRequest(ctx, operationStores, params) if err != nil { - return &APIError{Err: err, StatusCode: http.StatusInternalServerError} + return nil, fmt.Errorf("%w: %w", ErrNewRequest, err) } resp, err := c.Do(req) if err != nil { - return &APIError{Err: fmt.Errorf("%w: %w", ErrFailedRequest, err), StatusCode: http.StatusInternalServerError} + return nil, fmt.Errorf("%w: %w", ErrFailedRequest, err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return &APIError{ - Err: fmt.Errorf("%w %d", ErrInvalidStatus, resp.StatusCode), - StatusCode: http.StatusInternalServerError, - } + return nil, fmt.Errorf("%w: %d", ErrInvalidStatus, resp.StatusCode) } + var res FindItemsInEBayStoresResponse if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return &APIError{ - Err: fmt.Errorf("%w: %w", ErrDecodeAPIResponse, err), - StatusCode: http.StatusInternalServerError, + return nil, fmt.Errorf("%w: %w", ErrDecodeAPIResponse, err) + } + return &res, nil +} + +func (c *FindingClient) newRequest(ctx context.Context, op string, params map[string]string) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.URL, nil) + if err != nil { + return nil, err + } + qry := req.URL.Query() + qry.Set("Operation-Name", op) + qry.Set("Service-Version", serviceVersion) + qry.Set("Security-AppName", c.AppID) + qry.Set("Response-Data-Format", responseFormat) + qry.Set("REST-Payload", restPayload) + for k, v := range params { + if v != "" { + qry.Set(k, v) } } - return nil + req.URL.RawQuery = qry.Encode() + return req, nil } diff --git a/finding_test.go b/finding_test.go index 374b373..57d9b50 100644 --- a/finding_test.go +++ b/finding_test.go @@ -1,4903 +1,421 @@ -// Copyright 2023 Matthew P. Dargan. -// SPDX-License-Identifier: Apache-2.0 - -package ebay_test +package ebay import ( "context" "encoding/json" "errors" - "fmt" - "maps" "net/http" "net/http/httptest" "reflect" - "strconv" - "strings" "testing" - "time" - - "github.com/matthewdargan/ebay" ) -type findItemsTestCase struct { - Name string - Params map[string]string - Err error -} - -var ( - appID = "super secret ID" - itemsResp = []ebay.FindItemsResponse{ - { - Ack: []string{"Success"}, - Version: []string{"1.0"}, - Timestamp: []time.Time{time.Date(2023, 6, 24, 0, 0, 0, 0, time.UTC)}, - SearchResult: []ebay.SearchResult{ - { - Count: "1", - Item: []ebay.SearchItem{ - { - ItemID: []string{"1234567890"}, - Title: []string{"Sample Item"}, - GlobalID: []string{"global-id-123"}, - Subtitle: []string{"Sample Item Subtitle"}, - PrimaryCategory: []ebay.Category{ - { - CategoryID: []string{"category-id-123"}, - CategoryName: []string{"Sample Category"}, - }, - }, - GalleryURL: []string{"https://example.com/sample-item.jpg"}, - ViewItemURL: []string{"https://example.com/sample-item"}, - ProductID: []ebay.ProductID{ - { - Type: "product-type-123", - Value: "product-value-123", - }, - }, - AutoPay: []string{"true"}, - PostalCode: []string{"12345"}, - Location: []string{"Sample Location"}, - Country: []string{"Sample Country"}, - ShippingInfo: []ebay.ShippingInfo{ - { - ShippingServiceCost: []ebay.Price{ - { - CurrencyID: "USD", - Value: "5.99", - }, - }, - ShippingType: []string{"Standard"}, - ShipToLocations: []string{"US"}, - ExpeditedShipping: []string{"false"}, - OneDayShippingAvailable: []string{"false"}, - HandlingTime: []string{"1"}, - }, - }, - SellingStatus: []ebay.SellingStatus{ - { - CurrentPrice: []ebay.Price{ - { - CurrencyID: "USD", - Value: "19.99", - }, - }, - ConvertedCurrentPrice: []ebay.Price{ - { - CurrencyID: "USD", - Value: "19.99", - }, - }, - SellingState: []string{"Active"}, - TimeLeft: []string{"P1D"}, - }, - }, - ListingInfo: []ebay.ListingInfo{ - { - BestOfferEnabled: []string{"true"}, - BuyItNowAvailable: []string{"false"}, - StartTime: []time.Time{time.Date(2023, 6, 24, 0, 0, 0, 0, time.UTC)}, - EndTime: []time.Time{time.Date(2023, 7, 24, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0)}, - ListingType: []string{"Auction"}, - Gift: []string{"false"}, - WatchCount: []string{"10"}, - }, - }, - ReturnsAccepted: []string{"true"}, - Condition: []ebay.Condition{ - { - ConditionID: []string{"1000"}, - ConditionDisplayName: []string{"New"}, - }, - }, - IsMultiVariationListing: []string{"false"}, - TopRatedListing: []string{"true"}, - DiscountPriceInfo: []ebay.DiscountPriceInfo{ - { - OriginalRetailPrice: []ebay.Price{ - { - CurrencyID: "USD", - Value: "29.99", - }, - }, - PricingTreatment: []string{"STP"}, - SoldOnEbay: []string{"true"}, - SoldOffEbay: []string{"false"}, - }, - }, - }, - }, - }, - }, - PaginationOutput: []ebay.PaginationOutput{ - { - PageNumber: []string{"1"}, - EntriesPerPage: []string{"10"}, - TotalPages: []string{"1"}, - TotalEntries: []string{"1"}, - }, - }, - ItemSearchURL: []string{"https://example.com/search?q=sample"}, - }, - } - findItemsByCategoriesResp = ebay.FindItemsByCategoriesResponse{ - ItemsResponse: itemsResp, - } - findItemsByKeywordsResp = ebay.FindItemsByKeywordsResponse{ - ItemsResponse: itemsResp, - } - findItemsAdvancedResp = ebay.FindItemsAdvancedResponse{ - ItemsResponse: itemsResp, - } - findItemsByProductResp = ebay.FindItemsByProductResponse{ - ItemsResponse: itemsResp, - } - findItemsInEBayStoresResp = ebay.FindItemsInEBayStoresResponse{ - ItemsResponse: itemsResp, - } - - findItemsByCategories = "FindItemsByCategories" - findItemsByKeywords = "FindItemsByKeywords" - findItemsAdvanced = "FindItemsAdvanced" - findItemsByProduct = "FindItemsByProduct" - findItemsInEBayStores = "FindItemsInEBayStores" - - categoryIDTCs = []findItemsTestCase{ - { - Name: "can find items if params contains categoryId of length 1", - Params: map[string]string{"categoryId": "1"}, - }, - { - Name: "can find items if params contains categoryId of length 5", - Params: map[string]string{"categoryId": "1234567890"}, - }, - { - Name: "can find items if params contains categoryId of length 10", - Params: map[string]string{"categoryId": "1234567890"}, - }, - { - Name: "returns error if params contains empty categoryId", - Params: map[string]string{"categoryId": ""}, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidCategoryID, `strconv.Atoi: parsing ""`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains categoryId of length 11", - Params: map[string]string{"categoryId": "12345678901"}, - Err: ebay.ErrInvalidCategoryIDLength, - }, - { - Name: "returns error if params contains non-numbered, invalid categoryId", - Params: map[string]string{"categoryId": "a"}, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidCategoryID, `strconv.Atoi: parsing "a"`, strconv.ErrSyntax), - }, - { - // categoryId(1) will be ignored because indexing does not start at 0. - Name: "can find items if params contains categoryId, categoryId(1)", - Params: map[string]string{"categoryId": "1", "categoryId(1)": "2"}, - }, - { - Name: "returns error if params contain numbered and non-numbered categoryId syntax types", - Params: map[string]string{"categoryId": "1", "categoryId(0)": "2"}, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "can find items by numbered categoryId", - Params: map[string]string{"categoryId(0)": "1"}, - }, - { - Name: "can find items if params contains 2 categoryIds of length 1", - Params: map[string]string{"categoryId(0)": "1", "categoryId(1)": "2"}, - }, - { - Name: "can find items if params contains 2 categoryIds of length 10", - Params: map[string]string{ - "categoryId(0)": "1234567890", - "categoryId(1)": "9876543210", - }, - }, - { - Name: "returns error if params contains 1 categoryId of length 1, 1 categoryId of length 11", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "12345678901", - }, - Err: ebay.ErrInvalidCategoryIDLength, - }, - { - Name: "returns error if params contains 1 categoryId of length 11, 1 categoryId of length 1", - Params: map[string]string{ - "categoryId(0)": "12345678901", - "categoryId(1)": "1", - }, - Err: ebay.ErrInvalidCategoryIDLength, - }, - { - Name: "can find items if params contains 3 categoryIds of length 1", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "2", - "categoryId(2)": "3", - }, - }, - { - Name: "can find items if params contains 3 categoryIds of length 10", - Params: map[string]string{ - "categoryId(0)": "1234567890", - "categoryId(1)": "9876543210", - "categoryId(2)": "8976543210", - }, - }, - { - Name: "returns error if params contains 1 categoryId of length 11, 2 categoryIds of length 1", - Params: map[string]string{ - "categoryId(0)": "12345678901", - "categoryId(1)": "1", - "categoryId(2)": "2", - }, - Err: ebay.ErrInvalidCategoryIDLength, - }, - { - Name: "returns error if params contains 2 categoryIds of length 1, 1 middle categoryId of length 11", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "12345678901", - "categoryId(2)": "2", - }, - Err: ebay.ErrInvalidCategoryIDLength, - }, - { - Name: "returns error if params contains 2 categoryIds of length 1, 1 categoryId of length 11", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "2", - "categoryId(2)": "12345678901", - }, - Err: ebay.ErrInvalidCategoryIDLength, - }, - { - Name: "returns error if params contains numbered, invalid categoryId", - Params: map[string]string{"categoryId(0)": "a"}, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidCategoryID, `strconv.Atoi: parsing "a"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains 1 valid, 1 invalid categoryId", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "a", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidCategoryID, `strconv.Atoi: parsing "a"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains 4 categoryIds", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "2", - "categoryId(2)": "3", - "categoryId(3)": "4", - }, - Err: ebay.ErrMaxCategoryIDs, - }, - } - keywordsTCs = []findItemsTestCase{ - { - Name: "can find items if params contains keywords of length 2", - Params: map[string]string{"keywords": generateStringWithLen(2, true)}, - }, - { - Name: "can find items if params contains keywords of length 12", - Params: map[string]string{"keywords": generateStringWithLen(12, true)}, - }, - { - Name: "can find items if params contains keywords of length 350", - Params: map[string]string{"keywords": generateStringWithLen(350, true)}, - }, - { - Name: "returns error if params contains empty keywords", - Params: map[string]string{"keywords": ""}, - Err: ebay.ErrInvalidKeywordsLength, - }, - { - Name: "returns error if params contains keywords of length 1", - Params: map[string]string{"keywords": generateStringWithLen(1, true)}, - Err: ebay.ErrInvalidKeywordsLength, - }, - { - Name: "returns error if params contains keywords of length 351", - Params: map[string]string{"keywords": generateStringWithLen(351, true)}, - Err: ebay.ErrInvalidKeywordsLength, - }, - { - Name: "can find items if params contains 1 keyword of length 2", - Params: map[string]string{"keywords": generateStringWithLen(2, false)}, - }, - { - Name: "can find items if params contains 1 keyword of length 98", - Params: map[string]string{"keywords": generateStringWithLen(98, false)}, - }, - { - Name: "returns error if params contains 1 keyword of length 99", - Params: map[string]string{"keywords": generateStringWithLen(99, false)}, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains 2 keywords of length 1", - Params: map[string]string{ - "keywords": generateStringWithLen(1, false) + "," + generateStringWithLen(1, false), - }, - }, - { - Name: "can find items if params contains 2 keywords of length 98", - Params: map[string]string{ - "keywords": generateStringWithLen(98, false) + "," + generateStringWithLen(98, false), - }, - }, - { - Name: "can find items if params contains keywords of length 1 and 98", - Params: map[string]string{ - "keywords": generateStringWithLen(1, false) + "," + generateStringWithLen(98, false), - }, - }, - { - Name: "can find items if params contains keywords of length 98 and 1", - Params: map[string]string{ - "keywords": generateStringWithLen(98, false) + "," + generateStringWithLen(1, false), - }, - }, - { - Name: "returns error if params contains 2 keywords of length 99", - Params: map[string]string{ - "keywords": generateStringWithLen(99, false) + "," + generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "returns error if params contains keywords of length 1 and 99", - Params: map[string]string{ - "keywords": generateStringWithLen(1, false) + "," + generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "returns error if params contains keywords of length 99 and 1", - Params: map[string]string{ - "keywords": generateStringWithLen(99, false) + "," + generateStringWithLen(1, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=baseball card", - Params: map[string]string{ - "keywords": "baseball card", - }, - }, - { - Name: "returns error if params contains space-separated keywords, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "baseball " + generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=baseball,card", - Params: map[string]string{ - "keywords": "baseball,card", - }, - }, - { - Name: "returns error if params contains comma-separated keywords, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "baseball," + generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=(baseball,card)", - Params: map[string]string{ - "keywords": "(baseball,card)", - }, - }, - { - Name: "returns error if params contains comma-separated, parenthesis keywords, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "(baseball," + generateStringWithLen(99, false) + ")", - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: `can find items if params contains keywords="baseball card"`, - Params: map[string]string{ - "keywords": `"baseball card"`, - }, - }, - { - Name: "returns error if params contains double-quoted keywords, 1 keyword of length 99", - Params: map[string]string{ - "keywords": `"baseball ` + generateStringWithLen(99, false) + `"`, - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=baseball -autograph", - Params: map[string]string{ - "keywords": "baseball -autograph", - }, - }, - { - Name: "returns error if params contains 1 keyword with minus sign before it, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "baseball -" + generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=baseball -(autograph,card,star)", - Params: map[string]string{ - "keywords": "baseball -(autograph,card,star)", - }, - }, - { - Name: "returns error if params contains minus sign before group of keywords, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "baseball -(autograph,card," + generateStringWithLen(99, false) + ")", - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=baseball*", - Params: map[string]string{ - "keywords": "baseball*", - }, - }, - { - Name: "returns error if params contains keyword of length 99 and asterisk", - Params: map[string]string{ - "keywords": generateStringWithLen(99, false) + "*", - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=@1 baseball autograph card", - Params: map[string]string{ - "keywords": "@1 baseball autograph card", - }, - }, - { - Name: "returns error if params contains @ with group of keywords, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "@1 baseball autograph " + generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=@1 baseball autograph card +star", - Params: map[string]string{ - "keywords": "@1 baseball autograph card +star", - }, - }, - { - Name: "returns error if params contains @ with group of keywords, plus sign, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "@1 baseball autograph card +" + generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains keywords=ap* ip*", - Params: map[string]string{ - "keywords": "ap* ip*", - }, - }, - { - Name: "returns error if params contains 2 asterisk keyword groups, 1 keyword of length 99", - Params: map[string]string{ - "keywords": "ap* " + generateStringWithLen(99, false) + "*", - }, - Err: ebay.ErrInvalidKeywordLength, - }, - } - categoryIDKeywordsTCs = []findItemsTestCase{ - { - Name: "can find items if params contains 1 categoryId of length 1, keywords of length 2", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(2, true), - }, - }, - { - Name: "can find items if params contains 2 categoryIds of length 1, keywords of length 2", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "2", - "keywords": generateStringWithLen(2, true), - }, - }, - { - Name: "returns error if params contains empty categoryId, keywords of length 2", - Params: map[string]string{ - "categoryId": "", - "keywords": generateStringWithLen(2, true), - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidCategoryID, `strconv.Atoi: parsing ""`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains 4 categoryIds, keywords of length 2", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "2", - "categoryId(2)": "3", - "categoryId(3)": "4", - "keywords": generateStringWithLen(2, true), - }, - Err: ebay.ErrMaxCategoryIDs, - }, - { - Name: "can find items if params contains 1 categoryId of length 1, 2 keywords of length 1", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(1, false) + "," + generateStringWithLen(1, false), - }, - }, - { - Name: "returns error if params contains 1 categoryId of length 1, empty keywords", - Params: map[string]string{"categoryId": "1", "keywords": ""}, - Err: ebay.ErrInvalidKeywordsLength, - }, - { - Name: "returns error if params contains 1 categoryId of length 1, 1 keyword of length 99", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(99, false), - }, - Err: ebay.ErrInvalidKeywordLength, - }, - } - missingSearchParamTCs = []findItemsTestCase{ - { - Name: "returns error if params does not contain ", - Params: map[string]string{}, - }, - { - Name: "returns error if params contains Global ID but not ", - Params: map[string]string{"Global-ID": "EBAY-AT"}, - }, - { - Name: "returns error if params contains non-numbered itemFilter but not ", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "returns error if params contains numbered itemFilter but not ", - Params: map[string]string{ - "itemFilter(0).name": "BestOfferOnly", - "itemFilter(0).value": "true", - }, - }, - { - Name: "returns error if params contains outputSelector but not ", - Params: map[string]string{"outputSelector": "AspectHistogram"}, - }, - { - Name: "returns error if params contains affiliate but not ", - Params: map[string]string{ - "affiliate.customId": "123", - "affiliate.geoTargeting": "true", - "affiliate.networkId": "2", - "affiliate.trackingId": "123", - }, - }, - { - Name: "returns error if params contains buyerPostalCode but not ", - Params: map[string]string{"buyerPostalCode": "111"}, - }, - { - Name: "returns error if params contains paginationInput but not ", - Params: map[string]string{ - "paginationInput.entriesPerPage": "1", - "paginationInput.pageNumber": "1", - }, - }, - { - Name: "returns error if params contains sortOrder but not ", - Params: map[string]string{"sortOrder": "BestMatch"}, - }, - } - aspectFilterMissingSearchParamTCs = []findItemsTestCase{ - { - Name: "returns error if params contains non-numbered aspectFilter but not ", - Params: map[string]string{ - "aspectFilter.aspectName": "Size", - "aspectFilter.aspectValueName": "10", - }, - }, - { - Name: "returns error if params contains numbered aspectFilter but not ", - Params: map[string]string{ - "aspectFilter(0).aspectName": "Size", - "aspectFilter(0).aspectValueName": "10", - }, - }, - } - easternTime = time.FixedZone("EasternTime", -5*60*60) - testCases = []findItemsTestCase{ - { - Name: "can find items if params contains Global ID EBAY-AT", - Params: map[string]string{"Global-ID": "EBAY-AT"}, - }, - { - Name: "can find items if params contains Global ID EBAY-AU", - Params: map[string]string{"Global-ID": "EBAY-AU"}, - }, - { - Name: "can find items if params contains Global ID EBAY-CH", - Params: map[string]string{"Global-ID": "EBAY-CH"}, - }, - { - Name: "can find items if params contains Global ID EBAY-DE", - Params: map[string]string{"Global-ID": "EBAY-DE"}, - }, - { - Name: "can find items if params contains Global ID EBAY-ENCA", - Params: map[string]string{"Global-ID": "EBAY-ENCA"}, - }, - { - Name: "can find items if params contains Global ID EBAY-ES", - Params: map[string]string{"Global-ID": "EBAY-ES"}, - }, - { - Name: "can find items if params contains Global ID EBAY-FR", - Params: map[string]string{"Global-ID": "EBAY-FR"}, - }, - { - Name: "can find items if params contains Global ID EBAY-FRBE", - Params: map[string]string{"Global-ID": "EBAY-FRBE"}, - }, - { - Name: "can find items if params contains Global ID EBAY-FRCA", - Params: map[string]string{"Global-ID": "EBAY-FRCA"}, - }, - { - Name: "can find items if params contains Global ID EBAY-GB", - Params: map[string]string{"Global-ID": "EBAY-GB"}, - }, - { - Name: "can find items if params contains Global ID EBAY-HK", - Params: map[string]string{"Global-ID": "EBAY-HK"}, - }, - { - Name: "can find items if params contains Global ID EBAY-IE", - Params: map[string]string{"Global-ID": "EBAY-IE"}, - }, - { - Name: "can find items if params contains Global ID EBAY-IN", - Params: map[string]string{"Global-ID": "EBAY-IN"}, - }, - { - Name: "can find items if params contains Global ID EBAY-IT", - Params: map[string]string{"Global-ID": "EBAY-IT"}, - }, - { - Name: "can find items if params contains Global ID EBAY-MOTOR", - Params: map[string]string{"Global-ID": "EBAY-MOTOR"}, - }, - { - Name: "can find items if params contains Global ID EBAY-MY", - Params: map[string]string{"Global-ID": "EBAY-MY"}, - }, - { - Name: "can find items if params contains Global ID EBAY-NL", - Params: map[string]string{"Global-ID": "EBAY-NL"}, - }, - { - Name: "can find items if params contains Global ID EBAY-NLBE", - Params: map[string]string{"Global-ID": "EBAY-NLBE"}, - }, - { - Name: "can find items if params contains Global ID EBAY-PH", - Params: map[string]string{"Global-ID": "EBAY-PH"}, - }, - { - Name: "can find items if params contains Global ID EBAY-PL", - Params: map[string]string{"Global-ID": "EBAY-PL"}, - }, - { - Name: "can find items if params contains Global ID EBAY-SG", - Params: map[string]string{"Global-ID": "EBAY-SG"}, - }, - { - Name: "can find items if params contains Global ID EBAY-US", - Params: map[string]string{"Global-ID": "EBAY-US"}, - }, - { - Name: "returns error if params contains Global ID EBAY-ZZZ", - Params: map[string]string{"Global-ID": "EBAY-ZZZ"}, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidGlobalID, "EBAY-ZZZ"), - }, - { - Name: "can find items by itemFilter.name, value", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items by itemFilter.name, value(0), value(1)", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value(0)": "1", - "itemFilter.value(1)": "2", - }, - }, - { - Name: "can find items by itemFilter.name, value, paramName, paramValue", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "5.0", - "itemFilter.paramName": "Currency", - "itemFilter.paramValue": "EUR", - }, - }, - { - Name: "returns error if params contains itemFilter.name but not value", - Params: map[string]string{"itemFilter.name": "BestOfferOnly"}, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "itemFilter.value"), - }, - { - // itemFilter.value(1) will be ignored because indexing does not start at 0. - Name: "returns error if params contains itemFilter.name, value(1)", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value(1)": "true", - }, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "itemFilter.value"), - }, - { - // itemFilter.value(1) will be ignored because indexing does not start at 0. - // Therefore, only itemFilter.value is considered and this becomes a non-numbered itemFilter. - Name: "can find items by itemFilter.name, value, value(1)", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value": "true", - "itemFilter.value(1)": "true", - }, - }, - { - // The itemFilter will be ignored if no itemFilter.name param is found before other itemFilter params. - Name: "can find items if params contains itemFilter.value only", - Params: map[string]string{"itemFilter.value": "true"}, - }, - { - // The itemFilter will be ignored if no itemFilter.name param is found before other itemFilter params. - Name: "can find items if params contains itemFilter.paramName only", - Params: map[string]string{"itemFilter.paramName": "Currency"}, - }, - { - // The itemFilter will be ignored if no itemFilter.name param is found before other itemFilter params. - Name: "can find items if params contains itemFilter.paramValue only", - Params: map[string]string{"itemFilter.paramValue": "EUR"}, - }, - { - Name: "returns error if params contains itemFilter.paramName but not paramValue", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "5.0", - "itemFilter.paramName": "Currency", - }, - Err: ebay.ErrIncompleteItemFilterParam, - }, - { - Name: "returns error if params contains itemFilter.paramValue but not paramName", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "5.0", - "itemFilter.paramValue": "EUR", - }, - Err: ebay.ErrIncompleteItemFilterParam, - }, - { - Name: "returns error if params contain numbered and non-numbered itemFilter syntax types", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value": "true", - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "5.0", - }, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "returns error if params contain itemFilter.name, value, value(0)", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value": "1", - "itemFilter.value(0)": "2", - }, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "returns error if params contain itemFilter(0).name, value, value(0)", - Params: map[string]string{ - "itemFilter(0).name": "ExcludeCategory", - "itemFilter(0).value": "1", - "itemFilter(0).value(0)": "2", - }, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "can find items by itemFilter(0).name, value", - Params: map[string]string{ - "itemFilter(0).name": "BestOfferOnly", - "itemFilter(0).value": "true", - }, - }, - { - Name: "can find items by itemFilter(0).name, value(0), value(1)", - Params: map[string]string{ - "itemFilter(0).name": "ExcludeCategory", - "itemFilter(0).value(0)": "1", - "itemFilter(0).value(1)": "2", - }, - }, - { - Name: "can find items by itemFilter(0).name, value, paramName, and paramValue", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "5.0", - "itemFilter(0).paramName": "Currency", - "itemFilter(0).paramValue": "EUR", - }, - }, - { - Name: "can find items by 2 basic, numbered itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "BestOfferOnly", - "itemFilter(0).value": "true", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "5.0", - }, - }, - { - Name: "can find items by 1st advanced, numbered and 2nd basic, numbered itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "5.0", - "itemFilter(0).paramName": "Currency", - "itemFilter(0).paramValue": "EUR", - "itemFilter(1).name": "BestOfferOnly", - "itemFilter(1).value": "true", - }, - }, - { - Name: "can find items by 1st basic, numbered and 2nd advanced, numbered itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "BestOfferOnly", - "itemFilter(0).value": "true", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "5.0", - "itemFilter(1).paramName": "Currency", - "itemFilter(1).paramValue": "EUR", - }, - }, - { - Name: "can find items by 2 advanced, numbered itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "1.0", - "itemFilter(0).paramName": "Currency", - "itemFilter(0).paramValue": "EUR", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "5.0", - "itemFilter(1).paramName": "Currency", - "itemFilter(1).paramValue": "EUR", - }, - }, - { - Name: "returns error if params contains itemFilter(0).name but not value", - Params: map[string]string{"itemFilter(0).name": "BestOfferOnly"}, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "itemFilter(0).value"), - }, - { - // itemFilter(0).value(1) will be ignored because indexing does not start at 0. - Name: "returns error if params contains itemFilter(0).name, value(1)", - Params: map[string]string{ - "itemFilter(0).name": "BestOfferOnly", - "itemFilter(0).value(1)": "true", - }, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "itemFilter(0).value"), - }, - { - // itemFilter(0).value(1) will be ignored because indexing does not start at 0. - // Therefore, only itemFilter(0).value is considered and this becomes a numbered itemFilter. - Name: "can find items by itemFilter(0).name, value, value(1)", - Params: map[string]string{ - "itemFilter(0).name": "BestOfferOnly", - "itemFilter(0).value": "true", - "itemFilter(0).value(1)": "true", - }, - }, - { - // The itemFilter will be ignored if no itemFilter(0).name param is found before other itemFilter params. - Name: "can find items if params contains itemFilter(0).value only", - Params: map[string]string{"itemFilter(0).value": "true"}, - }, - { - // The itemFilter will be ignored if no itemFilter(0).name param is found before other itemFilter params. - Name: "can find items if params contains itemFilter(0).paramName only", - Params: map[string]string{"itemFilter(0).paramName": "Currency"}, - }, - { - // The itemFilter will be ignored if no itemFilter(0).name param is found before other itemFilter params. - Name: "can find items if params contains itemFilter(0).paramValue only", - Params: map[string]string{"itemFilter(0).paramValue": "EUR"}, - }, - { - Name: "returns error if params contains itemFilter(0).paramName but not paramValue", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "5.0", - "itemFilter(0).paramName": "Currency", - }, - Err: ebay.ErrIncompleteItemFilterParam, - }, - { - Name: "returns error if params contains itemFilter(0).paramValue but not paramName", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "5.0", - "itemFilter(0).paramValue": "EUR", - }, - Err: ebay.ErrIncompleteItemFilterParam, - }, - { - Name: "returns error if params contains non-numbered, unsupported itemFilter name", - Params: map[string]string{ - "itemFilter.name": "UnsupportedFilter", - "itemFilter.value": "true", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrUnsupportedItemFilterType, "UnsupportedFilter"), - }, - { - Name: "returns error if params contains numbered, unsupported itemFilter name", - Params: map[string]string{ - "itemFilter(0).name": "UnsupportedFilter", - "itemFilter(0).value": "true", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrUnsupportedItemFilterType, "UnsupportedFilter"), - }, - { - Name: "returns error if params contains numbered supported and unsupported itemFilter names", - Params: map[string]string{ - "itemFilter(0).name": "BestOfferOnly", - "itemFilter(0).value": "true", - "itemFilter(1).name": "UnsupportedFilter", - "itemFilter(1).value": "true", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrUnsupportedItemFilterType, "UnsupportedFilter"), - }, - { - Name: "can find items if params contains AuthorizedSellerOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "AuthorizedSellerOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains AuthorizedSellerOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "AuthorizedSellerOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains AuthorizedSellerOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "AuthorizedSellerOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains valid AvailableTo itemFilter", - Params: map[string]string{ - "itemFilter.name": "AvailableTo", - "itemFilter.value": "US", - }, - }, - { - Name: "returns error if params contains AvailableTo itemFilter with lowercase characters", - Params: map[string]string{ - "itemFilter.name": "AvailableTo", - "itemFilter.value": "us", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCountryCode, "us"), - }, - { - Name: "returns error if params contains AvailableTo itemFilter with 1 uppercase character", - Params: map[string]string{ - "itemFilter.name": "AvailableTo", - "itemFilter.value": "U", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCountryCode, "U"), - }, - { - Name: "returns error if params contains AvailableTo itemFilter with 3 uppercase character", - Params: map[string]string{ - "itemFilter.name": "AvailableTo", - "itemFilter.value": "USA", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCountryCode, "USA"), - }, - { - Name: "can find items if params contains BestOfferOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains BestOfferOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains BestOfferOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "BestOfferOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains CharityOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "CharityOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains CharityOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "CharityOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains CharityOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "CharityOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains Condition itemFilter with condition name", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "dirty", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 1000", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "1000", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 1500", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "1500", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 1750", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "1750", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 2000", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "2000", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 2010", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "2010", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 2020", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "2020", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 2030", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "2030", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 2500", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "2500", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 2750", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "2750", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 3000", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "3000", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 4000", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "4000", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 5000", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "5000", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 6000", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "6000", - }, - }, - { - Name: "can find items if params contains Condition itemFilter with condition ID 7000", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "7000", - }, - }, - { - Name: "returns error if params contains Condition itemFilter with condition ID 1", - Params: map[string]string{ - "itemFilter.name": "Condition", - "itemFilter.value": "1", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCondition, "1"), - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID AUD", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "AUD", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID CAD", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "CAD", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID CHF", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "CHF", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID CNY", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "CNY", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID EUR", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "EUR", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID GBP", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "GBP", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID HKD", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "HKD", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID INR", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "INR", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID MYR", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "MYR", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID PHP", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "PHP", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID PLN", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "PLN", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID SEK", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "SEK", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID SGD", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "SGD", - }, - }, - { - Name: "can find items if params contains Currency itemFilter with currency ID TWD", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "TWD", - }, - }, - { - Name: "returns error if params contains Currency itemFilter with currency ID ZZZ", - Params: map[string]string{ - "itemFilter.name": "Currency", - "itemFilter.value": "ZZZ", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCurrencyID, "ZZZ"), - }, - { - Name: "can find items if params contains EndTimeFrom itemFilter with future timestamp", - Params: map[string]string{ - "itemFilter.name": "EndTimeFrom", - "itemFilter.value": time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339), - }, - }, - { - Name: "returns error if params contains EndTimeFrom itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "EndTimeFrom", - "itemFilter.value": "not a timestamp", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidDateTime, "not a timestamp"), - }, - { - Name: "returns error if params contains EndTimeFrom itemFilter with non-UTC timestamp", - Params: map[string]string{ - "itemFilter.name": "EndTimeFrom", - "itemFilter.value": time.Now().Add(1 * time.Second).In(easternTime).Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(1*time.Second).In(easternTime).Format(time.RFC3339)), - }, - { - Name: "returns error if params contains EndTimeFrom itemFilter with past timestamp", - Params: map[string]string{ - "itemFilter.name": "EndTimeFrom", - "itemFilter.value": time.Now().Add(-1 * time.Second).UTC().Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(-1*time.Second).UTC().Format(time.RFC3339)), - }, - { - Name: "can find items if params contains EndTimeTo itemFilter with future timestamp", - Params: map[string]string{ - "itemFilter.name": "EndTimeTo", - "itemFilter.value": time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339), - }, - }, - { - Name: "returns error if params contains EndTimeTo itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "EndTimeTo", - "itemFilter.value": "not a timestamp", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidDateTime, "not a timestamp"), - }, - { - Name: "returns error if params contains EndTimeTo itemFilter with non-UTC timestamp", - Params: map[string]string{ - "itemFilter.name": "EndTimeTo", - "itemFilter.value": time.Now().Add(1 * time.Second).In(easternTime).Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(1*time.Second).In(easternTime).Format(time.RFC3339)), - }, - { - Name: "returns error if params contains EndTimeTo itemFilter with past timestamp", - Params: map[string]string{ - "itemFilter.name": "EndTimeTo", - "itemFilter.value": time.Now().Add(-1 * time.Second).UTC().Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(-1*time.Second).UTC().Format(time.RFC3339)), - }, - { - Name: "can find items if params contains ExcludeAutoPay itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "ExcludeAutoPay", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains ExcludeAutoPay itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "ExcludeAutoPay", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains ExcludeAutoPay itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "ExcludeAutoPay", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains ExcludeCategory itemFilter with category ID 0", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value": "0", - }, - }, - { - Name: "can find items if params contains ExcludeCategory itemFilter with category ID 5", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains ExcludeCategory itemFilter with unparsable category ID", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value": "not a category ID", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "not a category ID", 0), - }, - { - Name: "returns error if params contains ExcludeCategory itemFilter with category ID -1", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "can find items if params contains ExcludeCategory itemFilter with category IDs 0 and 1", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value(0)": "0", - "itemFilter.value(1)": "1", - }, - }, - { - Name: "returns error if params contains ExcludeCategory itemFilter with category IDs 0 and -1", - Params: map[string]string{ - "itemFilter.name": "ExcludeCategory", - "itemFilter.value(0)": "0", - "itemFilter.value(1)": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "can find items if params contains ExcludeCategory itemFilter with 25 category IDs", - Params: generateFilterParams("ExcludeCategory", 25), - }, - { - Name: "returns error if params contains ExcludeCategory itemFilter with 26 category IDs", - Params: generateFilterParams("ExcludeCategory", 26), - Err: ebay.ErrMaxExcludeCategories, - }, - { - Name: "can find items if params contains ExcludeSeller itemFilter with seller ID 0", - Params: map[string]string{ - "itemFilter.name": "ExcludeSeller", - "itemFilter.value": "0", - }, - }, - { - Name: "can find items if params contains ExcludeSeller itemFilter with seller IDs 0 and 1", - Params: map[string]string{ - "itemFilter.name": "ExcludeSeller", - "itemFilter.value(0)": "0", - "itemFilter.value(1)": "1", - }, - }, - { - Name: "returns error if params contains ExcludeSeller and Seller itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "ExcludeSeller", - "itemFilter(0).value": "0", - "itemFilter(1).name": "Seller", - "itemFilter(1).value": "0", - }, - Err: ebay.ErrExcludeSellerCannotBeUsedWithSellers, - }, - { - Name: "returns error if params contains ExcludeSeller and TopRatedSellerOnly itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "ExcludeSeller", - "itemFilter(0).value": "0", - "itemFilter(1).name": "TopRatedSellerOnly", - "itemFilter(1).value": "true", - }, - Err: ebay.ErrExcludeSellerCannotBeUsedWithSellers, - }, - { - Name: "can find items if params contains ExcludeSeller itemFilter with 100 seller IDs", - Params: generateFilterParams("ExcludeSeller", 100), - }, - { - Name: "returns error if params contains ExcludeSeller itemFilter with 101 seller IDs", - Params: generateFilterParams("ExcludeSeller", 101), - Err: ebay.ErrMaxExcludeSellers, - }, - { - Name: "can find items if params contains ExpeditedShippingType itemFilter.value=Expedited", - Params: map[string]string{ - "itemFilter.name": "ExpeditedShippingType", - "itemFilter.value": "Expedited", - }, - }, - { - Name: "can find items if params contains ExpeditedShippingType itemFilter.value=OneDayShipping", - Params: map[string]string{ - "itemFilter.name": "ExpeditedShippingType", - "itemFilter.value": "OneDayShipping", - }, - }, - { - Name: "returns error if params contains ExpeditedShippingType itemFilter with invalid shipping type", - Params: map[string]string{ - "itemFilter.name": "ExpeditedShippingType", - "itemFilter.value": "InvalidShippingType", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidExpeditedShippingType, "InvalidShippingType"), - }, - { - Name: "can find items if params contains FeedbackScoreMax itemFilter with max 0", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMax", - "itemFilter.value": "0", - }, - }, - { - Name: "can find items if params contains FeedbackScoreMax itemFilter with max 5", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMax", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains FeedbackScoreMax itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMax", - "itemFilter.value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains FeedbackScoreMax itemFilter with max -1", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMax", - "itemFilter.value": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "can find items if params contains FeedbackScoreMin itemFilter with max 0", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMin", - "itemFilter.value": "0", - }, - }, - { - Name: "can find items if params contains FeedbackScoreMin itemFilter with max 5", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMin", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains FeedbackScoreMin itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMin", - "itemFilter.value": "not a minimum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains FeedbackScoreMin itemFilter with max -1", - Params: map[string]string{ - "itemFilter.name": "FeedbackScoreMin", - "itemFilter.value": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "can find items if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max 1 and min 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "1", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "0", - }, - }, - { - Name: "can find items if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min 0 and max 1", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "1", - }, - }, - { - Name: "can find items if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max and min 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "0", - }, - }, - { - Name: "can find items if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min and max 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "0", - }, - }, - { - Name: "can find items if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max 10 and min 5", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "10", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "5", - }, - }, - { - Name: "can find items if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min 5 and max 10", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "5", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "10", - }, - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max 0 and unparsable min", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "not a minimum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with unparsable min and max 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "not a minimum", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max 0 and min -1", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min -1 and max 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "-1", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max 0 and min 1", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "1", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "FeedbackScoreMax", "FeedbackScoreMin"), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min 1 and max 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "1", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "FeedbackScoreMax", "FeedbackScoreMin"), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max 5 and min 10", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "5", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "10", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "FeedbackScoreMax", "FeedbackScoreMin"), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min 10 and max 5", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "10", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "5", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "FeedbackScoreMax", "FeedbackScoreMin"), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with unparsable max and min 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "not a maximum", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min 0 and unparsable max", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with max -1 and min 0", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMax", - "itemFilter(0).value": "-1", - "itemFilter(1).name": "FeedbackScoreMin", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "returns error if params contains FeedbackScoreMin/FeedbackScoreMax itemFilters with min 0 and max -1", - Params: map[string]string{ - "itemFilter(0).name": "FeedbackScoreMin", - "itemFilter(0).value": "0", - "itemFilter(1).name": "FeedbackScoreMax", - "itemFilter(1).value": "-1", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "FeedbackScoreMax", "FeedbackScoreMin"), - }, - { - Name: "can find items if params contains FreeShippingOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "FreeShippingOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains FreeShippingOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "FreeShippingOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains FreeShippingOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "FreeShippingOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains HideDuplicateItems itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "HideDuplicateItems", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains HideDuplicateItems itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "HideDuplicateItems", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains HideDuplicateItems itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "HideDuplicateItems", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-AT", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-AT", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-AU", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-AU", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-CH", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-CH", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-DE", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-DE", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-ENCA", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-ENCA", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-ES", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-ES", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-FR", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-FR", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-FRBE", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-FRBE", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-FRCA", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-FRCA", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-GB", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-GB", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-HK", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-HK", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-IE", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-IE", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-IN", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-IN", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-IT", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-IT", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-MOTOR", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-MOTOR", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-MY", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-MY", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-NL", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-NL", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-NLBE", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-NLBE", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-PH", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-PH", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-PL", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-PL", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-SG", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-SG", - }, - }, - { - Name: "can find items if params contains ListedIn itemFilter with Global ID EBAY-US", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-US", - }, - }, - { - Name: "returns error if params contains ListedIn itemFilter with Global ID EBAY-ZZZ", - Params: map[string]string{ - "itemFilter.name": "ListedIn", - "itemFilter.value": "EBAY-ZZZ", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidGlobalID, "EBAY-ZZZ"), - }, - { - Name: "can find items if params contains ListingType itemFilter with listing type Auction", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "Auction", - }, - }, - { - Name: "can find items if params contains ListingType itemFilter with listing type AuctionWithBIN", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "AuctionWithBIN", - }, - }, - { - Name: "can find items if params contains ListingType itemFilter with listing type Classified", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "Classified", - }, - }, - { - Name: "can find items if params contains ListingType itemFilter with listing type FixedPrice", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "FixedPrice", - }, - }, - { - Name: "can find items if params contains ListingType itemFilter with listing type StoreInventory", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "StoreInventory", - }, - }, - { - Name: "can find items if params contains ListingType itemFilter with listing type All", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "All", - }, - }, - { - Name: "returns error if params contains ListingType itemFilter with invalid listing type", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "not a listing type", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidListingType, "not a listing type"), - }, - { - Name: "returns error if params contains ListingType itemFilters with All and Auction listing types", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value(0)": "All", - "itemFilter.value(1)": "Auction", - }, - Err: ebay.ErrInvalidAllListingType, - }, - { - Name: "returns error if params contains ListingType itemFilters with StoreInventory and All listing types", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value(0)": "StoreInventory", - "itemFilter.value(1)": "All", - }, - Err: ebay.ErrInvalidAllListingType, - }, - { - Name: "returns error if params contains ListingType itemFilters with 2 Auction listing types", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value(0)": "Auction", - "itemFilter.value(1)": "Auction", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrDuplicateListingType, "Auction"), - }, - { - Name: "returns error if params contains ListingType itemFilters with 2 StoreInventory listing types", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value(0)": "StoreInventory", - "itemFilter.value(1)": "StoreInventory", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrDuplicateListingType, "StoreInventory"), - }, - { - Name: "returns error if params contains ListingType itemFilters with Auction and AuctionWithBIN listing types", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value(0)": "Auction", - "itemFilter.value(1)": "AuctionWithBIN", - }, - Err: ebay.ErrInvalidAuctionListingTypes, - }, - { - Name: "returns error if params contains ListingType itemFilters with AuctionWithBIN and Auction listing types", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value(0)": "AuctionWithBIN", - "itemFilter.value(1)": "Auction", - }, - Err: ebay.ErrInvalidAuctionListingTypes, - }, - { - Name: "can find items if params contains LocalPickupOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "LocalPickupOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains LocalPickupOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "LocalPickupOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains LocalPickupOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "LocalPickupOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains LocalSearchOnly itemFilter.value=true, buyerPostalCode, and MaxDistance", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter(0).name": "LocalSearchOnly", - "itemFilter(0).value": "true", - "itemFilter(1).name": "MaxDistance", - "itemFilter(1).value": "5", - }, - }, - { - Name: "can find items if params contains LocalSearchOnly itemFilter.value=false, buyerPostalCode, and MaxDistance", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter(0).name": "LocalSearchOnly", - "itemFilter(0).value": "false", - "itemFilter(1).name": "MaxDistance", - "itemFilter(1).value": "5", - }, - }, - { - Name: "can find items if params contains LocalSearchOnly itemFilter with non-boolean value", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter(0).name": "LocalSearchOnly", - "itemFilter(0).value": "123", - "itemFilter(1).name": "MaxDistance", - "itemFilter(1).value": "5", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "returns error if params contains LocalSearchOnly itemFilter but no buyerPostalCode", - Params: map[string]string{ - "itemFilter(0).name": "LocalSearchOnly", - "itemFilter(0).value": "true", - "itemFilter(1).name": "MaxDistance", - "itemFilter(1).value": "5", - }, - Err: ebay.ErrBuyerPostalCodeMissing, - }, - { - Name: "returns error if params contains LocalSearchOnly itemFilter but no MaxDistance itemFilter", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter.name": "LocalSearchOnly", - "itemFilter.value": "true", - }, - Err: ebay.ErrMaxDistanceMissing, - }, - { - Name: "can find items if params contains valid LocatedIn itemFilter", - Params: map[string]string{ - "itemFilter.name": "LocatedIn", - "itemFilter.value": "US", - }, - }, - { - Name: "returns error if params contains LocatedIn itemFilter with lowercase characters", - Params: map[string]string{ - "itemFilter.name": "LocatedIn", - "itemFilter.value": "us", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCountryCode, "us"), - }, - { - Name: "returns error if params contains LocatedIn itemFilter with 1 uppercase character", - Params: map[string]string{ - "itemFilter.name": "LocatedIn", - "itemFilter.value": "U", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCountryCode, "U"), - }, - { - Name: "returns error if params contains LocatedIn itemFilter with 3 uppercase character", - Params: map[string]string{ - "itemFilter.name": "LocatedIn", - "itemFilter.value": "USA", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCountryCode, "USA"), - }, - { - Name: "can find items if params contains LocatedIn itemFilter with 25 country codes", - Params: map[string]string{ - "itemFilter.name": "LocatedIn", - "itemFilter.value(0)": "AA", - "itemFilter.value(1)": "AB", - "itemFilter.value(2)": "AC", - "itemFilter.value(3)": "AD", - "itemFilter.value(4)": "AE", - "itemFilter.value(5)": "AF", - "itemFilter.value(6)": "AG", - "itemFilter.value(7)": "AH", - "itemFilter.value(8)": "AI", - "itemFilter.value(9)": "AJ", - "itemFilter.value(10)": "AK", - "itemFilter.value(11)": "AL", - "itemFilter.value(12)": "AM", - "itemFilter.value(13)": "AN", - "itemFilter.value(14)": "AO", - "itemFilter.value(15)": "AP", - "itemFilter.value(16)": "AQ", - "itemFilter.value(17)": "AR", - "itemFilter.value(18)": "AS", - "itemFilter.value(19)": "AT", - "itemFilter.value(20)": "AU", - "itemFilter.value(21)": "AV", - "itemFilter.value(22)": "AW", - "itemFilter.value(23)": "AX", - "itemFilter.value(24)": "AY", - }, - }, - { - Name: "returns error if params contains LocatedIn itemFilter with 26 country codes", - Params: map[string]string{ - "itemFilter.name": "LocatedIn", - "itemFilter.value(0)": "AA", - "itemFilter.value(1)": "AB", - "itemFilter.value(2)": "AC", - "itemFilter.value(3)": "AD", - "itemFilter.value(4)": "AE", - "itemFilter.value(5)": "AF", - "itemFilter.value(6)": "AG", - "itemFilter.value(7)": "AH", - "itemFilter.value(8)": "AI", - "itemFilter.value(9)": "AJ", - "itemFilter.value(10)": "AK", - "itemFilter.value(11)": "AL", - "itemFilter.value(12)": "AM", - "itemFilter.value(13)": "AN", - "itemFilter.value(14)": "AO", - "itemFilter.value(15)": "AP", - "itemFilter.value(16)": "AQ", - "itemFilter.value(17)": "AR", - "itemFilter.value(18)": "AS", - "itemFilter.value(19)": "AT", - "itemFilter.value(20)": "AU", - "itemFilter.value(21)": "AV", - "itemFilter.value(22)": "AW", - "itemFilter.value(23)": "AX", - "itemFilter.value(24)": "AY", - "itemFilter.value(25)": "AZ", - }, - Err: ebay.ErrMaxLocatedIns, - }, - { - Name: "can find items if params contains LotsOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "LotsOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains LotsOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "LotsOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains LotsOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "LotsOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains MaxBids itemFilter with max 0", - Params: map[string]string{ - "itemFilter.name": "MaxBids", - "itemFilter.value": "0", - }, - }, - { - Name: "can find items if params contains MaxBids itemFilter with max 5", - Params: map[string]string{ - "itemFilter.name": "MaxBids", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains MaxBids itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "MaxBids", - "itemFilter.value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MaxBids itemFilter with max -1", - Params: map[string]string{ - "itemFilter.name": "MaxBids", - "itemFilter.value": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "can find items if params contains MinBids itemFilter with max 0", - Params: map[string]string{ - "itemFilter.name": "MinBids", - "itemFilter.value": "0", - }, - }, - { - Name: "can find items if params contains MinBids itemFilter with max 5", - Params: map[string]string{ - "itemFilter.name": "MinBids", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains MinBids itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "MinBids", - "itemFilter.value": "not a minimum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinBids itemFilter with max -1", - Params: map[string]string{ - "itemFilter.name": "MinBids", - "itemFilter.value": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "can find items if params contains MinBids/MaxBids itemFilters with max 1 and min 0", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "0", - }, - }, - { - Name: "can find items if params contains MinBids/MaxBids itemFilters with min 0 and max 1", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "1", - }, - }, - { - Name: "can find items if params contains MinBids/MaxBids itemFilters with max and min 0", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "0", - }, - }, - { - Name: "can find items if params contains MinBids/MaxBids itemFilters with min and max 0", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "0", - }, - }, - { - Name: "can find items if params contains MinBids/MaxBids itemFilters with max 10 and min 5", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "10", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "5", - }, - }, - { - Name: "can find items if params contains MinBids/MaxBids itemFilters with min 5 and max 10", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "5", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "10", - }, - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with max 0 and unparsable min", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "not a minimum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with unparsable min and max 0", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "not a minimum", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with max 0 and min -1", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "-1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with min -1 and max 0", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "-1", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with max 0 and min 1", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "1", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxBids", "MinBids"), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with min 1 and max 0", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxBids", "MinBids"), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with max 5 and min 10", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "5", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "10", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxBids", "MinBids"), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with min 10 and max 5", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "10", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "5", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxBids", "MinBids"), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with unparsable max and min 0", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "not a maximum", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with min 0 and unparsable max", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with max -1 and min 0", - Params: map[string]string{ - "itemFilter(0).name": "MaxBids", - "itemFilter(0).value": "-1", - "itemFilter(1).name": "MinBids", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "-1", 0), - }, - { - Name: "returns error if params contains MinBids/MaxBids itemFilters with min 0 and max -1", - Params: map[string]string{ - "itemFilter(0).name": "MinBids", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MaxBids", - "itemFilter(1).value": "-1", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxBids", "MinBids"), - }, - { - Name: "can find items if params contains MaxDistance itemFilter with max 5 and buyerPostalCode", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter.name": "MaxDistance", - "itemFilter.value": "5", - }, - }, - { - Name: "can find items if params contains MaxDistance itemFilter with max 6 and buyerPostalCode", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter.name": "MaxDistance", - "itemFilter.value": "6", - }, - }, - { - Name: "returns error if params contains MaxDistance itemFilter with unparsable max", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter.name": "MaxDistance", - "itemFilter.value": "not a maximum", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "not a maximum", 5), - }, - { - Name: "returns error if params contains MaxDistance itemFilter with max 4 and buyerPostalCode", - Params: map[string]string{ - "buyerPostalCode": "123", - "itemFilter.name": "MaxDistance", - "itemFilter.value": "4", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "4", 5), - }, - { - Name: "returns error if params contains MaxDistance itemFilter with max 5 but no buyerPostalCode", - Params: map[string]string{ - "itemFilter.name": "MaxDistance", - "itemFilter.value": "5", - }, - Err: ebay.ErrBuyerPostalCodeMissing, - }, - { - Name: "can find items if params contains MaxHandlingTime itemFilter with max 1", - Params: map[string]string{ - "itemFilter.name": "MaxHandlingTime", - "itemFilter.value": "1", - }, - }, - { - Name: "can find items if params contains MaxHandlingTime itemFilter with max 5", - Params: map[string]string{ - "itemFilter.name": "MaxHandlingTime", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains MaxHandlingTime itemFilter with max 0", - Params: map[string]string{ - "itemFilter.name": "MaxHandlingTime", - "itemFilter.value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "0", 1), - }, - { - Name: "returns error if params contains MaxHandlingTime itemFilter with unparsable max", - Params: map[string]string{ - "itemFilter.name": "MaxHandlingTime", - "itemFilter.value": "not a maximum", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "not a maximum", 1), - }, - { - Name: "can find items if params contains MaxPrice itemFilter with max 0.0", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "0.0", - }, - }, - { - Name: "can find items if params contains MaxPrice itemFilter with max 5.0", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "5.0", - }, - }, - { - Name: "can find items if params contains MaxPrice itemFilter with max 0.0, paramName Currency, and paramValue EUR", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "0.0", - "itemFilter.paramName": "Currency", - "itemFilter.paramValue": "EUR", - }, - }, - { - Name: "returns error if params contains MaxPrice itemFilter with unparsable max", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", - ebay.ErrInvalidPrice, `strconv.ParseFloat: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MaxPrice itemFilter with max -1.0", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "-1.0", - }, - Err: fmt.Errorf("%w: %f (minimum value: %f)", ebay.ErrInvalidPrice, -1.0, 0.0), - }, - { - Name: "returns error if params contains MaxPrice itemFilter with max 0.0, paramName NotCurrency, and paramValue EUR", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "0.0", - "itemFilter.paramName": "NotCurrency", - "itemFilter.paramValue": "EUR", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidPriceParamName, "NotCurrency"), - }, - { - Name: "returns error if params contains MaxPrice itemFilter with max 0.0, paramName Currency, and paramValue ZZZ", - Params: map[string]string{ - "itemFilter.name": "MaxPrice", - "itemFilter.value": "0.0", - "itemFilter.paramName": "Currency", - "itemFilter.paramValue": "ZZZ", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCurrencyID, "ZZZ"), - }, - { - Name: "can find items if params contains MinPrice itemFilter with max 0.0", - Params: map[string]string{ - "itemFilter.name": "MinPrice", - "itemFilter.value": "0.0", - }, - }, - { - Name: "can find items if params contains MinPrice itemFilter with max 5.0", - Params: map[string]string{ - "itemFilter.name": "MinPrice", - "itemFilter.value": "5.0", - }, - }, - { - Name: "can find items if params contains MinPrice itemFilter with max 0.0, paramName Currency, and paramValue EUR", - Params: map[string]string{ - "itemFilter.name": "MinPrice", - "itemFilter.value": "0.0", - "itemFilter.paramName": "Currency", - "itemFilter.paramValue": "EUR", - }, - }, - { - Name: "returns error if params contains MinPrice itemFilter with unparsable max", - Params: map[string]string{ - "itemFilter.name": "MinPrice", - "itemFilter.value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", - ebay.ErrInvalidPrice, `strconv.ParseFloat: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinPrice itemFilter with max -1.0", - Params: map[string]string{ - "itemFilter.name": "MinPrice", - "itemFilter.value": "-1.0", - }, - Err: fmt.Errorf("%w: %f (minimum value: %f)", ebay.ErrInvalidPrice, -1.0, 0.0), - }, - { - Name: "returns error if params contains MinPrice itemFilter with max 0.0, paramName NotCurrency, and paramValue EUR", - Params: map[string]string{ - "itemFilter.name": "MinPrice", - "itemFilter.value": "0.0", - "itemFilter.paramName": "NotCurrency", - "itemFilter.paramValue": "EUR", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidPriceParamName, "NotCurrency"), - }, - { - Name: "returns error if params contains MinPrice itemFilter with max 0.0, paramName Currency, and paramValue ZZZ", - Params: map[string]string{ - "itemFilter.name": "MinPrice", - "itemFilter.value": "0.0", - "itemFilter.paramName": "Currency", - "itemFilter.paramValue": "ZZZ", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCurrencyID, "ZZZ"), - }, - { - Name: "can find items if params contains MinPrice/MaxPrice itemFilters with max 1.0 and min 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "1.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "0.0", - }, - }, - { - Name: "can find items if params contains MinPrice/MaxPrice itemFilters with min 0.0 and max 1.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "1.0", - }, - }, - { - Name: "can find items if params contains MinPrice/MaxPrice itemFilters with max and min 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "0.0", - }, - }, - { - Name: "can find items if params contains MinPrice/MaxPrice itemFilters with min and max 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "0.0", - }, - }, - { - Name: "can find items if params contains MinPrice/MaxPrice itemFilters with max 10.0 and min 5.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "10.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "5.0", - }, - }, - { - Name: "can find items if params contains MinPrice/MaxPrice itemFilters with min 5.0 and max 10.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "5.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "10.0", - }, - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with max 0.0 and unparsable min", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "not a minimum", - }, - Err: fmt.Errorf("%w: %s: %w", - ebay.ErrInvalidPrice, `strconv.ParseFloat: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with unparsable min and max 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "not a minimum", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "0.0", - }, - Err: fmt.Errorf("%w: %s: %w", - ebay.ErrInvalidPrice, `strconv.ParseFloat: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with max 0.0 and min -1.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "-1.0", - }, - Err: fmt.Errorf("%w: %f (minimum value: %f)", ebay.ErrInvalidPrice, -1.0, 0.0), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with min -1.0 and max 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "-1.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "0.0", - }, - Err: fmt.Errorf("%w: %f (minimum value: %f)", ebay.ErrInvalidPrice, -1.0, 0.0), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with max 0.0 and min 1.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "1.0", - }, - Err: ebay.ErrInvalidMaxPrice, - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with min 1.0 and max 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "1.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "0.0", - }, - Err: ebay.ErrInvalidMaxPrice, - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with max 5.0 and min 10.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "5.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "10.0", - }, - Err: ebay.ErrInvalidMaxPrice, - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with min 10.0 and max 5.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "10.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "5.0", - }, - Err: ebay.ErrInvalidMaxPrice, - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with unparsable max and min 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "not a maximum", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "0.0", - }, - Err: fmt.Errorf("%w: %s: %w", - ebay.ErrInvalidPrice, `strconv.ParseFloat: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with min 0.0 and unparsable max", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", - ebay.ErrInvalidPrice, `strconv.ParseFloat: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with max -1.0 and min 0.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "-1.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "0.0", - }, - Err: fmt.Errorf("%w: %f (minimum value: %f)", ebay.ErrInvalidPrice, -1.0, 0.0), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with min 0.0 and max -1.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "0.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "-1.0", - }, - Err: fmt.Errorf("%w: %f (minimum value: %f)", ebay.ErrInvalidPrice, -1.0, 0.0), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with max 10.0 and min 5.0, paramName Invalid", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "10.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "5.0", - "itemFilter(1).paramName": "Invalid", - "itemFilter(1).paramValue": "EUR", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidPriceParamName, "Invalid"), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with min 5.0, paramName Invalid and max 10.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "5.0", - "itemFilter(0).paramName": "Invalid", - "itemFilter(0).paramValue": "EUR", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "10.0", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidPriceParamName, "Invalid"), - }, - { - Name: "returns error if params contains MinPrice itemFilter with max 10.0 and min 5.0, paramValue ZZZ", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "10.0", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "5.0", - "itemFilter(1).paramName": "Currency", - "itemFilter(1).paramValue": "ZZZ", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCurrencyID, "ZZZ"), - }, - { - Name: "returns error if params contains MinPrice itemFilter with min 5.0, paramValue ZZZ and max 10.0", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "5.0", - "itemFilter(0).paramName": "Currency", - "itemFilter(0).paramValue": "ZZZ", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "10.0", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCurrencyID, "ZZZ"), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with max 10.0, paramName Invalid and min 5.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "10.0", - "itemFilter(0).paramName": "Invalid", - "itemFilter(0).paramValue": "EUR", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "5.0", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidPriceParamName, "Invalid"), - }, - { - Name: "returns error if params contains MinPrice/MaxPrice itemFilters with min 5.0 and max 10.0, paramName Invalid", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "5.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "10.0", - "itemFilter(1).paramName": "Invalid", - "itemFilter(1).paramValue": "EUR", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidPriceParamName, "Invalid"), - }, - { - Name: "returns error if params contains MinPrice itemFilter with max 10.0, paramValue ZZZ and min 5.0", - Params: map[string]string{ - "itemFilter(0).name": "MaxPrice", - "itemFilter(0).value": "10.0", - "itemFilter(0).paramName": "Currency", - "itemFilter(0).paramValue": "ZZZ", - "itemFilter(1).name": "MinPrice", - "itemFilter(1).value": "5.0", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCurrencyID, "ZZZ"), - }, - { - Name: "returns error if params contains MinPrice itemFilter with min 5.0 and max 10.0, paramValue ZZZ", - Params: map[string]string{ - "itemFilter(0).name": "MinPrice", - "itemFilter(0).value": "5.0", - "itemFilter(1).name": "MaxPrice", - "itemFilter(1).value": "10.0", - "itemFilter(1).paramName": "Currency", - "itemFilter(1).paramValue": "ZZZ", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidCurrencyID, "ZZZ"), - }, - { - Name: "can find items if params contains MaxQuantity itemFilter with max 1", - Params: map[string]string{ - "itemFilter.name": "MaxQuantity", - "itemFilter.value": "1", - }, - }, - { - Name: "can find items if params contains MaxQuantity itemFilter with max 5", - Params: map[string]string{ - "itemFilter.name": "MaxQuantity", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains MaxQuantity itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "MaxQuantity", - "itemFilter.value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MaxQuantity itemFilter with max 0", - Params: map[string]string{ - "itemFilter.name": "MaxQuantity", - "itemFilter.value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "0", 1), - }, - { - Name: "can find items if params contains MinQuantity itemFilter with max 1", - Params: map[string]string{ - "itemFilter.name": "MinQuantity", - "itemFilter.value": "1", - }, - }, - { - Name: "can find items if params contains MinQuantity itemFilter with max 5", - Params: map[string]string{ - "itemFilter.name": "MinQuantity", - "itemFilter.value": "5", - }, - }, - { - Name: "returns error if params contains MinQuantity itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "MinQuantity", - "itemFilter.value": "not a minimum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinQuantity itemFilter with max 0", - Params: map[string]string{ - "itemFilter.name": "MinQuantity", - "itemFilter.value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "0", 1), - }, - { - Name: "can find items if params contains MinQuantity/MaxQuantity itemFilters with max 2 and min 1", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "2", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "1", - }, - }, - { - Name: "can find items if params contains MinQuantity/MaxQuantity itemFilters with min 1 and max 2", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "2", - }, - }, - { - Name: "can find items if params contains MinQuantity/MaxQuantity itemFilters with max and min 1", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "1", - }, - }, - { - Name: "can find items if params contains MinQuantity/MaxQuantity itemFilters with min and max 1", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "1", - }, - }, - { - Name: "can find items if params contains MinQuantity/MaxQuantity itemFilters with max 10 and min 5", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "10", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "5", - }, - }, - { - Name: "can find items if params contains MinQuantity/MaxQuantity itemFilters with min 5 and max 10", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "5", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "10", - }, - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with max 1 and unparsable min", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "not a minimum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with unparsable min and max 1", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "not a minimum", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "1", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a minimum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with max 1 and min 0", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "0", 1), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with min 0 and max 1", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "0", 1), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with max 1 and min 2", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "2", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxQuantity", "MinQuantity"), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with min 2 and max 1", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "2", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "1", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxQuantity", "MinQuantity"), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with max 5 and min 10", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "5", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "10", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxQuantity", "MinQuantity"), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with min 10 and max 5", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "10", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "5", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxQuantity", "MinQuantity"), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with unparsable max and min 1", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "not a maximum", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "1", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with min 1 and unparsable max", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "not a maximum", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidInteger, `strconv.Atoi: parsing "not a maximum"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with max 0 and min 1", - Params: map[string]string{ - "itemFilter(0).name": "MaxQuantity", - "itemFilter(0).value": "0", - "itemFilter(1).name": "MinQuantity", - "itemFilter(1).value": "1", - }, - Err: fmt.Errorf("%w: %q (minimum value: %d)", ebay.ErrInvalidInteger, "0", 1), - }, - { - Name: "returns error if params contains MinQuantity/MaxQuantity itemFilters with min 1 and max 0", - Params: map[string]string{ - "itemFilter(0).name": "MinQuantity", - "itemFilter(0).value": "1", - "itemFilter(1).name": "MaxQuantity", - "itemFilter(1).value": "0", - }, - Err: fmt.Errorf("%w: %q must be greater than or equal to %q", - ebay.ErrInvalidNumericFilter, "MaxQuantity", "MinQuantity"), - }, - { - Name: "can find items if params contains ModTimeFrom itemFilter with past timestamp", - Params: map[string]string{ - "itemFilter.name": "ModTimeFrom", - "itemFilter.value": time.Now().Add(-1 * time.Second).UTC().Format(time.RFC3339), - }, - }, - { - Name: "returns error if params contains ModTimeFrom itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "ModTimeFrom", - "itemFilter.value": "not a timestamp", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidDateTime, "not a timestamp"), - }, - { - Name: "returns error if params contains ModTimeFrom itemFilter with non-UTC timestamp", - Params: map[string]string{ - "itemFilter.name": "ModTimeFrom", - "itemFilter.value": time.Now().Add(1 * time.Second).In(easternTime).Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(1*time.Second).In(easternTime).Format(time.RFC3339)), - }, - { - Name: "returns error if params contains ModTimeFrom itemFilter with future timestamp", - Params: map[string]string{ - "itemFilter.name": "ModTimeFrom", - "itemFilter.value": time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(5*time.Second).UTC().Format(time.RFC3339)), - }, - { - Name: "can find items if params contains ReturnsAcceptedOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "ReturnsAcceptedOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains ReturnsAcceptedOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "ReturnsAcceptedOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains ReturnsAcceptedOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "ReturnsAcceptedOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains Seller itemFilter with seller ID 0", - Params: map[string]string{ - "itemFilter.name": "Seller", - "itemFilter.value": "0", - }, - }, - { - Name: "can find items if params contains Seller itemFilter with seller IDs 0 and 1", - Params: map[string]string{ - "itemFilter.name": "Seller", - "itemFilter.value(0)": "0", - "itemFilter.value(1)": "1", - }, - }, - { - Name: "returns error if params contains Seller and ExcludeSeller itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "Seller", - "itemFilter(0).value": "0", - "itemFilter(1).name": "ExcludeSeller", - "itemFilter(1).value": "0", - }, - Err: ebay.ErrSellerCannotBeUsedWithOtherSellers, - }, - { - Name: "returns error if params contains Seller and TopRatedSellerOnly itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "Seller", - "itemFilter(0).value": "0", - "itemFilter(1).name": "TopRatedSellerOnly", - "itemFilter(1).value": "true", - }, - Err: ebay.ErrSellerCannotBeUsedWithOtherSellers, - }, - { - Name: "can find items if params contains Seller itemFilter with 100 seller IDs", - Params: generateFilterParams("Seller", 100), - }, - { - Name: "returns error if params contains Seller itemFilter with 101 seller IDs", - Params: generateFilterParams("Seller", 101), - Err: ebay.ErrMaxSellers, - }, - { - Name: "can find items if params contains SellerBusinessType itemFilter with Business type", - Params: map[string]string{ - "itemFilter.name": "SellerBusinessType", - "itemFilter.value": "Business", - }, - }, - { - Name: "can find items if params contains SellerBusinessType itemFilter with Private type", - Params: map[string]string{ - "itemFilter.name": "SellerBusinessType", - "itemFilter.value": "Private", - }, - }, - { - Name: "returns error if params contains SellerBusinessType itemFilter with NotBusiness type", - Params: map[string]string{ - "itemFilter.name": "SellerBusinessType", - "itemFilter.value": "NotBusiness", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidSellerBusinessType, "NotBusiness"), - }, - { - Name: "returns error if params contains SellerBusinessType itemFilter with Business and Private types", - Params: map[string]string{ - "itemFilter.name": "SellerBusinessType", - "itemFilter.value(0)": "Business", - "itemFilter.value(1)": "Private", - }, - Err: ebay.ErrMultipleSellerBusinessTypes, - }, - { - Name: "can find items if params contains SoldItemsOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "SoldItemsOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains SoldItemsOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "SoldItemsOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains SoldItemsOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "SoldItemsOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contains StartTimeFrom itemFilter with future timestamp", - Params: map[string]string{ - "itemFilter.name": "StartTimeFrom", - "itemFilter.value": time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339), - }, - }, - { - Name: "returns error if params contains StartTimeFrom itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "StartTimeFrom", - "itemFilter.value": "not a timestamp", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidDateTime, "not a timestamp"), - }, - { - Name: "returns error if params contains StartTimeFrom itemFilter with non-UTC timestamp", - Params: map[string]string{ - "itemFilter.name": "StartTimeFrom", - "itemFilter.value": time.Now().Add(1 * time.Second).In(easternTime).Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(1*time.Second).In(easternTime).Format(time.RFC3339)), - }, - { - Name: "returns error if params contains StartTimeFrom itemFilter with past timestamp", - Params: map[string]string{ - "itemFilter.name": "StartTimeFrom", - "itemFilter.value": time.Now().Add(-1 * time.Second).UTC().Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(-1*time.Second).UTC().Format(time.RFC3339)), - }, - { - Name: "can find items if params contains StartTimeTo itemFilter with future timestamp", - Params: map[string]string{ - "itemFilter.name": "StartTimeTo", - "itemFilter.value": time.Now().Add(5 * time.Second).UTC().Format(time.RFC3339), - }, - }, - { - Name: "returns error if params contains StartTimeTo itemFilter with unparsable value", - Params: map[string]string{ - "itemFilter.name": "StartTimeTo", - "itemFilter.value": "not a timestamp", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidDateTime, "not a timestamp"), - }, - { - Name: "returns error if params contains StartTimeTo itemFilter with non-UTC timestamp", - Params: map[string]string{ - "itemFilter.name": "StartTimeTo", - "itemFilter.value": time.Now().Add(1 * time.Second).In(easternTime).Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(1*time.Second).In(easternTime).Format(time.RFC3339)), - }, - { - Name: "returns error if params contains StartTimeTo itemFilter with past timestamp", - Params: map[string]string{ - "itemFilter.name": "StartTimeTo", - "itemFilter.value": time.Now().Add(-1 * time.Second).UTC().Format(time.RFC3339), - }, - Err: fmt.Errorf("%w: %q", - ebay.ErrInvalidDateTime, time.Now().Add(-1*time.Second).UTC().Format(time.RFC3339)), - }, - { - Name: "can find items if params contains TopRatedSellerOnly itemFilter.value=true", - Params: map[string]string{ - "itemFilter.name": "TopRatedSellerOnly", - "itemFilter.value": "true", - }, - }, - { - Name: "can find items if params contains TopRatedSellerOnly itemFilter.value=false", - Params: map[string]string{ - "itemFilter.name": "TopRatedSellerOnly", - "itemFilter.value": "false", - }, - }, - { - Name: "returns error if params contains TopRatedSellerOnly itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "TopRatedSellerOnly", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "returns error if params contains TopRatedSellerOnly and Seller itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "TopRatedSellerOnly", - "itemFilter(0).value": "true", - "itemFilter(1).name": "Seller", - "itemFilter(1).value": "0", - }, - Err: ebay.ErrTopRatedSellerCannotBeUsedWithSellers, - }, - { - Name: "returns error if params contains TopRatedSellerOnly and ExcludeSeller itemFilters", - Params: map[string]string{ - "itemFilter(0).name": "TopRatedSellerOnly", - "itemFilter(0).value": "true", - "itemFilter(1).name": "ExcludeSeller", - "itemFilter(1).value": "0", - }, - Err: ebay.ErrTopRatedSellerCannotBeUsedWithSellers, - }, - { - Name: "can find items if params contains ValueBoxInventory itemFilter.value=1", - Params: map[string]string{ - "itemFilter.name": "ValueBoxInventory", - "itemFilter.value": "1", - }, - }, - { - Name: "can find items if params contains ValueBoxInventory itemFilter.value=0", - Params: map[string]string{ - "itemFilter.name": "ValueBoxInventory", - "itemFilter.value": "0", - }, - }, - { - Name: "returns error if params contains ValueBoxInventory itemFilter with non-boolean value", - Params: map[string]string{ - "itemFilter.name": "ValueBoxInventory", - "itemFilter.value": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidValueBoxInventory, "123"), - }, - { - Name: "can find items if params contains AspectHistogram outputSelector", - Params: map[string]string{"outputSelector": "AspectHistogram"}, - }, - { - Name: "can find items if params contains CategoryHistogram outputSelector", - Params: map[string]string{"outputSelector": "CategoryHistogram"}, - }, - { - Name: "can find items if params contains ConditionHistogram outputSelector", - Params: map[string]string{"outputSelector": "ConditionHistogram"}, - }, - { - Name: "can find items if params contains GalleryInfo outputSelector", - Params: map[string]string{"outputSelector": "GalleryInfo"}, - }, - { - Name: "can find items if params contains PictureURLLarge outputSelector", - Params: map[string]string{"outputSelector": "PictureURLLarge"}, - }, - { - Name: "can find items if params contains PictureURLSuperSize outputSelector", - Params: map[string]string{"outputSelector": "PictureURLSuperSize"}, - }, - { - Name: "can find items if params contains SellerInfo outputSelector", - Params: map[string]string{"outputSelector": "SellerInfo"}, - }, - { - Name: "can find items if params contains StoreInfo outputSelector", - Params: map[string]string{"outputSelector": "StoreInfo"}, - }, - { - Name: "can find items if params contains UnitPriceInfo outputSelector", - Params: map[string]string{"outputSelector": "UnitPriceInfo"}, - }, - { - Name: "returns error if params contains non-numbered, unsupported outputSelector name", - Params: map[string]string{"outputSelector": "UnsupportedOutputSelector"}, - Err: ebay.ErrInvalidOutputSelector, - }, - { - // outputSelector(1) will be ignored because indexing does not start at 0. - Name: "can find items if params contains outputSelector, outputSelector(1)", - Params: map[string]string{ - "outputSelector": "AspectHistogram", - "outputSelector(1)": "CategoryHistogram", - }, - }, - { - Name: "returns error if params contain numbered and non-numbered outputSelector syntax types", - Params: map[string]string{ - "outputSelector": "AspectHistogram", - "outputSelector(0)": "CategoryHistogram", - }, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "can find items by numbered outputSelector", - Params: map[string]string{"outputSelector(0)": "AspectHistogram"}, - }, - { - Name: "can find items by 2 numbered outputSelector", - Params: map[string]string{ - "outputSelector(0)": "AspectHistogram", - "outputSelector(1)": "CategoryHistogram", - }, - }, - { - Name: "returns error if params contains numbered, unsupported outputSelector name", - Params: map[string]string{"outputSelector(0)": "UnsupportedOutputSelector"}, - Err: ebay.ErrInvalidOutputSelector, - }, - { - Name: "returns error if params contains 1 supported, 1 unsupported outputSelector name", - Params: map[string]string{ - "outputSelector(0)": "AspectHistogram", - "outputSelector(1)": "UnsupportedOutputSelector", - }, - Err: ebay.ErrInvalidOutputSelector, - }, - { - Name: "can find items if params contains affiliate.customId=1", - Params: map[string]string{"affiliate.customId": "1"}, - }, - { - Name: "can find items if params contains affiliate.customId of length 256", - Params: map[string]string{"affiliate.customId": generateStringWithLen(256, false)}, - }, - { - Name: "returns error if params contains affiliate.customId of length 257", - Params: map[string]string{"affiliate.customId": generateStringWithLen(257, false)}, - Err: ebay.ErrInvalidCustomIDLength, - }, - { - Name: "can find items if params contains affiliate.geoTargeting=true", - Params: map[string]string{"affiliate.geoTargeting": "true"}, - }, - { - Name: "can find items if params contains affiliate.geoTargeting=false", - Params: map[string]string{"affiliate.geoTargeting": "false"}, - }, - { - Name: "returns error if params contains affiliate.geoTargeting with non-boolean value", - Params: map[string]string{"affiliate.geoTargeting": "123"}, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "can find items if params contain affiliate.networkId=2 and trackingId", - Params: map[string]string{ - "affiliate.networkId": "2", - "affiliate.trackingId": "1", - }, - }, - { - Name: "can find items if params contain affiliate.networkId=9 and trackingId=1234567890", - Params: map[string]string{ - "affiliate.networkId": "9", - "affiliate.trackingId": "1234567890", - }, - }, - { - Name: "can find items if params contain affiliate.networkId=5 and trackingId=veryunique", - Params: map[string]string{ - "affiliate.networkId": "5", - "affiliate.trackingId": "veryunique", - }, - }, - { - Name: "returns error if params contains affiliate.networkId but no trackingId", - Params: map[string]string{"affiliate.networkId": "2"}, - Err: ebay.ErrIncompleteAffiliateParams, - }, - { - Name: "returns error if params contains affiliate.trackingId but no networkId", - Params: map[string]string{"affiliate.trackingId": "1"}, - Err: ebay.ErrIncompleteAffiliateParams, - }, - { - Name: "returns error if params contain affiliate.networkId=abc and trackingId", - Params: map[string]string{ - "affiliate.networkId": "abc", - "affiliate.trackingId": "1", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidNetworkID, `strconv.Atoi: parsing "abc"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contain affiliate.networkId=1 and trackingId", - Params: map[string]string{ - "affiliate.networkId": "1", - "affiliate.trackingId": "1", - }, - Err: ebay.ErrInvalidNetworkIDRange, - }, - { - Name: "returns error if params contain affiliate.networkId=10 and trackingId", - Params: map[string]string{ - "affiliate.networkId": "10", - "affiliate.trackingId": "1", - }, - Err: ebay.ErrInvalidNetworkIDRange, - }, - { - Name: "returns error if params contain affiliate.networkId=9 and trackingId=abc", - Params: map[string]string{ - "affiliate.networkId": "9", - "affiliate.trackingId": "abc", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidTrackingID, `strconv.Atoi: parsing "abc"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contain affiliate.networkId=9 and trackingId=123456789", - Params: map[string]string{ - "affiliate.networkId": "9", - "affiliate.trackingId": "123456789", - }, - Err: ebay.ErrInvalidCampaignID, - }, - { - Name: "can find items if params contain affiliate.customId, geoTargeting, networkId, trackingId", - Params: map[string]string{ - "affiliate.customId": "abc123", - "affiliate.geoTargeting": "true", - "affiliate.networkId": "2", - "affiliate.trackingId": "123abc", - }, - }, - { - Name: "can find items if params contains buyerPostalCode=111", - Params: map[string]string{"buyerPostalCode": "111"}, - }, - { - Name: "can find items if params contains buyerPostalCode=aaaaa", - Params: map[string]string{"buyerPostalCode": "aaaaa"}, - }, - { - Name: "can find items if params contains buyerPostalCode=Postal Code Here", - Params: map[string]string{"buyerPostalCode": "Postal Code Here"}, - }, - { - Name: "returns error if params contains buyerPostalCode=11", - Params: map[string]string{"buyerPostalCode": "11"}, - Err: ebay.ErrInvalidPostalCode, - }, - { - Name: "can find items if params contains paginationInput.entriesPerPage=1", - Params: map[string]string{"paginationInput.entriesPerPage": "1"}, - }, - { - Name: "can find items if params contains paginationInput.entriesPerPage=50", - Params: map[string]string{"paginationInput.entriesPerPage": "50"}, - }, - { - Name: "can find items if params contains paginationInput.entriesPerPage=100", - Params: map[string]string{"paginationInput.entriesPerPage": "100"}, - }, - { - Name: "returns error if params contains paginationInput.entriesPerPage=a", - Params: map[string]string{"paginationInput.entriesPerPage": "a"}, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidEntriesPerPage, `strconv.Atoi: parsing "a"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains paginationInput.entriesPerPage=0", - Params: map[string]string{"paginationInput.entriesPerPage": "0"}, - Err: ebay.ErrInvalidEntriesPerPageRange, - }, - { - Name: "returns error if params contains paginationInput.entriesPerPage=101", - Params: map[string]string{"paginationInput.entriesPerPage": "101"}, - Err: ebay.ErrInvalidEntriesPerPageRange, - }, - { - Name: "can find items if params contains paginationInput.pageNumber=1", - Params: map[string]string{"paginationInput.pageNumber": "1"}, - }, - { - Name: "can find items if params contains paginationInput.pageNumber=50", - Params: map[string]string{"paginationInput.pageNumber": "50"}, - }, - { - Name: "can find items if params contains paginationInput.pageNumber=100", - Params: map[string]string{"paginationInput.pageNumber": "100"}, - }, - { - Name: "returns error if params contains paginationInput.pageNumber=a", - Params: map[string]string{"paginationInput.pageNumber": "a"}, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidPageNumber, `strconv.Atoi: parsing "a"`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains paginationInput.pageNumber=0", - Params: map[string]string{"paginationInput.pageNumber": "0"}, - Err: ebay.ErrInvalidPageNumberRange, - }, - { - Name: "returns error if params contains paginationInput.pageNumber=101", - Params: map[string]string{"paginationInput.pageNumber": "101"}, - Err: ebay.ErrInvalidPageNumberRange, - }, - { - Name: "can find items if params contains paginationInput.entriesPerPage=1, paginationInput.pageNumber=1", - Params: map[string]string{ - "paginationInput.entriesPerPage": "1", - "paginationInput.pageNumber": "1", - }, - }, - { - Name: "can find items if params contains paginationInput.entriesPerPage=100, paginationInput.pageNumber=100", - Params: map[string]string{ - "paginationInput.entriesPerPage": "100", - "paginationInput.pageNumber": "100", - }, - }, - { - Name: "returns if params contains paginationInput.entriesPerPage=0, paginationInput.pageNumber=1", - Params: map[string]string{ - "paginationInput.entriesPerPage": "0", - "paginationInput.pageNumber": "1", - }, - Err: ebay.ErrInvalidEntriesPerPageRange, - }, - { - Name: "returns if params contains paginationInput.entriesPerPage=101, paginationInput.pageNumber=1", - Params: map[string]string{ - "paginationInput.entriesPerPage": "101", - "paginationInput.pageNumber": "1", - }, - Err: ebay.ErrInvalidEntriesPerPageRange, - }, - { - Name: "returns if params contains paginationInput.entriesPerPage=1, paginationInput.pageNumber=0", - Params: map[string]string{ - "paginationInput.entriesPerPage": "1", - "paginationInput.pageNumber": "0", - }, - Err: ebay.ErrInvalidPageNumberRange, - }, - { - Name: "returns if params contains paginationInput.entriesPerPage=1, paginationInput.pageNumber=101", - Params: map[string]string{ - "paginationInput.entriesPerPage": "1", - "paginationInput.pageNumber": "101", - }, - Err: ebay.ErrInvalidPageNumberRange, - }, - { - Name: "returns if params contains paginationInput.entriesPerPage=0, paginationInput.pageNumber=0", - Params: map[string]string{ - "paginationInput.entriesPerPage": "0", - "paginationInput.pageNumber": "0", - }, - Err: ebay.ErrInvalidEntriesPerPageRange, - }, - { - Name: "returns if params contains paginationInput.entriesPerPage=101, paginationInput.pageNumber=101", - Params: map[string]string{ - "paginationInput.entriesPerPage": "101", - "paginationInput.pageNumber": "101", - }, - Err: ebay.ErrInvalidEntriesPerPageRange, - }, - { - Name: "can find items if params contains BestMatch sortOrder", - Params: map[string]string{ - "sortOrder": "BestMatch", - }, - }, - { - Name: "can find items if params contains BidCountFewest sortOrder and Auction listing type", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "Auction", - "sortOrder": "BidCountFewest", - }, - }, - { - Name: "returns error if params contains BidCountFewest sortOrder but no Auction listing type", - Params: map[string]string{"sortOrder": "BidCountFewest"}, - Err: ebay.ErrAuctionListingMissing, - }, - { - Name: "can find items if params contains BidCountMost sortOrder and Auction listing type", - Params: map[string]string{ - "itemFilter.name": "ListingType", - "itemFilter.value": "Auction", - "sortOrder": "BidCountMost", - }, - }, - { - Name: "returns error if params contains BidCountMost sortOrder but no Auction listing type", - Params: map[string]string{"sortOrder": "BidCountMost"}, - Err: ebay.ErrAuctionListingMissing, - }, - { - Name: "can find items if params contains CountryAscending sortOrder", - Params: map[string]string{"sortOrder": "CountryAscending"}, - }, - { - Name: "can find items if params contains CountryDescending sortOrder", - Params: map[string]string{"sortOrder": "CountryDescending"}, - }, - { - Name: "can find items if params contains CurrentPriceHighest sortOrder", - Params: map[string]string{"sortOrder": "CurrentPriceHighest"}, - }, - { - Name: "can find items if params contains DistanceNearest sortOrder and buyerPostalCode", - Params: map[string]string{ - "buyerPostalCode": "111", - "sortOrder": "DistanceNearest", - }, - }, - { - Name: "returns error if params contains DistanceNearest sortOrder but no buyerPostalCode", - Params: map[string]string{"sortOrder": "DistanceNearest"}, - Err: ebay.ErrBuyerPostalCodeMissing, - }, - { - Name: "can find items if params contains EndTimeSoonest sortOrder", - Params: map[string]string{"sortOrder": "EndTimeSoonest"}, - }, - { - Name: "can find items if params contains PricePlusShippingHighest sortOrder", - Params: map[string]string{"sortOrder": "PricePlusShippingHighest"}, - }, - { - Name: "can find items if params contains PricePlusShippingLowest sortOrder", - Params: map[string]string{"sortOrder": "PricePlusShippingLowest"}, - }, - { - Name: "can find items if params contains StartTimeNewest sortOrder", - Params: map[string]string{"sortOrder": "StartTimeNewest"}, - }, - { - Name: "can find items if params contains WatchCountDecreaseSort sortOrder", - Params: map[string]string{"sortOrder": "WatchCountDecreaseSort"}, - }, - { - Name: "returns error if params contains unsupported sortOrder name", - Params: map[string]string{"sortOrder": "UnsupportedSortOrder"}, - Err: ebay.ErrUnsupportedSortOrderType, - }, +func TestNewFindingClient(t *testing.T) { + t.Parallel() + client := http.DefaultClient + appID := "ebay-app-id" + got := NewFindingClient(client, appID) + want := &FindingClient{ + Client: client, + AppID: appID, + URL: findingURL, } - aspectFilterTestCases = []findItemsTestCase{ - { - Name: "can find items by aspectFilter.aspectName, aspectValueName", - Params: map[string]string{ - "aspectFilter.aspectName": "Size", - "aspectFilter.aspectValueName": "10", - }, - }, - { - Name: "can find items by aspectFilter.aspectName, aspectValueName(0), aspectValueName(1)", - Params: map[string]string{ - "aspectFilter.aspectName": "Size", - "aspectFilter.aspectValueName(0)": "10", - "aspectFilter.aspectValueName(1)": "11", - }, - }, - { - Name: "returns error if params contains aspectFilter.aspectName but not aspectValueName", - Params: map[string]string{"aspectFilter.aspectName": "Size"}, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "aspectFilter.aspectValueName"), - }, - { - // aspectFilter.aspectValueName(1) will be ignored because indexing does not start at 0. - Name: "returns error if params contains aspectFilter.aspectName, aspectValueName(1)", - Params: map[string]string{ - "aspectFilter.aspectName": "Size", - "aspectFilter.aspectValueName(1)": "10", - }, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "aspectFilter.aspectValueName"), - }, - { - // aspectFilter.aspectValueName(1) will be ignored because indexing does not start at 0. - // Therefore, only aspectFilter.aspectValueName is considered and this becomes a non-numbered aspectFilter. - Name: "can find items by aspectFilter.aspectName, aspectValueName, aspectValueName(1)", - Params: map[string]string{ - "aspectFilter.aspectName": "Size", - "aspectFilter.aspectValueName": "10", - "aspectFilter.aspectValueName(1)": "11", - }, - }, - { - // The aspectFilter will be ignored if no aspectFilter.aspectName param is found before other aspectFilter params. - Name: "can find items if params contains aspectFilter.aspectValueName only", - Params: map[string]string{"aspectFilter.aspectValueName": "10"}, - }, - { - Name: "returns error if params contain numbered and non-numbered aspectFilter syntax types", - Params: map[string]string{ - "aspectFilter.aspectName": "Size", - "aspectFilter.aspectValueName": "10", - "aspectFilter(0).aspectName": "Running", - "aspectFilter(0).aspectValueName": "true", - }, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "returns error if params contain aspectFilter.aspectName, aspectValueName, aspectValueName(0)", - Params: map[string]string{ - "aspectFilter.aspectName": "Size", - "aspectFilter.aspectValueName": "10", - "aspectFilter.aspectValueName(0)": "11", - }, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "returns error if params contain aspectFilter(0).aspectName, aspectValueName, aspectValueName(0)", - Params: map[string]string{ - "aspectFilter(0).aspectName": "Size", - "aspectFilter(0).aspectValueName": "10", - "aspectFilter(0).aspectValueName(0)": "11", - }, - Err: ebay.ErrInvalidIndexSyntax, - }, - { - Name: "can find items by aspectFilter(0).aspectName, aspectValueName", - Params: map[string]string{ - "aspectFilter(0).aspectName": "Size", - "aspectFilter(0).aspectValueName": "10", - }, - }, - { - Name: "can find items by aspectFilter(0).aspectName, aspectValueName(0), aspectValueName(1)", - Params: map[string]string{ - "aspectFilter(0).aspectName": "Size", - "aspectFilter(0).aspectValueName(0)": "10", - "aspectFilter(0).aspectValueName(1)": "11", - }, - }, - { - Name: "can find items by 2 numbered aspectFilters", - Params: map[string]string{ - "aspectFilter(0).aspectName": "Size", - "aspectFilter(0).aspectValueName": "10", - "aspectFilter(1).aspectName": "Running", - "aspectFilter(1).aspectValueName": "true", - }, - }, - { - Name: "returns error if params contains aspectFilter(0).aspectName but not aspectValueName", - Params: map[string]string{"aspectFilter(0).aspectName": "Size"}, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "aspectFilter(0).aspectValueName"), - }, - { - // aspectFilter(0).aspectValueName(1) will be ignored because indexing does not start at 0. - Name: "returns error if params contains aspectFilter(0).aspectName, aspectValueName(1)", - Params: map[string]string{ - "aspectFilter(0).aspectName": "Size", - "aspectFilter(0).aspectValueName(1)": "10", - }, - Err: fmt.Errorf("%w %q", ebay.ErrIncompleteFilterNameOnly, "aspectFilter(0).aspectValueName"), - }, - { - // aspectFilter(0).aspectValueName(1) will be ignored because indexing does not start at 0. - // Therefore, only aspectFilter(0).aspectValueName is considered and this becomes a numbered aspectFilter. - Name: "can find items by aspectFilter(0).aspectName, aspectValueName aspectValueName(1)", - Params: map[string]string{ - "aspectFilter(0).aspectName": "Size", - "aspectFilter(0).aspectValueName": "10", - "aspectFilter(0).aspectValueName(1)": "11", - }, - }, - { - // The aspectFilter will be ignored if no aspectFilter(0).aspectName param is found before other aspectFilter params. - Name: "can find items if params contains aspectFilter(0).aspectValueName only", - Params: map[string]string{"aspectFilter(0).aspectValueName": "10"}, - }, + if !reflect.DeepEqual(got, want) { + t.Errorf("NewFindingClient() = %v, want %v", got, want) } -) - -func TestFindItemsByCategories(t *testing.T) { - t.Parallel() - params := map[string]string{"categoryId": "12345"} - findItemsByCategoriesTCs := combineTestCases(t, findItemsByCategories, categoryIDTCs) - testFindItems(t, params, findItemsByCategories, findItemsByCategoriesResp, findItemsByCategoriesTCs) } -func TestFindItemsByKeywords(t *testing.T) { +func TestFindingClient_FindItemsAdvanced(t *testing.T) { t.Parallel() - params := map[string]string{"keywords": "marshmallows"} - findItemsByKeywordsTCs := combineTestCases(t, findItemsByKeywords, keywordsTCs) - testFindItems(t, params, findItemsByKeywords, findItemsByKeywordsResp, findItemsByKeywordsTCs) -} - -func TestFindItemsAdvanced(t *testing.T) { - t.Parallel() - params := map[string]string{"categoryId": "12345"} - findItemsAdvancedTCs := []findItemsTestCase{ - { - Name: "can find items if params contains descriptionSearch=true", - Params: map[string]string{ - "categoryId": "1", - "descriptionSearch": "true", - }, - }, - { - Name: "can find items if params contains descriptionSearch=false", - Params: map[string]string{ - "categoryId": "1", - "descriptionSearch": "false", - }, - }, - { - Name: "returns error if params contains descriptionSearch with non-boolean value", - Params: map[string]string{ - "categoryId": "1", - "descriptionSearch": "123", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrInvalidBooleanValue, "123"), - }, - { - Name: "returns error if params contains descriptionSearch but not categoryId or keywords", - Params: map[string]string{"descriptionSearch": "true"}, - Err: ebay.ErrCategoryIDKeywordsMissing, - }, - } - combinedTCs := combineTestCases( - t, findItemsAdvanced, categoryIDTCs, keywordsTCs, categoryIDKeywordsTCs, findItemsAdvancedTCs) - testFindItems(t, params, findItemsAdvanced, findItemsAdvancedResp, combinedTCs) -} + t.Run("ResponseSuccess", func(t *testing.T) { + t.Parallel() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(&FindItemsAdvancedResponse{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + })) + defer ts.Close() + client := NewFindingClient(ts.Client(), "ebay-app-id") + client.URL = ts.URL + params := map[string]string{"categoryId": "123", "keywords": "testword"} + got, err := client.FindItemsAdvanced(context.Background(), params) + if err != nil { + t.Errorf("FindingClient.FindItemsAdvanced() error = %v, want nil", err) + return + } + want := &FindItemsAdvancedResponse{} + if !reflect.DeepEqual(got, want) { + t.Errorf("FindingClient.FindItemsAdvanced() = %v, want %v", got, want) + } + }) -func TestFindItemsByProduct(t *testing.T) { - t.Parallel() - params := map[string]string{ - "productId.@type": "ReferenceID", - "productId": "123", - } - findItemsByProductTCs := []findItemsTestCase{ - { - Name: "returns error if params contains productId but not productId.@type", - Params: map[string]string{"productId": "123"}, - Err: ebay.ErrProductIDMissing, - }, - { - Name: "returns error if params contains productId.@type but not productId", - Params: map[string]string{"productId.@type": "ReferenceID"}, - Err: ebay.ErrProductIDMissing, - }, - { - Name: "returns error if params contains productId.@type=UnsupportedProductID, productId=1", - Params: map[string]string{ - "productId.@type": "UnsupportedProductID", - "productId": "1", - }, - Err: fmt.Errorf("%w: %q", ebay.ErrUnsupportedProductIDType, "UnsupportedProductID"), - }, - { - Name: "can find items if params contains productId.@type=ReferenceID, productId=1", - Params: map[string]string{ - "productId.@type": "ReferenceID", - "productId": "1", - }, - }, - { - Name: "can find items if params contains productId.@type=ReferenceID, productId=123", - Params: map[string]string{ - "productId.@type": "ReferenceID", - "productId": "123", - }, - }, - { - Name: "returns error if params contains productId.@type=ReferenceID, empty productId", - Params: map[string]string{"productId.@type": "ReferenceID", "productId": ""}, - Err: ebay.ErrInvalidProductIDLength, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=0131103628", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "0131103628", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=954911659X", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "954911659X", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=802510897X", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "802510897X", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=7111075897", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "7111075897", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=986154142X", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "986154142X", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=9780131101630", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "9780131101630", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=9780131103627", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "9780131103627", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=9780133086249", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "9780133086249", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=9789332549449", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "9789332549449", - }, - }, - { - Name: "can find items if params contains productId.@type=ISBN, productId=9780131158177", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "9780131158177", - }, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of length 9", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "111111111", - }, - Err: ebay.ErrInvalidISBNLength, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of length 11", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "11111111111", - }, - Err: ebay.ErrInvalidISBNLength, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of length 12", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "111111111111", - }, - Err: ebay.ErrInvalidISBNLength, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of length 14", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "11111111111111", - }, - Err: ebay.ErrInvalidISBNLength, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of invalid ISBN-10 (invalid first digit)", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "886154142X", - }, - Err: ebay.ErrInvalidISBN, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of invalid ISBN-13 (invalid first digit)", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "8780131158177", - }, - Err: ebay.ErrInvalidISBN, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of invalid ISBN-10 (invalid last digit)", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "9861541429", - }, - Err: ebay.ErrInvalidISBN, - }, - { - Name: "returns error if params contains productId.@type=ISBN, productId of invalid ISBN-13 (invalid last digit)", - Params: map[string]string{ - "productId.@type": "ISBN", - "productId": "9780131158178", - }, - Err: ebay.ErrInvalidISBN, - }, - { - Name: "can find items if params contains productId.@type=UPC, productId=036000291452", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "036000291452", - }, - }, - { - Name: "can find items if params contains productId.@type=UPC, productId=194253378907", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "194253378907", - }, - }, - { - Name: "can find items if params contains productId.@type=UPC, productId=753575979881", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "753575979881", - }, - }, - { - Name: "can find items if params contains productId.@type=UPC, productId=194253402220", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "194253402220", - }, - }, - { - Name: "can find items if params contains productId.@type=UPC, productId=194253407980", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "194253407980", - }, - }, - { - Name: "returns error if params contains productId.@type=UPC, productId of length 11", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "11111111111", - }, - Err: ebay.ErrInvalidUPCLength, - }, - { - Name: "returns error if params contains productId.@type=UPC, productId of length 13", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "1111111111111", - }, - Err: ebay.ErrInvalidUPCLength, - }, - { - Name: "returns error if params contains productId.@type=UPC, productId of invalid UPC (invalid first digit)", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "294253407980", - }, - Err: ebay.ErrInvalidUPC, - }, - { - Name: "returns error if params contains productId.@type=UPC, productId of invalid UPC (invalid last digit)", - Params: map[string]string{ - "productId.@type": "UPC", - "productId": "194253407981", - }, - Err: ebay.ErrInvalidUPC, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=73513537", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "73513537", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=96385074", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "96385074", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=29033706", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "29033706", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=40170725", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "40170725", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=40123455", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "40123455", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=4006381333931", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "4006381333931", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=0194253373933", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "0194253373933", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=0194253374398", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "0194253374398", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=0194253381099", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "0194253381099", - }, - }, - { - Name: "can find items if params contains productId.@type=EAN, productId=0194253373476", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "0194253373476", - }, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of length 7", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "1111111", - }, - Err: ebay.ErrInvalidEANLength, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of length 9", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "111111111", - }, - Err: ebay.ErrInvalidEANLength, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of length 10", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "1111111111", - }, - Err: ebay.ErrInvalidEANLength, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of length 11", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "11111111111", - }, - Err: ebay.ErrInvalidEANLength, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of length 12", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "111111111111", - }, - Err: ebay.ErrInvalidEANLength, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of length 14", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "11111111111111", - }, - Err: ebay.ErrInvalidEANLength, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of invalid EAN-8 (invalid first digit)", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "50123455", - }, - Err: ebay.ErrInvalidEAN, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of invalid EAN-13 (invalid first digit)", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "1194253373476", - }, - Err: ebay.ErrInvalidEAN, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of invalid EAN-8 (invalid last digit)", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "40123456", - }, - Err: ebay.ErrInvalidEAN, - }, - { - Name: "returns error if params contains productId.@type=EAN, productId of invalid EAN-13 (invalid last digit)", - Params: map[string]string{ - "productId.@type": "EAN", - "productId": "0194253373477", - }, - Err: ebay.ErrInvalidEAN, - }, - } - combinedTCs := combineTestCases(t, findItemsByProduct, findItemsByProductTCs) - testFindItems(t, params, findItemsByProduct, findItemsByProductResp, combinedTCs) -} + t.Run("HTTPNewRequestError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://example.com/\x00invalid" + _, err := client.FindItemsAdvanced(context.Background(), map[string]string{}) + if !errors.Is(err, ErrNewRequest) { + t.Errorf("FindingClient.FindItemsAdvanced() error = %v, want %v", err, ErrNewRequest) + } + }) -func TestFindItemsInEBayStores(t *testing.T) { - t.Parallel() - params := map[string]string{"storeName": "Supplytronics"} - findItemsInEBayStoresTCs := []findItemsTestCase{ - { - Name: "can find items if params contains storeName=a", - Params: map[string]string{"storeName": "a"}, - }, - { - Name: "returns error if params contains empty storeName", - Params: map[string]string{"storeName": ""}, - Err: ebay.ErrInvalidStoreNameLength, - }, - { - Name: "can find items if params contains storeName=Ben & Jerry's", - Params: map[string]string{"storeName": "Ben & Jerry's"}, - }, - { - Name: "returns error if params contains storeName=Ben & Jerry's", - Params: map[string]string{"storeName": "Ben & Jerry's"}, - Err: ebay.ErrInvalidStoreNameAmpersand, - }, - { - Name: "can find items if params contains 1 categoryId of length 1, keywords of length 2, storeName of length 1", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(2, true), - "storeName": "a", - }, - }, - { - Name: "returns error if params contains empty categoryId, keywords of length 2, storeName of length 1", - Params: map[string]string{ - "categoryId": "", - "keywords": generateStringWithLen(2, true), - "storeName": "a", - }, - Err: fmt.Errorf("%w: %s: %w", ebay.ErrInvalidCategoryID, `strconv.Atoi: parsing ""`, strconv.ErrSyntax), - }, - { - Name: "returns error if params contains 4 categoryIds, keywords of length 2, storeName of length 1", - Params: map[string]string{ - "categoryId(0)": "1", - "categoryId(1)": "2", - "categoryId(2)": "3", - "categoryId(3)": "4", - "keywords": generateStringWithLen(2, true), - "storeName": "a", - }, - Err: ebay.ErrMaxCategoryIDs, - }, - { - Name: "returns error if params contains 1 categoryId of length 1, empty keywords, storeName of length 1", - Params: map[string]string{ - "categoryId": "1", - "keywords": "", - "storeName": "a", - }, - Err: ebay.ErrInvalidKeywordsLength, - }, - { - Name: "returns error if params contains 1 categoryId of length 1, 1 keyword of length 99, storeName of length 1", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(99, false), - "storeName": "a", - }, - Err: ebay.ErrInvalidKeywordLength, - }, - { - Name: "can find items if params contains 1 categoryId of length 1, keywords of length 2, &-escaped storeName", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(2, true), - "storeName": "Ben & Jerry's", - }, - }, - { - Name: "returns error if params contains 1 categoryId of length 1, keywords of length 2, empty storeName", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(2, true), - "storeName": "", - }, - Err: ebay.ErrInvalidStoreNameLength, - }, - { - Name: "returns error if params contains 1 categoryId of length 1, keywords of length 2, storeName=Ben & Jerry's", - Params: map[string]string{ - "categoryId": "1", - "keywords": generateStringWithLen(2, true), - "storeName": "Ben & Jerry's", - }, - Err: ebay.ErrInvalidStoreNameAmpersand, - }, - } - combinedTCs := combineTestCases( - t, findItemsInEBayStores, categoryIDTCs, keywordsTCs, categoryIDKeywordsTCs, findItemsInEBayStoresTCs) - testFindItems(t, params, findItemsInEBayStores, findItemsInEBayStoresResp, combinedTCs) -} + t.Run("ClientDoError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://localhost" + _, err := client.FindItemsAdvanced(context.Background(), map[string]string{}) + if !errors.Is(err, ErrFailedRequest) { + t.Errorf("FindingClient.FindItemsAdvanced() error = %v, want %v", err, ErrFailedRequest) + } + }) -var findMethodConfigs = map[string]struct { - missingDesc string - searchErr error - params map[string]string -}{ - findItemsByCategories: { - missingDesc: "categoryId", - searchErr: ebay.ErrCategoryIDMissing, - params: map[string]string{"categoryId": "12345"}, - }, - findItemsByKeywords: { - missingDesc: "keywords", - searchErr: ebay.ErrKeywordsMissing, - params: map[string]string{"keywords": "marshmallows"}, - }, - findItemsAdvanced: { - missingDesc: "categoryId or keywords", - searchErr: ebay.ErrCategoryIDKeywordsMissing, - params: map[string]string{"categoryId": "12345"}, - }, - findItemsByProduct: { - missingDesc: "productId", - searchErr: ebay.ErrProductIDMissing, - params: map[string]string{"productId.@type": "ReferenceID", "productId": "123"}, - }, - findItemsInEBayStores: { - missingDesc: "categoryId, keywords, or storeName", - searchErr: ebay.ErrCategoryIDKeywordsStoreNameMissing, - params: map[string]string{"storeName": "Supplytronics"}, - }, -} + t.Run("InvalidStatusError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsAdvanced(context.Background(), map[string]string{}) + if !errors.Is(err, ErrInvalidStatus) { + t.Errorf("FindingClient.FindItemsAdvanced() error = %v, want %v", err, ErrInvalidStatus) + } + }) -func combineTestCases(t *testing.T, findMethod string, tcs ...[]findItemsTestCase) []findItemsTestCase { - t.Helper() - config, ok := findMethodConfigs[findMethod] - if !ok { - t.Fatalf("unsupported findMethod: %s", findMethod) - } - missingParamTCs := append([]findItemsTestCase{}, missingSearchParamTCs...) - commonTCs := append([]findItemsTestCase{}, testCases...) - if findMethod != findItemsByProduct { - missingParamTCs = append(missingParamTCs, aspectFilterMissingSearchParamTCs...) - commonTCs = append(commonTCs, aspectFilterTestCases...) - } - for i := range missingParamTCs { - missingParamTCs[i].Name += config.missingDesc - missingParamTCs[i].Err = config.searchErr - } - for i := range commonTCs { - paramsCopy := make(map[string]string) - maps.Copy(paramsCopy, config.params) - maps.Copy(paramsCopy, commonTCs[i].Params) - commonTCs[i].Params = paramsCopy - } - combinedTCs := append([]findItemsTestCase{}, missingParamTCs...) - combinedTCs = append(combinedTCs, commonTCs...) - for _, cs := range tcs { - combinedTCs = append(combinedTCs, cs...) - } - return combinedTCs + t.Run("JSONDecodeError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`baddata123`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsAdvanced(context.Background(), map[string]string{}) + if !errors.Is(err, ErrDecodeAPIResponse) { + t.Errorf("FindingClient.FindItemsAdvanced() error = %v, want %v", err, ErrDecodeAPIResponse) + } + }) } -func testFindItems(t *testing.T, params map[string]string, findMethod string, wantResp ebay.ResultProvider, tcs []findItemsTestCase) { - t.Helper() - t.Run(fmt.Sprintf("can find items by %s", findMethod), func(t *testing.T) { +func TestFindingClient_FindItemsByCategory(t *testing.T) { + t.Parallel() + t.Run("ResponseSuccess", func(t *testing.T) { t.Parallel() ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := json.Marshal(wantResp) - if err != nil { - t.Fatal(err) - } w.WriteHeader(http.StatusOK) - if _, err = w.Write(body); err != nil { - t.Fatal(err) + err := json.NewEncoder(w).Encode(&FindItemsByCategoryResponse{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) } })) defer ts.Close() - client := ts.Client() - fc := ebay.NewFindingClient(client, appID) - fc.URL = ts.URL - - var resp ebay.ResultProvider - var err error - switch findMethod { - case findItemsByCategories: - resp, err = fc.FindItemsByCategories(context.Background(), params) - case findItemsByKeywords: - resp, err = fc.FindItemsByKeywords(context.Background(), params) - case findItemsAdvanced: - resp, err = fc.FindItemsAdvanced(context.Background(), params) - case findItemsByProduct: - resp, err = fc.FindItemsByProduct(context.Background(), params) - case findItemsInEBayStores: - resp, err = fc.FindItemsInEBayStores(context.Background(), params) - default: - t.Fatalf("unsupported findMethod: %s", findMethod) - } + client := NewFindingClient(ts.Client(), "ebay-app-id") + client.URL = ts.URL + params := map[string]string{"categoryId": "123"} + got, err := client.FindItemsByCategory(context.Background(), params) if err != nil { - t.Fatal(err) + t.Errorf("FindingClient.FindItemsByCategory() error = %v, want nil", err) + return } - if !reflect.DeepEqual(resp, wantResp) { - t.Errorf("got %v, want %v", resp, wantResp) + want := &FindItemsByCategoryResponse{} + if !reflect.DeepEqual(got, want) { + t.Errorf("FindingClient.FindItemsByCategory() = %v, want %v", got, want) } }) - t.Run("returns error if the client returns an error", func(t *testing.T) { + t.Run("HTTPNewRequestError", func(t *testing.T) { t.Parallel() - fc := ebay.NewFindingClient(http.DefaultClient, appID) - fc.URL = "http://localhost" + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://example.com/\x00invalid" + _, err := client.FindItemsByCategory(context.Background(), map[string]string{}) + if !errors.Is(err, ErrNewRequest) { + t.Errorf("FindingClient.FindItemsByCategory() error = %v, want %v", err, ErrNewRequest) + } + }) - var err error - switch findMethod { - case findItemsByCategories: - _, err = fc.FindItemsByCategories(context.Background(), params) - case findItemsByKeywords: - _, err = fc.FindItemsByKeywords(context.Background(), params) - case findItemsAdvanced: - _, err = fc.FindItemsAdvanced(context.Background(), params) - case findItemsByProduct: - _, err = fc.FindItemsByProduct(context.Background(), params) - case findItemsInEBayStores: - _, err = fc.FindItemsInEBayStores(context.Background(), params) - default: - t.Fatalf("unsupported findMethod: %s", findMethod) + t.Run("ClientDoError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://localhost" + _, err := client.FindItemsByCategory(context.Background(), map[string]string{}) + if !errors.Is(err, ErrFailedRequest) { + t.Errorf("FindingClient.FindItemsByCategory() error = %v, want %v", err, ErrFailedRequest) } - if err == nil { - t.Fatal("err == nil; want != nil") + }) + + t.Run("InvalidStatusError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsByCategory(context.Background(), map[string]string{}) + if !errors.Is(err, ErrInvalidStatus) { + t.Errorf("FindingClient.FindItemsByCategory() error = %v, want %v", err, ErrInvalidStatus) } - want := ebay.APIError{Err: ebay.ErrFailedRequest, StatusCode: http.StatusInternalServerError} - var got *ebay.APIError - if !errors.As(err, &got) { - t.Fatalf("error %v does not wrap ebay.APIError", err) + }) + + t.Run("JSONDecodeError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`baddata123`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsByCategory(context.Background(), map[string]string{}) + if !errors.Is(err, ErrDecodeAPIResponse) { + t.Errorf("FindingClient.FindItemsByCategory() error = %v, want %v", err, ErrDecodeAPIResponse) } - if !strings.HasPrefix(got.Error(), want.Error()) { - t.Errorf("expected error with prefix %q, got: %q", want.Error(), got.Error()) + }) +} + +func TestFindingClient_FindItemsByKeywords(t *testing.T) { + t.Parallel() + t.Run("ResponseSuccess", func(t *testing.T) { + t.Parallel() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(&FindItemsByKeywordsResponse{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + })) + defer ts.Close() + client := NewFindingClient(ts.Client(), "ebay-app-id") + client.URL = ts.URL + params := map[string]string{"keywords": "testword"} + got, err := client.FindItemsByKeywords(context.Background(), params) + if err != nil { + t.Errorf("FindingClient.FindItemsByKeywords() error = %v, want nil", err) + return } - if got.StatusCode != want.StatusCode { - t.Errorf("got status %q, want %q", got.StatusCode, want.StatusCode) + want := &FindItemsByKeywordsResponse{} + if !reflect.DeepEqual(got, want) { + t.Errorf("FindingClient.FindItemsByKeywords() = %v, want %v", got, want) } }) - badStatusCodes := []int{ - http.StatusBadRequest, - http.StatusUnauthorized, - http.StatusPaymentRequired, - http.StatusForbidden, - http.StatusNotFound, - http.StatusMethodNotAllowed, - http.StatusNotAcceptable, - http.StatusProxyAuthRequired, - http.StatusRequestTimeout, - http.StatusConflict, - http.StatusGone, - http.StatusLengthRequired, - http.StatusPreconditionFailed, - http.StatusRequestEntityTooLarge, - http.StatusRequestURITooLong, - http.StatusUnsupportedMediaType, - http.StatusRequestedRangeNotSatisfiable, - http.StatusExpectationFailed, - http.StatusTeapot, - http.StatusMisdirectedRequest, - http.StatusUnprocessableEntity, - http.StatusLocked, - http.StatusFailedDependency, - http.StatusTooEarly, - http.StatusUpgradeRequired, - http.StatusPreconditionRequired, - http.StatusTooManyRequests, - http.StatusRequestHeaderFieldsTooLarge, - http.StatusUnavailableForLegalReasons, - http.StatusInternalServerError, - http.StatusNotImplemented, - http.StatusBadGateway, - http.StatusServiceUnavailable, - http.StatusGatewayTimeout, - http.StatusHTTPVersionNotSupported, - http.StatusVariantAlsoNegotiates, - http.StatusInsufficientStorage, - http.StatusLoopDetected, - http.StatusNotExtended, - http.StatusNetworkAuthenticationRequired, - } + t.Run("HTTPNewRequestError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://example.com/\x00invalid" + _, err := client.FindItemsByKeywords(context.Background(), map[string]string{}) + if !errors.Is(err, ErrNewRequest) { + t.Errorf("FindingClient.FindItemsByKeywords() error = %v, want %v", err, ErrNewRequest) + } + }) - t.Run("returns error if the client request was not successful", func(t *testing.T) { + t.Run("ClientDoError", func(t *testing.T) { t.Parallel() - for _, statusCode := range badStatusCodes { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(statusCode) - })) - defer ts.Close() - client := ts.Client() - fc := ebay.NewFindingClient(client, appID) - fc.URL = ts.URL + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://localhost" + _, err := client.FindItemsByKeywords(context.Background(), map[string]string{}) + if !errors.Is(err, ErrFailedRequest) { + t.Errorf("FindingClient.FindItemsByKeywords() error = %v, want %v", err, ErrFailedRequest) + } + }) - var err error - switch findMethod { - case findItemsByCategories: - _, err = fc.FindItemsByCategories(context.Background(), params) - case findItemsByKeywords: - _, err = fc.FindItemsByKeywords(context.Background(), params) - case findItemsAdvanced: - _, err = fc.FindItemsAdvanced(context.Background(), params) - case findItemsByProduct: - _, err = fc.FindItemsByProduct(context.Background(), params) - case findItemsInEBayStores: - _, err = fc.FindItemsInEBayStores(context.Background(), params) - default: - t.Fatalf("unsupported findMethod: %s", findMethod) + t.Run("InvalidStatusError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsByKeywords(context.Background(), map[string]string{}) + if !errors.Is(err, ErrInvalidStatus) { + t.Errorf("FindingClient.FindItemsByKeywords() error = %v, want %v", err, ErrInvalidStatus) + } + }) + + t.Run("JSONDecodeError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`baddata123`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) } - want := fmt.Errorf("%w %d", ebay.ErrInvalidStatus, statusCode) - assertAPIError(t, err, want, http.StatusInternalServerError) + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsByKeywords(context.Background(), map[string]string{}) + if !errors.Is(err, ErrDecodeAPIResponse) { + t.Errorf("FindingClient.FindItemsByKeywords() error = %v, want %v", err, ErrDecodeAPIResponse) } }) +} - t.Run("returns error if the response cannot be parsed into find items response", func(t *testing.T) { +func TestFindingClient_FindItemsByProduct(t *testing.T) { + t.Parallel() + t.Run("ResponseSuccess", func(t *testing.T) { t.Parallel() - badData := `[123.1, 234.2]` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - if _, err := w.Write([]byte(badData)); err != nil { - t.Fatal(err) + err := json.NewEncoder(w).Encode(&FindItemsByProductResponse{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) } })) defer ts.Close() - client := ts.Client() - fc := ebay.NewFindingClient(client, appID) - fc.URL = ts.URL + client := NewFindingClient(ts.Client(), "ebay-app-id") + client.URL = ts.URL + params := map[string]string{"productId.@type": "ReferenceID", "productId": "123"} + got, err := client.FindItemsByProduct(context.Background(), params) + if err != nil { + t.Errorf("FindingClient.FindItemsByProduct() error = %v, want nil", err) + return + } + want := &FindItemsByProductResponse{} + if !reflect.DeepEqual(got, want) { + t.Errorf("FindingClient.FindItemsByProduct() = %v, want %v", got, want) + } + }) + + t.Run("HTTPNewRequestError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://example.com/\x00invalid" + _, err := client.FindItemsByProduct(context.Background(), map[string]string{}) + if !errors.Is(err, ErrNewRequest) { + t.Errorf("FindingClient.FindItemsByProduct() error = %v, want %v", err, ErrNewRequest) + } + }) - var err error - switch findMethod { - case findItemsByCategories: - _, err = fc.FindItemsByCategories(context.Background(), params) - case findItemsByKeywords: - _, err = fc.FindItemsByKeywords(context.Background(), params) - case findItemsAdvanced: - _, err = fc.FindItemsAdvanced(context.Background(), params) - case findItemsByProduct: - _, err = fc.FindItemsByProduct(context.Background(), params) - case findItemsInEBayStores: - _, err = fc.FindItemsInEBayStores(context.Background(), params) - default: - t.Fatalf("unsupported findMethod: %s", findMethod) + t.Run("ClientDoError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://localhost" + _, err := client.FindItemsByProduct(context.Background(), map[string]string{}) + if !errors.Is(err, ErrFailedRequest) { + t.Errorf("FindingClient.FindItemsByProduct() error = %v, want %v", err, ErrFailedRequest) } - want := fmt.Errorf("%w: json: cannot unmarshal array into Go value of type ebay.%sResponse", ebay.ErrDecodeAPIResponse, findMethod) - assertAPIError(t, err, want, http.StatusInternalServerError) }) - for _, tc := range tcs { - testCase := tc - t.Run(testCase.Name, func(t *testing.T) { - t.Parallel() - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := json.Marshal(wantResp) - if err != nil { - t.Fatal(err) - } - w.WriteHeader(http.StatusOK) - if _, err = w.Write(body); err != nil { - t.Fatal(err) - } - })) - defer ts.Close() - client := ts.Client() - fc := ebay.NewFindingClient(client, appID) - fc.URL = ts.URL + t.Run("InvalidStatusError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsByProduct(context.Background(), map[string]string{}) + if !errors.Is(err, ErrInvalidStatus) { + t.Errorf("FindingClient.FindItemsByProduct() error = %v, want %v", err, ErrInvalidStatus) + } + }) - var resp ebay.ResultProvider - var err error - switch findMethod { - case findItemsByCategories: - resp, err = fc.FindItemsByCategories(context.Background(), testCase.Params) - case findItemsByKeywords: - resp, err = fc.FindItemsByKeywords(context.Background(), testCase.Params) - case findItemsAdvanced: - resp, err = fc.FindItemsAdvanced(context.Background(), testCase.Params) - case findItemsByProduct: - resp, err = fc.FindItemsByProduct(context.Background(), testCase.Params) - case findItemsInEBayStores: - resp, err = fc.FindItemsInEBayStores(context.Background(), testCase.Params) - default: - t.Fatalf("unsupported findMethod: %s", findMethod) - } - if testCase.Err != nil { - assertAPIError(t, err, testCase.Err, http.StatusBadRequest) - } else { - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(resp, wantResp) { - t.Errorf("got %v, want %v", resp, wantResp) - } + t.Run("JSONDecodeError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`baddata123`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) } - }) - } + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsByProduct(context.Background(), map[string]string{}) + if !errors.Is(err, ErrDecodeAPIResponse) { + t.Errorf("FindingClient.FindItemsByProduct() error = %v, want %v", err, ErrDecodeAPIResponse) + } + }) } -func assertAPIError(tb testing.TB, got, wantErr error, wantStatusCode int) { - tb.Helper() - var gotAPIError *ebay.APIError - if !errors.As(got, &gotAPIError) { - tb.Fatalf("error %v does not wrap ebay.APIError", got) - } - want := &ebay.APIError{Err: wantErr, StatusCode: wantStatusCode} - if gotAPIError.Err.Error() != want.Err.Error() { - tb.Errorf("got error %q, want %q", gotAPIError.Err.Error(), want.Err.Error()) - } - if gotAPIError.StatusCode != want.StatusCode { - tb.Errorf("got status %q, want %q", gotAPIError.StatusCode, want.StatusCode) - } -} +func TestFindingClient_FindItemsInEBayStores(t *testing.T) { + t.Parallel() + t.Run("ResponseSuccess", func(t *testing.T) { + t.Parallel() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(&FindItemsInEBayStoresResponse{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + })) + defer ts.Close() + client := NewFindingClient(ts.Client(), "ebay-app-id") + client.URL = ts.URL + params := map[string]string{"storeName": "teststore"} + got, err := client.FindItemsInEBayStores(context.Background(), params) + if err != nil { + t.Errorf("FindingClient.FindItemsInEBayStores() error = %v, want nil", err) + return + } + want := &FindItemsInEBayStoresResponse{} + if !reflect.DeepEqual(got, want) { + t.Errorf("FindingClient.FindItemsInEBayStores() = %v, want %v", got, want) + } + }) -func generateStringWithLen(length int, includeSpaces bool) string { - const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 " - var sbuilder strings.Builder - charSet := letters - if !includeSpaces { - charSet = letters[:len(letters)-1] // Exclude the space character - } - for i := 0; i < length; i++ { - sbuilder.WriteByte(charSet[i%len(charSet)]) - } - return sbuilder.String() -} + t.Run("HTTPNewRequestError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://example.com/\x00invalid" + _, err := client.FindItemsInEBayStores(context.Background(), map[string]string{}) + if !errors.Is(err, ErrNewRequest) { + t.Errorf("FindingClient.FindItemsInEBayStores() error = %v, want %v", err, ErrNewRequest) + } + }) -func generateFilterParams(filterName string, count int) map[string]string { - params := make(map[string]string) - params["itemFilter.name"] = filterName - for i := 0; i < count; i++ { - params[fmt.Sprintf("itemFilter.value(%d)", i)] = strconv.Itoa(i) - } - return params + t.Run("ClientDoError", func(t *testing.T) { + t.Parallel() + client := NewFindingClient(http.DefaultClient, "ebay-app-id") + client.URL = "http://localhost" + _, err := client.FindItemsInEBayStores(context.Background(), map[string]string{}) + if !errors.Is(err, ErrFailedRequest) { + t.Errorf("FindingClient.FindItemsInEBayStores() error = %v, want %v", err, ErrFailedRequest) + } + }) + + t.Run("InvalidStatusError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsInEBayStores(context.Background(), map[string]string{}) + if !errors.Is(err, ErrInvalidStatus) { + t.Errorf("FindingClient.FindItemsInEBayStores() error = %v, want %v", err, ErrInvalidStatus) + } + }) + + t.Run("JSONDecodeError", func(t *testing.T) { + t.Parallel() + errorSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`baddata123`)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + })) + defer errorSrv.Close() + client := NewFindingClient(errorSrv.Client(), "ebay-app-id") + client.URL = errorSrv.URL + _, err := client.FindItemsInEBayStores(context.Background(), map[string]string{}) + if !errors.Is(err, ErrDecodeAPIResponse) { + t.Errorf("FindingClient.FindItemsInEBayStores() error = %v, want %v", err, ErrDecodeAPIResponse) + } + }) } diff --git a/params.go b/params.go deleted file mode 100644 index 136e9bc..0000000 --- a/params.go +++ /dev/null @@ -1,1311 +0,0 @@ -// Copyright 2023 Matthew P. Dargan. -// SPDX-License-Identifier: Apache-2.0 - -package ebay - -import ( - "context" - "errors" - "fmt" - "net/http" - "slices" - "strconv" - "strings" -) - -const ( - findItemsByCategoryOperationName = "findItemsByCategory" - findItemsByKeywordsOperationName = "findItemsByKeywords" - findItemsAdvancedOperationName = "findItemsAdvanced" - findItemsByProductOperationName = "findItemsByProduct" - findItemsInEBayStoresOperationName = "findItemsIneBayStores" - findingServiceVersion = "1.0.0" - findingResponseDataFormat = "JSON" -) - -var ( - // ErrCategoryIDMissing is returned when the 'categoryId' parameter is missing in a findItemsByCategory request. - ErrCategoryIDMissing = errors.New("category ID parameter is missing") - - // ErrCategoryIDKeywordsMissing is returned when the 'categoryId' and 'keywords' parameters - // are missing in a findItemsAdvanced request. - ErrCategoryIDKeywordsMissing = errors.New("both category ID and keywords parameters are missing") - - // ErrProductIDMissing is returned when the 'productId' or 'productId.@type' parameters - // are missing in a findItemsByProduct request. - ErrProductIDMissing = errors.New("product ID parameter or product ID type are missing") - - // ErrCategoryIDKeywordsStoreNameMissing is returned when the 'categoryId', 'keywords', and 'storeName' parameters - // are missing in a findItemsIneBayStores request. - ErrCategoryIDKeywordsStoreNameMissing = errors.New("category ID, keywords, and store name parameters are missing") - - // ErrInvalidIndexSyntax is returned when index and non-index syntax are used in the params. - ErrInvalidIndexSyntax = errors.New("invalid filter syntax: both index and non-index syntax are present") - - maxCategoryIDs = 3 - - // ErrMaxCategoryIDs is returned when the 'categoryId' parameter contains more category IDs than the maximum allowed. - ErrMaxCategoryIDs = fmt.Errorf("maximum category IDs to specify is %d", maxCategoryIDs) - - maxCategoryIDLen = 10 - - // ErrInvalidCategoryIDLength is returned when an individual category ID in the 'categoryId' parameter - // exceed the maximum length of 10 characters or is empty. - ErrInvalidCategoryIDLength = fmt.Errorf("invalid category ID length: must be between 1 and %d characters", maxCategoryIDLen) - - // ErrInvalidCategoryID is returned when an individual category ID in the 'categoryId' parameter - // contains an invalid category ID. - ErrInvalidCategoryID = errors.New("invalid category ID") - - // ErrKeywordsMissing is returned when the 'keywords' parameter is missing. - ErrKeywordsMissing = errors.New("keywords parameter is missing") - - minKeywordsLen, maxKeywordsLen = 2, 350 - - // ErrInvalidKeywordsLength is returned when the 'keywords' parameter as a whole - // exceeds the maximum length of 350 characters or has a length less than 2 characters. - ErrInvalidKeywordsLength = fmt.Errorf("invalid keywords length: must be between %d and %d characters", minKeywordsLen, maxKeywordsLen) - - maxKeywordLen = 98 - - // ErrInvalidKeywordLength is returned when an individual keyword in the 'keywords' parameter - // exceeds the maximum length of 98 characters. - ErrInvalidKeywordLength = fmt.Errorf("invalid keyword length: must be no more than %d characters", maxKeywordLen) - - // ErrInvalidProductIDLength is returned when the 'productId' parameter is empty. - ErrInvalidProductIDLength = errors.New("invalid product ID length") - - isbnShortLen, isbnLongLen = 10, 13 - - // ErrInvalidISBNLength is returned when the 'productId.type' parameter is an ISBN (International Standard Book Number) - // and the 'productId' parameter is not exactly 10 or 13 characters. - ErrInvalidISBNLength = fmt.Errorf("invalid ISBN length: must be either %d or %d characters", isbnShortLen, isbnLongLen) - - // ErrInvalidISBN is returned when the 'productId.type' parameter is an ISBN (International Standard Book Number) - // and the 'productId' parameter contains an invalid ISBN. - ErrInvalidISBN = errors.New("invalid ISBN") - - upcLen = 12 - - // ErrInvalidUPCLength is returned when the 'productId.type' parameter is a UPC (Universal Product Code) - // and the 'productId' parameter is not 12 digits. - ErrInvalidUPCLength = fmt.Errorf("invalid UPC length: must be %d digits", upcLen) - - // ErrInvalidUPC is returned when the 'productId.type' parameter is a UPC (Universal Product Code) - // and the 'productId' parameter contains an invalid UPC. - ErrInvalidUPC = errors.New("invalid UPC") - - eanShortLen, eanLongLen = 8, 13 - - // ErrInvalidEANLength is returned when the 'productId.type' parameter is an EAN (European Article Number) - // and the 'productId' parameter is not exactly 8 or 13 characters. - ErrInvalidEANLength = fmt.Errorf("invalid EAN length: must be either %d or %d characters", eanShortLen, eanLongLen) - - // ErrInvalidEAN is returned when the 'productId.type' parameter is an EAN (European Article Number) - // and the 'productId' parameter contains an invalid EAN. - ErrInvalidEAN = errors.New("invalid EAN") - - // ErrUnsupportedProductIDType is returned when the 'productId.type' parameter has an unsupported type. - ErrUnsupportedProductIDType = errors.New("unsupported product ID type") - - // ErrInvalidStoreNameLength is returned when the 'storeName' parameter is empty. - ErrInvalidStoreNameLength = errors.New("invalid store name length") - - // ErrInvalidStoreNameAmpersand is returned when the 'storeName' parameter contains unescaped '&' characters. - ErrInvalidStoreNameAmpersand = errors.New("storeName contains unescaped '&' characters") - - // ErrInvalidGlobalID is returned when the 'Global-ID' or an item filter 'values' parameter contains an invalid global ID. - ErrInvalidGlobalID = errors.New("invalid global ID") - - // ErrInvalidBooleanValue is returned when a parameter has an invalid boolean value. - ErrInvalidBooleanValue = errors.New("invalid boolean value, allowed values are true and false") - - // ErrBuyerPostalCodeMissing is returned when the LocalSearchOnly, MaxDistance item filter, - // or DistanceNearest sortOrder is used, but the buyerPostalCode parameter is missing in the request. - ErrBuyerPostalCodeMissing = errors.New("buyerPostalCode is missing") - - // ErrInvalidOutputSelector is returned when the 'outputSelector' parameter contains an invalid output selector. - ErrInvalidOutputSelector = errors.New("invalid output selector") - - maxCustomIDLen = 256 - - // ErrInvalidCustomIDLength is returned when the 'affiliate.customId' parameter - // exceeds the maximum length of 256 characters. - ErrInvalidCustomIDLength = fmt.Errorf("invalid affiliate custom ID length: must be no more than %d characters", maxCustomIDLen) - - // ErrIncompleteAffiliateParams is returned when an affiliate is missing - // either the 'networkId' or 'trackingId' parameter, as both 'networkId' and 'trackingId' - // are required when either one is specified. - ErrIncompleteAffiliateParams = errors.New("incomplete affiliate: both network and tracking IDs must be specified together") - - // ErrInvalidNetworkID is returned when the 'affiliate.networkId' parameter - // contains an invalid network ID. - ErrInvalidNetworkID = errors.New("invalid affiliate network ID") - - beFreeID, ebayPartnerNetworkID = 2, 9 - - // ErrInvalidNetworkIDRange is returned when the 'affiliate.networkId' parameter - // is outside the valid range of 2 (Be Free) and 9 (eBay Partner Network). - ErrInvalidNetworkIDRange = fmt.Errorf("invalid affiliate network ID: must be between %d and %d", beFreeID, ebayPartnerNetworkID) - - // ErrInvalidTrackingID is returned when the 'affiliate.networkId' parameter is 9 (eBay Partner Network) - // and the 'affiliate.trackingId' parameter contains an invalid tracking ID. - ErrInvalidTrackingID = errors.New("invalid affiliate tracking ID") - - // ErrInvalidCampaignID is returned when the 'affiliate.networkId' parameter is 9 (eBay Partner Network) - // and the 'affiliate.trackingId' parameter is not a 10-digit number (eBay Partner Network's Campaign ID). - ErrInvalidCampaignID = errors.New("invalid affiliate Campaign ID length: must be a 10-digit number") - - // ErrInvalidPostalCode is returned when the 'buyerPostalCode' parameter contains an invalid postal code. - ErrInvalidPostalCode = errors.New("invalid postal code") - - // ErrInvalidEntriesPerPage is returned when the 'paginationInput.entriesPerPage' parameter - // contains an invalid entries value. - ErrInvalidEntriesPerPage = errors.New("invalid pagination entries per page") - - minPaginationValue, maxPaginationValue = 1, 100 - - // ErrInvalidEntriesPerPageRange is returned when the 'paginationInput.entriesPerPage' parameter - // is outside the valid range of 1 to 100. - ErrInvalidEntriesPerPageRange = fmt.Errorf("invalid pagination entries per page, must be between %d and %d", minPaginationValue, maxPaginationValue) - - // ErrInvalidPageNumber is returned when the 'paginationInput.pageNumber' parameter - // contains an invalid pages value. - ErrInvalidPageNumber = errors.New("invalid pagination page number") - - // ErrInvalidPageNumberRange is returned when the 'paginationInput.pageNumber' parameter - // is outside the valid range of 1 to 100. - ErrInvalidPageNumberRange = fmt.Errorf("invalid pagination page number, must be between %d and %d", minPaginationValue, maxPaginationValue) - - // ErrAuctionListingMissing is returned when the 'sortOrder' parameter BidCountFewest or BidCountMost, - // but a 'Auction' listing type is not specified in the item filters. - ErrAuctionListingMissing = errors.New("'Auction' listing type required for sorting by bid count") - - // ErrUnsupportedSortOrderType is returned when the 'sortOrder' parameter has an unsupported type. - ErrUnsupportedSortOrderType = errors.New("invalid sort order type") - - // ErrInvalidRequest is returned when the eBay Finding API request is invalid. - ErrInvalidRequest = errors.New("invalid request") -) - -type findParamsValidator interface { - validate(params map[string]string) error - newRequest(ctx context.Context, url string) (*http.Request, error) -} - -type findItemsByCategoryParams struct { - appID string - globalID *string - aspectFilters []aspectFilter - categoryIDs []string - itemFilters []itemFilter - outputSelectors []string - affiliate *affiliate - buyerPostalCode *string - paginationInput *paginationInput - sortOrder *string -} - -type aspectFilter struct { - aspectName string - aspectValueNames []string -} - -type itemFilter struct { - name string - values []string - paramName *string - paramValue *string -} - -type affiliate struct { - customID *string - geoTargeting *string - networkID *string - trackingID *string -} - -type paginationInput struct { - entriesPerPage *string - pageNumber *string -} - -func (fp *findItemsByCategoryParams) validate(params map[string]string) error { - _, ok := params["categoryId"] - _, nOk := params["categoryId(0)"] - if !ok && !nOk { - return ErrCategoryIDMissing - } - categoryIDs, err := processCategoryIDs(params) - if err != nil { - return err - } - fp.categoryIDs = categoryIDs - globalID, ok := params["Global-ID"] - if ok { - if err = validateGlobalID(globalID); err != nil { - return err - } - fp.globalID = &globalID - } - fp.aspectFilters, err = processAspectFilters(params) - if err != nil { - return err - } - fp.itemFilters, err = processItemFilters(params) - if err != nil { - return err - } - fp.outputSelectors, err = processOutputSelectors(params) - if err != nil { - return err - } - fp.affiliate, err = processAffiliate(params) - if err != nil { - return err - } - buyerPostalCode, ok := params["buyerPostalCode"] - if ok { - if !isValidPostalCode(buyerPostalCode) { - return ErrInvalidPostalCode - } - fp.buyerPostalCode = &buyerPostalCode - } - fp.paginationInput, err = processPaginationInput(params) - if err != nil { - return err - } - sortOrder, ok := params["sortOrder"] - if ok { - if err := validateSortOrder(sortOrder, fp.itemFilters, fp.buyerPostalCode != nil); err != nil { - return err - } - fp.sortOrder = &sortOrder - } - return nil -} - -func (fp *findItemsByCategoryParams) newRequest(ctx context.Context, url string) (*http.Request, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) - } - qry := req.URL.Query() - if fp.globalID != nil { - qry.Add("Global-ID", *fp.globalID) - } - qry.Add("OPERATION-NAME", findItemsByCategoryOperationName) - qry.Add("SERVICE-VERSION", findingServiceVersion) - qry.Add("SECURITY-APPNAME", fp.appID) - qry.Add("RESPONSE-DATA-FORMAT", findingResponseDataFormat) - for i, f := range fp.aspectFilters { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectName", i), f.aspectName) - for j, v := range f.aspectValueNames { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectValueName(%d)", i, j), v) - } - } - for i := range fp.categoryIDs { - qry.Add(fmt.Sprintf("categoryId(%d)", i), fp.categoryIDs[i]) - } - for i, f := range fp.itemFilters { - qry.Add(fmt.Sprintf("itemFilter(%d).name", i), f.name) - for j, v := range f.values { - qry.Add(fmt.Sprintf("itemFilter(%d).value(%d)", i, j), v) - } - if f.paramName != nil && f.paramValue != nil { - qry.Add(fmt.Sprintf("itemFilter(%d).paramName", i), *f.paramName) - qry.Add(fmt.Sprintf("itemFilter(%d).paramValue", i), *f.paramValue) - } - } - for i := range fp.outputSelectors { - qry.Add(fmt.Sprintf("outputSelector(%d)", i), fp.outputSelectors[i]) - } - if fp.affiliate != nil { - if fp.affiliate.customID != nil { - qry.Add("affiliate.customId", *fp.affiliate.customID) - } - if fp.affiliate.geoTargeting != nil { - qry.Add("affiliate.geoTargeting", *fp.affiliate.geoTargeting) - } - if fp.affiliate.networkID != nil { - qry.Add("affiliate.networkId", *fp.affiliate.networkID) - } - if fp.affiliate.trackingID != nil { - qry.Add("affiliate.trackingId", *fp.affiliate.trackingID) - } - } - if fp.buyerPostalCode != nil { - qry.Add("buyerPostalCode", *fp.buyerPostalCode) - } - if fp.paginationInput != nil { - if fp.paginationInput.entriesPerPage != nil { - qry.Add("paginationInput.entriesPerPage", *fp.paginationInput.entriesPerPage) - } - if fp.paginationInput.pageNumber != nil { - qry.Add("paginationInput.pageNumber", *fp.paginationInput.pageNumber) - } - } - if fp.sortOrder != nil { - qry.Add("sortOrder", *fp.sortOrder) - } - req.URL.RawQuery = qry.Encode() - return req, nil -} - -type findItemsByKeywordsParams struct { - appID string - globalID *string - aspectFilters []aspectFilter - itemFilters []itemFilter - keywords string - outputSelectors []string - affiliate *affiliate - buyerPostalCode *string - paginationInput *paginationInput - sortOrder *string -} - -func (fp *findItemsByKeywordsParams) validate(params map[string]string) error { - keywords, err := processKeywords(params) - if err != nil { - return err - } - fp.keywords = keywords - globalID, ok := params["Global-ID"] - if ok { - if err = validateGlobalID(globalID); err != nil { - return err - } - fp.globalID = &globalID - } - fp.aspectFilters, err = processAspectFilters(params) - if err != nil { - return err - } - fp.itemFilters, err = processItemFilters(params) - if err != nil { - return err - } - fp.outputSelectors, err = processOutputSelectors(params) - if err != nil { - return err - } - fp.affiliate, err = processAffiliate(params) - if err != nil { - return err - } - buyerPostalCode, ok := params["buyerPostalCode"] - if ok { - if !isValidPostalCode(buyerPostalCode) { - return ErrInvalidPostalCode - } - fp.buyerPostalCode = &buyerPostalCode - } - fp.paginationInput, err = processPaginationInput(params) - if err != nil { - return err - } - sortOrder, ok := params["sortOrder"] - if ok { - if err := validateSortOrder(sortOrder, fp.itemFilters, fp.buyerPostalCode != nil); err != nil { - return err - } - fp.sortOrder = &sortOrder - } - return nil -} - -func (fp *findItemsByKeywordsParams) newRequest(ctx context.Context, url string) (*http.Request, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) - } - qry := req.URL.Query() - if fp.globalID != nil { - qry.Add("Global-ID", *fp.globalID) - } - qry.Add("OPERATION-NAME", findItemsByKeywordsOperationName) - qry.Add("SERVICE-VERSION", findingServiceVersion) - qry.Add("SECURITY-APPNAME", fp.appID) - qry.Add("RESPONSE-DATA-FORMAT", findingResponseDataFormat) - for i, f := range fp.aspectFilters { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectName", i), f.aspectName) - for j, v := range f.aspectValueNames { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectValueName(%d)", i, j), v) - } - } - for i, f := range fp.itemFilters { - qry.Add(fmt.Sprintf("itemFilter(%d).name", i), f.name) - for j, v := range f.values { - qry.Add(fmt.Sprintf("itemFilter(%d).value(%d)", i, j), v) - } - if f.paramName != nil && f.paramValue != nil { - qry.Add(fmt.Sprintf("itemFilter(%d).paramName", i), *f.paramName) - qry.Add(fmt.Sprintf("itemFilter(%d).paramValue", i), *f.paramValue) - } - } - qry.Add("keywords", fp.keywords) - for i := range fp.outputSelectors { - qry.Add(fmt.Sprintf("outputSelector(%d)", i), fp.outputSelectors[i]) - } - if fp.affiliate != nil { - if fp.affiliate.customID != nil { - qry.Add("affiliate.customId", *fp.affiliate.customID) - } - if fp.affiliate.geoTargeting != nil { - qry.Add("affiliate.geoTargeting", *fp.affiliate.geoTargeting) - } - if fp.affiliate.networkID != nil { - qry.Add("affiliate.networkId", *fp.affiliate.networkID) - } - if fp.affiliate.trackingID != nil { - qry.Add("affiliate.trackingId", *fp.affiliate.trackingID) - } - } - if fp.buyerPostalCode != nil { - qry.Add("buyerPostalCode", *fp.buyerPostalCode) - } - if fp.paginationInput != nil { - if fp.paginationInput.entriesPerPage != nil { - qry.Add("paginationInput.entriesPerPage", *fp.paginationInput.entriesPerPage) - } - if fp.paginationInput.pageNumber != nil { - qry.Add("paginationInput.pageNumber", *fp.paginationInput.pageNumber) - } - } - if fp.sortOrder != nil { - qry.Add("sortOrder", *fp.sortOrder) - } - req.URL.RawQuery = qry.Encode() - return req, nil -} - -type findItemsAdvancedParams struct { - appID string - globalID *string - aspectFilters []aspectFilter - categoryIDs []string - descriptionSearch *string - itemFilters []itemFilter - keywords *string - outputSelectors []string - affiliate *affiliate - buyerPostalCode *string - paginationInput *paginationInput - sortOrder *string -} - -func (fp *findItemsAdvancedParams) validate(params map[string]string) error { - _, cOk := params["categoryId"] - _, csOk := params["categoryId(0)"] - _, ok := params["keywords"] - if !cOk && !csOk && !ok { - return ErrCategoryIDKeywordsMissing - } - if cOk || csOk { - categoryIDs, err := processCategoryIDs(params) - if err != nil { - return err - } - fp.categoryIDs = categoryIDs - } - if ok { - keywords, err := processKeywords(params) - if err != nil { - return err - } - fp.keywords = &keywords - } - globalID, ok := params["Global-ID"] - if ok { - if err := validateGlobalID(globalID); err != nil { - return err - } - fp.globalID = &globalID - } - aspectFilters, err := processAspectFilters(params) - if err != nil { - return err - } - fp.aspectFilters = aspectFilters - ds, ok := params["descriptionSearch"] - if ok { - if ds != trueValue && ds != falseValue { - return fmt.Errorf("%w: %q", ErrInvalidBooleanValue, ds) - } - fp.descriptionSearch = &ds - } - fp.itemFilters, err = processItemFilters(params) - if err != nil { - return err - } - fp.outputSelectors, err = processOutputSelectors(params) - if err != nil { - return err - } - fp.affiliate, err = processAffiliate(params) - if err != nil { - return err - } - buyerPostalCode, ok := params["buyerPostalCode"] - if ok { - if !isValidPostalCode(buyerPostalCode) { - return ErrInvalidPostalCode - } - fp.buyerPostalCode = &buyerPostalCode - } - fp.paginationInput, err = processPaginationInput(params) - if err != nil { - return err - } - sortOrder, ok := params["sortOrder"] - if ok { - if err := validateSortOrder(sortOrder, fp.itemFilters, fp.buyerPostalCode != nil); err != nil { - return err - } - fp.sortOrder = &sortOrder - } - return nil -} - -func (fp *findItemsAdvancedParams) newRequest(ctx context.Context, url string) (*http.Request, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) - } - qry := req.URL.Query() - if fp.globalID != nil { - qry.Add("Global-ID", *fp.globalID) - } - qry.Add("OPERATION-NAME", findItemsAdvancedOperationName) - qry.Add("SERVICE-VERSION", findingServiceVersion) - qry.Add("SECURITY-APPNAME", fp.appID) - qry.Add("RESPONSE-DATA-FORMAT", findingResponseDataFormat) - for i, f := range fp.aspectFilters { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectName", i), f.aspectName) - for j, v := range f.aspectValueNames { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectValueName(%d)", i, j), v) - } - } - for i := range fp.categoryIDs { - qry.Add(fmt.Sprintf("categoryId(%d)", i), fp.categoryIDs[i]) - } - if fp.descriptionSearch != nil { - qry.Add("descriptionSearch", *fp.descriptionSearch) - } - for i, f := range fp.itemFilters { - qry.Add(fmt.Sprintf("itemFilter(%d).name", i), f.name) - for j, v := range f.values { - qry.Add(fmt.Sprintf("itemFilter(%d).value(%d)", i, j), v) - } - if f.paramName != nil && f.paramValue != nil { - qry.Add(fmt.Sprintf("itemFilter(%d).paramName", i), *f.paramName) - qry.Add(fmt.Sprintf("itemFilter(%d).paramValue", i), *f.paramValue) - } - } - if fp.keywords != nil { - qry.Add("keywords", *fp.keywords) - } - for i := range fp.outputSelectors { - qry.Add(fmt.Sprintf("outputSelector(%d)", i), fp.outputSelectors[i]) - } - if fp.affiliate != nil { - if fp.affiliate.customID != nil { - qry.Add("affiliate.customId", *fp.affiliate.customID) - } - if fp.affiliate.geoTargeting != nil { - qry.Add("affiliate.geoTargeting", *fp.affiliate.geoTargeting) - } - if fp.affiliate.networkID != nil { - qry.Add("affiliate.networkId", *fp.affiliate.networkID) - } - if fp.affiliate.trackingID != nil { - qry.Add("affiliate.trackingId", *fp.affiliate.trackingID) - } - } - if fp.buyerPostalCode != nil { - qry.Add("buyerPostalCode", *fp.buyerPostalCode) - } - if fp.paginationInput != nil { - if fp.paginationInput.entriesPerPage != nil { - qry.Add("paginationInput.entriesPerPage", *fp.paginationInput.entriesPerPage) - } - if fp.paginationInput.pageNumber != nil { - qry.Add("paginationInput.pageNumber", *fp.paginationInput.pageNumber) - } - } - if fp.sortOrder != nil { - qry.Add("sortOrder", *fp.sortOrder) - } - req.URL.RawQuery = qry.Encode() - return req, nil -} - -type findItemsByProductParams struct { - appID string - globalID *string - itemFilters []itemFilter - outputSelectors []string - product productID - affiliate *affiliate - buyerPostalCode *string - paginationInput *paginationInput - sortOrder *string -} - -type productID struct { - idType string - value string -} - -func (fp *findItemsByProductParams) validate(params map[string]string) error { - productIDType, ptOk := params["productId.@type"] - productValue, pOk := params["productId"] - if !ptOk || !pOk { - return ErrProductIDMissing - } - fp.product = productID{idType: productIDType, value: productValue} - err := fp.product.processProductID() - if err != nil { - return err - } - globalID, ok := params["Global-ID"] - if ok { - if err = validateGlobalID(globalID); err != nil { - return err - } - fp.globalID = &globalID - } - fp.itemFilters, err = processItemFilters(params) - if err != nil { - return err - } - fp.outputSelectors, err = processOutputSelectors(params) - if err != nil { - return err - } - fp.affiliate, err = processAffiliate(params) - if err != nil { - return err - } - buyerPostalCode, ok := params["buyerPostalCode"] - if ok { - if !isValidPostalCode(buyerPostalCode) { - return ErrInvalidPostalCode - } - fp.buyerPostalCode = &buyerPostalCode - } - fp.paginationInput, err = processPaginationInput(params) - if err != nil { - return err - } - sortOrder, ok := params["sortOrder"] - if ok { - if err := validateSortOrder(sortOrder, fp.itemFilters, fp.buyerPostalCode != nil); err != nil { - return err - } - fp.sortOrder = &sortOrder - } - return nil -} - -func (fp *findItemsByProductParams) newRequest(ctx context.Context, url string) (*http.Request, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) - } - qry := req.URL.Query() - if fp.globalID != nil { - qry.Add("Global-ID", *fp.globalID) - } - qry.Add("OPERATION-NAME", findItemsByProductOperationName) - qry.Add("SERVICE-VERSION", findingServiceVersion) - qry.Add("SECURITY-APPNAME", fp.appID) - qry.Add("RESPONSE-DATA-FORMAT", findingResponseDataFormat) - for i, f := range fp.itemFilters { - qry.Add(fmt.Sprintf("itemFilter(%d).name", i), f.name) - for j, v := range f.values { - qry.Add(fmt.Sprintf("itemFilter(%d).value(%d)", i, j), v) - } - if f.paramName != nil && f.paramValue != nil { - qry.Add(fmt.Sprintf("itemFilter(%d).paramName", i), *f.paramName) - qry.Add(fmt.Sprintf("itemFilter(%d).paramValue", i), *f.paramValue) - } - } - for i := range fp.outputSelectors { - qry.Add(fmt.Sprintf("outputSelector(%d)", i), fp.outputSelectors[i]) - } - qry.Add("productId.@type", fp.product.idType) - qry.Add("productId", fp.product.value) - if fp.affiliate != nil { - if fp.affiliate.customID != nil { - qry.Add("affiliate.customId", *fp.affiliate.customID) - } - if fp.affiliate.geoTargeting != nil { - qry.Add("affiliate.geoTargeting", *fp.affiliate.geoTargeting) - } - if fp.affiliate.networkID != nil { - qry.Add("affiliate.networkId", *fp.affiliate.networkID) - } - if fp.affiliate.trackingID != nil { - qry.Add("affiliate.trackingId", *fp.affiliate.trackingID) - } - } - if fp.buyerPostalCode != nil { - qry.Add("buyerPostalCode", *fp.buyerPostalCode) - } - if fp.paginationInput != nil { - if fp.paginationInput.entriesPerPage != nil { - qry.Add("paginationInput.entriesPerPage", *fp.paginationInput.entriesPerPage) - } - if fp.paginationInput.pageNumber != nil { - qry.Add("paginationInput.pageNumber", *fp.paginationInput.pageNumber) - } - } - if fp.sortOrder != nil { - qry.Add("sortOrder", *fp.sortOrder) - } - req.URL.RawQuery = qry.Encode() - return req, nil -} - -type findItemsInEBayStoresParams struct { - appID string - globalID *string - aspectFilters []aspectFilter - categoryIDs []string - itemFilters []itemFilter - keywords *string - outputSelectors []string - storeName *string - affiliate *affiliate - buyerPostalCode *string - paginationInput *paginationInput - sortOrder *string -} - -func (fp *findItemsInEBayStoresParams) validate(params map[string]string) error { - _, cOk := params["categoryId"] - _, csOk := params["categoryId(0)"] - _, kwOk := params["keywords"] - storeName, ok := params["storeName"] - if !cOk && !csOk && !kwOk && !ok { - return ErrCategoryIDKeywordsStoreNameMissing - } - if cOk || csOk { - categoryIDs, err := processCategoryIDs(params) - if err != nil { - return err - } - fp.categoryIDs = categoryIDs - } - if kwOk { - keywords, err := processKeywords(params) - if err != nil { - return err - } - fp.keywords = &keywords - } - if ok { - if err := validateStoreName(storeName); err != nil { - return err - } - fp.storeName = &storeName - } - globalID, ok := params["Global-ID"] - if ok { - if err := validateGlobalID(globalID); err != nil { - return err - } - fp.globalID = &globalID - } - aspectFilters, err := processAspectFilters(params) - if err != nil { - return err - } - fp.aspectFilters = aspectFilters - fp.itemFilters, err = processItemFilters(params) - if err != nil { - return err - } - fp.outputSelectors, err = processOutputSelectors(params) - if err != nil { - return err - } - fp.affiliate, err = processAffiliate(params) - if err != nil { - return err - } - buyerPostalCode, ok := params["buyerPostalCode"] - if ok { - if !isValidPostalCode(buyerPostalCode) { - return ErrInvalidPostalCode - } - fp.buyerPostalCode = &buyerPostalCode - } - fp.paginationInput, err = processPaginationInput(params) - if err != nil { - return err - } - sortOrder, ok := params["sortOrder"] - if ok { - if err := validateSortOrder(sortOrder, fp.itemFilters, fp.buyerPostalCode != nil); err != nil { - return err - } - fp.sortOrder = &sortOrder - } - return nil -} - -func (fp *findItemsInEBayStoresParams) newRequest(ctx context.Context, url string) (*http.Request, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidRequest, err) - } - qry := req.URL.Query() - if fp.globalID != nil { - qry.Add("Global-ID", *fp.globalID) - } - qry.Add("OPERATION-NAME", findItemsInEBayStoresOperationName) - qry.Add("SERVICE-VERSION", findingServiceVersion) - qry.Add("SECURITY-APPNAME", fp.appID) - qry.Add("RESPONSE-DATA-FORMAT", findingResponseDataFormat) - for i, f := range fp.aspectFilters { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectName", i), f.aspectName) - for j, v := range f.aspectValueNames { - qry.Add(fmt.Sprintf("aspectFilter(%d).aspectValueName(%d)", i, j), v) - } - } - for i := range fp.categoryIDs { - qry.Add(fmt.Sprintf("categoryId(%d)", i), fp.categoryIDs[i]) - } - for i, f := range fp.itemFilters { - qry.Add(fmt.Sprintf("itemFilter(%d).name", i), f.name) - for j, v := range f.values { - qry.Add(fmt.Sprintf("itemFilter(%d).value(%d)", i, j), v) - } - if f.paramName != nil && f.paramValue != nil { - qry.Add(fmt.Sprintf("itemFilter(%d).paramName", i), *f.paramName) - qry.Add(fmt.Sprintf("itemFilter(%d).paramValue", i), *f.paramValue) - } - } - if fp.keywords != nil { - qry.Add("keywords", *fp.keywords) - } - for i := range fp.outputSelectors { - qry.Add(fmt.Sprintf("outputSelector(%d)", i), fp.outputSelectors[i]) - } - if fp.storeName != nil { - qry.Add("storeName", *fp.storeName) - } - if fp.affiliate != nil { - if fp.affiliate.customID != nil { - qry.Add("affiliate.customId", *fp.affiliate.customID) - } - if fp.affiliate.geoTargeting != nil { - qry.Add("affiliate.geoTargeting", *fp.affiliate.geoTargeting) - } - if fp.affiliate.networkID != nil { - qry.Add("affiliate.networkId", *fp.affiliate.networkID) - } - if fp.affiliate.trackingID != nil { - qry.Add("affiliate.trackingId", *fp.affiliate.trackingID) - } - } - if fp.buyerPostalCode != nil { - qry.Add("buyerPostalCode", *fp.buyerPostalCode) - } - if fp.paginationInput != nil { - if fp.paginationInput.entriesPerPage != nil { - qry.Add("paginationInput.entriesPerPage", *fp.paginationInput.entriesPerPage) - } - if fp.paginationInput.pageNumber != nil { - qry.Add("paginationInput.pageNumber", *fp.paginationInput.pageNumber) - } - } - if fp.sortOrder != nil { - qry.Add("sortOrder", *fp.sortOrder) - } - req.URL.RawQuery = qry.Encode() - return req, nil -} - -func processCategoryIDs(params map[string]string) ([]string, error) { - categoryID, nonNumberedExists := params["categoryId"] - _, numberedExists := params["categoryId(0)"] - if nonNumberedExists && numberedExists { - return nil, ErrInvalidIndexSyntax - } - if nonNumberedExists { - if err := validateCategoryID(categoryID); err != nil { - return nil, err - } - return []string{categoryID}, nil - } - var categoryIDs []string - for i := 0; ; i++ { - cID, ok := params[fmt.Sprintf("categoryId(%d)", i)] - if !ok { - break - } - if err := validateCategoryID(cID); err != nil { - return nil, err - } - categoryIDs = append(categoryIDs, cID) - if len(categoryIDs) > maxCategoryIDs { - return nil, ErrMaxCategoryIDs - } - } - return categoryIDs, nil -} - -func validateCategoryID(id string) error { - if len(id) > maxCategoryIDLen { - return ErrInvalidCategoryIDLength - } - if _, err := strconv.Atoi(id); err != nil { - return fmt.Errorf("%w: %w", ErrInvalidCategoryID, err) - } - return nil -} - -func processKeywords(params map[string]string) (string, error) { - keywords, ok := params["keywords"] - if !ok { - return "", ErrKeywordsMissing - } - if len(keywords) < minKeywordsLen || len(keywords) > maxKeywordsLen { - return "", ErrInvalidKeywordsLength - } - individualKeywords := splitKeywords(keywords) - for _, k := range individualKeywords { - if len(k) > maxKeywordLen { - return "", ErrInvalidKeywordLength - } - } - return keywords, nil -} - -// Split keywords based on special characters acting as search operators. -// See https://developer.ebay.com/api-docs/user-guides/static/finding-user-guide/finding-searching-by-keywords.html. -func splitKeywords(keywords string) []string { - const specialChars = ` ,()"-*@+` - return strings.FieldsFunc(keywords, func(r rune) bool { - return strings.ContainsRune(specialChars, r) - }) -} - -const ( - // Product ID type enumeration values from the eBay documentation. - // See https://developer.ebay.com/Devzone/finding/CallRef/types/ProductId.html. - referenceID = "ReferenceID" - isbn = "ISBN" - upc = "UPC" - ean = "EAN" -) - -func (p *productID) processProductID() error { - switch p.idType { - case referenceID: - if len(p.value) < 1 { - return ErrInvalidProductIDLength - } - case isbn: - if len(p.value) != isbnShortLen && len(p.value) != isbnLongLen { - return ErrInvalidISBNLength - } - if !isValidISBN(p.value) { - return ErrInvalidISBN - } - case upc: - if len(p.value) != upcLen { - return ErrInvalidUPCLength - } - if !isValidEAN(p.value) { - return ErrInvalidUPC - } - case ean: - if len(p.value) != eanShortLen && len(p.value) != eanLongLen { - return ErrInvalidEANLength - } - if !isValidEAN(p.value) { - return ErrInvalidEAN - } - default: - return fmt.Errorf("%w: %q", ErrUnsupportedProductIDType, p.idType) - } - return nil -} - -func isValidISBN(isbn string) bool { - if len(isbn) == isbnShortLen { - var sum, acc int - for i, r := range isbn { - digit := int(r - '0') - if !isDigit(digit) { - if i == 9 && r == 'X' { - digit = 10 - } else { - return false - } - } - - acc += digit - sum += acc - } - return sum%11 == 0 - } - - const altMultiplier = 3 - var sum int - for i, r := range isbn { - digit := int(r - '0') - if !isDigit(digit) { - return false - } - if i%2 == 0 { - sum += digit - } else { - sum += digit * altMultiplier - } - } - return sum%10 == 0 -} - -func isDigit(digit int) bool { - return digit >= 0 && digit <= 9 -} - -func isValidEAN(ean string) bool { - const altMultiplier = 3 - var sum int - for i, r := range ean[:len(ean)-1] { - digit := int(r - '0') - if !isDigit(digit) { - return false - } - switch { - case len(ean) == eanShortLen && i%2 == 0, - len(ean) == eanLongLen && i%2 != 0, - len(ean) == upcLen && i%2 == 0: - sum += digit * altMultiplier - default: - sum += digit - } - } - checkDigit := int(ean[len(ean)-1] - '0') - if !isDigit(checkDigit) { - return false - } - return (sum+checkDigit)%10 == 0 -} - -func validateStoreName(storeName string) error { - if storeName == "" { - return ErrInvalidStoreNameLength - } - if strings.Contains(storeName, "&") && !strings.Contains(storeName, "&") { - return ErrInvalidStoreNameAmpersand - } - return nil -} - -// Valid Global ID values from the eBay documentation. -// See https://developer.ebay.com/devzone/finding/CallRef/Enums/GlobalIdList.html. -var validGlobalIDs = []string{ - "EBAY-AT", - "EBAY-AU", - "EBAY-CH", - "EBAY-DE", - "EBAY-ENCA", - "EBAY-ES", - "EBAY-FR", - "EBAY-FRBE", - "EBAY-FRCA", - "EBAY-GB", - "EBAY-HK", - "EBAY-IE", - "EBAY-IN", - "EBAY-IT", - "EBAY-MOTOR", - "EBAY-MY", - "EBAY-NL", - "EBAY-NLBE", - "EBAY-PH", - "EBAY-PL", - "EBAY-SG", - "EBAY-US", -} - -func validateGlobalID(globalID string) error { - if !slices.Contains(validGlobalIDs, globalID) { - return fmt.Errorf("%w: %q", ErrInvalidGlobalID, globalID) - } - return nil -} - -// Valid OutputSelectorType values from the eBay documentation. -// See https://developer.ebay.com/devzone/finding/callref/types/OutputSelectorType.html. -var validOutputSelectors = []string{ - "AspectHistogram", - "CategoryHistogram", - "ConditionHistogram", - "GalleryInfo", - "PictureURLLarge", - "PictureURLSuperSize", - "SellerInfo", - "StoreInfo", - "UnitPriceInfo", -} - -func processOutputSelectors(params map[string]string) ([]string, error) { - outputSelector, nonNumberedExists := params["outputSelector"] - _, numberedExists := params["outputSelector(0)"] - if nonNumberedExists && numberedExists { - return nil, ErrInvalidIndexSyntax - } - if nonNumberedExists { - if !slices.Contains(validOutputSelectors, outputSelector) { - return nil, ErrInvalidOutputSelector - } - return []string{outputSelector}, nil - } - var os []string - for i := 0; ; i++ { - s, ok := params[fmt.Sprintf("outputSelector(%d)", i)] - if !ok { - break - } - if !slices.Contains(validOutputSelectors, s) { - return nil, ErrInvalidOutputSelector - } - os = append(os, s) - } - return os, nil -} - -func processAffiliate(params map[string]string) (*affiliate, error) { - var aff affiliate - customID, ok := params["affiliate.customId"] - if ok { - if len(customID) > maxCustomIDLen { - return nil, ErrInvalidCustomIDLength - } - aff.customID = &customID - } - geoTargeting, ok := params["affiliate.geoTargeting"] - if ok { - if geoTargeting != trueValue && geoTargeting != falseValue { - return nil, fmt.Errorf("%w: %q", ErrInvalidBooleanValue, geoTargeting) - } - aff.geoTargeting = &geoTargeting - } - networkID, nOk := params["affiliate.networkId"] - trackingID, tOk := params["affiliate.trackingId"] - if nOk != tOk { - return nil, ErrIncompleteAffiliateParams - } - if !nOk { - return &aff, nil - } - nID, err := strconv.Atoi(networkID) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidNetworkID, err) - } - if nID < beFreeID || nID > ebayPartnerNetworkID { - return nil, ErrInvalidNetworkIDRange - } - if nID == ebayPartnerNetworkID { - if err := validateTrackingID(trackingID); err != nil { - return nil, err - } - } - aff.networkID = &networkID - aff.trackingID = &trackingID - return &aff, nil -} - -func validateTrackingID(trackingID string) error { - if _, err := strconv.Atoi(trackingID); err != nil { - return fmt.Errorf("%w: %w", ErrInvalidTrackingID, err) - } - const maxCampIDLen = 10 - if len(trackingID) != maxCampIDLen { - return ErrInvalidCampaignID - } - return nil -} - -func isValidPostalCode(postalCode string) bool { - const minPostalCodeLen = 3 - return len(postalCode) >= minPostalCodeLen -} - -func processPaginationInput(params map[string]string) (*paginationInput, error) { - entriesPerPage, eOk := params["paginationInput.entriesPerPage"] - pageNumber, pOk := params["paginationInput.pageNumber"] - if !eOk && !pOk { - return &paginationInput{}, nil - } - var pInput paginationInput - if eOk { - v, err := strconv.Atoi(entriesPerPage) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidEntriesPerPage, err) - } - if v < minPaginationValue || v > maxPaginationValue { - return nil, ErrInvalidEntriesPerPageRange - } - pInput.entriesPerPage = &entriesPerPage - } - if pOk { - v, err := strconv.Atoi(pageNumber) - if err != nil { - return nil, fmt.Errorf("%w: %w", ErrInvalidPageNumber, err) - } - if v < minPaginationValue || v > maxPaginationValue { - return nil, ErrInvalidPageNumberRange - } - pInput.pageNumber = &pageNumber - } - return &pInput, nil -} - -const ( - // SortOrderType enumeration values from the eBay documentation. - // See https://developer.ebay.com/devzone/finding/CallRef/types/SortOrderType.html. - bestMatch = "BestMatch" - bidCountFewest = "BidCountFewest" - bidCountMost = "BidCountMost" - countryAscending = "CountryAscending" - countryDescending = "CountryDescending" - currentPriceHighest = "CurrentPriceHighest" - distanceNearest = "DistanceNearest" - endTimeSoonest = "EndTimeSoonest" - pricePlusShippingHighest = "PricePlusShippingHighest" - pricePlusShippingLowest = "PricePlusShippingLowest" - startTimeNewest = "StartTimeNewest" - watchCountDecreaseSort = "WatchCountDecreaseSort" -) - -func validateSortOrder(sortOrder string, itemFilters []itemFilter, hasBuyerPostalCode bool) error { - switch sortOrder { - case bestMatch, countryAscending, countryDescending, currentPriceHighest, endTimeSoonest, - pricePlusShippingHighest, pricePlusShippingLowest, startTimeNewest, watchCountDecreaseSort: - return nil - case bidCountFewest, bidCountMost: - hasAuctionListing := slices.ContainsFunc(itemFilters, func(f itemFilter) bool { - return f.name == listingType && slices.Contains(f.values, "Auction") - }) - if !hasAuctionListing { - return ErrAuctionListingMissing - } - case distanceNearest: - if !hasBuyerPostalCode { - return ErrBuyerPostalCodeMissing - } - default: - return ErrUnsupportedSortOrderType - } - return nil -} diff --git a/response.go b/response.go index b78867f..86e1c3d 100644 --- a/response.go +++ b/response.go @@ -1,71 +1,32 @@ -// Copyright 2023 Matthew P. Dargan. -// SPDX-License-Identifier: Apache-2.0 - package ebay import "time" -// A ResultProvider represents results from eBay Finding API endpoints. -type ResultProvider interface { - // Results returns results from an eBay Finding API endpoint. - Results() []FindItemsResponse +// FindItemsAdvancedResponse represents the response from [FindingClient.FindItemsAdvanced]. +type FindItemsAdvancedResponse struct { + ItemsResponse []FindItemsResponse `json:"findItemsAdvancedResponse"` } -// FindItemsByCategoriesResponse represents the response from [FindingClient.FindItemsByCategories]. -type FindItemsByCategoriesResponse struct { +// FindItemsByCategoryResponse represents the response from [FindingClient.FindItemsByCategory]. +type FindItemsByCategoryResponse struct { ItemsResponse []FindItemsResponse `json:"findItemsByCategoryResponse"` } -// Results returns results from the eBay Finding API findItemsByCategory endpoint. -// See https://developer.ebay.com/devzone/finding/CallRef/findItemsByCategory.html. -func (r FindItemsByCategoriesResponse) Results() []FindItemsResponse { - return r.ItemsResponse -} - // FindItemsByKeywordsResponse represents the response from [FindingClient.FindItemsByKeywords]. type FindItemsByKeywordsResponse struct { ItemsResponse []FindItemsResponse `json:"findItemsByKeywordsResponse"` } -// Results returns results from the eBay Finding API findItemsByKeywords endpoint. -// See https://developer.ebay.com/devzone/finding/CallRef/findItemsByKeywords.html. -func (r FindItemsByKeywordsResponse) Results() []FindItemsResponse { - return r.ItemsResponse -} - -// FindItemsAdvancedResponse represents the response from [FindingClient.FindItemsAdvanced]. -type FindItemsAdvancedResponse struct { - ItemsResponse []FindItemsResponse `json:"findItemsAdvancedResponse"` -} - -// Results returns results from the eBay Finding API findItemsAdvanced endpoint. -// See https://developer.ebay.com/devzone/finding/CallRef/findItemsAdvanced.html. -func (r FindItemsAdvancedResponse) Results() []FindItemsResponse { - return r.ItemsResponse -} - // FindItemsByProductResponse represents the response from [FindingClient.FindItemsByProduct]. type FindItemsByProductResponse struct { ItemsResponse []FindItemsResponse `json:"findItemsByProductResponse"` } -// Results returns results from the eBay Finding API findItemsByProduct endpoint. -// See https://developer.ebay.com/devzone/finding/CallRef/findItemsByProduct.html. -func (r FindItemsByProductResponse) Results() []FindItemsResponse { - return r.ItemsResponse -} - // FindItemsInEBayStoresResponse represents the response from [FindingClient.FindItemsInEBayStores]. type FindItemsInEBayStoresResponse struct { ItemsResponse []FindItemsResponse `json:"findItemsIneBayStoresResponse"` } -// Results returns results from the eBay Finding API findItemsIneBayStores endpoint. -// See https://developer.ebay.com/devzone/finding/CallRef/findItemsIneBayStores.html. -func (r FindItemsInEBayStoresResponse) Results() []FindItemsResponse { - return r.ItemsResponse -} - // FindItemsResponse represents the base response container for all Finding Service operations. // // See [BaseServiceResponse] for details about generic response fields.