versatile

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: GPL-2.0 Imports: 15 Imported by: 0

README

Versatile

Versatile is a Go library providing flexible type conversion, comparison, and reflection utilities. It aims to bridge strict type boundaries safely at runtime, making it easier to work with dynamic data, function signatures, and interface abstractions.

Core Features

  • Flexible Comparison (compare.go): Compare values with customizable looseness, handling pointers, type conversions, zero values, and driver values seamlessly. Supports custom equality via interfaces.
  • Function Casting (func.go): Safely cast functions and methods from one signature to another, automatically resolving argument types, dropping extraneous returns, and injecting parameters like context.Context and pre-bound arguments.
  • Dynamic Setters (setters.go): Reflectively assign and convert values from diverse source types into destination pointers, with support for SQL scanners, standard driver.Valuer, string parsing, and complex types.
  • Reflection Utilities (django_reflect.go): Deep zero-value checking and safe reflection-based type conversions.

Usage Examples

Flexible Comparison

The Equals function allows comparing values that wouldn't normally pass standard equality checks.
You can use FLAG_EQ constants to dictate the looseness of the comparison, such as:

  • EQ_NONE: Exact equality only.
  • EQ_TYPE_CONVERT: Converts types automatically (e.g., int8 to int64, string to []byte).
  • EQ_IGNORE_PTR: Ignores pointer boundaries, deferencing automatically.
  • EQ_ZEROS: Considers nil slices/maps and empty slices/maps as equal.
  • EQ_DRIVER_VALUE: Checks for and calls driver.Valuer on types if applicable.
  • EQ_DFLT: Includes EQ_DRIVER_VALUE, EQ_ZEROS, EQ_IGNORE_PTR, and EQ_TYPE_CONVERT.

It also respects interfaces! If a struct implements EqualityChecker (Equal(any) bool) or EqualityChecker2 (Equals(any) bool), those methods will be used.

package main

import (
    "fmt"
    "github.com/Nigel2392/versatile"
)

// Implementing EqualityChecker
type MyType struct { Val int }

func (m MyType) Equal(other any) bool {
    if o, ok := other.(MyType); ok {
        return m.Val == o.Val
    }
    return false
}

func main() {
    // Auto-convert numeric types
    res := versatile.Equals(1, int64(1), versatile.EQ_TYPE_CONVERT)
    fmt.Println(res) // true

    // Compare strings to byte slices
    res = versatile.Equals("test", []byte("test"), versatile.EQ_TYPE_CONVERT)
    fmt.Println(res) // true

    // Ignore pointers (dereferences automatically)
    val := 1
    res = versatile.Equals(1, &val, versatile.EQ_IGNORE_PTR)
    fmt.Println(res) // true

    // Treat nil slices/maps as equal to empty slices/maps
    res = versatile.Equals([]int(nil), []int{}, versatile.EQ_ZEROS)
    fmt.Println(res) // true
    
    // Using custom interfaces
    a, b := MyType{Val: 10}, MyType{Val: 10}
    res = versatile.Equals(a, b)
    fmt.Println(res) // true
}
Function Casting

CastFunc allows wrapping functions to match a desired signature. It handles type coercion, interface unpacking (any to specific types and vice versa), variadic functions, and dropping unused return variables.
You can use options like WithContext or WithFuncArgs to partially apply arguments on creation!

package main

import (
    "context"
    "fmt"
    "github.com/Nigel2392/versatile"
)

func main() {
    // Cast and convert arguments automatically (float64 -> int)
    src := func(a, b float64) float64 { return a + b }
    out, _ := versatile.CastFunc[func(int, int) float64](src)
    fmt.Println(out(2, 5)) // 7

    // Inject context seamlessly 
    srcCtx := func(ctx context.Context, a int) int { return a * 2 }
    outCtx, _ := versatile.CastFunc[func(int) int](srcCtx, versatile.WithContext(context.Background()))
    fmt.Println(outCtx(5)) // 10

    // Pre-bind discrete arguments using WithFuncArgs
    srcMany := func(ctx context.Context, a, b, c int64) float32 { return float32(a + b + c) }
    outMany, _ := versatile.CastFunc[func(int8, int8) int64](
        srcMany, 
        versatile.WithContext(context.Background()),
        versatile.WithFuncArgs(10), // pre-binds 10 to 'a'
    )
    fmt.Println(outMany(5, 5)) // 20

    // Discard extra return values (e.g., returning only the error)
    srcMulti := func(s string, n int) (string, int, error) { return s + "!", n, nil }
    outErrOnly, _ := versatile.CastFunc[func(string, int) error](srcMulti)
    err := outErrOnly("hello", 3)
    fmt.Println(err) // <nil>

    // Interface unwrapping: mapping 'any' to discrete structs/types
    srcAny := func(a int, b string) string { return fmt.Sprintf("%d:%s", a, b) }
    outAny, _ := versatile.CastFunc[func(any, any) string](srcAny)
    fmt.Println(outAny(42, "test")) // "42:test"
}
Dynamic Setters

ScanTo sets a value into a destination pointer, parsing and converting the source data as required based on the provided flags (SF_SQL_SCANNER, SF_STRCONV, SF_REFLECTCONV).

package main

import (
    "fmt"
    "github.com/Nigel2392/versatile"
)

func main() {
    var dstInt int
    var dstBool bool
    var dstFloat float64
    var dstBytes []byte

    // Parse a string directly into an int pointer
    versatile.ScanTo(&dstInt, "123", versatile.SF_STRCONV)
    fmt.Println(dstInt) // 123

    // Parse strings to booleans
    versatile.ScanTo(&dstBool, "true", versatile.SF_STRCONV)
    fmt.Println(dstBool) // true

    // Number to Float conversions
    versatile.ScanTo(&dstFloat, 123, versatile.SF_DEFAULT)
    fmt.Println(dstFloat) // 123.0

    // String to byte slice
    versatile.ScanTo(&dstBytes, "hello", versatile.SF_DEFAULT)
    fmt.Println(string(dstBytes)) // "hello"
}
Deep Zero Checking

IsZero handles nested and complex types to determine if they are empty or contain only zero values. It supports interfaces by inspecting IsZeroer if implemented.

package main

import (
    "fmt"
    "github.com/Nigel2392/versatile"
)

func main() {
    fmt.Println(versatile.IsZero(0))           // true
    fmt.Println(versatile.IsZero([]int{}))     // true
    fmt.Println(versatile.IsZero(nil))         // true
    fmt.Println(versatile.IsZero([]int{0, 0})) // true
    fmt.Println(versatile.IsZero(map[string]int{})) // true
}

Documentation

Index

Constants

View Source
const (
	CodeFunctionError errors.GoCode = "FunctionError"
	CodeTypeMismatch  errors.GoCode = "TypeMismatch"
)

Variables

View Source
var (
	ErrFunction       = errors.New(CodeFunctionError, "function error")
	ErrTypeMismatch   = errors.New(CodeTypeMismatch, "type mismatch")
	ErrNotFunc        = ErrFunction.WithCause(goerrors.New("fn must be a function"))
	ErrArgCount       = ErrFunction.WithCause(goerrors.New("argument count mismatch"))
	ErrReturnCount    = ErrFunction.WithCause(goerrors.New("return count mismatch"))
	ErrNilObject      = ErrFunction.WithCause(goerrors.New("object is nil"))
	ErrMethodNotFound = ErrFunction.WithCause(goerrors.New("method not found"))
)

Functions

func CastFunc

func CastFunc[OUT Function](fn any, opts ...func(*FuncConfig)) (OUT, error)

func ConvertToType

func ConvertToType(value re.Value, targetType re.Type) (re.Value, error)

func ConvertToUniformType

func ConvertToUniformType(val any) any

Tries to convert val to an expected type.

For example, all ints will be converted to int64 The same logic goes for uint, float and complex respectively.

func EQ_BYTES_RUNES

func EQ_BYTES_RUNES(state *EqStepState) (eq bool, ok bool)

func EQ_DEREF_POINTERS

func EQ_DEREF_POINTERS(state *EqStepState) (eq bool, retEq bool)

func EQ_DRIVER_VALUER

func EQ_DRIVER_VALUER(state *EqStepState) (eq bool, ok bool)

func EQ_ISZEROER

func EQ_ISZEROER(state *EqStepState) (eq bool, ok bool)

func EQ_NIL_LEN_ZERO

func EQ_NIL_LEN_ZERO(state *EqStepState) (eq, retEq bool)

func EQ_NIL_ZEROVALS

func EQ_NIL_ZEROVALS(state *EqStepState) (eq bool, retEq bool)

func EQ_TYPES_OPT_CNV

func EQ_TYPES_OPT_CNV(state *EqStepState) (eq, retEq bool)

func EQ_UNDERLYING_KIND

func EQ_UNDERLYING_KIND(state *EqStepState) (eq, retEq bool)

func Equals

func Equals(a, b any, opts ...any) bool

Equals checks if a is equal to b.

This does not nescessarily mean a == b, see the EQ_FLAGS documentation above for more details.

Custom comparison operations can be added with RegisterCompareStep

func IsZero

func IsZero(value interface{}) bool

func Method

func Method[T Function](obj interface{}, name string, opts ...func(*FuncConfig)) (n T, err error)

func RCastFunc

func RCastFunc(out reflect.Type, fn any, opts ...func(*FuncConfig)) (reflect.Value, error)

func RConvert

func RConvert(v *re.Value, t re.Type) (*re.Value, bool)

RConvert converts a re.Value to a different type.

If the value is not convertible to the type, the original value is returned.

If the pointer of `v` is invalid, a new value of type `t` is created, and the pointer is set to it, then the pointer is returned.

func RScanTo

func RScanTo(dstPtr reflect.Value, src any, flags ScanFlag) (wasSet bool, err error)

func RSet

func RSet(src, dst *re.Value, convert bool) bool

RSet sets a value from one re.Value to another.

If the destination value is not settable, this function will return false.

If the source value is not immediately assignable to the destination value, and the convert parameter is true, the source value will be converted to the destination value's type.

If the source value is not immediately assignable to the destination value, and the convert parameter is false, this function will return false.

func ReflectValue

func ReflectValue(value interface{}) re.Value

func RegisterCompareStep

func RegisterCompareStep(order int, step func(*EqStepState) (eq bool, ok bool))

func ScanTo

func ScanTo[DST any](dstPtr *DST, src any, flags ScanFlag) (wasSet bool, err error)

func WithContext

func WithContext(ctx context.Context) func(*FuncConfig)

func WithFuncArgs

func WithFuncArgs(args ...any) func(*FuncConfig)

Types

type Argument

type Argument interface {
	Type() reflect.Type
	Arg() any
}

func Arg

func Arg[T any](v T) Argument

type EqStepState

type EqStepState struct {
	A, B   any
	V1, V2 reflect.Value
	Opts   FLAG_EQ
	// contains filtered or unexported fields
}

func (*EqStepState) Equals

func (e *EqStepState) Equals(a, b any) bool

type EqualityChecker

type EqualityChecker interface {
	Equal(other any) bool
}

type EqualityChecker2

type EqualityChecker2 interface {
	Equals(other any) bool
}

type FLAG_EQ

type FLAG_EQ = bitcheck.Flag

type Func

type Func struct {
	Fn          Function
	Type        reflect.Type
	Value       reflect.Value
	ReturnTypes []reflect.Type
	BeforeExec  func(in []reflect.Value) error
	// contains filtered or unexported fields
}

func NewFunc

func NewFunc(fn Function, returns ...reflect.Type) *Func

func (*Func) AdheresTo

func (c *Func) AdheresTo(fn any) bool

func (*Func) Call

func (c *Func) Call(args ...interface{}) []interface{}

func (*Func) CallFunc

func (c *Func) CallFunc(in []reflect.Value) []interface{}

func (*Func) Requires

func (c *Func) Requires(index int, typ reflect.Type) *Func

func (*Func) Returns

func (c *Func) Returns(returns ...reflect.Type) *Func

type FuncConfig

type FuncConfig struct {
	InjectContext reflect.Value
	Wrappers      []func(src reflect.Value, srcTyp reflect.Type, dst reflect.Type, injectCtx bool) (newSrc reflect.Value)
	Decorators    []func(fn func([]reflect.Value) []reflect.Value) func([]reflect.Value) []reflect.Value
}

type Function

type Function = interface{} // func(...interface{}) -> Component

type IsZeroer

type IsZeroer interface {
	IsZero() bool
}

type ScanFlag

type ScanFlag = bitcheck.Flag
const (
	EQ_NONE ScanFlag = 0

	// convert x and y to driver.Value
	EQ_DRIVER_VALUE ScanFlag = 1 << iota

	// check for IsZero method on both
	// special case when x and y are of kinds Array, Slice, Map, Chan:
	// 	 x == nil || y == nil is ignored and only length is checked.
	// 	 go psuedocode: len(x) == 0 && len(y) == 0
	EQ_ZEROS

	// if x is a pointer and y isn't, dereference x and vice versa
	//   psuedocode: (*a == b || a == *b) -> a == b
	EQ_IGNORE_PTR

	/*
		ignores the first check performed by [equals], this /CAN/ be useful in certain situations
		for example, the following struct is defined:

		“`
			type myStruct{value int}

			func (m *mystruct) Equals(other any) bool {
				// ...
				if o, ok := other.(*myStruct); ok {
					if m == nil || other == nil {
						return m == nil && (o == nil || o.value == 0) || o == nil && (m == nil || m.value == 0)
					}

					return m.value == o.value
				}
				// ...
				return false
			}
		“`

		Would mean the following call is true:

		“`
			Equals((*mystruct)(nil), &myStruct{value: 0}, EQ_IGNORE_NIL) == true
		“`

		As mentioned, this is only valuable in extremely rare situations, which is why
		it isn't included by default.
	*/
	EQ_IGNORE_NIL

	// auto-convert types
	//   psuedocode: int8(5) == int64(5)
	EQ_TYPE_CONVERT

	// include all above conversions
	EQ_DFLT = EQ_DRIVER_VALUE |
		EQ_ZEROS |
		EQ_IGNORE_PTR |
		EQ_TYPE_CONVERT
)
const (
	SF_NONE        ScanFlag = 0
	SF_SQL_SCANNER ScanFlag = 1 << iota
	SF_STRCONV
	SF_REFLECTCONV

	SF_CONVS   = SF_STRCONV | SF_REFLECTCONV
	SF_DEFAULT = SF_SQL_SCANNER | SF_STRCONV | SF_REFLECTCONV
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL