github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/utils/vdutil/common_rules.go (about)

     1  package vdutil
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"strconv"
     9  
    10  	"github.com/jxskiss/gopkg/v2/internal/constraints"
    11  	"github.com/jxskiss/gopkg/v2/unsafe/reflectx"
    12  )
    13  
    14  type IntegerOrString interface {
    15  	constraints.Integer | ~string
    16  }
    17  
    18  // GreaterThanZero validates that value is greater than zero.
    19  // value can be either an integer or a string.
    20  // If save is true, the result integer value will be saved to
    21  // Result.Data using name as key.
    22  func GreaterThanZero[T IntegerOrString](name string, value T, save bool) RuleFunc {
    23  	return _greaterThanZero(name, value, save)
    24  }
    25  
    26  // AllElementsGreaterThanZero validates that all elements in slice
    27  // are greater than zero.
    28  // If save is true, the result integer slice will be saved to
    29  // Result.Data using name as key.
    30  func AllElementsGreaterThanZero[T IntegerOrString](name string, slice []T, save bool) RuleFunc {
    31  	var zero T
    32  	typ := reflect.TypeOf(zero)
    33  	switch typ.Kind() {
    34  	case reflect.String:
    35  		return func(ctx context.Context, result *Result) (any, error) {
    36  			out := make([]int64, 0, len(slice))
    37  			for _, elem := range slice {
    38  				i64Val, err := strconv.ParseInt(reflect.ValueOf(elem).String(), 10, 64)
    39  				if err != nil {
    40  					return nil, &ValidationError{Name: name, Err: fmt.Errorf("slice has non-integer element")}
    41  				}
    42  				if i64Val <= 0 {
    43  					return nil, &ValidationError{Name: name, Err: fmt.Errorf("slice element %v <= 0", elem)}
    44  				}
    45  			}
    46  			if save && name != "" {
    47  				result.Data.Set(name, out)
    48  			}
    49  			return out, nil
    50  		}
    51  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    52  		return func(ctx context.Context, result *Result) (any, error) {
    53  			for _, elem := range slice {
    54  				i64Val := reflect.ValueOf(elem).Int()
    55  				if i64Val <= 0 {
    56  					return nil, &ValidationError{Name: name, Err: fmt.Errorf("slice element %v <= 0", elem)}
    57  				}
    58  			}
    59  			if save && name != "" {
    60  				result.Data.Set(name, slice)
    61  			}
    62  			return slice, nil
    63  		}
    64  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
    65  		return func(ctx context.Context, result *Result) (any, error) {
    66  			for _, elem := range slice {
    67  				u64Val := reflect.ValueOf(elem).Uint()
    68  				if u64Val <= 0 {
    69  					return nil, &ValidationError{Name: name, Err: fmt.Errorf("slice element %v <= 0", elem)}
    70  				}
    71  			}
    72  			if save && name != "" {
    73  				result.Data.Set(name, slice)
    74  			}
    75  			return slice, nil
    76  		}
    77  	default:
    78  		panic("bug: unreachable code")
    79  	}
    80  }
    81  
    82  // AllElementsNotZero validates that all elements in slice
    83  // are not equal to the zero value of type T.
    84  func AllElementsNotZero[T comparable](name string, slice []T) RuleFunc {
    85  	return func(ctx context.Context, result *Result) (any, error) {
    86  		var zero T
    87  		for _, elem := range slice {
    88  			if elem == zero {
    89  				return nil, &ValidationError{Name: name, Err: fmt.Errorf("slice has zero element")}
    90  			}
    91  		}
    92  		return nil, nil
    93  	}
    94  }
    95  
    96  func _greaterThanZero(name string, value any, save bool) RuleFunc {
    97  	rv := reflect.ValueOf(value)
    98  	switch rv.Kind() {
    99  	case reflect.String:
   100  		return func(ctx context.Context, result *Result) (any, error) {
   101  			i64Val, err := strconv.ParseInt(rv.String(), 10, 64)
   102  			if err != nil {
   103  				return int64(0), &ValidationError{Name: name, Err: fmt.Errorf("value %v is not integer: %w", value, err)}
   104  			}
   105  			if i64Val <= 0 {
   106  				return i64Val, &ValidationError{Name: name, Err: fmt.Errorf("value %v <= 0", value)}
   107  			}
   108  			if save && name != "" {
   109  				result.Data.Set(name, i64Val)
   110  			}
   111  			return i64Val, nil
   112  		}
   113  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   114  		return func(ctx context.Context, result *Result) (any, error) {
   115  			i64Val := rv.Int()
   116  			if i64Val <= 0 {
   117  				return value, &ValidationError{Name: name, Err: fmt.Errorf("value %v <= 0", value)}
   118  			}
   119  			if save && name != "" {
   120  				result.Data.Set(name, value)
   121  			}
   122  			return value, nil
   123  		}
   124  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   125  		return func(ctx context.Context, result *Result) (any, error) {
   126  			u64Val := rv.Uint()
   127  			if u64Val <= 0 {
   128  				return value, &ValidationError{Name: name, Err: fmt.Errorf("value %v <= 0", value)}
   129  			}
   130  			if save && name != "" {
   131  				result.Data.Set(name, value)
   132  			}
   133  			return value, nil
   134  		}
   135  	default:
   136  		panic("bug: unreachable code")
   137  	}
   138  }
   139  
   140  // LessThanOrEqual validates that value <= limit.
   141  func LessThanOrEqual[T constraints.Ordered](name string, limit, value T) RuleFunc {
   142  	return func(ctx context.Context, result *Result) (any, error) {
   143  		var err error
   144  		if value > limit {
   145  			err = &ValidationError{Name: name, Err: fmt.Errorf("value %v > %v", value, limit)}
   146  		}
   147  		return value, err
   148  	}
   149  }
   150  
   151  // RangeMode tells InRangeMode how to handle lower and upper bound
   152  // when validating a value against a range.
   153  type RangeMode int
   154  
   155  const (
   156  	GtAndLte  RangeMode = iota // min < x && x <= max
   157  	GtAndLt                    // min < x && x < max
   158  	GteAndLte                  // min <= x && x <= max
   159  	GteAndLt                   // min <= x && x < max
   160  )
   161  
   162  // InRange validates that value >= min and value <= max.
   163  func InRange[T constraints.Ordered](name string, min, max T, value T) RuleFunc {
   164  	return InRangeMode(name, GteAndLte, min, max, value)
   165  }
   166  
   167  // InRangeMode validates that value is in range of min and max,
   168  // according to RangeMode.
   169  func InRangeMode[T constraints.Ordered](name string, mode RangeMode, min, max T, value T) RuleFunc {
   170  	return func(ctx context.Context, result *Result) (any, error) {
   171  		var err error
   172  		switch mode {
   173  		case GtAndLte:
   174  			if !(value > min && value <= max) {
   175  				err = &ValidationError{Name: name, Err: fmt.Errorf("value %v is not in range (%v, %v]", value, min, max)}
   176  			}
   177  		case GtAndLt:
   178  			if !(value > min && value < max) {
   179  				err = &ValidationError{Name: name, Err: fmt.Errorf("value %v is not in range (%v, %v)", value, min, max)}
   180  			}
   181  		case GteAndLte:
   182  			if !(value >= min && value <= max) {
   183  				err = &ValidationError{Name: name, Err: fmt.Errorf("value %v is not in range [%v, %v]", value, min, max)}
   184  			}
   185  		case GteAndLt:
   186  			if !(value >= min && value < max) {
   187  				err = &ValidationError{Name: name, Err: fmt.Errorf("value %v is not in range [%v, %v)", value, min, max)}
   188  			}
   189  		default:
   190  			err = fmt.Errorf("%s: unknown range mode %v", name, mode)
   191  		}
   192  		return value, err
   193  	}
   194  }
   195  
   196  // ParseStrsToInt64Slice validates all elements in values are valid integer
   197  // and convert values to be an []int64 slice, the result slice will be
   198  // saved to Result.Data using name as key.
   199  func ParseStrsToInt64Slice[T ~string](name string, values []T) RuleFunc {
   200  	return func(ctx context.Context, result *Result) (any, error) {
   201  		out := make([]int64, 0, len(values))
   202  		for _, v := range values {
   203  			intVal, err := strconv.ParseInt(string(v), 10, 64)
   204  			if err != nil {
   205  				return nil, &ValidationError{Name: name, Err: fmt.Errorf("value %v is not integer", v)}
   206  			}
   207  			out = append(out, intVal)
   208  		}
   209  		if name != "" {
   210  			result.Data.Set(name, out)
   211  		}
   212  		return out, nil
   213  	}
   214  }
   215  
   216  // ParseStrsToInt64Map validates all elements in values are valid integer
   217  // and convert values to be a map[int64]bool, the result map will be
   218  // saved to Result.Data using name as key.
   219  func ParseStrsToInt64Map[T ~string](name string, values []T) RuleFunc {
   220  	return func(ctx context.Context, result *Result) (any, error) {
   221  		out := make(map[int64]bool, len(values))
   222  		for _, v := range values {
   223  			intVal, err := strconv.ParseInt(string(v), 10, 64)
   224  			if err != nil {
   225  				return nil, &ValidationError{Name: name, Err: fmt.Errorf("value %v is not integer", v)}
   226  			}
   227  			out[intVal] = true
   228  		}
   229  		if name != "" {
   230  			result.Data.Set(name, out)
   231  		}
   232  		return out, nil
   233  	}
   234  }
   235  
   236  // NotNil validates value is not nil (e.g. nil pointer, nil slice, nil map).
   237  func NotNil(name string, value any) RuleFunc {
   238  	return func(ctx context.Context, result *Result) (any, error) {
   239  		var err error
   240  		if reflectx.IsNil(value) {
   241  			err = &ValidationError{Name: name, Err: errors.New("value is nil")}
   242  		}
   243  		return value, err
   244  	}
   245  }