Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ValidateStruct to validate a struct without a request #57

Merged
merged 1 commit into from
Feb 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
})
}