-
Notifications
You must be signed in to change notification settings - Fork 145
/
decoder.go
150 lines (121 loc) · 4.03 KB
/
decoder.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
148
149
150
package webhookutils
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/gorilla/schema"
hatcheterrors "github.com/hatchet-dev/hatchet/pkg/errors"
)
// Decoder populates a request form from the request body and URL.
type Decoder interface {
// Decode accepts a target struct, a reader for the request body, and a URL
// for the request endpoint
Decode(s interface{}, r *http.Request) error
// DecodeQueryOnly is Decode but only looks at the query parameters
DecodeQueryOnly(s interface{}, r *http.Request) error
}
// DefaultDecoder decodes the request body with `json` and the URL query params with gorilla/schema,
type DefaultDecoder struct {
// we set the schema.Decoder on the global (shared across all endpoints) decoder
// because it caches metadata about structs, but does not cache values
schemaDecoder *schema.Decoder
}
// NewDefaultDecoder returns an implementation of Decoder that uses `json` and `gorilla/schema`.
func NewDefaultDecoder() Decoder {
decoder := schema.NewDecoder()
return &DefaultDecoder{decoder}
}
// Decode reads the request and populates the target request object.
func (d *DefaultDecoder) Decode(
s interface{},
r *http.Request,
) error {
if r == nil || r.URL == nil {
return hatcheterrors.NewErrInternal(fmt.Errorf("decode: request or request.URL cannot be nil"))
}
// read query values from URL and decode using schema library
vals := r.URL.Query()
if err := d.schemaDecoder.Decode(s, vals); err != nil {
return requestErrorFromSchemaErr(err)
}
// decode into the request object
// a nil body is not a fatal error
if r.Body != nil {
if err := json.NewDecoder(r.Body).Decode(s); err != nil && !errors.Is(err, io.EOF) {
return requestErrorFromJSONErr(err)
}
}
return nil
}
// Decode reads the request and populates the target request object.
func (d *DefaultDecoder) DecodeQueryOnly(
s interface{},
r *http.Request,
) error {
if r == nil || r.URL == nil {
return hatcheterrors.NewErrInternal(fmt.Errorf("decode: request or request.URL cannot be nil"))
}
// read query values from URL and decode using schema library
vals := r.URL.Query()
if err := d.schemaDecoder.Decode(s, vals); err != nil {
return requestErrorFromSchemaErr(err)
}
return nil
}
func requestErrorFromJSONErr(err error) error {
var syntaxErr *json.SyntaxError
var typeErr *json.UnmarshalTypeError
var clientErr string
switch {
case errors.As(err, &syntaxErr):
clientErr = fmt.Sprintf("JSON syntax error at character %d", syntaxErr.Offset)
case errors.As(err, &typeErr):
clientErr = fmt.Sprintf("Invalid type for body param %s: expected %s, got %s", typeErr.Field, typeErr.Type.Kind().String(), typeErr.Value)
default:
return hatcheterrors.NewError(
400,
"Bad Request",
"Could not parse JSON request",
"",
)
}
return hatcheterrors.NewError(
400,
"Bad Request",
clientErr,
"",
)
}
func requestErrorFromSchemaErr(err error) error {
if multiErr := (schema.MultiError{}); errors.As(err, &multiErr) {
errMap := map[string]error(multiErr)
resStrArr := make([]string, 0)
for _, err := range errMap {
resStrArr = append(resStrArr, readableStringFromSchemaErr(err))
}
clientErr := strings.Join(resStrArr, ",")
return hatcheterrors.NewError(
400,
"Bad Request",
clientErr,
"",
)
}
// if not castable to multi-error, this is likely a server-side error, such as the
// passed struct being nil; thus, we throw an internal server error
return hatcheterrors.NewErrInternal(err)
}
func readableStringFromSchemaErr(err error) string {
var str string
if typeErr := (schema.ConversionError{}); errors.As(err, &typeErr) {
str = fmt.Sprintf("Invalid type for query param %s: expected %s", typeErr.Key, typeErr.Type.Kind().String())
} else if emptyFieldErr := (schema.EmptyFieldError{}); errors.As(err, &emptyFieldErr) {
str = fmt.Sprintf("Query param %s cannot be empty", emptyFieldErr.Key)
} else if unknownKeyErr := (schema.UnknownKeyError{}); errors.As(err, &unknownKeyErr) {
str = fmt.Sprintf("Unknown query param %s", unknownKeyErr.Key)
}
return str
}