Skip to content

Commit 0413a0e

Browse files
authored
Added ValidateStruct to validate a struct without a request (#57) (#58)
Fixes #12
1 parent a730cd5 commit 0413a0e

File tree

5 files changed

+211
-14
lines changed

5 files changed

+211
-14
lines changed

Diff for: README.md

+4
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ Send request to the server using curl or postman: `curl GET "http://localhost:90
124124
* [Validate JSON to nested struct](doc/NESTED_STRUCT.md)
125125
* [Validate using custom rule](doc/CUSTOM_RULE.md)
126126

127+
***Validate struct directly***
128+
129+
* [Validate Struct](doc/STRUCT_VALIDATION.md)
130+
127131
### Validation Rules
128132
* `alpha` The field under validation must be entirely alphabetic characters.
129133
* `alpha_dash` The field under validation may have alpha-numeric characters, as well as dashes and underscores.

Diff for: doc/STRUCT_VALIDATION.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
3+
### Validate Struct
4+
5+
When using ValidateStruct you must provide data struct and rules. You can also pass message rules if you need custom message or localization.
6+
7+
```go
8+
package main
9+
10+
import (
11+
"encoding/json"
12+
"fmt"
13+
14+
"github.com/thedevsaddam/govalidator"
15+
)
16+
17+
type user struct {
18+
Username string `json:"username"`
19+
Email string `json:"email"`
20+
Web string `json:"web"`
21+
}
22+
23+
func validate(user *user) {
24+
rules := govalidator.MapData{
25+
"username": []string{"required", "between:3,5"},
26+
"email": []string{"required", "min:4", "max:20", "email"},
27+
"web": []string{"url"},
28+
}
29+
30+
opts := govalidator.Options{
31+
Data: &user,
32+
Rules: rules,
33+
}
34+
35+
v := govalidator.New(opts)
36+
e := v.ValidateStruct()
37+
if len(e) > 0 {
38+
data, _ := json.MarshalIndent(e, "", " ")
39+
fmt.Println(string(data))
40+
}
41+
}
42+
43+
func main() {
44+
validate(&user{
45+
Username: "john",
46+
Email: "invalid",
47+
})
48+
}
49+
```
50+
***Prints***
51+
```json
52+
{
53+
"email": [
54+
"The email field is required",
55+
"The email field must be a valid email address"
56+
],
57+
"username": [
58+
"The username field is required"
59+
]
60+
}
61+
```

Diff for: errors.go

+3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import "errors"
55
var (
66
errStringToInt = errors.New("govalidator: unable to parse string to integer")
77
errStringToFloat = errors.New("govalidator: unable to parse string to float")
8+
errRequireRules = errors.New("govalidator: provide at least rules for Validate* method")
89
errValidateArgsMismatch = errors.New("govalidator: provide at least *http.Request and rules for Validate method")
910
errInvalidArgument = errors.New("govalidator: invalid number of argument")
1011
errRequirePtr = errors.New("govalidator: provide pointer to the data structure")
12+
errRequireData = errors.New("govalidator: provide non-nil data structure for ValidateStruct method")
13+
errRequestNotAccepted = errors.New("govalidator: cannot provide an *http.Request for ValidateStruct method")
1114
)

Diff for: validator.go

+30-5
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,39 @@ func (v *Validator) ValidateJSON() url.Values {
146146
if reflect.TypeOf(v.Opts.Data).Kind() != reflect.Ptr {
147147
panic(errRequirePtr)
148148
}
149+
150+
return v.internalValidateStruct()
151+
}
152+
153+
func (v *Validator) ValidateStruct() url.Values {
154+
if len(v.Opts.Rules) == 0 {
155+
panic(errRequireRules)
156+
}
157+
if v.Opts.Request != nil {
158+
panic(errRequestNotAccepted)
159+
}
160+
if v.Opts.Data != nil && reflect.TypeOf(v.Opts.Data).Kind() != reflect.Ptr {
161+
panic(errRequirePtr)
162+
}
163+
if v.Opts.Data == nil {
164+
panic(errRequireData)
165+
}
166+
167+
return v.internalValidateStruct()
168+
}
169+
170+
func (v *Validator) internalValidateStruct() url.Values {
149171
errsBag := url.Values{}
150172

151-
defer v.Opts.Request.Body.Close()
152-
err := json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data)
153-
if err != nil {
154-
errsBag.Add("_error", err.Error())
155-
return errsBag
173+
if v.Opts.Request != nil {
174+
defer v.Opts.Request.Body.Close()
175+
err := json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data)
176+
if err != nil {
177+
errsBag.Add("_error", err.Error())
178+
return errsBag
179+
}
156180
}
181+
157182
r := roller{}
158183
r.setTagIdentifier(tagIdentifier)
159184
if v.Opts.TagIdentifier != "" {

Diff for: validator_test.go

+113-9
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,122 @@ func TestValidator_ValidateJSON_NULLValue(t *testing.T) {
170170
}
171171
}
172172

173-
func TestValidator_ValidateJSON_panic(t *testing.T) {
174-
defer func() {
175-
if r := recover(); r == nil {
176-
t.Errorf("ValidateJSON did not panic")
177-
}
178-
}()
173+
func TestValidator_ValidateStruct(t *testing.T) {
174+
type User struct {
175+
Name string `json:"name"`
176+
Email string `json:"email"`
177+
Address string `json:"address"`
178+
Age int `json:"age"`
179+
Zip string `json:"zip"`
180+
Color int `json:"color"`
181+
}
179182

180-
opts := Options{}
183+
postUser := User{
184+
Name: "",
185+
Email: "inalid email",
186+
Address: "",
187+
Age: 1,
188+
Zip: "122",
189+
Color: 5,
190+
}
191+
192+
rules := MapData{
193+
"name": []string{"required"},
194+
"email": []string{"email"},
195+
"address": []string{"required", "between:3,5"},
196+
"age": []string{"bool"},
197+
"zip": []string{"len:4"},
198+
"color": []string{"min:10"},
199+
}
200+
201+
opts := Options{
202+
Data: &postUser,
203+
Rules: rules,
204+
}
181205

182206
vd := New(opts)
183-
validationErr := vd.ValidateJSON()
207+
vd.SetTagIdentifier("json")
208+
validationErr := vd.ValidateStruct()
184209
if len(validationErr) != 5 {
185-
t.Error("ValidateJSON failed")
210+
t.Error("ValidateStruct failed")
186211
}
187212
}
213+
214+
func TestValidator_ValidateJSON_NoRules_panic(t *testing.T) {
215+
opts := Options{}
216+
217+
assertPanicWith(t, errValidateArgsMismatch, func() {
218+
New(opts).ValidateJSON()
219+
})
220+
}
221+
222+
func TestValidator_ValidateJSON_NonPointer_panic(t *testing.T) {
223+
req, _ := http.NewRequest("POST", "/", nil)
224+
225+
type User struct {
226+
}
227+
228+
var user User
229+
opts := Options{
230+
Request: req,
231+
Data: user,
232+
Rules: MapData{
233+
"name": []string{"required"},
234+
},
235+
}
236+
237+
assertPanicWith(t, errRequirePtr, func() {
238+
New(opts).ValidateJSON()
239+
})
240+
}
241+
242+
func TestValidator_ValidateStruct_NoRules_panic(t *testing.T) {
243+
opts := Options{}
244+
245+
assertPanicWith(t, errRequireRules, func() {
246+
New(opts).ValidateStruct()
247+
})
248+
}
249+
250+
func TestValidator_ValidateStruct_RequestProvided_panic(t *testing.T) {
251+
req, _ := http.NewRequest("POST", "/", nil)
252+
opts := Options{
253+
Request: req,
254+
Rules: MapData{
255+
"name": []string{"required"},
256+
},
257+
}
258+
259+
assertPanicWith(t, errRequestNotAccepted, func() {
260+
New(opts).ValidateStruct()
261+
})
262+
}
263+
264+
func TestValidator_ValidateStruct_NonPointer_panic(t *testing.T) {
265+
type User struct {
266+
}
267+
268+
var user User
269+
opts := Options{
270+
Data: user,
271+
Rules: MapData{
272+
"name": []string{"required"},
273+
},
274+
}
275+
276+
assertPanicWith(t, errRequirePtr, func() {
277+
New(opts).ValidateStruct()
278+
})
279+
}
280+
281+
func TestValidator_ValidateStruct_DataNil_panic(t *testing.T) {
282+
opts := Options{
283+
Rules: MapData{
284+
"name": []string{"required"},
285+
},
286+
}
287+
288+
assertPanicWith(t, errRequireData, func() {
289+
New(opts).ValidateStruct()
290+
})
291+
}

0 commit comments

Comments
 (0)