util.go

Summary

Maintainability
A
40 mins
Test Coverage
A
100%
package rel

import (
    "fmt"
    "math"
    "reflect"
    "strconv"
    "strings"
)

func indirectInterface(rv reflect.Value) any {
    if rv.Kind() == reflect.Ptr {
        if rv.IsNil() {
            return nil
        }

        rv = rv.Elem()
    }

    return rv.Interface()
}

func indirectReflectType(rt reflect.Type) reflect.Type {
    if rt.Kind() == reflect.Ptr {
        return rt.Elem()
    }

    return rt
}

func must(err error) {
    if err != nil {
        panic(err)
    }
}

func mustTrue(flag bool, msg string) {
    if !flag {
        panic(msg)
    }
}

type isZeroer interface {
    IsZero() bool
}

// isZero shallowly check wether a field in struct is zero or not
func isZero(value any) bool {
    var (
        zero bool
    )

    switch v := value.(type) {
    case nil:
        zero = true
    case bool:
        zero = !v
    case string:
        zero = v == ""
    case int:
        zero = v == 0
    case int8:
        zero = v == 0
    case int16:
        zero = v == 0
    case int32:
        zero = v == 0
    case int64:
        zero = v == 0
    case uint:
        zero = v == 0
    case uint8:
        zero = v == 0
    case uint16:
        zero = v == 0
    case uint32:
        zero = v == 0
    case uint64:
        zero = v == 0
    case uintptr:
        zero = v == 0
    case float32:
        zero = v == 0
    case float64:
        zero = v == 0
    case isZeroer:
        zero = v.IsZero()
    default:
        zero = isDeepZero(reflect.ValueOf(value), 0)
    }

    return zero
}

// modified from https://golang.org/src/reflect/value.go?s=33807:33835#L1077
func isDeepZero(rv reflect.Value, depth int) bool {
    if depth < 0 {
        return true
    }

    switch rv.Kind() {
    case reflect.Bool:
        return !rv.Bool()
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return rv.Int() == 0
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return rv.Uint() == 0
    case reflect.Float32, reflect.Float64:
        return math.Float64bits(rv.Float()) == 0
    case reflect.Complex64, reflect.Complex128:
        c := rv.Complex()
        return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
    case reflect.Array:
        // check one level deeper if it's an uuid ([16]byte)
        if rv.Type().Elem().Kind() == reflect.Uint8 && rv.Len() == 16 {
            depth += 1
        }

        for i := 0; i < rv.Len(); i++ {
            if !isDeepZero(rv.Index(i), depth-1) {
                return false
            }
        }
        return true
    case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.UnsafePointer:
        return rv.IsNil()
    case reflect.Slice:
        return rv.IsNil() || rv.Len() == 0
    case reflect.String:
        return rv.Len() == 0
    case reflect.Struct:
        for i := 0; i < rv.NumField(); i++ {
            if !isDeepZero(rv.Field(i), depth-1) {
                return false
            }
        }
        return true
    default:
        return true
    }
}

func setPointerValue(ft reflect.Type, fv reflect.Value, rt reflect.Type, rv reflect.Value) bool {
    if ft.Elem() != rt && !rt.AssignableTo(ft.Elem()) {
        return false
    }

    if fv.IsNil() {
        fv.Set(reflect.New(ft.Elem()))
    }
    fv.Elem().Set(rv)

    return true
}

func setConvertValue(ft reflect.Type, fv reflect.Value, rt reflect.Type, rv reflect.Value) bool {
    var (
        rk = rt.Kind()
        fk = ft.Kind()
    )

    // prevents unintentional conversion
    if (rk >= reflect.Int || rk <= reflect.Uint64) && fk == reflect.String {
        return false
    }

    fv.Set(rv.Convert(ft))
    return true
}

func fmtAny(v any) string {
    if str, ok := v.(string); ok {
        return "\"" + str + "\""
    }

    return fmt.Sprint(v)
}

func fmtAnys(v []any) string {
    var str strings.Builder
    for i := range v {
        if i > 0 {
            str.WriteString(", ")
        }
        str.WriteString(fmtAny(v[i]))
    }

    return str.String()
}

// Encode index slice into single string
func encodeIndices(indices []int) string {
    var sb strings.Builder
    for _, index := range indices {
        sb.WriteString("/")
        sb.WriteString(strconv.Itoa(index))
    }
    return sb.String()
}

// Get field by index and init pointers on path if flag is true
//
//    modified from: https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/reflect/value.go;l=1228-1245;bpv
func reflectValueFieldByIndex(rv reflect.Value, index []int, init bool) reflect.Value {
    if len(index) == 1 {
        return rv.Field(index[0])
    }

    for depth := 0; depth < len(index)-1; depth += 1 {
        field := rv.Field(index[depth])

        if field.Kind() != reflect.Ptr {
            rv = field
            continue
        }

        if field.IsNil() {
            if !init {
                targetType := field.Type().Elem().FieldByIndex(index[depth+1:]).Type
                return reflect.Zero(reflect.PtrTo(targetType))
            }
            field.Set(reflect.New(field.Type().Elem()))
        }

        rv = field.Elem()
    }
    return rv.Field(index[len(index)-1])
}