-
Notifications
You must be signed in to change notification settings - Fork 0
/
apiError.go
143 lines (114 loc) · 2.96 KB
/
apiError.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
// Copyright (c) APIMatic. All rights reserved.
package https
import (
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"regexp"
"strings"
"github.com/go-openapi/jsonpointer"
)
// ApiError is the base struct for all error responses from the server.
// It holds information about the original HTTP request, the status code, headers, and response body.
type ApiError struct {
Request http.Request
StatusCode int
Headers http.Header
Body []byte
Message string
}
func (a ApiError) Error() string {
return fmt.Sprintf("ApiError occured: %v", a.Message)
}
type ErrorBuilder[T error] struct {
Message string
TemplatedMessage string
Unmarshaller func(ApiError) T
}
func (eb ErrorBuilder[T]) Build(httpctx HttpContext) error {
res := httpctx.Response
message := eb.Message
if eb.TemplatedMessage != "" {
message = renderErrorTemplate(eb.TemplatedMessage, *res)
}
body, _ := io.ReadAll(res.Body)
err := ApiError{
Request: *httpctx.Request,
StatusCode: res.StatusCode,
Headers: res.Header,
Body: body,
Message: message,
}
if eb.Unmarshaller != nil {
return eb.Unmarshaller(err)
}
return err
}
func renderErrorTemplate(tpl string, res http.Response) string {
placeholderRegex := `\{\$(.*?)\}`
re := regexp.MustCompile(placeholderRegex)
// Extract placeholders into an array of strings
placeholders := re.FindAllString(tpl, -1)
renderedVals := []any{}
for _, placeholder := range placeholders {
renderedVals = append(renderedVals, renderPlaceholder(placeholder, res))
}
// Replace each instance of a placeholder with "%v"
formattedTpl := re.ReplaceAllString(tpl, "%v")
return fmt.Sprintf(formattedTpl, renderedVals...)
}
func renderPlaceholder(placeholder string, res http.Response) any {
if placeholder == "{$statusCode}" {
return res.StatusCode
}
if strings.HasPrefix(placeholder, "{$response.header.") {
headerName := placeholder[len("{$response.header.") : len(placeholder)-1]
return res.Header.Get(headerName)
}
// Return Response Body as-is
if placeholder == "{$response.body}" {
serializedBody, err := io.ReadAll(res.Body)
if err != nil {
return ""
}
return string(serializedBody)
}
// Use JSON Pointer to get the desired value from a JSON Response Body
if strings.HasPrefix(placeholder, "{$response.body#") {
body, err := io.ReadAll(res.Body)
if err != nil {
return ""
}
jsonPtr := placeholder[len("{$response.body#") : len(placeholder)-1]
if jsonPtr == "" {
return ""
}
return getValueFromJSON(body, jsonPtr)
}
return placeholder
}
func getValueFromJSON(rawJSON []byte, jsonPtr string) any {
var jsonBody any
if err := json.Unmarshal(rawJSON, &jsonBody); err != nil {
return ""
}
p, err := jsonpointer.New(jsonPtr)
if err != nil {
return ""
}
val, kind, err := p.Get(jsonBody)
if err != nil {
return ""
}
switch kind {
case reflect.Map:
obj, err := json.Marshal(val)
if err != nil {
return ""
}
return string(obj)
}
return val
}