-
Notifications
You must be signed in to change notification settings - Fork 8
/
prehandler.go
155 lines (144 loc) · 4.35 KB
/
prehandler.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
package crud
import (
"fmt"
"net/url"
"strconv"
)
// Validate checks the spec against the inputs and returns an error if it finds one.
func (r *Router) Validate(val Validate, query url.Values, body interface{}, path map[string]string) error {
if val.Query.kind == KindObject { // not sure how any other type makes sense
// reject unknown values
if (val.Query.unknown == nil && r.allowUnknown == false) || !val.Query.isAllowUnknown() {
for key := range query {
if _, ok := val.Query.obj[key]; !ok {
return fmt.Errorf("unexpected query parameter %s: %w", key, errUnknown)
}
}
}
// strip unknown values
if (val.Query.strip == nil && r.stripUnknown == true) || val.Query.isStripUnknown() {
for key := range query {
if _, ok := val.Query.obj[key]; !ok {
delete(query, key)
}
}
}
for field, schema := range val.Query.obj {
// query values are always strings, so we must try to convert
queryValue := query[field]
if len(queryValue) == 0 {
if schema.required != nil && *schema.required {
return fmt.Errorf("query validation failed for field %v: %w", field, errRequired)
}
if schema._default != nil {
query[field] = []string{fmt.Sprint(schema._default)}
}
continue
}
if len(queryValue) > 1 {
if schema.kind != KindArray {
return fmt.Errorf("query validation failed for field %v: %w", field, errWrongType)
}
}
if schema.kind == KindArray {
if schema.min != nil && float64(len(queryValue)) < *schema.min {
return errMinimum
}
if schema.max != nil && float64(len(queryValue)) > *schema.max {
return errMaximum
}
// sadly we have to convert to a []interface{} to simplify the validation code
var intray []interface{}
for _, v := range queryValue {
intray = append(intray, v)
}
if schema.arr != nil {
for _, v := range queryValue {
convertedValue, err := convert(v, *schema.arr)
if err != nil {
return fmt.Errorf("query validation failed for field %v: %w", field, err)
}
if err = schema.arr.Validate(convertedValue); err != nil {
return fmt.Errorf("query validation failed for field %v: %w", field, err)
}
}
}
} else {
convertedValue, err := convert(queryValue[0], schema)
if err != nil {
return fmt.Errorf("query validation failed for field %v: %w", field, err)
}
if err = schema.Validate(convertedValue); err != nil {
return fmt.Errorf("query validation failed for field %v: %w", field, err)
}
}
}
}
if val.Body.Initialized() && val.Body.kind != KindFile {
// use router defaults if the object doesn't have anything set
f := val.Body
if f.strip == nil {
f = f.Strip(r.stripUnknown)
}
if f.unknown == nil {
f = f.Unknown(r.allowUnknown)
}
// ensure Required() since it's confusing and error-prone otherwise
f = f.Required()
if err := f.Validate(body); err != nil {
return err
}
}
if val.Path.kind == KindObject {
for field, schema := range val.Path.obj {
param := path[field]
convertedValue, err := convert(param, schema)
if err != nil {
return fmt.Errorf("path validation failed for field %v: %w", field, err)
}
if err = schema.Validate(convertedValue); err != nil {
return fmt.Errorf("path validation failed for field %v: %w", field, err)
}
}
}
return nil
}
// For certain types of data passed like Query and Header, the value is always
// a string. So this function attempts to convert the string into the desired field kind.
func convert(inputValue string, schema Field) (interface{}, error) {
// don't try to convert if the field is empty
if inputValue == "" {
if schema.required != nil && *schema.required {
return nil, errRequired
}
return nil, nil
}
var convertedValue interface{}
switch schema.kind {
case KindBoolean:
if inputValue == "true" {
convertedValue = true
} else if inputValue == "false" {
convertedValue = false
} else {
return nil, errWrongType
}
case KindString:
convertedValue = inputValue
case KindNumber:
var err error
convertedValue, err = strconv.ParseFloat(inputValue, 64)
if err != nil {
return nil, errWrongType
}
case KindInteger:
var err error
convertedValue, err = strconv.Atoi(inputValue)
if err != nil {
return nil, errWrongType
}
default:
return nil, fmt.Errorf("unknown kind: %v", schema.kind)
}
return convertedValue, nil
}