I have a validation function Positive, it's works but looks ugly.
type Positiver interface {
decimal.Decimal | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
//nolint:cyclop
func Positive[T Positiver](value T, name string, errs *[]error) {
addError := func() {
err := fmt.Errorf(`%s %w, but it's %v`, name, failures.ShouldBePositive, value)
*errs = append(*errs, err)
}
const prescision = 8
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it's %s`, name, failures.ShouldBePositive, val.StringFixedBank(prescision))
*errs = append(*errs, err)
}
return
case int:
if val <= 0 {
addError()
}
case int64:
if val <= 0 {
addError()
}
case int32:
if val <= 0 {
addError()
}
case int16:
if val <= 0 {
addError()
}
case int8:
if val <= 0 {
addError()
}
case uint:
if val <= 0 {
addError()
}
case uint64:
if val <= 0 {
addError()
}
case uint32:
if val <= 0 {
addError()
}
case uint16:
if val <= 0 {
addError()
}
case uint8:
if val <= 0 {
addError()
}
case float32:
if val <= 0 {
addError()
}
case float64:
if val <= 0 {
addError()
}
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}
I know that is bad approach to use []error, it's better just to return a wrapped error. But it's a compatibility issue.
I tried to do like this:
func Positive[T Positiver](value T, name string, errs *[]error) {
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if val.(int64) <= 0 {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case float32, float64:
if val.(float64) < 0 {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}
But this approach returns me an error: test panicked: interface conversion: interface {} is int, not int64
What is a better way to compare that value exceeds zero?
Your code doesn't work with "interface conversion: interface {} is int, not int64" because in multiple-type
casethe type switch variablevalkeeps its original type. See also: golang multiple case in type switch for details.So in this case you have indeed to assert to something in order to use the order operators. That "something" could be a type parameter.
BUT this code still can't use the order operator because the constraint
Positiveincludesdecimal.Decimal, which doesn't support ordering.Attempting to write a
casefordecimal.Decimaland acasefor other numeric types won't work well either, because you haven't a good way to reduce the type set of a type constraint. You are back writing onecasefor each type. One day Go might allow using union constraints in type switches.What you can do today is to statically handle
decimal.Decimaland other numeric types differently. You can use the types in the packageconstraintsto avoid redeclaring everything:Signed,UnsignedandFloat. Then a naive function with only numerical types is as simple as this:BUT with floats, using
<is not enough. Float variables could also be NaN or +/-infinity. You have to decide how to order NaNs; infinities have the sign bit, but IMO it's better to usemath.IsInfto not hide stuff behind the order operators.So in conclusion I think this function is better off with reflection, which might be slower, but the code doesn't totally suck. The following is a simplified version of your example: