diff --git a/README.md b/README.md index 367781e..347e7b4 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/doc/STRUCT_VALIDATION.md b/doc/STRUCT_VALIDATION.md new file mode 100644 index 0000000..84d7cfd --- /dev/null +++ b/doc/STRUCT_VALIDATION.md @@ -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" + ] +} +``` diff --git a/errors.go b/errors.go index dd96b3a..727506c 100644 --- a/errors.go +++ b/errors.go @@ -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") ) diff --git a/validator.go b/validator.go index 55b8f76..0f034c5 100644 --- a/validator.go +++ b/validator.go @@ -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 != "" { diff --git a/validator_test.go b/validator_test.go index 5b6014e..ab9896e 100644 --- a/validator_test.go +++ b/validator_test.go @@ -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() + }) +}