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 }