GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Pull Request — master (#215)
by Victor Hugo
01:28
created

mollie.NewClient   B

Complexity

Conditions 4

Size

Total Lines 54
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 4.0005

Importance

Changes 0
Metric Value
cc 4
eloc 40
dl 0
loc 54
c 0
b 0
f 0
ccs 30
cts 31
cp 0.9677
crap 4.0005
rs 8.92
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
package mollie
2
3
import (
4
	"bytes"
5
	"context"
6
	"encoding/json"
7
	"errors"
8
	"fmt"
9
	"io"
10
	"net/http"
11
	"net/url"
12
	"os"
13
	"regexp"
14
	"runtime"
15
	"strings"
16
17
	"github.com/VictorAvelar/mollie-api-go/v3/mollie/tools/idempotency"
18
	"github.com/google/go-querystring/query"
19
)
20
21
// Constants holding values for client initialization and request instantiation.
22
const (
23
	BaseURL            string = "https://api.mollie.com/"
24
	AuthHeader         string = "Authorization"
25
	TokenType          string = "Bearer"
26
	APITokenEnv        string = "MOLLIE_API_TOKEN"
27
	OrgTokenEnv        string = "MOLLIE_ORG_TOKEN"
28
	RequestContentType string = "application/json"
29
)
30
31
var (
32
	accessTokenExpr = regexp.MustCompile(`(?m)^access_`)
33
	errEmptyAuthKey = errors.New("you must provide a non-empty authentication key")
34
	errBadBaseURL   = errors.New("malformed base url, it must contain a trailing slash")
35
)
36
37
// Client manages communication with Mollie's API.
38
type Client struct {
39
	BaseURL        *url.URL
40
	authentication string
41
	userAgent      string
42
	client         *http.Client
43
	common         service // Reuse a single struct instead of allocating one for each service on the heap.
44
	config         *Config
45
	// Tools
46
	idempotencyKeyProvider idempotency.KeyGenerator
47
	// Services
48
	Payments       *PaymentsService
49
	Chargebacks    *ChargebacksService
50
	PaymentMethods *PaymentMethodsService
51
	Invoices       *InvoicesService
52
	Organizations  *OrganizationsService
53
	Profiles       *ProfilesService
54
	Refunds        *RefundsService
55
	Shipments      *ShipmentsService
56
	Orders         *OrdersService
57
	Settlements    *SettlementsService
58
	Captures       *CapturesService
59
	Subscriptions  *SubscriptionsService
60
	Customers      *CustomersService
61
	Miscellaneous  *MiscellaneousService
62
	Mandates       *MandatesService
63
	Permissions    *PermissionsService
64
	Onboarding     *OnboardingService
65
	PaymentLinks   *PaymentLinksService
66
	Partners       *PartnerService
67
}
68
69
type service struct {
70
	client *Client
71
}
72
73
func (c *Client) get(ctx context.Context, uri string, options interface{}) (res *Response, err error) {
74 1
	if options != nil {
75 1
		v, _ := query.Values(options)
76 1
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
77
	}
78
79 1
	req, err := c.NewAPIRequest(ctx, http.MethodGet, uri, nil)
80 1
	if err != nil {
81 1
		return
82
	}
83
84 1
	return c.Do(req)
85
}
86
87
func (c *Client) post(ctx context.Context, uri string, body interface{}, options interface{}) (res *Response, err error) {
88 1
	if options != nil {
89 1
		v, _ := query.Values(options)
90 1
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
91
	}
92
93 1
	req, err := c.NewAPIRequest(ctx, http.MethodPost, uri, body)
94 1
	if err != nil {
95 1
		return
96
	}
97
98 1
	return c.Do(req)
99
}
100
101
func (c *Client) patch(ctx context.Context, uri string, body interface{}, options interface{}) (res *Response, err error) {
102 1
	if options != nil {
103
		v, _ := query.Values(options)
104
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
105
	}
106
107 1
	req, err := c.NewAPIRequest(ctx, http.MethodPatch, uri, body)
108 1
	if err != nil {
109 1
		return
110
	}
111
112 1
	return c.Do(req)
113
}
114
115
func (c *Client) delete(ctx context.Context, uri string, options interface{}) (res *Response, err error) {
116 1
	if options != nil {
117
		v, _ := query.Values(options)
118
		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
119
	}
120
121 1
	req, err := c.NewAPIRequest(ctx, http.MethodDelete, uri, nil)
122 1
	if err != nil {
123 1
		return
124
	}
125
126 1
	return c.Do(req)
127
}
128
129
// WithAuthenticationValue offers a convenient setter for any of the valid authentication
130
// tokens provided by Mollie.
131
//
132
// Ideally your API key will be provided from and environment variable or
133
// a secret management engine.
134
// This should only be used when environment variables are "impossible" to be used.
135
func (c *Client) WithAuthenticationValue(k string) error {
136 1
	if k == "" {
137 1
		return errEmptyAuthKey
138
	}
139
140 1
	c.authentication = strings.TrimSpace(k)
141
142 1
	return nil
143
}
144
145
// HasAccessToken will return true when the provided authentication token
146
// complies with the access token REGEXP match check.
147
// This will enable TestMode inside the request body.
148
//
149
// See: https://github.com/VictorAvelar/mollie-api-go/issues/123
150
func (c *Client) HasAccessToken() bool {
151 1
	return accessTokenExpr.Match([]byte(c.authentication))
152
}
153
154
// SetIdempotencyKeyGenerator allows you to pass your own idempotency
155
// key generator.
156
func (c *Client) SetIdempotencyKeyGenerator(kg idempotency.KeyGenerator) {
157
	c.idempotencyKeyProvider = kg
158
}
159
160
// NewAPIRequest is a wrapper around the http.NewRequest function.
161
//
162
// It will setup the authentication headers/parameters according to the client config.
163
func (c *Client) NewAPIRequest(ctx context.Context, method string, uri string, body interface{}) (req *http.Request, err error) {
164 1
	if !strings.HasSuffix(c.BaseURL.Path, "/") {
165 1
		return nil, errBadBaseURL
166
	}
167
168 1
	url, err := c.BaseURL.Parse(uri)
169 1
	if err != nil {
170 1
		return nil, fmt.Errorf("url_parsing_error: %w", err)
171
	}
172
173 1
	if c.config.testing && c.HasAccessToken() {
174 1
		qp := url.Query()
175 1
		qp.Add("testmode", "true")
176 1
		url.RawQuery = qp.Encode()
177
	}
178
179 1
	var buf io.ReadWriter
180 1
	if body != nil {
181 1
		buf = new(bytes.Buffer)
182 1
		enc := json.NewEncoder(buf)
183 1
		enc.SetEscapeHTML(false)
184
185 1
		err := enc.Encode(body)
186 1
		if err != nil {
187 1
			return nil, fmt.Errorf("encoding_error: %w", err)
188
		}
189
	}
190
191 1
	if ctx == nil {
192 1
		ctx = context.Background()
193
	}
194
195 1
	req, err = http.NewRequestWithContext(ctx, method, url.String(), buf)
196 1
	if err != nil {
197 1
		return
198
	}
199
200 1
	req.Header.Add(AuthHeader, strings.Join([]string{TokenType, c.authentication}, " "))
201 1
	req.Header.Set("Content-Type", RequestContentType)
202 1
	req.Header.Set("Accept", RequestContentType)
203 1
	req.Header.Set("User-Agent", c.userAgent)
204
205 1
	return
206
}
207
208
// Do sends an API request and returns the API response or returned as an
209
// error if an API error has occurred.
210
func (c *Client) Do(req *http.Request) (*Response, error) {
211 1
	resp, err := c.client.Do(req)
212 1
	if err != nil {
213 1
		return nil, fmt.Errorf("httperror: %w", err)
214
	}
215
216 1
	defer resp.Body.Close()
217 1
	response, err := newResponse(resp)
218 1
	if err != nil {
219
		return response, err
220
	}
221
222 1
	err = CheckResponse(response)
223 1
	if err != nil {
224 1
		return response, err
225
	}
226
227 1
	return response, nil
228
}
229
230
// NewClient returns a new Mollie HTTP API client.
231
// You can pass a previously build http client, if none is provided then
232
// http.DefaultClient will be used.
233
//
234
// NewClient will lookup the environment for values to assign to the
235
// API token (`MOLLIE_API_TOKEN`) and the Organization token (`MOLLIE_ORG_TOKEN`)
236
// according to the provided Config object.
237
//
238
// You can also set the token values programmatically by using the Client
239
// WithAPIKey and WithOrganizationKey functions.
240
func NewClient(baseClient *http.Client, conf *Config) (mollie *Client, err error) {
241 1
	if baseClient == nil {
242 1
		baseClient = http.DefaultClient
243
	}
244
245 1
	uri, _ := url.Parse(BaseURL)
246
247 1
	mollie = &Client{
248
		BaseURL:                uri,
249
		client:                 baseClient,
250
		config:                 conf,
251
		idempotencyKeyProvider: nil,
252
	}
253
254 1
	mollie.common.client = mollie
255
256 1
	if mollie.config.reqIdempotency {
257
		mollie.common.client.idempotencyKeyProvider = idempotency.NewStdGenerator()
258
	}
259
260
	// services for resources
261 1
	mollie.Payments = (*PaymentsService)(&mollie.common)
262 1
	mollie.Chargebacks = (*ChargebacksService)(&mollie.common)
263 1
	mollie.PaymentMethods = (*PaymentMethodsService)(&mollie.common)
264 1
	mollie.Invoices = (*InvoicesService)(&mollie.common)
265 1
	mollie.Organizations = (*OrganizationsService)(&mollie.common)
266 1
	mollie.Profiles = (*ProfilesService)(&mollie.common)
267 1
	mollie.Refunds = (*RefundsService)(&mollie.common)
268 1
	mollie.Shipments = (*ShipmentsService)(&mollie.common)
269 1
	mollie.Orders = (*OrdersService)(&mollie.common)
270 1
	mollie.Captures = (*CapturesService)(&mollie.common)
271 1
	mollie.Settlements = (*SettlementsService)(&mollie.common)
272 1
	mollie.Subscriptions = (*SubscriptionsService)(&mollie.common)
273 1
	mollie.Customers = (*CustomersService)(&mollie.common)
274 1
	mollie.Miscellaneous = (*MiscellaneousService)(&mollie.common)
275 1
	mollie.Mandates = (*MandatesService)(&mollie.common)
276 1
	mollie.Permissions = (*PermissionsService)(&mollie.common)
277 1
	mollie.Onboarding = (*OnboardingService)(&mollie.common)
278 1
	mollie.PaymentLinks = (*PaymentLinksService)(&mollie.common)
279 1
	mollie.Partners = (*PartnerService)(&mollie.common)
280
281 1
	mollie.userAgent = strings.Join([]string{
282
		runtime.GOOS,
283
		runtime.GOARCH,
284
		runtime.Version(),
285
	}, ";")
286
287
	// Parse authorization from specified environment variable
288 1
	tkn, ok := os.LookupEnv(mollie.config.auth)
289 1
	if ok {
290 1
		mollie.authentication = tkn
291
	}
292
293 1
	return
294
}
295
296
/*
297
Constructor for Error.
298
*/
299
func newError(rsp *Response) error {
300 1
	merr := &BaseError{}
301
302 1
	if rsp.ContentLength > 0 {
303 1
		err := json.Unmarshal(rsp.content, merr)
304 1
		if err != nil {
305
			return err
306
		}
307
	} else {
308 1
		merr.Status = rsp.StatusCode
309 1
		merr.Title = rsp.Status
310 1
		merr.Detail = string(rsp.content)
311
	}
312
313 1
	return merr
314
}
315
316
// Response is a Mollie API response. This wraps the standard http.Response
317
// returned from Mollie and provides convenient access to things like
318
// pagination links.
319
type Response struct {
320
	*http.Response
321
	content []byte
322
}
323
324
func newResponse(rsp *http.Response) (*Response, error) {
325 1
	res := Response{Response: rsp}
326
327 1
	data, err := io.ReadAll(rsp.Body)
328 1
	if err != nil {
329
		return &res, err
330
	}
331
332 1
	res.content = data
333
334 1
	rsp.Body = io.NopCloser(bytes.NewBuffer(data))
335 1
	res.Response = rsp
336
337 1
	return &res, nil
338
}
339
340
// CheckResponse checks the API response for errors, and returns them if
341
// present. A response is considered an error if it has a status code outside
342
// the 200 range.
343
// API error responses are expected to have either no response
344
// body, or a JSON response body.
345
func CheckResponse(r *Response) error {
346 1
	if r.StatusCode >= http.StatusMultipleChoices {
347 1
		return newError(r)
348
	}
349
350 1
	return nil
351
}
352