-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdecoder.go
145 lines (124 loc) · 4.09 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
package hrt
import (
"encoding/json"
"net/http"
"reflect"
"strings"
"github.com/go-chi/chi/v5"
"github.com/pkg/errors"
"libdb.so/hrt/v2/internal/rfutil"
)
// Decoder describes a decoder that decodes the request type.
type Decoder interface {
// Decode decodes the given value from the given reader.
Decode(*http.Request, any) error
}
// MethodDecoder is an encoder that only encodes or decodes if the request
// method matches the methods in it.
type MethodDecoder map[string]Decoder
// Decode implements the Decoder interface.
func (e MethodDecoder) Decode(r *http.Request, v any) error {
dec, ok := e[r.Method]
if !ok {
dec, ok = e["*"]
}
if !ok {
return WrapHTTPError(http.StatusMethodNotAllowed, errors.New("method not allowed"))
}
return dec.Decode(r, v)
}
// URLDecoder decodes chi.URLParams and url.Values into a struct. It only does
// Decoding; the Encode method is a no-op. The decoder makes no effort to
// traverse the struct and decode nested structs. If neither a chi.URLParam nor
// a url.Value is found for a field, the field is left untouched.
//
// The following tags are supported:
//
// - `url` - uses chi.URLParam to decode the value.
// - `form` - uses r.FormValue to decode the value.
// - `query` - similar to `form`.
// - `schema` - similar to `form`, exists for compatibility with gorilla/schema.
// - `json` - uses either chi.URLParam or r.FormValue to decode the value.
// If the value is provided within the form, then it is unmarshaled as JSON
// into the field unless the type is a string. If the value is provided within
// the URL, then it is unmarshaled as a primitive value.
//
// If a struct field has no tag, it is assumed to be the same as the field name.
// If a struct field has a tag, then only that tag is used.
//
// # Example
//
// The following Go type would be decoded to have 2 URL parameters:
//
// type Data struct {
// ID string
// Num int `url:"num"`
// Nested struct {
// ID string
// }
// }
var URLDecoder Decoder = urlDecoder{}
type urlDecoder struct{}
func (d urlDecoder) Decode(r *http.Request, v any) error {
return rfutil.EachStructField(v, func(rft reflect.StructField, rfv reflect.Value) error {
for _, tag := range []string{"form", "query", "schema"} {
if tagValue := rft.Tag.Get(tag); tagValue != "" {
val := r.FormValue(tagValue)
return rfutil.SetPrimitiveFromString(rft.Type, rfv, val)
}
}
if tagValue := rft.Tag.Get("url"); tagValue != "" {
val := chi.URLParam(r, tagValue)
return rfutil.SetPrimitiveFromString(rft.Type, rfv, val)
}
if tagValue := rft.Tag.Get("json"); tagValue != "" {
if val := chi.URLParam(r, tagValue); val != "" {
return rfutil.SetPrimitiveFromString(rft.Type, rfv, val)
}
val := r.FormValue(tagValue)
if rft.Type.Kind() == reflect.String {
rfv.SetString(val)
return nil
}
jsonValue := reflect.New(rft.Type)
if err := json.Unmarshal([]byte(val), jsonValue.Interface()); err != nil {
return errors.Wrap(err, "failed to unmarshal JSON")
}
rfv.Set(jsonValue.Elem())
}
// Search for the URL parameters manually.
if rctx := chi.RouteContext(r.Context()); rctx != nil {
for i, k := range rctx.URLParams.Keys {
if strings.EqualFold(k, rft.Name) {
return rfutil.SetPrimitiveFromString(rfv.Type(), rfv, rctx.URLParams.Values[i])
}
}
}
// Trigger form parsing.
r.FormValue("")
// Search for URL form values manually.
for k, v := range r.Form {
if strings.EqualFold(k, rft.Name) {
return rfutil.SetPrimitiveFromString(rfv.Type(), rfv, v[0])
}
}
return nil // ignore
})
}
// DecoderWithValidator wraps an encoder with one that calls Validate() on the
// value after decoding and before encoding if the value implements Validator.
func DecoderWithValidator(enc Decoder) Decoder {
return validatorDecoder{enc}
}
type validatorDecoder struct{ dec Decoder }
func (e validatorDecoder) Decode(r *http.Request, v any) error {
if err := e.dec.Decode(r, v); err != nil {
return err
}
if validator, ok := v.(Validator); ok {
if err := validator.Validate(); err != nil {
return err
}
}
return nil
}