Skip to content

Commit

Permalink
Try Go generics
Browse files Browse the repository at this point in the history
Run `gotip test -gcflags=-G=3 example_simple_value_test.go`.
  • Loading branch information
RussellLuo committed Oct 7, 2021
1 parent b98ec44 commit 21aa3a7
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 80 deletions.
94 changes: 26 additions & 68 deletions builtin.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package validating

import (
/*import (
"regexp"
"strconv"
"time"
"unicode/utf8"
)
*/

// Func is an adapter to allow the use of ordinary functions as
// validators. If f is a function with the appropriate signature,
// Func(f) is a Validator that calls f.
type Func func(field Field) Errors
type Func[T any] func(field *Field[T]) Errors

// Validate calls f(field).
func (f Func) Validate(field Field) Errors {
func (f Func[T]) Validate(field *Field[T]) Errors {
return f(field)
}

// validateSchema do the validation per the given schema, which is associated
// with the given field.
func validateSchema(schema Schema, field Field, prefixFunc func(string) string) (errs Errors) {
func validateSchema[T any](schema Schema[T], field *Field[T], prefixFunc func(string) string) (errs Errors) {
prefix := prefixFunc(field.Name)

for f, v := range schema {
Expand All @@ -28,7 +29,7 @@ func validateSchema(schema Schema, field Field, prefixFunc func(string) string)
if f.Name != "" {
name = name + "." + f.Name
}
f = F(name, f.ValuePtr)
f = F[T](name, f.Value)
}
if err := v.Validate(f); err != nil {
errs.Extend(err)
Expand All @@ -39,22 +40,22 @@ func validateSchema(schema Schema, field Field, prefixFunc func(string) string)

// Schema is a field mapping, which defines
// the corresponding validator for each field.
type Schema map[Field]Validator
type Schema[T any] map[*Field[T]]Validator[T]

// Validate validates fields per the given according to the schema.
func (s Schema) Validate(field Field) (errs Errors) {
return validateSchema(s, field, func(name string) string {
func (s Schema[T]) Validate(field *Field[T]) (errs Errors) {
return validateSchema[T](s, field, func(name string) string {
return name
})
}

// Value is a shortcut function used to create a schema for a simple value.
func Value(valuePtr interface{}, validator Validator) Schema {
return Schema{
F("", valuePtr): validator,
func Value[T any](value T, validator Validator[T]) Schema[T] {
return Schema[T]{
F[T]("", value): validator,
}
}

/*
// Map is a composite validator factory used to create a validator, which will
// do the validation per the schemas associated with a map.
func Map(f func() map[string]Schema) Validator {
Expand Down Expand Up @@ -91,27 +92,29 @@ func Slice(f func() []Schema) Validator {
// Array is an alias of Slice.
var Array = Slice
*/

// MessageValidator is a validator that allows users to customize the INVALID
// error message by calling Msg().
type MessageValidator struct {
type MessageValidator[T any] struct {
Message string
Validator Validator
Validator Validator[T]
}

// Msg sets the INVALID error message.
func (mv *MessageValidator) Msg(msg string) *MessageValidator {
func (mv *MessageValidator[T]) Msg(msg string) *MessageValidator[T] {
if msg != "" {
mv.Message = msg
}
return mv
}

// Validate delegates the actual validation to its inner validator.
func (mv *MessageValidator) Validate(field Field) Errors {
func (mv *MessageValidator[T]) Validate(field *Field[T]) Errors {
return mv.Validator.Validate(field)
}

/*
// All is a composite validator factory used to create a validator, which will
// succeed only when all sub-validators succeed.
func All(validators ...Validator) Validator {
Expand Down Expand Up @@ -491,62 +494,15 @@ func RuneCount(min, max int) (mv *MessageValidator) {
}
return
}
*/

// Eq is a leaf validator factory used to create a validator, which will
// succeed when the field's value equals the given value.
func Eq(value interface{}) (mv *MessageValidator) {
mv = &MessageValidator{
func Eq[T comparable](value T) (mv *MessageValidator[T]) {
mv = &MessageValidator[T]{
Message: "does not equal the given value",
Validator: Func(func(field Field) Errors {
valid := false

switch t := field.ValuePtr.(type) {
case **uint8, *[]uint8, **uint16, *[]uint16,
**uint32, *[]uint32, **uint64, *[]uint64,
**int8, *[]int8, **int16, *[]int16,
**int32, *[]int32, **int64, *[]int64,
**float32, *[]float32, **float64, *[]float64,
**uint, *[]uint, **int, *[]int,
*bool, **bool, *[]bool,
**string, *[]string,
**time.Time, *[]time.Time,
**time.Duration, *[]time.Duration:
return NewErrors(field.Name, ErrUnsupported, "cannot use validator `Eq`")
case *uint8:
valid = *t == value.(uint8)
case *uint16:
valid = *t == value.(uint16)
case *uint32:
valid = *t == value.(uint32)
case *uint64:
valid = *t == value.(uint64)
case *int8:
valid = *t == value.(int8)
case *int16:
valid = *t == value.(int16)
case *int32:
valid = *t == value.(int32)
case *int64:
valid = *t == value.(int64)
case *float32:
valid = *t == value.(float32)
case *float64:
valid = *t == value.(float64)
case *uint:
valid = *t == value.(uint)
case *int:
valid = *t == value.(int)
case *string:
valid = *t == value.(string)
case *time.Time:
valid = (*t).Equal(value.(time.Time))
case *time.Duration:
valid = *t == value.(time.Duration)
default:
return NewErrors(field.Name, ErrUnrecognized, "of an unrecognized type")
}

if !valid {
Validator: Func[T](func(field *Field[T]) Errors {
if field.Value != value {
return NewErrors(field.Name, ErrInvalid, mv.Message)
}
return nil
Expand All @@ -555,6 +511,7 @@ func Eq(value interface{}) (mv *MessageValidator) {
return
}

/*
// Ne is a leaf validator factory used to create a validator, which will
// succeed when the field's value does not equal the given value.
func Ne(value interface{}) (mv *MessageValidator) {
Expand Down Expand Up @@ -859,3 +816,4 @@ func Match(re *regexp.Regexp) (mv *MessageValidator) {
}
return
}
*/
3 changes: 2 additions & 1 deletion example_simple_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (

func Example_simpleValue() {
value := 0
err := v.Validate(v.Value(&value, v.Range(1, 5)))
// See https://github.com/golang/go/issues/41176.
err := v.Validate(v.Validator[int](v.Value(value, v.Validator[int](v.Eq(2)))))
fmt.Printf("%+v\n", err)

// Output:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/RussellLuo/validating/v2

go 1.14
go 1.18
20 changes: 10 additions & 10 deletions validating.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package validating

// Field represents a (Name, ValuePtr) pair need to be validated.
type Field struct {
Name string
ValuePtr interface{}
// Field represents a (Name, Value) pair that needs to be validated.
type Field[T any] struct {
Name string
Value T
}

// F is a shortcut for creating an instance of Field.
func F(name string, valuePtr interface{}) Field {
return Field{name, valuePtr}
func F[T any](name string, value T) *Field[T] {
return &Field[T]{name, value}
}

// Validator is an interface for representing a validating's validator.
type Validator interface {
Validate(field Field) Errors
type Validator[T any] interface {
Validate(field *Field[T]) Errors
}

// Validate invokes v.Validate with an empty field.
func Validate(v Validator) (errs Errors) {
return v.Validate(Field{})
func Validate[T any](v Validator[T]) (errs Errors) {
return v.Validate(&Field[T]{})
}

0 comments on commit 21aa3a7

Please sign in to comment.