feat: add field validation API with signup form example
Introduces Field, Rule, ValidateAll, ResetFields, and AddError for declarative input validation. Includes built-in rules (Required, MinLen, MaxLen, Min, Max, Email, Pattern, Custom) and a signup example exercising the full API surface.
This commit is contained in:
128
rule.go
Normal file
128
rule.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package via
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Rule defines a single validation check for a Field.
|
||||
type Rule struct {
|
||||
validate func(val string) error
|
||||
}
|
||||
|
||||
// Required rejects empty or whitespace-only values.
|
||||
func Required(msg ...string) Rule {
|
||||
m := "This field is required"
|
||||
if len(msg) > 0 {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if strings.TrimSpace(val) == "" {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// MinLen rejects values shorter than n characters.
|
||||
func MinLen(n int, msg ...string) Rule {
|
||||
m := fmt.Sprintf("Must be at least %d characters", n)
|
||||
if len(msg) > 0 {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if len(val) < n {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// MaxLen rejects values longer than n characters.
|
||||
func MaxLen(n int, msg ...string) Rule {
|
||||
m := fmt.Sprintf("Must be at most %d characters", n)
|
||||
if len(msg) > 0 {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if len(val) > n {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// Min parses the value as an integer and rejects values less than n.
|
||||
func Min(n int, msg ...string) Rule {
|
||||
m := fmt.Sprintf("Must be at least %d", n)
|
||||
if len(msg) > 0 {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
v, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
if v < n {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// Max parses the value as an integer and rejects values greater than n.
|
||||
func Max(n int, msg ...string) Rule {
|
||||
m := fmt.Sprintf("Must be at most %d", n)
|
||||
if len(msg) > 0 {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
v, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
if v > n {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// Pattern rejects values that don't match the regular expression re.
|
||||
func Pattern(re string, msg ...string) Rule {
|
||||
m := "Invalid format"
|
||||
if len(msg) > 0 {
|
||||
m = msg[0]
|
||||
}
|
||||
compiled := regexp.MustCompile(re)
|
||||
return Rule{func(val string) error {
|
||||
if !compiled.MatchString(val) {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
var emailRegexp = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
||||
|
||||
// Email rejects values that don't look like an email address.
|
||||
func Email(msg ...string) Rule {
|
||||
m := "Invalid email address"
|
||||
if len(msg) > 0 {
|
||||
m = msg[0]
|
||||
}
|
||||
return Rule{func(val string) error {
|
||||
if !emailRegexp.MatchString(val) {
|
||||
return fmt.Errorf("%s", m)
|
||||
}
|
||||
return nil
|
||||
}}
|
||||
}
|
||||
|
||||
// Custom creates a rule from a user-provided validation function.
|
||||
// The function should return nil for valid input and an error for invalid input.
|
||||
func Custom(fn func(string) error) Rule {
|
||||
return Rule{validate: fn}
|
||||
}
|
||||
Reference in New Issue
Block a user