diff --git a/rules.go b/rules.go index c171783..08f909f 100644 --- a/rules.go +++ b/rules.go @@ -19,6 +19,7 @@ type Rules struct { MustMatch *regexp.Regexp MinLength, MaxLength int MinLetters int + MinLower, MinUpper int MinNumeric int MinSymbols int AllowWhitespace bool @@ -49,10 +50,16 @@ func (r Rules) Errors(toValidate string) ErrorList { errs = append(errs, FieldError{r.FieldName, errors.New(r.lengthErr())}) } - chars, numeric, symbols, whitespace := tallyCharacters(toValidate) - if chars < r.MinLetters { + upper, lower, numeric, symbols, whitespace := tallyCharacters(toValidate) + if upper+lower < r.MinLetters { errs = append(errs, FieldError{r.FieldName, errors.New(r.charErr())}) } + if upper < r.MinUpper { + errs = append(errs, FieldError{r.FieldName, errors.New(r.upperErr())}) + } + if upper < r.MinLower { + errs = append(errs, FieldError{r.FieldName, errors.New(r.lowerErr())}) + } if numeric < r.MinNumeric { errs = append(errs, FieldError{r.FieldName, errors.New(r.numericErr())}) } @@ -89,6 +96,12 @@ func (r Rules) Rules() []string { if e := r.charErr(); len(e) > 0 { rules = append(rules, e) } + if e := r.upperErr(); len(e) > 0 { + rules = append(rules, e) + } + if e := r.lowerErr(); len(e) > 0 { + rules = append(rules, e) + } if e := r.numericErr(); len(e) > 0 { rules = append(rules, e) } @@ -119,6 +132,20 @@ func (r Rules) charErr() (err string) { return err } +func (r Rules) upperErr() (err string) { + if r.MinUpper > 0 { + err = fmt.Sprintf("Must contain at least %d uppercase letters", r.MinUpper) + } + return err +} + +func (r Rules) lowerErr() (err string) { + if r.MinLower > 0 { + err = fmt.Sprintf("Must contain at least %d lowercase letters", r.MinLower) + } + return err +} + func (r Rules) numericErr() (err string) { if r.MinNumeric > 0 { err = fmt.Sprintf("Must contain at least %d numbers", r.MinNumeric) @@ -133,11 +160,15 @@ func (r Rules) symbolErr() (err string) { return err } -func tallyCharacters(s string) (chars, numeric, symbols, whitespace int) { +func tallyCharacters(s string) (upper, lower, numeric, symbols, whitespace int) { for _, c := range s { switch { case unicode.IsLetter(c): - chars++ + if unicode.IsUpper(c) { + upper++ + } else { + lower++ + } case unicode.IsDigit(c): numeric++ case unicode.IsSpace(c): @@ -147,5 +178,5 @@ func tallyCharacters(s string) (chars, numeric, symbols, whitespace int) { } } - return chars, numeric, symbols, whitespace + return upper, lower, numeric, symbols, whitespace } diff --git a/rules_test.go b/rules_test.go index 15ce0b8..07b2e4a 100644 --- a/rules_test.go +++ b/rules_test.go @@ -48,6 +48,16 @@ func TestRules_Errors(t *testing.T) { "13345", "email: Must contain at least 5 letters", }, + { + Rules{FieldName: "email", MinUpper: 5}, + "hi", + "email: Must contain at least 5 uppercase letters", + }, + { + Rules{FieldName: "email", MinLower: 5}, + "hi", + "email: Must contain at least 5 lowercase letters", + }, { Rules{FieldName: "email", MinSymbols: 5}, "hi", @@ -90,8 +100,10 @@ func TestRules_Rules(t *testing.T) { MinLength: 1, MaxLength: 2, MinLetters: 3, - MinNumeric: 4, - MinSymbols: 5, + MinUpper: 4, + MinLower: 5, + MinNumeric: 6, + MinSymbols: 7, AllowWhitespace: false, } @@ -101,8 +113,10 @@ func TestRules_Rules(t *testing.T) { "Must adhere to this regexp", "Must be between 1 and 2 characters", "Must contain at least 3 letters", - "Must contain at least 4 numbers", - "Must contain at least 5 symbols", + "Must contain at least 4 uppercase letters", + "Must contain at least 5 lowercase letters", + "Must contain at least 6 numbers", + "Must contain at least 7 symbols", } for i, toFind := range mustFind { @@ -128,9 +142,12 @@ 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) + u, l, n, s, w := tallyCharacters("123abcDEF@#$%^* ") + if u != 3 { + t.Error("Number of upper:", u) + } + if l != 3 { + t.Error("Number of lower:", l) } if n != 3 { t.Error("Number of numerics:", n)