-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathparse.go
184 lines (160 loc) · 5.57 KB
/
parse.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package queryparam
import (
"errors"
"fmt"
"net/url"
"reflect"
)
var (
// ErrNonPointerTarget is returned when the given interface does not represent a pointer
ErrNonPointerTarget = errors.New("invalid target. must be a non nil pointer")
// ErrInvalidURLValues is returned when the given *url.URL is nil
ErrInvalidURLValues = errors.New("invalid url provided")
// ErrUnhandledFieldType is returned when a struct property is tagged but has an unhandled type.
ErrUnhandledFieldType = errors.New("unhandled field type")
// ErrInvalidTag is returned when the tag value is invalid
ErrInvalidTag = errors.New("invalid tag")
)
// ErrInvalidParameterValue is an error adds extra context to a parser error.
type ErrInvalidParameterValue struct {
Err error
Parameter string
Field string
Value string
Type reflect.Type
}
// Error returns the full error message.
func (e *ErrInvalidParameterValue) Error() string {
return fmt.Sprintf("invalid parameter value for field %s (%s) from parameter %s (%s): %s", e.Field, e.Type, e.Parameter, e.Value, e.Err.Error())
}
// Unwrap returns the wrapped error.
func (e *ErrInvalidParameterValue) Unwrap() error {
return e.Err
}
// ErrCannotSetValue is an error adds extra context to a setter error.
type ErrCannotSetValue struct {
Err error
Parameter string
Field string
Value string
Type reflect.Type
ParsedValue reflect.Value
}
// Error returns the full error message.
func (e *ErrCannotSetValue) Error() string {
return fmt.Sprintf("cannot set value for field %s (%s) from parameter %s (%s - %v): %s", e.Field, e.Type, e.Parameter, e.Value, e.ParsedValue, e.Err.Error())
}
// Unwrap returns the wrapped error.
func (e *ErrCannotSetValue) Unwrap() error {
return e.Err
}
// Present allows you to determine whether or not a query parameter was present in a request.
type Present bool
// DefaultParser is a default parser.
var DefaultParser = &Parser{
Tag: "queryparam",
DelimiterTag: "queryparamdelim",
Delimiter: ",",
ValueParsers: DefaultValueParsers(),
ValueSetters: DefaultValueSetters(),
}
// Parser is used to parse a URL.
type Parser struct {
// Tag is the name of the struct tag where the query parameter name is set.
Tag string
// Delimiter is the name of the struct tag where a string delimiter override is set.
DelimiterTag string
// Delimiter is the default string delimiter.
Delimiter string
// ValueParsers is a map[reflect.Type]ValueParser that defines how we parse query
// parameters based on the destination variable type.
ValueParsers map[reflect.Type]ValueParser
// ValueSetters is a map[reflect.Type]ValueSetter that defines how we set values
// onto target variables.
ValueSetters map[reflect.Type]ValueSetter
}
// ValueParser is a func used to parse a value.
type ValueParser func(value string, delimiter string) (reflect.Value, error)
// ValueSetter is a func used to set a value on a target variable.
type ValueSetter func(value reflect.Value, target reflect.Value) error
// FieldDelimiter returns a delimiter to be used with the given field.
func (p *Parser) FieldDelimiter(field reflect.StructField) string {
if customDelimiter := field.Tag.Get(p.DelimiterTag); customDelimiter != "" {
return customDelimiter
}
return p.Delimiter
}
// Parse attempts to parse query parameters from the specified URL and store any found values
// into the given target interface.
func (p *Parser) Parse(urlValues url.Values, target interface{}) error {
if urlValues == nil {
return ErrInvalidURLValues
}
targetValue := reflect.ValueOf(target)
if targetValue.Kind() != reflect.Ptr || targetValue.IsNil() {
return ErrNonPointerTarget
}
targetElement := targetValue.Elem()
targetType := targetElement.Type()
for i := 0; i < targetType.NumField(); i++ {
if err := p.ParseField(targetType.Field(i), targetElement.Field(i), urlValues); err != nil {
return err
}
}
return nil
}
// ParseField parses the given field and sets the given value on the target.
func (p *Parser) ParseField(field reflect.StructField, value reflect.Value, urlValues url.Values) error {
queryParameterName, ok := field.Tag.Lookup(p.Tag)
if !ok {
return nil
}
if queryParameterName == "" {
return fmt.Errorf("missing tag value for field: %s: %w", field.Name, ErrInvalidTag)
}
queryParameterValue := urlValues.Get(queryParameterName)
valueParser, ok := p.ValueParsers[field.Type]
if !ok {
return fmt.Errorf("%w: %s: %v", ErrUnhandledFieldType, field.Name, field.Type.String())
}
parsedValue, err := valueParser(queryParameterValue, p.FieldDelimiter(field))
if err != nil {
return &ErrInvalidParameterValue{
Err: err,
Value: queryParameterValue,
Parameter: queryParameterName,
Type: field.Type,
Field: field.Name,
}
}
valueSetter, ok := p.ValueSetters[field.Type]
if !ok {
valueSetter, ok = p.ValueSetters[GenericType]
}
if !ok {
return &ErrCannotSetValue{
Err: ErrUnhandledFieldType,
Value: queryParameterValue,
ParsedValue: parsedValue,
Parameter: queryParameterName,
Type: field.Type,
Field: field.Name,
}
}
if err := valueSetter(parsedValue, value); err != nil {
return &ErrCannotSetValue{
Err: err,
Value: queryParameterValue,
ParsedValue: parsedValue,
Parameter: queryParameterName,
Type: field.Type,
Field: field.Name,
}
}
return nil
}
// Parse attempts to parse query parameters from the specified URL and store any found values
// into the given target interface.
func Parse(urlValues url.Values, target interface{}) error {
return DefaultParser.Parse(urlValues, target)
}