-
Notifications
You must be signed in to change notification settings - Fork 0
/
checker.go
165 lines (139 loc) · 4.04 KB
/
checker.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
// Package checker is a Go library for validating user input through struct tags.
//
// https://github.com/cinar/checker
//
// Copyright 2023 Onur Cinar. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package checker
import (
"fmt"
"reflect"
"strings"
)
// Result is a unique textual identifier for the mistake.
type Result string
// CheckFunc defines the checker function.
type CheckFunc func(value, parent reflect.Value) Result
// MakeFunc defines the maker function.
type MakeFunc func(params string) CheckFunc
// Mistakes provides mapping to checker result for the invalid fields.
type Mistakes map[string]Result
type checkerJob struct {
Parent reflect.Value
Name string
Value reflect.Value
Config string
}
// ResultValid result indicates that the user input is valid.
const ResultValid Result = "VALID"
// makers provides mapping to maker function for the checkers.
var makers = map[string]MakeFunc{
CheckerAlphanumeric: makeAlphanumeric,
CheckerASCII: makeASCII,
CheckerCreditCard: makeCreditCard,
CheckerCidr: makeCidr,
CheckerDigits: makeDigits,
CheckerEmail: makeEmail,
CheckerFqdn: makeFqdn,
CheckerIP: makeIP,
CheckerIPV4: makeIPV4,
CheckerIPV6: makeIPV6,
CheckerISBN: makeISBN,
CheckerLuhn: makeLuhn,
CheckerMac: makeMac,
CheckerMax: makeMax,
CheckerMaxLength: makeMaxLength,
CheckerMin: makeMin,
CheckerMinLength: makeMinLength,
CheckerRegexp: makeRegexp,
CheckerRequired: makeRequired,
CheckerSame: makeSame,
CheckerURL: makeURL,
NormalizerLower: makeLower,
NormalizerUpper: makeUpper,
NormalizerTitle: makeTitle,
NormalizerTrim: makeTrim,
NormalizerTrimLeft: makeTrimLeft,
NormalizerTrimRight: makeTrimRight,
}
// Register registers the given checker name and the maker function.
func Register(name string, maker MakeFunc) {
makers[name] = maker
}
// Check checks the given struct based on the checkers listed in each field's strcut tag named checkers.
func Check(s interface{}) (Mistakes, bool) {
root := reflect.Indirect(reflect.ValueOf(s))
if root.Kind() != reflect.Struct {
panic("expecting struct")
}
mistakes := Mistakes{}
jobs := []checkerJob{
{
Parent: reflect.ValueOf(nil),
Name: "",
Value: root,
Config: "",
},
}
for len(jobs) > 0 {
job := jobs[0]
jobs = jobs[1:]
if job.Value.Kind() == reflect.Struct {
for i := 0; i < job.Value.NumField(); i++ {
field := job.Value.Type().Field(i)
addJob := field.Type.Kind() == reflect.Struct
config := ""
if !addJob {
config = field.Tag.Get("checkers")
addJob = config != ""
}
if addJob {
name := field.Name
if job.Name != "" {
name = job.Name + "." + name
}
jobs = append(jobs, checkerJob{
Parent: job.Value,
Name: name,
Value: reflect.Indirect(job.Value.FieldByIndex(field.Index)),
Config: config,
})
}
}
} else {
for _, checker := range initCheckers(job.Config) {
if result := checker(job.Value, job.Parent); result != ResultValid {
mistakes[job.Name] = result
break
}
}
}
}
return mistakes, len(mistakes) == 0
}
// initCheckers initializes the checkers provided in the config.
func initCheckers(config string) []CheckFunc {
fields := strings.Fields(config)
checkers := make([]CheckFunc, len(fields))
for i, field := range fields {
name, params, _ := strings.Cut(field, ":")
maker, ok := makers[name]
if !ok {
panic(fmt.Sprintf("checker %s is unknown", name))
}
checkers[i] = maker(params)
}
return checkers
}
// numberOf gives value's numerical value given that it is either an int or a float.
func numberOf(value reflect.Value) float64 {
switch value.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(value.Int())
case reflect.Float32, reflect.Float64:
return value.Float()
default:
panic("expecting int or float")
}
}