Skip to content

Commit

Permalink
Added ValidateStruct to validate a struct without a request (#57)
Browse files Browse the repository at this point in the history
Fixes #12
  • Loading branch information
maoueh authored and thedevsaddam committed Feb 7, 2019
1 parent a730cd5 commit 9f5dc88
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 14 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ Send request to the server using curl or postman: `curl GET "http://localhost:90
* [Validate JSON to nested struct](doc/NESTED_STRUCT.md)
* [Validate using custom rule](doc/CUSTOM_RULE.md)

***Validate struct directly***

* [Validate Struct](doc/STRUCT_VALIDATION.md)

### Validation Rules
* `alpha` The field under validation must be entirely alphabetic characters.
* `alpha_dash` The field under validation may have alpha-numeric characters, as well as dashes and underscores.
Expand Down
61 changes: 61 additions & 0 deletions doc/STRUCT_VALIDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@


### Validate Struct

When using ValidateStruct you must provide data struct and rules. You can also pass message rules if you need custom message or localization.

```go
package main

import (
"encoding/json"
"fmt"

"github.com/thedevsaddam/govalidator"
)

type user struct {
Username string `json:"username"`
Email string `json:"email"`
Web string `json:"web"`
}

func validate(user *user) {
rules := govalidator.MapData{
"username": []string{"required", "between:3,5"},
"email": []string{"required", "min:4", "max:20", "email"},
"web": []string{"url"},
}

opts := govalidator.Options{
Data: &user,
Rules: rules,
}

v := govalidator.New(opts)
e := v.ValidateStruct()
if len(e) > 0 {
data, _ := json.MarshalIndent(e, "", " ")
fmt.Println(string(data))
}
}

func main() {
validate(&user{
Username: "john",
Email: "invalid",
})
}
```
***Prints***
```json
{
"email": [
"The email field is required",
"The email field must be a valid email address"
],
"username": [
"The username field is required"
]
}
```
3 changes: 3 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import "errors"
var (
errStringToInt = errors.New("govalidator: unable to parse string to integer")
errStringToFloat = errors.New("govalidator: unable to parse string to float")
errRequireRules = errors.New("govalidator: provide at least rules for Validate* method")
errValidateArgsMismatch = errors.New("govalidator: provide at least *http.Request and rules for Validate method")
errInvalidArgument = errors.New("govalidator: invalid number of argument")
errRequirePtr = errors.New("govalidator: provide pointer to the data structure")
errRequireData = errors.New("govalidator: provide non-nil data structure for ValidateStruct method")
errRequestNotAccepted = errors.New("govalidator: cannot provide an *http.Request for ValidateStruct method")
)
35 changes: 30 additions & 5 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,39 @@ func (v *Validator) ValidateJSON() url.Values {
if reflect.TypeOf(v.Opts.Data).Kind() != reflect.Ptr {
panic(errRequirePtr)
}

return v.internalValidateStruct()
}

func (v *Validator) ValidateStruct() url.Values {
if len(v.Opts.Rules) == 0 {
panic(errRequireRules)
}
if v.Opts.Request != nil {
panic(errRequestNotAccepted)
}
if v.Opts.Data != nil && reflect.TypeOf(v.Opts.Data).Kind() != reflect.Ptr {
panic(errRequirePtr)
}
if v.Opts.Data == nil {
panic(errRequireData)
}

return v.internalValidateStruct()
}

func (v *Validator) internalValidateStruct() url.Values {
errsBag := url.Values{}

defer v.Opts.Request.Body.Close()
err := json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data)
if err != nil {
errsBag.Add("_error", err.Error())
return errsBag
if v.Opts.Request != nil {
defer v.Opts.Request.Body.Close()
err := json.NewDecoder(v.Opts.Request.Body).Decode(v.Opts.Data)
if err != nil {
errsBag.Add("_error", err.Error())
return errsBag
}
}

r := roller{}
r.setTagIdentifier(tagIdentifier)
if v.Opts.TagIdentifier != "" {
Expand Down
122 changes: 113 additions & 9 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,122 @@ func TestValidator_ValidateJSON_NULLValue(t *testing.T) {
}
}

func TestValidator_ValidateJSON_panic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("ValidateJSON did not panic")
}
}()
func TestValidator_ValidateStruct(t *testing.T) {
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Address string `json:"address"`
Age int `json:"age"`
Zip string `json:"zip"`
Color int `json:"color"`
}

opts := Options{}
postUser := User{
Name: "",
Email: "inalid email",
Address: "",
Age: 1,
Zip: "122",
Color: 5,
}

rules := MapData{
"name": []string{"required"},
"email": []string{"email"},
"address": []string{"required", "between:3,5"},
"age": []string{"bool"},
"zip": []string{"len:4"},
"color": []string{"min:10"},
}

opts := Options{
Data: &postUser,
Rules: rules,
}

vd := New(opts)
validationErr := vd.ValidateJSON()
vd.SetTagIdentifier("json")
validationErr := vd.ValidateStruct()
if len(validationErr) != 5 {
t.Error("ValidateJSON failed")
t.Error("ValidateStruct failed")
}
}

func TestValidator_ValidateJSON_NoRules_panic(t *testing.T) {
opts := Options{}

assertPanicWith(t, errValidateArgsMismatch, func() {
New(opts).ValidateJSON()
})
}

func TestValidator_ValidateJSON_NonPointer_panic(t *testing.T) {
req, _ := http.NewRequest("POST", "/", nil)

type User struct {
}

var user User
opts := Options{
Request: req,
Data: user,
Rules: MapData{
"name": []string{"required"},
},
}

assertPanicWith(t, errRequirePtr, func() {
New(opts).ValidateJSON()
})
}

func TestValidator_ValidateStruct_NoRules_panic(t *testing.T) {
opts := Options{}

assertPanicWith(t, errRequireRules, func() {
New(opts).ValidateStruct()
})
}

func TestValidator_ValidateStruct_RequestProvided_panic(t *testing.T) {
req, _ := http.NewRequest("POST", "/", nil)
opts := Options{
Request: req,
Rules: MapData{
"name": []string{"required"},
},
}

assertPanicWith(t, errRequestNotAccepted, func() {
New(opts).ValidateStruct()
})
}

func TestValidator_ValidateStruct_NonPointer_panic(t *testing.T) {
type User struct {
}

var user User
opts := Options{
Data: user,
Rules: MapData{
"name": []string{"required"},
},
}

assertPanicWith(t, errRequirePtr, func() {
New(opts).ValidateStruct()
})
}

func TestValidator_ValidateStruct_DataNil_panic(t *testing.T) {
opts := Options{
Rules: MapData{
"name": []string{"required"},
},
}

assertPanicWith(t, errRequireData, func() {
New(opts).ValidateStruct()
})
}

0 comments on commit 9f5dc88

Please sign in to comment.