Skip to content

Commit 74acb62

Browse files
committed
Add ValueSetters
1 parent 57b100d commit 74acb62

File tree

7 files changed

+288
-111
lines changed

7 files changed

+288
-111
lines changed

README.MD

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Go Report Card](https://goreportcard.com/badge/github.com/TomWright/queryparam)](https://goreportcard.com/report/github.com/TomWright/queryparam)
66
[![Documentation](https://godoc.org/github.com/TomWright/queryparam?status.svg)](https://godoc.org/github.com/TomWright/queryparam)
77

8-
Query param makes it easy to access the query parameters stored in a URL.
8+
Stop accessing query strings and parsing repeatedly parsing them into your preferred values - `queryparam` can do that for you!
99

1010
## Installation
1111

@@ -15,74 +15,4 @@ go get -u github.com/tomwright/queryparam
1515

1616
## Examples
1717

18-
For more complete examples see [godoc examples](https://godoc.org/github.com/TomWright/queryparam#example-Parse).
19-
20-
### Parsing a simple URL
21-
```
22-
type GetUsersRequest struct {
23-
Name string `queryparam:"name"`
24-
Age int `queryparam:"age"`
25-
Gender string `queryparam:"gender"`
26-
}
27-
28-
u, err := url.Parse("https://example.com/users?name=Tom&age=23")
29-
if err != nil {
30-
panic(err)
31-
}
32-
33-
req := &GetUsersRequest{}
34-
35-
err = queryparam.Parse(u, req)
36-
if err != nil {
37-
panic(err)
38-
}
39-
40-
fmt.Println(req.Name) // "Tom"
41-
fmt.Println(req.Age) // 23
42-
fmt.Println(req.Gender) // ""
43-
```
44-
45-
### Parsing a URL with delimited parameters
46-
```
47-
type GetUsersRequest struct {
48-
Name []string `queryparam:"name"`
49-
}
50-
51-
u, err := url.Parse("https://example.com/users?name=Tom,Jim")
52-
if err != nil {
53-
panic(err)
54-
}
55-
56-
req := &GetUsersRequest{}
57-
58-
err = queryparam.Parse(u, req)
59-
if err != nil {
60-
panic(err)
61-
}
62-
63-
fmt.Println(req.Name) // { "Tom", "Jim" }
64-
```
65-
66-
If you want to change the delimiter you can do so by changing the value of `queryparam.Delimiter`.
67-
68-
The default value is `,`.
69-
70-
### Parsing a URL from a HTTP Request
71-
```
72-
var r *http.Request
73-
// Receive r in a http handler...
74-
75-
type GetUsersRequest struct {
76-
Name string `queryparam:"name"`
77-
Age int `queryparam:"age"`
78-
Gender string `queryparam:"gender"`
79-
}
80-
81-
req := &GetUsersRequest{}
82-
err = queryparam.Parse(r.URL, req)
83-
if err != nil {
84-
panic(err)
85-
}
86-
87-
// Do something with req...
88-
```
18+
For examples see [godoc examples](https://godoc.org/github.com/TomWright/queryparam#example-Parse).

parse.go

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"net/url"
77
"reflect"
8-
"time"
98
)
109

1110
var (
@@ -38,49 +37,60 @@ func (e *ErrInvalidParameterValue) Unwrap() error {
3837
return e.Err
3938
}
4039

40+
// ErrCannotSetValue is an error adds extra context to a setter error.
41+
type ErrCannotSetValue struct {
42+
Err error
43+
Parameter string
44+
Field string
45+
Value string
46+
Type reflect.Type
47+
ParsedValue reflect.Value
48+
}
49+
50+
// Error returns the full error message.
51+
func (e *ErrCannotSetValue) Error() string {
52+
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())
53+
}
54+
55+
// Unwrap returns the wrapped error.
56+
func (e *ErrCannotSetValue) Unwrap() error {
57+
return e.Err
58+
}
59+
4160
// Present allows you to determine whether or not a query parameter was present in a request.
4261
type Present bool
4362

4463
// DefaultParser is a default parser.
4564
var DefaultParser = &Parser{
46-
// Tag is the name of the struct tag where the query parameter name is set.
47-
Tag: "queryparam",
48-
// Delimiter is the name of the struct tag where a string delimiter override is set.
65+
Tag: "queryparam",
4966
DelimiterTag: "queryparamdelim",
50-
// Delimiter is the default string delimiter.
51-
Delimiter: ",",
52-
// ValueParsers is a map[reflect.Type]ValueParser that defines how we parse query
53-
// parameters based on the destination variable type.
67+
Delimiter: ",",
5468
ValueParsers: DefaultValueParsers(),
55-
}
56-
57-
// DefaultValueParsers returns a set of default value parsers.
58-
func DefaultValueParsers() map[reflect.Type]ValueParser {
59-
return map[reflect.Type]ValueParser{
60-
reflect.TypeOf(""): StringValueParser,
61-
reflect.TypeOf([]string{}): StringSliceValueParser,
62-
reflect.TypeOf(0): IntValueParser,
63-
reflect.TypeOf(int32(0)): Int32ValueParser,
64-
reflect.TypeOf(int64(0)): Int64ValueParser,
65-
reflect.TypeOf(float32(0)): Float32ValueParser,
66-
reflect.TypeOf(float64(0)): Float64ValueParser,
67-
reflect.TypeOf(time.Time{}): TimeValueParser,
68-
reflect.TypeOf(false): BoolValueParser,
69-
reflect.TypeOf(Present(false)): PresentValueParser,
70-
}
69+
ValueSetters: DefaultValueSetters(),
7170
}
7271

7372
// Parser is used to parse a URL.
7473
type Parser struct {
75-
Tag string
74+
// Tag is the name of the struct tag where the query parameter name is set.
75+
Tag string
76+
// Delimiter is the name of the struct tag where a string delimiter override is set.
7677
DelimiterTag string
77-
Delimiter string
78+
// Delimiter is the default string delimiter.
79+
Delimiter string
80+
// ValueParsers is a map[reflect.Type]ValueParser that defines how we parse query
81+
// parameters based on the destination variable type.
7882
ValueParsers map[reflect.Type]ValueParser
83+
// ValueSetters is a map[reflect.Type]ValueSetter that defines how we set values
84+
// onto target variables.
85+
ValueSetters map[reflect.Type]ValueSetter
7986
}
8087

8188
// ValueParser is a func used to parse a value.
8289
type ValueParser func(value string, delimiter string) (reflect.Value, error)
8390

91+
// ValueSetter is a func used to set a value on a target variable.
92+
type ValueSetter func(value reflect.Value, target reflect.Value) error
93+
8494
// FieldDelimiter returns a delimiter to be used with the given field.
8595
func (p *Parser) FieldDelimiter(field reflect.StructField) string {
8696
if customDelimiter := field.Tag.Get(p.DelimiterTag); customDelimiter != "" {
@@ -129,24 +139,39 @@ func (p *Parser) ParseField(field reflect.StructField, value reflect.Value, urlV
129139

130140
parsedValue, err := valueParser(queryParameterValue, p.FieldDelimiter(field))
131141
if err != nil {
132-
err = &ErrInvalidParameterValue{
142+
return &ErrInvalidParameterValue{
133143
Err: err,
134144
Value: queryParameterValue,
135145
Parameter: queryParameterName,
136146
Type: field.Type,
137147
Field: field.Name,
138148
}
139-
return err
140149
}
141150

142-
// handle edge case value types
143-
switch field.Type {
144-
case reflect.TypeOf(int32(0)):
145-
value.SetInt(parsedValue.Int())
146-
case reflect.TypeOf(float32(0)):
147-
value.SetFloat(parsedValue.Float())
148-
default:
149-
value.Set(parsedValue)
151+
valueSetter, ok := p.ValueSetters[field.Type]
152+
if !ok {
153+
valueSetter, ok = p.ValueSetters[GenericType]
154+
}
155+
if !ok {
156+
return &ErrCannotSetValue{
157+
Err: ErrUnhandledFieldType,
158+
Value: queryParameterValue,
159+
ParsedValue: parsedValue,
160+
Parameter: queryParameterName,
161+
Type: field.Type,
162+
Field: field.Name,
163+
}
164+
}
165+
166+
if err := valueSetter(parsedValue, value); err != nil {
167+
return &ErrCannotSetValue{
168+
Err: err,
169+
Value: queryParameterValue,
170+
ParsedValue: parsedValue,
171+
Parameter: queryParameterName,
172+
Type: field.Type,
173+
Field: field.Name,
174+
}
150175
}
151176

152177
return nil

parse_test.go

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func TestParse_NonPointerTarget(t *testing.T) {
132132
}
133133
}
134134

135-
func TestParse_UnhandledFieldType(t *testing.T) {
135+
func TestParse_ParserUnhandledFieldType(t *testing.T) {
136136
t.Parallel()
137137

138138
req := &struct {
@@ -145,6 +145,54 @@ func TestParse_UnhandledFieldType(t *testing.T) {
145145
}
146146
}
147147

148+
func TestParse_SetterUnhandledFieldType(t *testing.T) {
149+
t.Parallel()
150+
151+
req := &struct {
152+
Age int `queryparam:"age"`
153+
}{}
154+
155+
p := &queryparam.Parser{
156+
Tag: "queryparam",
157+
DelimiterTag: "queryparamdelim",
158+
Delimiter: ",",
159+
ValueParsers: queryparam.DefaultValueParsers(),
160+
ValueSetters: map[reflect.Type]queryparam.ValueSetter{},
161+
}
162+
163+
err := p.Parse(urlValuesNameAge, req)
164+
if !errors.Is(err, queryparam.ErrUnhandledFieldType) {
165+
t.Errorf("unexpected error: %v", err)
166+
}
167+
}
168+
169+
func TestParse_ValueParserErrorReturned(t *testing.T) {
170+
t.Parallel()
171+
172+
tmpErr := errors.New("something bad happened")
173+
174+
p := &queryparam.Parser{
175+
Tag: "queryparam",
176+
DelimiterTag: "queryparamdelim",
177+
Delimiter: ",",
178+
ValueParsers: queryparam.DefaultValueParsers(),
179+
ValueSetters: map[reflect.Type]queryparam.ValueSetter{
180+
reflect.TypeOf(""): func(value reflect.Value, target reflect.Value) error {
181+
return tmpErr
182+
},
183+
},
184+
}
185+
186+
req := &struct {
187+
Name string `queryparam:"name"`
188+
}{}
189+
190+
err := p.Parse(urlValuesNameAge, req)
191+
if !errors.Is(err, tmpErr) {
192+
t.Errorf("unexpected error: %v", err)
193+
}
194+
}
195+
148196
func TestParse_EmptyTag(t *testing.T) {
149197
t.Parallel()
150198

@@ -158,7 +206,7 @@ func TestParse_EmptyTag(t *testing.T) {
158206
}
159207
}
160208

161-
func TestParse_ValueParserErrorReturned(t *testing.T) {
209+
func TestParse_ValueSetterErrorReturned(t *testing.T) {
162210
t.Parallel()
163211

164212
tmpErr := errors.New("something bad happened")
@@ -203,8 +251,9 @@ func BenchmarkParse(b *testing.B) {
203251
}
204252

205253
func TestErrInvalidParameterValue_Unwrap(t *testing.T) {
254+
tmpErr := errors.New("something bad")
206255
e := &queryparam.ErrInvalidParameterValue{
207-
Err: errors.New("something bad"),
256+
Err: tmpErr,
208257
Parameter: "Name",
209258
Field: "name",
210259
Value: "asd",
@@ -214,4 +263,26 @@ func TestErrInvalidParameterValue_Unwrap(t *testing.T) {
214263
if got := e.Error(); exp != got {
215264
t.Errorf("expected `%s`, got `%s`", exp, got)
216265
}
266+
if !errors.Is(e, tmpErr) {
267+
t.Error("expected is to return true")
268+
}
269+
}
270+
271+
func TestCannotSetValue_Unwrap(t *testing.T) {
272+
tmpErr := errors.New("something bad")
273+
e := &queryparam.ErrCannotSetValue{
274+
Err: tmpErr,
275+
Parameter: "Name",
276+
Field: "name",
277+
Value: "asd",
278+
Type: reflect.TypeOf(""),
279+
ParsedValue: reflect.ValueOf("asd"),
280+
}
281+
exp := "cannot set value for field name (string) from parameter Name (asd - asd): something bad"
282+
if got := e.Error(); exp != got {
283+
t.Errorf("expected `%s`, got `%s`", exp, got)
284+
}
285+
if !errors.Is(e, tmpErr) {
286+
t.Error("expected is to return true")
287+
}
217288
}

parsers.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,22 @@ import (
1111
// ErrInvalidBoolValue is returned when an unhandled string is parsed.
1212
var ErrInvalidBoolValue = errors.New("unknown bool value")
1313

14+
// DefaultValueParsers returns a set of default value parsers.
15+
func DefaultValueParsers() map[reflect.Type]ValueParser {
16+
return map[reflect.Type]ValueParser{
17+
reflect.TypeOf(""): StringValueParser,
18+
reflect.TypeOf([]string{}): StringSliceValueParser,
19+
reflect.TypeOf(0): IntValueParser,
20+
reflect.TypeOf(int32(0)): Int32ValueParser,
21+
reflect.TypeOf(int64(0)): Int64ValueParser,
22+
reflect.TypeOf(float32(0)): Float32ValueParser,
23+
reflect.TypeOf(float64(0)): Float64ValueParser,
24+
reflect.TypeOf(time.Time{}): TimeValueParser,
25+
reflect.TypeOf(false): BoolValueParser,
26+
reflect.TypeOf(Present(false)): PresentValueParser,
27+
}
28+
}
29+
1430
// StringValueParser parses a string into a string.
1531
func StringValueParser(value string, _ string) (reflect.Value, error) {
1632
return reflect.ValueOf(value), nil

0 commit comments

Comments
 (0)