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
Push — master ( 57c3d8...a6b7ad )
by
unknown
01:54 queued 16s
created

mollie/mollie.go   A

Size/Duplication

Total Lines 371
Duplicated Lines 0 %

Test Coverage

Coverage 94.44%

Importance

Changes 0
Metric Value
cc 42
eloc 218
dl 0
loc 371
rs 9.0399
c 0
b 0
f 0
ccs 119
cts 126
cp 0.9444
crap 42.3031

14 Methods

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