From 816eeef808fdf5bd35d65402e988b9eaa733bc98 Mon Sep 17 00:00:00 2001 From: Steven Santos Erenst Date: Sun, 15 Dec 2024 02:52:47 -0800 Subject: [PATCH] Add version to User-Agent header Scryfall has requested that the version is included in the User-Agent header. Lets do that to avoid future trouble. Also add the ability to customize the User-Agent header. Fixes #31 --- CHANGELOG.md | 6 ++++++ scryfall.go | 19 ++++++++++++++++--- scryfall_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..637267c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 0.6.0 + +* Add version to User-Agent header +* Add WithUserAgent option to enable customization of the User-Agent header diff --git a/scryfall.go b/scryfall.go index c24a0f0..07ce887 100644 --- a/scryfall.go +++ b/scryfall.go @@ -16,10 +16,12 @@ import ( ) const ( + Version = "0.6.0" + defaultBaseURL = "https://api.scryfall.com" + defaultUserAgent = "go-scryfall/" + Version defaultTimeout = 30 * time.Second defaultReqPerSecond = 10 - userAgent = "go-scryfall" dateFormat = "2006-01-02" timestampFormat = "2006-01-02T15:04:05.999Z07:00" @@ -111,6 +113,7 @@ func (e *Error) Error() string { type clientOptions struct { baseURL string + userAgent string clientSecret string grantSecret string client *http.Client @@ -127,6 +130,13 @@ func WithBaseURL(baseURL string) ClientOption { } } +// WithUserAgent returns an option which overrides the default HTTP user agent. +func WithUserAgent(userAgent string) ClientOption { + return func(o *clientOptions) { + o.userAgent = userAgent + } +} + // WithClientSecret returns an option which sets the client secret. The client // secret will configure the client to perform requests as the application // associated with the client secret. @@ -163,6 +173,7 @@ func WithLimiter(limiter ratelimit.Limiter) ClientOption { // Client is a Scryfall API client. type Client struct { baseURL *url.URL + userAgent string authorization string client *http.Client @@ -172,7 +183,8 @@ type Client struct { // NewClient returns a new Scryfall API client. func NewClient(options ...ClientOption) (*Client, error) { co := &clientOptions{ - baseURL: defaultBaseURL, + baseURL: defaultBaseURL, + userAgent: defaultUserAgent, client: &http.Client{ Timeout: defaultTimeout, }, @@ -201,6 +213,7 @@ func NewClient(options ...ClientOption) (*Client, error) { c := &Client{ baseURL: baseURL, + userAgent: co.userAgent, authorization: authorization, client: co.client, limiter: co.limiter, @@ -209,7 +222,7 @@ func NewClient(options ...ClientOption) (*Client, error) { } func (c *Client) doReq(ctx context.Context, req *http.Request, respBody interface{}) error { - req.Header.Set("User-Agent", userAgent) + req.Header.Set("User-Agent", c.userAgent) if len(c.authorization) != 0 { req.Header.Set("Authorization", c.authorization) } diff --git a/scryfall_test.go b/scryfall_test.go index 94e9371..1d313f8 100644 --- a/scryfall_test.go +++ b/scryfall_test.go @@ -35,7 +35,6 @@ func setupTestServer(pattern string, handler func(http.ResponseWriter, *http.Req } func TestDateUnmarshalJSON(t *testing.T) { - tests := []struct { in []byte out Date @@ -186,6 +185,53 @@ func TestError(t *testing.T) { } } +func TestNewClientUserAgent(t *testing.T) { + tests := []struct { + name string + clientOptions []ClientOption + expectedUserAgent string + }{ + { + name: "default user agent", + clientOptions: nil, + expectedUserAgent: defaultUserAgent, + }, + { + name: "custom user agent", + clientOptions: []ClientOption{WithUserAgent("custom/1.2.3")}, + expectedUserAgent: "custom/1.2.3", + }, + } + + for _, test := range tests { + t.Run(string(test.name), func(t *testing.T) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userAgent := r.Header.Get("User-Agent") + if userAgent != test.expectedUserAgent { + // I don't beleive they currently return an error for user agent + // issues but we want to make the test fail. + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, `{"object": "error", "code": "bad_request", "status": 400, "details": ""}`) + return + } + + fmt.Fprintln(w, `{"object": "list", "has_more": false, "data": []}`) + }) + client, ts, err := setupTestServer("/symbology", handler, test.clientOptions...) + if err != nil { + t.Fatalf("Error setting up test server: %v", err) + } + defer ts.Close() + + ctx := context.Background() + _, err = client.ListCardSymbols(ctx) + if err != nil { + t.Fatalf("Error validating user agent: %v", err) + } + }) + } +} + func TestNewClientWithClientSecret(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authorizationHeader := r.Header.Get("Authorization")