1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-04-15 11:56:31 +02:00

Second shot at proper validation.

This commit is contained in:
Aaron 2015-01-24 16:07:41 -08:00
parent fe7b990095
commit 65f7fad5fc
5 changed files with 135 additions and 31 deletions

View File

@ -1,5 +1,11 @@
package authboss
import (
"bytes"
"fmt"
"net/http"
)
type mockUser struct {
Email string
Password string
@ -31,3 +37,44 @@ func (m mockClientStore) Get(key string) (string, bool) {
}
func (m mockClientStore) Put(key, val string) { m[key] = val }
func (m mockClientStore) Del(key string) { delete(m, key) }
func mockRequestContext(postKeyValues ...string) *Context {
keyValues := &bytes.Buffer{}
for i := 0; i < len(postKeyValues); i += 2 {
if i != 0 {
keyValues.WriteByte('&')
}
fmt.Fprintf(keyValues, "%s=%s", postKeyValues[i], postKeyValues[i+1])
}
req, err := http.NewRequest("POST", "http://localhost", keyValues)
if err != nil {
panic(err.Error())
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
ctx, err := ContextFromRequest(req)
if err != nil {
panic(err)
}
return ctx
}
type mockValidator struct {
FieldName string
Errs ErrorList
Ruleset []string
}
func (m mockValidator) Field() string {
return m.FieldName
}
func (m mockValidator) Errors(in string) ErrorList {
return m.Errs
}
func (m mockValidator) Rules() []string {
return m.Ruleset
}

View File

@ -1,18 +1,16 @@
package validate
package authboss
import (
"errors"
"fmt"
"regexp"
"unicode"
"gopkg.in/authboss.v0"
)
// Rules defines a ruleset by which a string can be validated.
type Rules struct {
// Field is the name of the field this is intended to validate.
Field string
// FieldName is the name of the field this is intended to validate.
FieldName string
// MatchError describes the MustMatch regexp to a user.
MatchError string
MustMatch *regexp.Regexp
@ -23,39 +21,44 @@ type Rules struct {
AllowWhitespace bool
}
// Field names the field this ruleset applies to.
func (r Rules) Field() string {
return r.FieldName
}
// Errors returns an array of errors for each validation error that
// is present in the given string. Returns nil if there are no errors.
func (r Rules) Errors(toValidate string) authboss.ErrorList {
errs := make(authboss.ErrorList, 0)
func (r Rules) Errors(toValidate string) ErrorList {
errs := make(ErrorList, 0)
ln := len(toValidate)
if ln == 0 {
errs = append(errs, authboss.FieldError{r.Field, errors.New("Cannot be blank")})
errs = append(errs, FieldError{r.FieldName, errors.New("Cannot be blank")})
return errs
}
if r.MustMatch != nil {
if !r.MustMatch.MatchString(toValidate) {
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.MatchError)})
errs = append(errs, FieldError{r.FieldName, errors.New(r.MatchError)})
}
}
if (r.MinLength > 0 && ln < r.MinLength) || (r.MaxLength > 0 && ln > r.MaxLength) {
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.lengthErr())})
errs = append(errs, FieldError{r.FieldName, errors.New(r.lengthErr())})
}
chars, numeric, symbols, whitespace := tallyCharacters(toValidate)
if chars < r.MinLetters {
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.charErr())})
errs = append(errs, FieldError{r.FieldName, errors.New(r.charErr())})
}
if numeric < r.MinNumeric {
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.numericErr())})
errs = append(errs, FieldError{r.FieldName, errors.New(r.numericErr())})
}
if symbols < r.MinSymbols {
errs = append(errs, authboss.FieldError{r.Field, errors.New(r.symbolErr())})
errs = append(errs, FieldError{r.FieldName, errors.New(r.symbolErr())})
}
if !r.AllowWhitespace && whitespace > 0 {
errs = append(errs, authboss.FieldError{r.Field, errors.New("No whitespace permitted")})
errs = append(errs, FieldError{r.FieldName, errors.New("No whitespace permitted")})
}
if len(errs) == 0 {

View File

@ -1,4 +1,4 @@
package validate
package authboss
import (
"regexp"
@ -6,53 +6,55 @@ import (
)
func TestRules_Errors(t *testing.T) {
t.Parallel()
tests := []struct {
Rules Rules
In string
Error string
}{
{
Rules{Field: "email"},
Rules{FieldName: "email"},
"",
"email: Cannot be blank",
},
{
Rules{Field: "email", MatchError: "Regexp must match!", MustMatch: regexp.MustCompile("abc")},
Rules{FieldName: "email", MatchError: "Regexp must match!", MustMatch: regexp.MustCompile("abc")},
"hello",
"email: Regexp must match!",
},
{
Rules{Field: "email", MinLength: 5},
Rules{FieldName: "email", MinLength: 5},
"hi",
"email: Must be at least 5 characters",
},
{
Rules{Field: "email", MaxLength: 3},
Rules{FieldName: "email", MaxLength: 3},
"hello",
"email: Must be at most 3 characters",
},
{
Rules{Field: "email", MinLength: 3, MaxLength: 5},
Rules{FieldName: "email", MinLength: 3, MaxLength: 5},
"hi",
"email: Must be between 3 and 5 characters",
},
{
Rules{Field: "email", MinLetters: 5},
Rules{FieldName: "email", MinLetters: 5},
"13345",
"email: Must contain at least 5 letters",
},
{
Rules{Field: "email", MinSymbols: 5},
Rules{FieldName: "email", MinSymbols: 5},
"hi",
"email: Must contain at least 5 symbols",
},
{
Rules{Field: "email", MinNumeric: 5},
Rules{FieldName: "email", MinNumeric: 5},
"hi",
"email: Must contain at least 5 numbers",
},
{
Rules{Field: "email"},
Rules{FieldName: "email"},
"hi whitespace",
"email: No whitespace permitted",
},
@ -74,8 +76,10 @@ func TestRules_Errors(t *testing.T) {
}
func TestRules_Rules(t *testing.T) {
t.Parallel()
r := Rules{
Field: "email",
FieldName: "email",
MatchError: "Must adhere to this regexp",
MustMatch: regexp.MustCompile(""),
MinLength: 1,
@ -104,7 +108,9 @@ func TestRules_Rules(t *testing.T) {
}
func TestRules_IsValid(t *testing.T) {
r := Rules{Field: "email"}
t.Parallel()
r := Rules{FieldName: "email"}
if r.IsValid("") {
t.Error("It should not be valid.")
}
@ -115,6 +121,8 @@ func TestRules_IsValid(t *testing.T) {
}
func TestTallyCharacters(t *testing.T) {
t.Parallel()
c, n, s, w := tallyCharacters("123abcDEF@#$%^* ")
if c != 6 {
t.Error("Number of chars:", c)

View File

@ -5,6 +5,14 @@ import (
"fmt"
)
// Validator is anything that can validate a string and provide a list of errors
// and describe its set of rules.
type Validator interface {
Field() string
Errors(in string) ErrorList
Rules() []string
}
type ErrorList []error
// Error satisfies the error interface.
@ -49,9 +57,18 @@ func (f FieldError) Error() string {
return fmt.Sprintf("%s: %v", f.Name, f.Err)
}
// Validator is anything that can validate a string and provide a list of errors
// and describe its set of rules.
type Validator interface {
Errors(in string) ErrorList
Rules() []string
// Validate validates a request using the given ruleset.
func (ctx *Context) Validate(ruleset []Validator) ErrorList {
errList := make(ErrorList, 0)
for _, validator := range ruleset {
field := validator.Field()
val, _ := ctx.FirstFormValue(field)
if errs := validator.Errors(val); len(errs) > 0 {
errList = append(errList, errs...)
}
}
return errList
}

View File

@ -60,3 +60,32 @@ func TestErrorList_Map(t *testing.T) {
t.Error("Wrong unkown error at 0:", unknownErrs[0])
}
}
func TestValidate(t *testing.T) {
t.Parallel()
ctx := mockRequestContext("username", "john", "email", "john@john.com")
errList := ctx.Validate([]Validator{
mockValidator{
FieldName: "username", Errs: ErrorList{FieldError{"username", errors.New("must be longer than 4")}},
},
mockValidator{
FieldName: "missing_field", Errs: ErrorList{FieldError{"missing_field", errors.New("Expected field to exist.")}},
},
mockValidator{
FieldName: "email", Errs: nil,
},
})
errs := errList.Map()
if errs["username"][0] != "must be longer than 4" {
t.Error("Expected a different error for username:", errs["username"][0])
}
if errs["missing_field"][0] != "Expected field to exist." {
t.Error("Expected a different error for missing_field:", errs["missing_field"][0])
}
if _, ok := errs["email"]; ok {
t.Error("Expected no errors for email.")
}
}