package vouch import ( "errors" "fmt" "net" "net/url" "reflect" "regexp" "strings" "time" ) func typeMismatchError(expected, actual reflect.Kind) error { return fmt.Errorf("expected type %s, but got %s", Types[expected], Types[actual]) } func Required() Rule { return func(v any, kind reflect.Kind) error { val := reflect.ValueOf(v) if v == nil { return fmt.Errorf("is required") } switch kind { case reflect.String, reflect.Array, reflect.Slice, reflect.Map: if val.Len() == 0 { return fmt.Errorf("is required") } case reflect.Ptr, reflect.Interface: if val.IsNil() { return fmt.Errorf("is required") } } return nil } } func MaxLen(n int) Rule { return func(v any, kind reflect.Kind) error { switch kind { case reflect.String: val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } if len(val) > n { return fmt.Errorf("must be at most %d characters long", n) } case reflect.Array, reflect.Slice, reflect.Map: val := reflect.ValueOf(v) if val.Kind() != kind { return typeMismatchError(kind, val.Kind()) } if val.Len() > n { return fmt.Errorf("must have at most %d elements", n) } default: return fmt.Errorf("unsupported type for MaxLen") } return nil } } func MinLen(n int) Rule { return func(v any, kind reflect.Kind) error { switch kind { case reflect.String: val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } if len(val) < n { return fmt.Errorf("must be at least %d characters long", n) } case reflect.Array, reflect.Slice, reflect.Map: val := reflect.ValueOf(v) if val.Kind() != kind { return typeMismatchError(kind, val.Kind()) } if val.Len() < n { return fmt.Errorf("must have at least %d elements", n) } default: return fmt.Errorf("unsupported type for MinLen") } return nil } } func ExactLen(n int) Rule { return func(v any, kind reflect.Kind) error { switch kind { case reflect.String: val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } if len(val) != n { return fmt.Errorf("must be exactly %d characters long", n) } case reflect.Array, reflect.Slice, reflect.Map: val := reflect.ValueOf(v) if val.Kind() != kind { return typeMismatchError(kind, val.Kind()) } if val.Len() != n { return fmt.Errorf("must have exactly %d elements", n) } default: return fmt.Errorf("unsupported type for ExactLen") } return nil } } func MaxValue(n float64) Rule { return func(v any, kind reflect.Kind) error { val := reflect.ValueOf(v) switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if val.Int() > int64(n) { return fmt.Errorf("must be at most %d", int64(n)) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if val.Uint() > uint64(n) { return fmt.Errorf("must be at most %d", uint64(n)) } case reflect.Float32, reflect.Float64: if val.Float() > float64(n) { return fmt.Errorf("must be at most %f", n) } default: return fmt.Errorf("unsupported type for MaxValue") } return nil } } func MinValue(n float64) Rule { return func(v any, kind reflect.Kind) error { val := reflect.ValueOf(v) switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if val.Int() < int64(n) { return fmt.Errorf("must be at least %d", int64(n)) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if val.Uint() < uint64(n) { return fmt.Errorf("must be at least %d", uint64(n)) } case reflect.Float32, reflect.Float64: if val.Float() < float64(n) { return fmt.Errorf("must be at least %f", n) } default: return fmt.Errorf("unsupported type for MinValue") } return nil } } func GreaterThan(n float64) Rule { return func(v any, kind reflect.Kind) error { val := reflect.ValueOf(v) switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if val.Int() <= int64(n) { return fmt.Errorf("must be greater than %d", int64(n)) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if val.Uint() <= uint64(n) { return fmt.Errorf("must be greater than %d", uint64(n)) } case reflect.Float32, reflect.Float64: if val.Float() <= float64(n) { return fmt.Errorf("must be greater than %f", n) } default: return fmt.Errorf("unsupported type for GreaterThan") } return nil } } func LessThan(n float64) Rule { return func(v any, kind reflect.Kind) error { val := reflect.ValueOf(v) switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if val.Int() >= int64(n) { return fmt.Errorf("must be less than %d", int64(n)) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if val.Uint() >= uint64(n) { return fmt.Errorf("must be less than %d", uint64(n)) } case reflect.Float32, reflect.Float64: if val.Float() >= float64(n) { return fmt.Errorf("must be less than %f", n) } default: return fmt.Errorf("unsupported type for LessThan") } return nil } } func Between(min, max float64) Rule { return func(v any, kind reflect.Kind) error { val := reflect.ValueOf(v) switch kind { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if val.Int() < int64(min) || val.Int() > int64(max) { return fmt.Errorf("must be between %d and %d", int64(min), int64(max)) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if val.Uint() < uint64(min) || val.Uint() > uint64(max) { return fmt.Errorf("must be between %d and %d", uint64(min), uint64(max)) } case reflect.Float32, reflect.Float64: if val.Float() < min || val.Float() > max { return fmt.Errorf("must be between %f and %f", min, max) } default: return fmt.Errorf("unsupported type for Between") } return nil } } func RegexMatch(pattern string) Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } reg := regexp.MustCompile(pattern) if !reg.MatchString(val) { return errors.New("is in an invalid format") } return nil } } func Email() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) if !emailRegex.MatchString(val) { return errors.New("must be a valid email address") } return nil } } func URL() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } if _, err := url.ParseRequestURI(val); err != nil { return errors.New("must be a valid url") } return nil } } func Alpha() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } emailRegex := regexp.MustCompile(`^[a-zA-Z]+$`) if !emailRegex.MatchString(val) { return errors.New("must contain only letters") } return nil } } func Numeric() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } emailRegex := regexp.MustCompile(`^[0-9]+$`) if !emailRegex.MatchString(val) { return errors.New("must contain only numbers") } return nil } } func Alphanumeric() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } emailRegex := regexp.MustCompile(`^[a-zA-Z0-9]+$`) if !emailRegex.MatchString(val) { return errors.New("must contain only letters and numbers") } return nil } } func ContainsUpper() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } upperRegex := regexp.MustCompile(`[A-Z]`) if !upperRegex.MatchString(val) { return errors.New("must contain at least one uppercase letter") } return nil } } func ContainsLower() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } lowerRegex := regexp.MustCompile(`[a-z]`) if !lowerRegex.MatchString(val) { return errors.New("must contain at least one lowercase letter") } return nil } } func ContainsDigit() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } digitRegex := regexp.MustCompile(`[0-9]`) if !digitRegex.MatchString(val) { return errors.New("must contain at least one digit") } return nil } } func ContainsSpecial() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } specialRegex := regexp.MustCompile(`[^a-zA-Z0-9]`) if !specialRegex.MatchString(val) { return errors.New("must contain at least one special character") } return nil } } func StartsWith(prefix string) Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } if !strings.HasPrefix(val, prefix) { return fmt.Errorf("must start with '%s'", prefix) } return nil } } func EndsWith(suffix string) Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } if !strings.HasSuffix(val, suffix) { return fmt.Errorf("must end with '%s'", suffix) } return nil } } func UUID() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } uuidRegex := regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) if !uuidRegex.MatchString(val) { return errors.New("must be a valid UUID") } return nil } } func IP() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } if net.ParseIP(val) == nil { return errors.New("must be a valid IP address") } return nil } } func IPv4() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } ip := net.ParseIP(val) if ip == nil || ip.To4() == nil { return errors.New("must be a valid IPv4 address") } return nil } } func IPv6() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } ip := net.ParseIP(val) if ip == nil || ip.To4() != nil { return errors.New("must be a valid IPv6 address") } return nil } } func Date() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } dateRegex := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) if !dateRegex.MatchString(val) { return errors.New("must be a valid date in format YYYY-MM-DD") } _, err := time.Parse("2006-01-02", val) if err != nil { return errors.New("must be a valid date") } return nil } } func Time() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } timeRegex := regexp.MustCompile(`^\d{2}:\d{2}:\d{2}$`) if !timeRegex.MatchString(val) { return errors.New("must be a valid time in format HH:MM:SS") } _, err := time.Parse("15:04:05", val) if err != nil { return errors.New("must be a valid time") } return nil } } func DateTime() Rule { return func(v any, kind reflect.Kind) error { val, ok := v.(string) if !ok { return typeMismatchError(reflect.String, kind) } dateTimeRegex := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$`) if !dateTimeRegex.MatchString(val) { return errors.New("must be a valid datetime in ISO 8601 format") } _, err := time.Parse(time.RFC3339, val) if err != nil { return errors.New("must be a valid datetime") } return nil } }