forked from jjideenschmiede/golexoffice
-
Notifications
You must be signed in to change notification settings - Fork 1
/
config.go
147 lines (125 loc) · 3.33 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//**********************************************************
//
// This file is part of lexoffice.
// All code may be used. Feel free and maybe code something better.
//
// Author: Jonas Kwiedor
//
//**********************************************************
package golexoffice
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
)
const (
baseURL = "https://api.lexoffice.io"
maxRateLimitTries = 3
)
// Config is to define the request data
type Config struct {
token string
baseUrl string
client *http.Client
}
func NewConfig(token string, httpClient *http.Client) *Config {
if httpClient == nil {
httpClient = &http.Client{}
}
return &Config{
token: token,
client: httpClient,
}
}
func (c *Config) SetBaseUrl(url string) {
c.baseUrl = url
}
// Send is to send a new request
func (c *Config) Send(path string, body io.Reader, method, contentType string) (*http.Response, error) {
// Set url
var url string
if c.baseUrl != "" {
url = c.baseUrl + path
} else {
url = baseURL + path
}
// Request
request, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
// Define header
request.Header.Set("Authorization", "Bearer "+c.token)
request.Header.Set("Content-Type", contentType)
request.Header.Set("Accept", "application/json")
var response *http.Response
// Send request & get response
for i := 1; ; i++ {
response, err = c.client.Do(request)
if err != nil {
return nil, err
}
if isSuccessful(response) {
// Return data
return response, nil
} else if hitRateLimit(response) {
if i > maxRateLimitTries {
break
}
// max 2 requests per second, so let's wait a bit and try again
time.Sleep(500 * time.Duration(i) * time.Millisecond)
} else {
break
}
}
// TODO(till): revisit parsing when we add more API endpoints
if strings.Contains(url, "invoices") {
return nil, parseErrorResponse(response)
}
return nil, parseLegacyErrorResponse(response)
}
func isSuccessful(response *http.Response) bool {
return response.StatusCode < 400
}
func hitRateLimit(response *http.Response) bool {
return response.StatusCode == 429
}
func parseErrorResponse(response *http.Response) error {
var errorResp ErrorResponse
err := json.NewDecoder(response.Body).Decode(&errorResp)
if err != nil {
return fmt.Errorf("decoding error while unpacking response: %s", err)
}
defer response.Body.Close()
var keep []error
for _, detail := range errorResp.Details {
keep = append(keep, fmt.Errorf(
"field: %s (%s): %s", detail.Field, detail.Violation, detail.Message,
))
}
if len(keep) == 0 {
return fmt.Errorf("error: %s (%d %s)", errorResp.Message, errorResp.Status, errorResp.Error)
}
return errors.Join(keep...)
}
func parseLegacyErrorResponse(response *http.Response) error {
var errorResp LegacyErrorResponse
err := json.NewDecoder(response.Body).Decode(&errorResp)
if err != nil {
return fmt.Errorf("decoding error while unpacking response: %s", err)
}
defer response.Body.Close()
// potentially multiple issues returned from the LexOffice API
var keep []error
for _, issue := range errorResp.IssueList {
keep = append(keep, fmt.Errorf("key: %s (%s): %s", issue.Key, issue.Source, issue.Type))
}
if len(keep) == 0 {
return fmt.Errorf("something went wrong but unclear what (empty IssueList)")
}
return errors.Join(keep...)
}