diff --git a/README.md b/README.md index e2546fc..112e9de 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # ebay -ebay is a Go client library for accessing the [eBay API](https://developer.ebay.com/). \ No newline at end of file +ebay is a Go client library for accessing the [eBay API](https://developer.ebay.com/). + + +# Thanks + +- https://github.com/google/go-github for their library design. \ No newline at end of file diff --git a/browse.go b/browse.go index bffd9ca..c54eef8 100644 --- a/browse.go +++ b/browse.go @@ -28,13 +28,287 @@ func OptBrowseContextualLocation(country, zip string) func(*http.Request) { } // Item represents an eBay item. -type Item struct{} +type Item struct { + AdditionalImages []struct { + Height string `json:"height"` + ImageURL string `json:"imageUrl"` + Width string `json:"width"` + } `json:"additionalImages"` + AdultOnly string `json:"adultOnly"` + AgeGroup string `json:"ageGroup"` + BidCount string `json:"bidCount"` + Brand string `json:"brand"` + BuyingOptions []string `json:"buyingOptions"` + CategoryID string `json:"categoryId"` + CategoryPath string `json:"categoryPath"` + Color string `json:"color"` + Condition string `json:"condition"` + ConditionID string `json:"conditionId"` + CurrentBidPrice struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"currentBidPrice"` + Description string `json:"description"` + EnabledForGuestCheckout string `json:"enabledForGuestCheckout"` + EnergyEfficiencyClass string `json:"energyEfficiencyClass"` + Epid string `json:"epid"` + EstimatedAvailabilities []struct { + AvailabilityThreshold string `json:"availabilityThreshold"` + AvailabilityThresholdType string `json:"availabilityThresholdType"` + DeliveryOptions []string `json:"deliveryOptions"` + EstimatedAvailabilityStatus string `json:"estimatedAvailabilityStatus"` + EstimatedAvailableQuantity string `json:"estimatedAvailableQuantity"` + EstimatedSoldQuantity string `json:"estimatedSoldQuantity"` + } `json:"estimatedAvailabilities"` + Gender string `json:"gender"` + Gtin string `json:"gtin"` + Image struct { + Height string `json:"height"` + ImageURL string `json:"imageUrl"` + Width string `json:"width"` + } `json:"image"` + InferredEpid string `json:"inferredEpid"` + ItemAffiliateWebURL string `json:"itemAffiliateWebUrl"` + ItemEndDate string `json:"itemEndDate"` + ItemID string `json:"itemId"` + ItemLocation struct { + AddressLine1 string `json:"addressLine1"` + AddressLine2 string `json:"addressLine2"` + City string `json:"city"` + Country string `json:"country"` + County string `json:"county"` + PostalCode string `json:"postalCode"` + StateOrProvince string `json:"stateOrProvince"` + } `json:"itemLocation"` + ItemWebURL string `json:"itemWebUrl"` + LocalizedAspects []struct { + Name string `json:"name"` + Type string `json:"type"` + Value string `json:"value"` + } `json:"localizedAspects"` + MarketingPrice struct { + DiscountAmount struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"discountAmount"` + DiscountPercentage string `json:"discountPercentage"` + OriginalPrice struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"originalPrice"` + } `json:"marketingPrice"` + Material string `json:"material"` + MinimumPriceToBid struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"minimumPriceToBid"` + Mpn string `json:"mpn"` + Pattern string `json:"pattern"` + Price struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"price"` + PriceDisplayCondition string `json:"priceDisplayCondition"` + PrimaryItemGroup struct { + ItemGroupAdditionalImages []struct { + Height string `json:"height"` + ImageURL string `json:"imageUrl"` + Width string `json:"width"` + } `json:"itemGroupAdditionalImages"` + ItemGroupHref string `json:"itemGroupHref"` + ItemGroupID string `json:"itemGroupId"` + ItemGroupImage struct { + Height string `json:"height"` + ImageURL string `json:"imageUrl"` + Width string `json:"width"` + } `json:"itemGroupImage"` + ItemGroupTitle string `json:"itemGroupTitle"` + ItemGroupType string `json:"itemGroupType"` + } `json:"primaryItemGroup"` + PrimaryProductReviewRating struct { + AverageRating string `json:"averageRating"` + RatingHistograms []struct { + Count string `json:"count"` + Rating string `json:"rating"` + } `json:"ratingHistograms"` + ReviewCount string `json:"reviewCount"` + } `json:"primaryProductReviewRating"` + Product struct { + AdditionalImages []struct { + Height string `json:"height"` + ImageURL string `json:"imageUrl"` + Width string `json:"width"` + } `json:"additionalImages"` + AdditionalProductIdentities []struct { + ProductIdentity []struct { + IdentifierType string `json:"identifierType"` + IdentifierValue string `json:"identifierValue"` + } `json:"productIdentity"` + } `json:"additionalProductIdentities"` + AspectGroups []struct { + Aspects []struct { + LocalizedName string `json:"localizedName"` + LocalizedValues []string `json:"localizedValues"` + } `json:"aspects"` + LocalizedGroupName string `json:"localizedGroupName"` + } `json:"aspectGroups"` + Brand string `json:"brand"` + Description string `json:"description"` + Gtins []string `json:"gtins"` + Image struct { + Height string `json:"height"` + ImageURL string `json:"imageUrl"` + Width string `json:"width"` + } `json:"image"` + Mpns []string `json:"mpns"` + Title string `json:"title"` + } `json:"product"` + ProductFicheWebURL string `json:"productFicheWebUrl"` + QuantityLimitPerBuyer string `json:"quantityLimitPerBuyer"` + ReservePriceMet string `json:"reservePriceMet"` + ReturnTerms struct { + ExtendedHolidayReturnsOffered string `json:"extendedHolidayReturnsOffered"` + RefundMethod string `json:"refundMethod"` + RestockingFeePercentage string `json:"restockingFeePercentage"` + ReturnInstructions string `json:"returnInstructions"` + ReturnMethod string `json:"returnMethod"` + ReturnPeriod struct { + Unit string `json:"unit"` + Value string `json:"value"` + } `json:"returnPeriod"` + ReturnsAccepted string `json:"returnsAccepted"` + ReturnShippingCostPayer string `json:"returnShippingCostPayer"` + } `json:"returnTerms"` + Seller struct { + FeedbackPercentage string `json:"feedbackPercentage"` + FeedbackScore string `json:"feedbackScore"` + SellerAccountType string `json:"sellerAccountType"` + SellerLegalInfo struct { + Email string `json:"email"` + Fax string `json:"fax"` + Imprint string `json:"imprint"` + LegalContactFirstName string `json:"legalContactFirstName"` + LegalContactLastName string `json:"legalContactLastName"` + Name string `json:"name"` + Phone string `json:"phone"` + RegistrationNumber string `json:"registrationNumber"` + SellerProvidedLegalAddress struct { + AddressLine1 string `json:"addressLine1"` + AddressLine2 string `json:"addressLine2"` + City string `json:"city"` + Country string `json:"country"` + CountryName string `json:"countryName"` + County string `json:"county"` + PostalCode string `json:"postalCode"` + StateOrProvince string `json:"stateOrProvince"` + } `json:"sellerProvidedLegalAddress"` + TermsOfService string `json:"termsOfService"` + VatDetails []struct { + IssuingCountry string `json:"issuingCountry"` + VatID string `json:"vatId"` + } `json:"vatDetails"` + } `json:"sellerLegalInfo"` + Username string `json:"username"` + } `json:"seller"` + SellerItemRevision string `json:"sellerItemRevision"` + ShippingOptions []struct { + AdditionalShippingCostPerUnit struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"additionalShippingCostPerUnit"` + CutOffDateUsedForEstimate string `json:"cutOffDateUsedForEstimate"` + MaxEstimatedDeliveryDate string `json:"maxEstimatedDeliveryDate"` + MinEstimatedDeliveryDate string `json:"minEstimatedDeliveryDate"` + QuantityUsedForEstimate string `json:"quantityUsedForEstimate"` + ShippingCarrierCode string `json:"shippingCarrierCode"` + ShippingCost struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"shippingCost"` + ShippingCostType string `json:"shippingCostType"` + ShippingServiceCode string `json:"shippingServiceCode"` + ShipToLocationUsedForEstimate struct { + Country string `json:"country"` + PostalCode string `json:"postalCode"` + } `json:"shipToLocationUsedForEstimate"` + TrademarkSymbol string `json:"trademarkSymbol"` + Type string `json:"type"` + } `json:"shippingOptions"` + ShipToLocations struct { + RegionExcluded []struct { + RegionName string `json:"regionName"` + RegionType string `json:"regionType"` + } `json:"regionExcluded"` + RegionIncluded []struct { + RegionName string `json:"regionName"` + RegionType string `json:"regionType"` + } `json:"regionIncluded"` + } `json:"shipToLocations"` + ShortDescription string `json:"shortDescription"` + Size string `json:"size"` + SizeSystem string `json:"sizeSystem"` + SizeType string `json:"sizeType"` + Subtitle string `json:"subtitle"` + Taxes []struct { + EbayCollectAndRemitTax string `json:"ebayCollectAndRemitTax"` + IncludedInPrice string `json:"includedInPrice"` + ShippingAndHandlingTaxed string `json:"shippingAndHandlingTaxed"` + TaxJurisdiction struct { + Region struct { + RegionName string `json:"regionName"` + RegionType string `json:"regionType"` + } `json:"region"` + TaxJurisdictionID string `json:"taxJurisdictionId"` + } `json:"taxJurisdiction"` + TaxPercentage string `json:"taxPercentage"` + TaxType string `json:"taxType"` + } `json:"taxes"` + Title string `json:"title"` + TopRatedBuyingExperience string `json:"topRatedBuyingExperience"` + UniqueBidderCount string `json:"uniqueBidderCount"` + UnitPrice struct { + ConvertedFromCurrency string `json:"convertedFromCurrency"` + ConvertedFromValue string `json:"convertedFromValue"` + Currency string `json:"currency"` + Value string `json:"value"` + } `json:"unitPrice"` + UnitPricingMeasure string `json:"unitPricingMeasure"` + Warnings []struct { + Category string `json:"category"` + Domain string `json:"domain"` + ErrorID string `json:"errorId"` + InputRefIds []string `json:"inputRefIds"` + LongMessage string `json:"longMessage"` + Message string `json:"message"` + OutputRefIds []string `json:"outputRefIds"` + Parameters []struct { + Name string `json:"name"` + Value string `json:"value"` + } `json:"parameters"` + Subdomain string `json:"subdomain"` + } `json:"warnings"` +} // GetItem retrieves the details of a specific item. +// Fieldgroups valid values are "PRODUCT" and "COMPACT". // // eBay API docs: https://developer.ebay.com/api-docs/buy/browse/resources/item/methods/getItem -func (s *BrowseService) GetItem(ctx context.Context, itemID string, opts ...Opt) (Item, error) { - u := fmt.Sprintf("buy/browse/v1/item/%s", itemID) +func (s *BrowseService) GetItem(ctx context.Context, itemID string, fieldgroups string, opts ...Opt) (Item, error) { + u := fmt.Sprintf("buy/browse/v1/item/%s?fieldgroups=%s", itemID, fieldgroups) req, err := s.client.NewRequest(http.MethodGet, u, opts...) if err != nil { return Item{}, err diff --git a/browse_test.go b/browse_test.go index 6f84edc..3eebd4f 100644 --- a/browse_test.go +++ b/browse_test.go @@ -1,6 +1,8 @@ package ebay_test import ( + "context" + "fmt" "net/http" "testing" @@ -20,3 +22,17 @@ func TestOptBrowseContextualLocationExistingHeader(t *testing.T) { ebay.OptBrowseContextualLocation("US", "19406")(r) assert.Equal(t, "affiliateCampaignId=1,contextualLocation=country%3DUS%2Czip%3D19406", r.Header.Get("X-EBAY-C-ENDUSERCTX")) } + +func TestGetItem(t *testing.T) { + client, mux, teardown := setup(t) + defer teardown() + + mux.HandleFunc("/buy/browse/v1/item/v1|202117468662|0", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{"itemId": "v1|202117468662|0", "title": "%s"}`, r.URL.Query().Get("fieldgroups")) + }) + + item, err := client.Buy.Browse.GetItem(context.Background(), "v1|202117468662|0", "COMPACT") + assert.Nil(t, err) + assert.Equal(t, "v1|202117468662|0", item.ItemID) + assert.Equal(t, "COMPACT", item.Title) +} diff --git a/ebay.go b/ebay.go index d3e5a95..2667787 100644 --- a/ebay.go +++ b/ebay.go @@ -3,6 +3,7 @@ package ebay import ( "context" "encoding/json" + "errors" "fmt" "net/http" "net/url" @@ -76,6 +77,9 @@ type Opt func(*http.Request) // NewRequest creates an API request. // url should always be specified without a preceding slash. func (c *Client) NewRequest(method, url string, opts ...Opt) (*http.Request, error) { + if strings.HasPrefix(url, "/") { + return nil, errors.New("url should always be specified without a preceding slash") + } u, err := c.baseURL.Parse(url) if err != nil { return nil, err diff --git a/ebay_test.go b/ebay_test.go index 2228700..3d2f311 100644 --- a/ebay_test.go +++ b/ebay_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/http/httptest" "testing" "github.com/jybp/ebay" @@ -66,3 +67,15 @@ func TestCheckResponse(t *testing.T) { assert.Equal(t, "itemId", err.Errors[0].Parameters[0].Name) assert.Equal(t, "2200077988|0", err.Errors[0].Parameters[0].Value) } + +// setup sets up a test HTTP server +func setup(t *testing.T) (client *ebay.Client, mux *http.ServeMux, teardown func()) { + mux = http.NewServeMux() + server := httptest.NewServer(mux) + var err error + client, err = ebay.NewCustomClient(nil, server.URL+"/") + if err != nil { + t.Fatal(err) + } + return client, mux, server.Close +} diff --git a/test/integration/browse_test.go b/test/integration/ebay_test.go similarity index 90% rename from test/integration/browse_test.go rename to test/integration/ebay_test.go index 7c65755..1538ca6 100644 --- a/test/integration/browse_test.go +++ b/test/integration/ebay_test.go @@ -1,4 +1,5 @@ // +build integration + package integration import ( @@ -28,8 +29,8 @@ func init() { client = ebay.NewSandboxClient(conf.Client(context.Background())) } +// https://developer.ebay.com/my/api_test_tool?index=0&api=browse&call=item_summary_search__GET&variation=json func TestAuthorization(t *testing.T) { - // https://developer.ebay.com/my/api_test_tool?index=0&api=browse&call=item_summary_search__GET&variation=json req, err := client.NewRequest("GET", "buy/browse/v1/item_summary/search?q=test") if err != nil { t.Fatal(err)