mirror of
https://github.com/cubixle/ebay.git
synced 2026-04-30 16:38:44 +01:00
Setup client
This commit is contained in:
@@ -1 +1,3 @@
|
|||||||
# ebay
|
# ebay
|
||||||
|
|
||||||
|
ebay is a Go client library for accessing the [eBay API](https://developer.ebay.com/).
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package ebay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BrowseService handles communication with the Browse API
|
||||||
|
//
|
||||||
|
// eBay API docs: https://developer.ebay.com/api-docs/buy/browse/overview.html
|
||||||
|
type BrowseService service
|
||||||
|
|
||||||
|
// OptContextualLocation adds the header containing contextualLocation.
|
||||||
|
// It is strongly recommended that you use it when submitting Browse API methods.
|
||||||
|
//
|
||||||
|
// eBay API docs: https://developer.ebay.com/api-docs/buy/static/api-browse.html#Headers
|
||||||
|
func OptContextualLocation(country, zip string) func(*http.Request) {
|
||||||
|
return func(req *http.Request) {
|
||||||
|
// X-EBAY-C-ENDUSERCTX: contextualLocation=country=US,zip=19406
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item represents a eBay item.
|
||||||
|
type Item struct{}
|
||||||
|
|
||||||
|
// GetItem retrieves the details of a specific item.
|
||||||
|
//
|
||||||
|
// 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("item/%s", itemID)
|
||||||
|
req, err := s.client.NewRequest(http.MethodGet, u, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return Item{}, err
|
||||||
|
}
|
||||||
|
var it Item
|
||||||
|
return it, s.client.Do(ctx, req, &it)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ebay_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jybp/ebay"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOptContextualLocation(t *testing.T) {
|
||||||
|
r, _ := http.NewRequest("", "", nil)
|
||||||
|
ebay.OptContextualLocation("US", "19406")(r)
|
||||||
|
assert.Equal(t, "country%3DUS%2Czip%3D19406", r.Header.Get("X-EBAY-C-ENDUSERCTX"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package ebay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseURL = "https://api.ebay.com/"
|
||||||
|
sandboxBaseURL = "https://api.sandbox.ebay.com/"
|
||||||
|
|
||||||
|
headerEndUserCtx = "X-EBAY-C-ENDUSERCTX"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuyAPI regroups the eBay Buy APIs.
|
||||||
|
//
|
||||||
|
// eBay API docs: https://developer.ebay.com/api-docs/buy/static/buy-landing.html
|
||||||
|
type BuyAPI struct {
|
||||||
|
Browse *BrowseService
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client manages communication with the eBay API.
|
||||||
|
type Client struct {
|
||||||
|
client *http.Client // Used to make actual API requests.
|
||||||
|
baseURL *url.URL // Base URL for API requests.
|
||||||
|
|
||||||
|
// eBay APIs.
|
||||||
|
Buy BuyAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new eBay API client.
|
||||||
|
// If a nil httpClient is provided, http.DefaultClient will be used.
|
||||||
|
func NewClient(httpclient *http.Client) *Client {
|
||||||
|
return newClient(httpclient, baseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSandboxClient returns a new eBay sandbox API client.
|
||||||
|
// If a nil httpClient is provided, http.DefaultClient will be used.
|
||||||
|
func NewSandboxClient(httpclient *http.Client) *Client {
|
||||||
|
return newClient(httpclient, sandboxBaseURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCustomClient returns a new custom eBay API client.
|
||||||
|
// BaseURL should have a trailing slash.
|
||||||
|
// If a nil httpClient is provided, http.DefaultClient will be used.
|
||||||
|
func NewCustomClient(httpclient *http.Client, baseURL string) (*Client, error) {
|
||||||
|
if !strings.HasSuffix(baseURL, "/") {
|
||||||
|
return nil, fmt.Errorf("BaseURL %s must have a trailing slash", baseURL)
|
||||||
|
}
|
||||||
|
return newClient(httpclient, baseURL), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(httpclient *http.Client, baseURL string) *Client {
|
||||||
|
if httpclient == nil {
|
||||||
|
httpclient = http.DefaultClient
|
||||||
|
}
|
||||||
|
url, _ := url.Parse(baseURL)
|
||||||
|
c := &Client{client: httpclient, baseURL: url}
|
||||||
|
c.Buy = BuyAPI{
|
||||||
|
Browse: (*BrowseService)(&service{c}),
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opt describes functional options for the eBay API.
|
||||||
|
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) {
|
||||||
|
u, err := c.baseURL.Parse(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(method, u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(req)
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do sends an API reauest and stores the JSON decoded value into v.
|
||||||
|
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) error {
|
||||||
|
resp, err := c.client.Do(req.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err := CheckResponse(resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.NewDecoder(resp.Body).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ErrorData reports one or more errors caused by an API request.
|
||||||
|
//
|
||||||
|
// eBay API docs: https://developer.ebay.com/api-docs/static/handling-error-messages.html
|
||||||
|
type ErrorData struct {
|
||||||
|
Errors []struct {
|
||||||
|
ErrorID int `json:"errorId,omitempty"`
|
||||||
|
Domain string `json:"domain,omitempty"`
|
||||||
|
SubDomain string `json:"subDomain,omitempty"`
|
||||||
|
Category string `json:"category,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
LongMessage string `json:"longMessage,omitempty"`
|
||||||
|
InputRefIds []string `json:"inputRefIds,omitempty"`
|
||||||
|
OuputRefIds []string `json:"outputRefIds,omitempty"`
|
||||||
|
Parameters []struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
} `json:"parameters,omitempty"`
|
||||||
|
} `json:"errors,omitempty"`
|
||||||
|
Response *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrorData) Error() string {
|
||||||
|
return fmt.Sprintf("%s %s: %d %+v", e.Response.Request.Method, e.Response.Request.URL,
|
||||||
|
e.Response.StatusCode, e.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckResponse checks the API response for errors, and returns them if present.
|
||||||
|
func CheckResponse(resp *http.Response) error {
|
||||||
|
if s := resp.StatusCode; 200 <= s && s < 300 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errorData := &ErrorData{Response: resp}
|
||||||
|
_ = json.NewDecoder(resp.Body).Decode(errorData)
|
||||||
|
return errorData
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package ebay_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jybp/ebay"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRequest(t *testing.T) {
|
||||||
|
testOpt := func(r *http.Request) {
|
||||||
|
r.URL.RawQuery = "q=1"
|
||||||
|
}
|
||||||
|
client, _ := ebay.NewCustomClient(nil, "https://api.ebay.com/")
|
||||||
|
r, _ := client.NewRequest(http.MethodPost, "test", testOpt)
|
||||||
|
assert.Equal(t, "https://api.ebay.com/test?q=1", fmt.Sprint(r.URL))
|
||||||
|
assert.Equal(t, http.MethodPost, r.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckResponseNoError(t *testing.T) {
|
||||||
|
resp := &http.Response{StatusCode: 200}
|
||||||
|
assert.Nil(t, ebay.CheckResponse(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckResponse(t *testing.T) {
|
||||||
|
body := ` {
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"errorId": 15008,
|
||||||
|
"domain": "API_ORDER",
|
||||||
|
"subDomain": "subdomain",
|
||||||
|
"category": "REQUEST",
|
||||||
|
"message": "Invalid Field : itemId.",
|
||||||
|
"longMessage": "longMessage",
|
||||||
|
"inputRefIds": [
|
||||||
|
"$.lineItemInputs[0].itemId"
|
||||||
|
],
|
||||||
|
"outputRefIds": [
|
||||||
|
"outputRefId"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "itemId",
|
||||||
|
"value": "2200077988|0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
resp := &http.Response{StatusCode: 400, Body: ioutil.NopCloser(bytes.NewBufferString(body))}
|
||||||
|
err, ok := ebay.CheckResponse(resp).(*ebay.ErrorData)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, 1, len(err.Errors))
|
||||||
|
assert.Equal(t, 15008, err.Errors[0].ErrorID)
|
||||||
|
assert.Equal(t, "API_ORDER", err.Errors[0].Domain)
|
||||||
|
assert.Equal(t, "subdomain", err.Errors[0].SubDomain)
|
||||||
|
assert.Equal(t, "REQUEST", err.Errors[0].Category)
|
||||||
|
assert.Equal(t, "Invalid Field : itemId.", err.Errors[0].Message)
|
||||||
|
assert.Equal(t, "longMessage", err.Errors[0].LongMessage)
|
||||||
|
assert.Equal(t, []string{"$.lineItemInputs[0].itemId"}, err.Errors[0].InputRefIds)
|
||||||
|
assert.Equal(t, []string{"outputRefId"}, err.Errors[0].OuputRefIds)
|
||||||
|
assert.Equal(t, "itemId", err.Errors[0].Parameters[0].Name)
|
||||||
|
assert.Equal(t, "2200077988|0", err.Errors[0].Parameters[0].Value)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user