From a528630e4ac7fc044ff951b8563c8ad95ab4537c Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Fri, 10 Jan 2025 11:12:57 +0800 Subject: [PATCH 1/9] feat: add plus func --- cast.go | 168 ++++++++++++++++++++++++++++++++++ caste.go | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 438 insertions(+) diff --git a/cast.go b/cast.go index 0cfe941..581f1f8 100644 --- a/cast.go +++ b/cast.go @@ -14,163 +14,331 @@ func ToBool(i interface{}) bool { return v } +// ToBoolP casts an interface to a bool type. +func ToBoolP(fn func(i interface{}) (bool, error), i interface{}) bool { + v, _ := ToBoolPE(fn, i) + return v +} + // ToTime casts an interface to a time.Time type. func ToTime(i interface{}) time.Time { v, _ := ToTimeE(i) return v } +// ToTimeP casts an interface to a time.Time type. +func ToTimeP(fn func(i interface{}) (time.Time, error), i interface{}) time.Time { + v, _ := ToTimePE(fn, i) + return v +} + func ToTimeInDefaultLocation(i interface{}, location *time.Location) time.Time { v, _ := ToTimeInDefaultLocationE(i, location) return v } +// ToTimeInDefaultLocationP casts an interface to a time.Time type. +func ToTimeInDefaultLocationP(fn func(i interface{}, location *time.Location) (time.Time, error), i interface{}, location *time.Location) time.Time { + v, _ := ToTimeInDefaultLocationPE(fn, i, location) + return v +} + // ToDuration casts an interface to a time.Duration type. func ToDuration(i interface{}) time.Duration { v, _ := ToDurationE(i) return v } +// ToDurationP casts an interface to a time.Duration type. +func ToDurationP(fn func(i interface{}) (time.Duration, error), i interface{}) time.Duration { + v, _ := ToDurationPE(fn, i) + return v +} + // ToFloat64 casts an interface to a float64 type. func ToFloat64(i interface{}) float64 { v, _ := ToFloat64E(i) return v } +// ToFloat64P casts an interface to a float64 type. +func ToFloat64P(fn func(i interface{}) (float64, error), i interface{}) float64 { + v, _ := ToFloat64PE(fn, i) + return v +} + // ToFloat32 casts an interface to a float32 type. func ToFloat32(i interface{}) float32 { v, _ := ToFloat32E(i) return v } +// ToFloat32P casts an interface to a float32 type. +func ToFloat32P(fn func(i interface{}) (float32, error), i interface{}) float32 { + v, _ := ToFloat32PE(fn, i) + return v +} + // ToInt64 casts an interface to an int64 type. func ToInt64(i interface{}) int64 { v, _ := ToInt64E(i) return v } +// ToInt64P casts an interface to an int64 type. +func ToInt64P(fn func(i interface{}) (int64, error), i interface{}) int64 { + v, _ := ToInt64PE(fn, i) + return v +} + // ToInt32 casts an interface to an int32 type. func ToInt32(i interface{}) int32 { v, _ := ToInt32E(i) return v } +// ToInt32P casts an interface to an int32 type. +func ToInt32P(fn func(i interface{}) (int32, error), i interface{}) int32 { + v, _ := ToInt32PE(fn, i) + return v +} + // ToInt16 casts an interface to an int16 type. func ToInt16(i interface{}) int16 { v, _ := ToInt16E(i) return v } +// ToInt16P casts an interface to an int16 type. +func ToInt16P(fn func(i interface{}) (int16, error), i interface{}) int16 { + v, _ := ToInt16PE(fn, i) + return v +} + // ToInt8 casts an interface to an int8 type. func ToInt8(i interface{}) int8 { v, _ := ToInt8E(i) return v } +// ToInt8P casts an interface to an int8 type. +func ToInt8P(fn func(i interface{}) (int8, error), i interface{}) int8 { + v, _ := ToInt8PE(fn, i) + return v +} + // ToInt casts an interface to an int type. func ToInt(i interface{}) int { v, _ := ToIntE(i) return v } +// ToIntP casts an interface to an int type. +func ToIntP(fn func(i interface{}) (int, error), i interface{}) int { + v, _ := ToIntPE(fn, i) + return v +} + // ToUint casts an interface to a uint type. func ToUint(i interface{}) uint { v, _ := ToUintE(i) return v } +// ToUintP casts an interface to a uint type. +func ToUintP(fn func(i interface{}) (uint, error), i interface{}) uint { + v, _ := ToUintPE(fn, i) + return v +} + // ToUint64 casts an interface to a uint64 type. func ToUint64(i interface{}) uint64 { v, _ := ToUint64E(i) return v } +// ToUint64P casts an interface to a uint64 type. +func ToUint64P(fn func(i interface{}) (uint64, error), i interface{}) uint64 { + v, _ := ToUint64PE(fn, i) + return v +} + // ToUint32 casts an interface to a uint32 type. func ToUint32(i interface{}) uint32 { v, _ := ToUint32E(i) return v } +// ToUint32P casts an interface to a uint32 type. +func ToUint32P(fn func(i interface{}) (uint32, error), i interface{}) uint32 { + v, _ := ToUint32PE(fn, i) + return v +} + // ToUint16 casts an interface to a uint16 type. func ToUint16(i interface{}) uint16 { v, _ := ToUint16E(i) return v } +// ToUint16P casts an interface to a uint16 type. +func ToUint16P(fn func(i interface{}) (uint16, error), i interface{}) uint16 { + v, _ := ToUint16PE(fn, i) + return v +} + // ToUint8 casts an interface to a uint8 type. func ToUint8(i interface{}) uint8 { v, _ := ToUint8E(i) return v } +// ToUint8P casts an interface to a uint8 type. +func ToUint8P(fn func(i interface{}) (uint8, error), i interface{}) uint8 { + v, _ := ToUint8PE(fn, i) + return v +} + // ToString casts an interface to a string type. func ToString(i interface{}) string { v, _ := ToStringE(i) return v } +// ToStringP casts an interface to a string type. +func ToStringP(fn func(i interface{}) (string, error), i interface{}) string { + v, _ := ToStringPE(fn, i) + return v +} + // ToStringMapString casts an interface to a map[string]string type. func ToStringMapString(i interface{}) map[string]string { v, _ := ToStringMapStringE(i) return v } +// ToStringMapStringP casts an interface to a map[string]string type. +func ToStringMapStringP(fn func(i interface{}) (map[string]string, error), i interface{}) map[string]string { + v, _ := ToStringMapStringPE(fn, i) + return v +} + // ToStringMapStringSlice casts an interface to a map[string][]string type. func ToStringMapStringSlice(i interface{}) map[string][]string { v, _ := ToStringMapStringSliceE(i) return v } +// ToStringMapStringSliceP casts an interface to a map[string][]string type. +func ToStringMapStringSliceP(fn func(i interface{}) (map[string][]string, error), i interface{}) map[string][]string { + v, _ := ToStringMapStringSlicePE(fn, i) + return v +} + // ToStringMapBool casts an interface to a map[string]bool type. func ToStringMapBool(i interface{}) map[string]bool { v, _ := ToStringMapBoolE(i) return v } +// ToStringMapBoolP casts an interface to a map[string]bool type. +func ToStringMapBoolP(fn func(i interface{}) (map[string]bool, error), i interface{}) map[string]bool { + v, _ := ToStringMapBoolPE(fn, i) + return v +} + // ToStringMapInt casts an interface to a map[string]int type. func ToStringMapInt(i interface{}) map[string]int { v, _ := ToStringMapIntE(i) return v } +// ToStringMapIntP casts an interface to a map[string]int type. +func ToStringMapIntP(fn func(i interface{}) (map[string]int, error), i interface{}) map[string]int { + v, _ := ToStringMapIntPE(fn, i) + return v +} + // ToStringMapInt64 casts an interface to a map[string]int64 type. func ToStringMapInt64(i interface{}) map[string]int64 { v, _ := ToStringMapInt64E(i) return v } +// ToStringMapInt64P casts an interface to a map[string]int64 type. +func ToStringMapInt64P(fn func(i interface{}) (map[string]int64, error), i interface{}) map[string]int64 { + v, _ := ToStringMapInt64PE(fn, i) + return v +} + // ToStringMap casts an interface to a map[string]interface{} type. func ToStringMap(i interface{}) map[string]interface{} { v, _ := ToStringMapE(i) return v } +// ToStringMapP casts an interface to a map[string]interface{} type. +func ToStringMapP(fn func(i interface{}) (map[string]interface{}, error), i interface{}) map[string]interface{} { + v, _ := ToStringMapPE(fn, i) + return v +} + // ToSlice casts an interface to a []interface{} type. func ToSlice(i interface{}) []interface{} { v, _ := ToSliceE(i) return v } +// ToSliceP casts an interface to a []interface{} type. +func ToSliceP(fn func(i interface{}) ([]interface{}, error), i interface{}) []interface{} { + v, _ := ToSlicePE(fn, i) + return v +} + // ToBoolSlice casts an interface to a []bool type. func ToBoolSlice(i interface{}) []bool { v, _ := ToBoolSliceE(i) return v } +// ToBoolSliceP casts an interface to a []bool type. +func ToBoolSliceP(fn func(i interface{}) ([]bool, error), i interface{}) []bool { + v, _ := ToBoolSlicePE(fn, i) + return v +} + // ToStringSlice casts an interface to a []string type. func ToStringSlice(i interface{}) []string { v, _ := ToStringSliceE(i) return v } +// ToStringSliceP casts an interface to a []string type. +func ToStringSliceP(fn func(i interface{}) ([]string, error), i interface{}) []string { + v, _ := ToStringSlicePE(fn, i) + return v +} + // ToIntSlice casts an interface to a []int type. func ToIntSlice(i interface{}) []int { v, _ := ToIntSliceE(i) return v } +// ToIntSliceP casts an interface to a []int type. +func ToIntSliceP(fn func(i interface{}) ([]int, error), i interface{}) []int { + v, _ := ToIntSlicePE(fn, i) + return v +} + // ToDurationSlice casts an interface to a []time.Duration type. func ToDurationSlice(i interface{}) []time.Duration { v, _ := ToDurationSliceE(i) return v } + +// ToDurationSliceP casts an interface to a []time.Duration type. +func ToDurationSliceP(fn func(i interface{}) ([]time.Duration, error), i interface{}) []time.Duration { + v, _ := ToDurationSlicePE(fn, i) + return v +} diff --git a/caste.go b/caste.go index 4181a2e..40e707e 100644 --- a/caste.go +++ b/caste.go @@ -31,6 +31,15 @@ func ToTimeE(i interface{}) (tim time.Time, err error) { return ToTimeInDefaultLocationE(i, time.UTC) } +// ToTimePE plus casts an interface to a time.Time type. +func ToTimePE(fn func(interface{}) (time.Time, error), i interface{}) (time.Time, error) { + v, err := ToTimeE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToTimeInDefaultLocationE casts an empty interface to time.Time, // interpreting inputs without a timezone to be in the given location, // or the local timezone if nil. @@ -65,6 +74,14 @@ func ToTimeInDefaultLocationE(i interface{}, location *time.Location) (tim time. } } +func ToTimeInDefaultLocationPE(fn func(interface{}, *time.Location) (time.Time, error), i interface{}, location *time.Location) (time.Time, error) { + v, err := ToTimeInDefaultLocationE(i, location) + if err == nil || fn == nil { + return v, err + } + return fn(i, location) +} + // ToDurationE casts an interface to a time.Duration type. func ToDurationE(i interface{}) (d time.Duration, err error) { i = indirect(i) @@ -99,6 +116,16 @@ func ToDurationE(i interface{}) (d time.Duration, err error) { } } +// ToDurationPE plus casts an interface to a time.Duration type. +func ToDurationPE(fn func(interface{}) (time.Duration, error), i interface{}) (time.Duration, error) { + v, err := ToDurationE(i) + if err == nil || fn == nil { + return v, err + } + + return fn(i) +} + // ToBoolE casts an interface to a bool type. func ToBoolE(i interface{}) (bool, error) { i = indirect(i) @@ -147,6 +174,15 @@ func ToBoolE(i interface{}) (bool, error) { } } +// ToBoolPE plus casts an interface to a bool type. +func ToBoolPE(fn func(interface{}) (bool, error), i interface{}) (bool, error) { + v, err := ToBoolE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToFloat64E casts an interface to a float64 type. func ToFloat64E(i interface{}) (float64, error) { i = indirect(i) @@ -205,6 +241,15 @@ func ToFloat64E(i interface{}) (float64, error) { } } +// ToFloat64PE plus casts an interface to a float64 type. +func ToFloat64PE(fn func(interface{}) (float64, error), i interface{}) (float64, error) { + v, err := ToFloat64E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToFloat32E casts an interface to a float32 type. func ToFloat32E(i interface{}) (float32, error) { i = indirect(i) @@ -263,6 +308,15 @@ func ToFloat32E(i interface{}) (float32, error) { } } +// ToFloat32PE plus casts an interface to a float32 type. +func ToFloat32PE(fn func(interface{}) (float32, error), i interface{}) (float32, error) { + v, err := ToFloat32E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToInt64E casts an interface to an int64 type. func ToInt64E(i interface{}) (int64, error) { i = indirect(i) @@ -315,6 +369,15 @@ func ToInt64E(i interface{}) (int64, error) { } } +// ToInt64PE plus casts an interface to an int64 type. +func ToInt64PE(fn func(interface{}) (int64, error), i interface{}) (int64, error) { + v, err := ToInt64E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToInt32E casts an interface to an int32 type. func ToInt32E(i interface{}) (int32, error) { i = indirect(i) @@ -367,6 +430,15 @@ func ToInt32E(i interface{}) (int32, error) { } } +// ToInt32PE plus casts an interface to an int32 type. +func ToInt32PE(fn func(interface{}) (int32, error), i interface{}) (int32, error) { + v, err := ToInt32E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToInt16E casts an interface to an int16 type. func ToInt16E(i interface{}) (int16, error) { i = indirect(i) @@ -419,6 +491,15 @@ func ToInt16E(i interface{}) (int16, error) { } } +// ToInt16PE plus casts an interface to an int16 type. +func ToInt16PE(fn func(interface{}) (int16, error), i interface{}) (int16, error) { + v, err := ToInt16E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToInt8E casts an interface to an int8 type. func ToInt8E(i interface{}) (int8, error) { i = indirect(i) @@ -471,6 +552,15 @@ func ToInt8E(i interface{}) (int8, error) { } } +// ToInt8PE plus casts an interface to an int8 type. +func ToInt8PE(fn func(interface{}) (int8, error), i interface{}) (int8, error) { + v, err := ToInt8E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToIntE casts an interface to an int type. func ToIntE(i interface{}) (int, error) { i = indirect(i) @@ -523,6 +613,15 @@ func ToIntE(i interface{}) (int, error) { } } +// ToIntPE plus casts an interface to an int type. +func ToIntPE(fn func(interface{}) (int, error), i interface{}) (int, error) { + v, err := ToIntE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToUintE casts an interface to a uint type. func ToUintE(i interface{}) (uint, error) { i = indirect(i) @@ -599,6 +698,15 @@ func ToUintE(i interface{}) (uint, error) { } } +// ToUintPE plus casts an interface to a uint type. +func ToUintPE(fn func(interface{}) (uint, error), i interface{}) (uint, error) { + v, err := ToUintE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToUint64E casts an interface to a uint64 type. func ToUint64E(i interface{}) (uint64, error) { i = indirect(i) @@ -675,6 +783,15 @@ func ToUint64E(i interface{}) (uint64, error) { } } +// ToUint64PE plus casts an interface to a uint64 type. +func ToUint64PE(fn func(interface{}) (uint64, error), i interface{}) (uint64, error) { + v, err := ToUint64E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToUint32E casts an interface to a uint32 type. func ToUint32E(i interface{}) (uint32, error) { i = indirect(i) @@ -751,6 +868,15 @@ func ToUint32E(i interface{}) (uint32, error) { } } +// ToUint32PE plus casts an interface to a uint32 type. +func ToUint32PE(fn func(interface{}) (uint32, error), i interface{}) (uint32, error) { + v, err := ToUint32E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToUint16E casts an interface to a uint16 type. func ToUint16E(i interface{}) (uint16, error) { i = indirect(i) @@ -827,6 +953,15 @@ func ToUint16E(i interface{}) (uint16, error) { } } +// ToUint16PE plus casts an interface to a uint16 type. +func ToUint16PE(fn func(interface{}) (uint16, error), i interface{}) (uint16, error) { + v, err := ToUint16E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToUint8E casts an interface to a uint type. func ToUint8E(i interface{}) (uint8, error) { i = indirect(i) @@ -903,6 +1038,15 @@ func ToUint8E(i interface{}) (uint8, error) { } } +// ToUint8PE plus casts an interface to a uint8 type. +func ToUint8PE(fn func(interface{}) (uint8, error), i interface{}) (uint8, error) { + v, err := ToUint8E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // From html/template/content.go // Copyright 2011 The Go Authors. All rights reserved. // indirect returns the value, after dereferencing as many times @@ -1000,6 +1144,15 @@ func ToStringE(i interface{}) (string, error) { } } +// ToStringPE plus casts an interface to a string type. +func ToStringPE(fn func(interface{}) (string, error), i interface{}) (string, error) { + v, err := ToStringE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToStringMapStringE casts an interface to a map[string]string type. func ToStringMapStringE(i interface{}) (map[string]string, error) { m := map[string]string{} @@ -1030,6 +1183,15 @@ func ToStringMapStringE(i interface{}) (map[string]string, error) { } } +// ToStringMapStringPE plus casts an interface to a map[string]string type. +func ToStringMapStringPE(fn func(interface{}) (map[string]string, error), i interface{}) (map[string]string, error) { + v, err := ToStringMapStringE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToStringMapStringSliceE casts an interface to a map[string][]string type. func ToStringMapStringSliceE(i interface{}) (map[string][]string, error) { m := map[string][]string{} @@ -1094,6 +1256,15 @@ func ToStringMapStringSliceE(i interface{}) (map[string][]string, error) { return m, nil } +// ToStringMapStringSlicePE plus casts an interface to a map[string][]string type. +func ToStringMapStringSlicePE(fn func(interface{}) (map[string][]string, error), i interface{}) (map[string][]string, error) { + v, err := ToStringMapStringSliceE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToStringMapBoolE casts an interface to a map[string]bool type. func ToStringMapBoolE(i interface{}) (map[string]bool, error) { m := map[string]bool{} @@ -1119,6 +1290,15 @@ func ToStringMapBoolE(i interface{}) (map[string]bool, error) { } } +// ToStringMapBoolPE plus casts an interface to a map[string]bool type. +func ToStringMapBoolPE(fn func(interface{}) (map[string]bool, error), i interface{}) (map[string]bool, error) { + v, err := ToStringMapBoolE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToStringMapE casts an interface to a map[string]interface{} type. func ToStringMapE(i interface{}) (map[string]interface{}, error) { m := map[string]interface{}{} @@ -1139,6 +1319,15 @@ func ToStringMapE(i interface{}) (map[string]interface{}, error) { } } +// ToStringMapPE plus casts an interface to a map[string]interface{} type. +func ToStringMapPE(fn func(interface{}) (map[string]interface{}, error), i interface{}) (map[string]interface{}, error) { + v, err := ToStringMapE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToStringMapIntE casts an interface to a map[string]int{} type. func ToStringMapIntE(i interface{}) (map[string]int, error) { m := map[string]int{} @@ -1180,6 +1369,15 @@ func ToStringMapIntE(i interface{}) (map[string]int, error) { return m, nil } +// ToStringMapIntPE plus casts an interface to a map[string]int{} type. +func ToStringMapIntPE(fn func(interface{}) (map[string]int, error), i interface{}) (map[string]int, error) { + v, err := ToStringMapIntE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToStringMapInt64E casts an interface to a map[string]int64{} type. func ToStringMapInt64E(i interface{}) (map[string]int64, error) { m := map[string]int64{} @@ -1220,6 +1418,15 @@ func ToStringMapInt64E(i interface{}) (map[string]int64, error) { return m, nil } +// ToStringMapInt64PE plus casts an interface to a map[string]int64{} type. +func ToStringMapInt64PE(fn func(interface{}) (map[string]int64, error), i interface{}) (map[string]int64, error) { + v, err := ToStringMapInt64E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToSliceE casts an interface to a []interface{} type. func ToSliceE(i interface{}) ([]interface{}, error) { var s []interface{} @@ -1237,6 +1444,15 @@ func ToSliceE(i interface{}) ([]interface{}, error) { } } +// ToSlicePE plus casts an interface to a []interface{} type. +func ToSlicePE(fn func(interface{}) ([]interface{}, error), i interface{}) ([]interface{}, error) { + v, err := ToSliceE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToBoolSliceE casts an interface to a []bool type. func ToBoolSliceE(i interface{}) ([]bool, error) { if i == nil { @@ -1266,6 +1482,15 @@ func ToBoolSliceE(i interface{}) ([]bool, error) { } } +// ToBoolSlicePE plus casts an interface to a []bool type. +func ToBoolSlicePE(fn func(interface{}) ([]bool, error), i interface{}) ([]bool, error) { + v, err := ToBoolSliceE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToStringSliceE casts an interface to a []string type. func ToStringSliceE(i interface{}) ([]string, error) { var a []string @@ -1326,6 +1551,15 @@ func ToStringSliceE(i interface{}) ([]string, error) { } } +// ToStringSlicePE plus casts an interface to a []string type. +func ToStringSlicePE(fn func(interface{}) ([]string, error), i interface{}) ([]string, error) { + v, err := ToStringSliceE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToIntSliceE casts an interface to a []int type. func ToIntSliceE(i interface{}) ([]int, error) { if i == nil { @@ -1355,6 +1589,15 @@ func ToIntSliceE(i interface{}) ([]int, error) { } } +// ToIntSlicePE plus casts an interface to a []int type. +func ToIntSlicePE(fn func(interface{}) ([]int, error), i interface{}) ([]int, error) { + v, err := ToIntSliceE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // ToDurationSliceE casts an interface to a []time.Duration type. func ToDurationSliceE(i interface{}) ([]time.Duration, error) { if i == nil { @@ -1384,6 +1627,15 @@ func ToDurationSliceE(i interface{}) ([]time.Duration, error) { } } +// ToDurationSlicePE plus casts an interface to a []time.Duration type. +func ToDurationSlicePE(fn func(interface{}) ([]time.Duration, error), i interface{}) ([]time.Duration, error) { + v, err := ToDurationSliceE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + // StringToDate attempts to parse a string into a time.Time type using a // predefined list of formats. If no suitable format is found, an error is // returned. @@ -1391,6 +1643,15 @@ func StringToDate(s string) (time.Time, error) { return parseDateWith(s, time.UTC, timeFormats) } +// StringToDatePE plus attempts to parse a string into a time.Time type using a +func StringToDatePE(fn func(string) (time.Time, error), s string) (time.Time, error) { + d, err := StringToDate(s) + if err == nil || fn == nil { + return d, nil + } + return fn(s) +} + // StringToDateInDefaultLocation casts an empty interface to a time.Time, // interpreting inputs without a timezone to be in the given location, // or the local timezone if nil. @@ -1398,6 +1659,15 @@ func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time return parseDateWith(s, location, timeFormats) } +// StringToDateInDefaultLocationPE plus casts an empty interface to a time.Time, +func StringToDateInDefaultLocationPE(fn func(string, *time.Location) (time.Time, error), s string, location *time.Location) (time.Time, error) { + d, err := StringToDateInDefaultLocation(s, location) + if err == nil || fn == nil { + return d, nil + } + return fn(s, location) +} + type timeFormatType int const ( From c49678701f8aa8095632711048dced2d985c960c Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Wed, 20 Aug 2025 18:31:01 +0800 Subject: [PATCH 2/9] feat: add plus functions with generic support - Add generic ToP[T] and ToPE[T] functions for Basic types - Add specific plus functions for all cast types (ToBoolP, ToStringP, etc.) - Add plus functions for maps and slices with fallback support - Maintain compatibility with upstream's new generic architecture - Add comprehensive unit tests covering all plus functions - Add real-world usage examples demonstrating practical scenarios - Plus functions provide fallback logic when standard casting fails - Enables extending cast's case coverage without modifying core library This addresses gaps in cast's type coverage by allowing users to provide custom fallback functions when the built-in conversion logic fails. --- basic.go | 2 + cast.go | 15 ++ cast_plus_test.go | 483 +++++++++++++++++++++++++++++++++++++++++++ example_plus_test.go | 161 +++++++++++++++ generator/main.go | 40 ++++ map.go | 54 +++++ number.go | 2 + slice.go | 4 + time.go | 9 + zz_generated.go | 332 ++++++++++++++++++++--------- 10 files changed, 1009 insertions(+), 93 deletions(-) create mode 100644 cast_plus_test.go create mode 100644 example_plus_test.go diff --git a/basic.go b/basic.go index fa330e2..f617b75 100644 --- a/basic.go +++ b/basic.go @@ -129,3 +129,5 @@ func ToStringE(i any) (string, error) { return "", fmt.Errorf(errorMsg, i, i, "") } } + + diff --git a/cast.go b/cast.go index 8d85539..ce471b9 100644 --- a/cast.go +++ b/cast.go @@ -82,3 +82,18 @@ func To[T Basic](i any) T { return v } + +// ToPE plus casts any value to a [Basic] type with fallback function. +func ToPE[T Basic](fn func(any) (T, error), i any) (T, error) { + v, err := ToE[T](i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + +// ToP plus casts any value to a [Basic] type with fallback function. +func ToP[T Basic](fn func(any) (T, error), i any) T { + v, _ := ToPE[T](fn, i) + return v +} diff --git a/cast_plus_test.go b/cast_plus_test.go new file mode 100644 index 0000000..3a5bf14 --- /dev/null +++ b/cast_plus_test.go @@ -0,0 +1,483 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast_test + +import ( + "fmt" + "testing" + "time" + + qt "github.com/frankban/quicktest" + + "github.com/spf13/cast" +) + +// TestPlusFunctions tests the "plus functions" that provide fallback logic +func TestPlusFunctions(t *testing.T) { + t.Parallel() + + t.Run("ToBoolP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion - fallback should not be called + result := cast.ToBoolP(func(i any) (bool, error) { + t.Error("Fallback should not be called for successful conversion") + return false, nil + }, true) + c.Assert(result, qt.Equals, true) + + // Test failed conversion - fallback should be called + fallbackCalled := false + result = cast.ToBoolP(func(i any) (bool, error) { + fallbackCalled = true + return true, nil + }, "invalid") + c.Assert(result, qt.Equals, true) + c.Assert(fallbackCalled, qt.Equals, true) + + // Test nil fallback function + result = cast.ToBoolP(nil, "invalid") + c.Assert(result, qt.Equals, false) // Should return zero value + }) + + t.Run("ToStringP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + result := cast.ToStringP(func(i any) (string, error) { + t.Error("Fallback should not be called for successful conversion") + return "", nil + }, 123) + c.Assert(result, qt.Equals, "123") + + // Test failed conversion with fallback + fallbackCalled := false + result = cast.ToStringP(func(i any) (string, error) { + fallbackCalled = true + return "fallback", nil + }, make(chan int)) + c.Assert(result, qt.Equals, "fallback") + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToIntP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + result := cast.ToIntP(func(i any) (int, error) { + t.Error("Fallback should not be called for successful conversion") + return 0, nil + }, "42") + c.Assert(result, qt.Equals, 42) + + // Test failed conversion with fallback + fallbackCalled := false + result = cast.ToIntP(func(i any) (int, error) { + fallbackCalled = true + return 999, nil + }, "invalid") + c.Assert(result, qt.Equals, 999) + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToTimeP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + now := time.Now() + result := cast.ToTimeP(func(i any) (time.Time, error) { + t.Error("Fallback should not be called for successful conversion") + return time.Time{}, nil + }, now) + c.Assert(result.UTC(), qt.Equals, now.UTC()) + + // Test failed conversion with fallback + fallbackTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + fallbackCalled := false + result = cast.ToTimeP(func(i any) (time.Time, error) { + fallbackCalled = true + return fallbackTime, nil + }, "invalid") + c.Assert(result.UTC(), qt.Equals, fallbackTime.UTC()) + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToTimeInDefaultLocationP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + loc := time.FixedZone("TEST", 3600) + + // Test successful conversion + result := cast.ToTimeInDefaultLocationP(func(i any, location *time.Location) (time.Time, error) { + t.Error("Fallback should not be called for successful conversion") + return time.Time{}, nil + }, "2023-01-01", loc) + c.Assert(result.Year(), qt.Equals, 2023) + + // Test failed conversion with fallback + fallbackTime := time.Date(2024, 1, 1, 0, 0, 0, 0, loc) + fallbackCalled := false + result = cast.ToTimeInDefaultLocationP(func(i any, location *time.Location) (time.Time, error) { + fallbackCalled = true + c.Assert(location, qt.Equals, loc) + return fallbackTime, nil + }, "invalid", loc) + c.Assert(result, qt.Equals, fallbackTime) + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToDurationP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + result := cast.ToDurationP(func(i any) (time.Duration, error) { + t.Error("Fallback should not be called for successful conversion") + return 0, nil + }, "5s") + c.Assert(result, qt.Equals, 5*time.Second) + + // Test failed conversion with fallback + fallbackDuration := 10 * time.Minute + fallbackCalled := false + result = cast.ToDurationP(func(i any) (time.Duration, error) { + fallbackCalled = true + return fallbackDuration, nil + }, "invalid") + c.Assert(result, qt.Equals, fallbackDuration) + c.Assert(fallbackCalled, qt.Equals, true) + }) +} + +// TestGenericPlusFunctions tests the generic ToP and ToPE functions +func TestGenericPlusFunctions(t *testing.T) { + t.Parallel() + + t.Run("ToP[int]", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + result := cast.ToP[int](func(i any) (int, error) { + t.Error("Fallback should not be called for successful conversion") + return 0, nil + }, "42") + c.Assert(result, qt.Equals, 42) + + // Test failed conversion with fallback + fallbackCalled := false + result = cast.ToP[int](func(i any) (int, error) { + fallbackCalled = true + return 999, nil + }, "invalid") + c.Assert(result, qt.Equals, 999) + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToPE[string]", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + result, err := cast.ToPE[string](func(i any) (string, error) { + t.Error("Fallback should not be called for successful conversion") + return "", nil + }, 123) + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, "123") + + // Test failed conversion with fallback + fallbackCalled := false + result, err = cast.ToPE[string](func(i any) (string, error) { + fallbackCalled = true + return "fallback", nil + }, make(chan int)) + c.Assert(err, qt.IsNil) + c.Assert(result, qt.Equals, "fallback") + c.Assert(fallbackCalled, qt.Equals, true) + + // Test fallback returning error + result, err = cast.ToPE[string](func(i any) (string, error) { + return "", fmt.Errorf("fallback error") + }, make(chan int)) + c.Assert(err, qt.IsNotNil) + c.Assert(err.Error(), qt.Equals, "fallback error") + }) + + t.Run("ToPE[bool] with nil fallback", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test with nil fallback - should return original error + result, err := cast.ToPE[bool](nil, "invalid") + c.Assert(err, qt.IsNotNil) + c.Assert(result, qt.Equals, false) + }) +} + +// TestMapPlusFunctions tests the map-related plus functions +func TestMapPlusFunctions(t *testing.T) { + t.Parallel() + + t.Run("ToStringMapStringP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + input := map[string]string{"key": "value"} + result := cast.ToStringMapStringP(func(i any) (map[string]string, error) { + t.Error("Fallback should not be called for successful conversion") + return nil, nil + }, input) + c.Assert(result, qt.DeepEquals, input) + + // Test failed conversion with fallback + fallbackMap := map[string]string{"fallback": "value"} + fallbackCalled := false + result = cast.ToStringMapStringP(func(i any) (map[string]string, error) { + fallbackCalled = true + return fallbackMap, nil + }, "invalid") + c.Assert(result, qt.DeepEquals, fallbackMap) + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToStringMapIntP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + input := map[string]int{"key": 42} + result := cast.ToStringMapIntP(func(i any) (map[string]int, error) { + t.Error("Fallback should not be called for successful conversion") + return nil, nil + }, input) + c.Assert(result, qt.DeepEquals, input) + + // Test failed conversion with fallback + fallbackMap := map[string]int{"fallback": 999} + fallbackCalled := false + result = cast.ToStringMapIntP(func(i any) (map[string]int, error) { + fallbackCalled = true + return fallbackMap, nil + }, "invalid") + c.Assert(result, qt.DeepEquals, fallbackMap) + c.Assert(fallbackCalled, qt.Equals, true) + }) +} + +// TestSlicePlusFunctions tests the slice-related plus functions +func TestSlicePlusFunctions(t *testing.T) { + t.Parallel() + + t.Run("ToSliceP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + input := []any{1, 2, 3} + result := cast.ToSliceP(func(i any) ([]any, error) { + t.Error("Fallback should not be called for successful conversion") + return nil, nil + }, input) + c.Assert(result, qt.DeepEquals, input) + + // Test failed conversion with fallback + fallbackSlice := []any{"fallback"} + fallbackCalled := false + result = cast.ToSliceP(func(i any) ([]any, error) { + fallbackCalled = true + return fallbackSlice, nil + }, "invalid") + c.Assert(result, qt.DeepEquals, fallbackSlice) + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToStringSliceP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + input := []string{"a", "b", "c"} + result := cast.ToStringSliceP(func(i any) ([]string, error) { + t.Error("Fallback should not be called for successful conversion") + return nil, nil + }, input) + c.Assert(result, qt.DeepEquals, input) + + // Test failed conversion with fallback - use a type that cannot be converted + fallbackSlice := []string{"fallback"} + fallbackCalled := false + result = cast.ToStringSliceP(func(i any) ([]string, error) { + fallbackCalled = true + return fallbackSlice, nil + }, make(chan int)) // channels cannot be converted to []string + c.Assert(result, qt.DeepEquals, fallbackSlice) + c.Assert(fallbackCalled, qt.Equals, true) + }) + + t.Run("ToIntSliceP", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Test successful conversion + input := []int{1, 2, 3} + result := cast.ToIntSliceP(func(i any) ([]int, error) { + t.Error("Fallback should not be called for successful conversion") + return nil, nil + }, input) + c.Assert(result, qt.DeepEquals, input) + + // Test failed conversion with fallback + fallbackSlice := []int{999} + fallbackCalled := false + result = cast.ToIntSliceP(func(i any) ([]int, error) { + fallbackCalled = true + return fallbackSlice, nil + }, "invalid") + c.Assert(result, qt.DeepEquals, fallbackSlice) + c.Assert(fallbackCalled, qt.Equals, true) + }) +} + +// TestRealWorldScenarios tests practical use cases for plus functions +func TestRealWorldScenarios(t *testing.T) { + t.Parallel() + + t.Run("CustomDateFormat", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Custom date format that cast doesn't support by default + customDateStr := "01/02/2006 15:04:05" + inputDate := "12/25/2023 14:30:00" + + result := cast.ToTimeP(func(i any) (time.Time, error) { + if str, ok := i.(string); ok { + return time.Parse(customDateStr, str) + } + return time.Time{}, fmt.Errorf("cannot parse %v as custom date", i) + }, inputDate) + + expected := time.Date(2023, 12, 25, 14, 30, 0, 0, time.UTC) + c.Assert(result.UTC(), qt.Equals, expected) + }) + + t.Run("CustomBooleanValues", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Custom boolean values that cast doesn't support by default + customBoolFallback := func(i any) (bool, error) { + if str, ok := i.(string); ok { + switch str { + case "yes", "Y", "on", "enabled": + return true, nil + case "no", "N", "off", "disabled": + return false, nil + } + } + return false, fmt.Errorf("cannot parse %v as custom boolean", i) + } + + testCases := []struct { + input string + expected bool + }{ + {"yes", true}, + {"Y", true}, + {"on", true}, + {"enabled", true}, + {"no", false}, + {"N", false}, + {"off", false}, + {"disabled", false}, + } + + for _, tc := range testCases { + result := cast.ToBoolP(customBoolFallback, tc.input) + c.Assert(result, qt.Equals, tc.expected, qt.Commentf("input: %s", tc.input)) + } + }) + + t.Run("LegacyDataMigration", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Simulate migrating legacy data with custom string format + legacyStringFallback := func(i any) (string, error) { + // Handle legacy format: "LEGACY:actual_value" + if str, ok := i.(string); ok && len(str) > 7 && str[:7] == "LEGACY:" { + return str[7:], nil + } + return fmt.Sprintf("migrated_%v", i), nil + } + + // Use types that cast cannot convert to string to trigger fallback + testCases := []struct { + input any + expected string + }{ + {make(chan int), "migrated_0x"}, // channels cannot be converted to string + {func() {}, "migrated_0x"}, // functions cannot be converted to string + } + + for _, tc := range testCases { + result := cast.ToStringP(legacyStringFallback, tc.input) + // For channels and functions, the result will contain memory addresses + // so we just check that it starts with "migrated_" + c.Assert(len(result) > 9, qt.Equals, true, qt.Commentf("input: %v", tc.input)) + c.Assert(result[:9], qt.Equals, "migrated_", qt.Commentf("input: %v", tc.input)) + } + + // Test the legacy format handling with a type that cast can't handle + type customType struct{ value string } + legacyInput := customType{value: "LEGACY:test_value"} + result := cast.ToStringP(legacyStringFallback, legacyInput) + c.Assert(len(result) > 9, qt.Equals, true) + c.Assert(result[:9], qt.Equals, "migrated_") + }) + + t.Run("ConfigurationDefaults", func(t *testing.T) { + t.Parallel() + c := qt.New(t) + + // Simulate configuration parsing with defaults + configDefaults := map[string]any{ + "timeout": 30, + "retries": 3, + "debug": false, + "server": "localhost", + "port": 8080, + } + + getConfigInt := func(key string) func(any) (int, error) { + return func(i any) (int, error) { + if defaultVal, ok := configDefaults[key].(int); ok { + return defaultVal, nil + } + return 0, fmt.Errorf("no default for key %s", key) + } + } + + // Test with invalid config values falling back to defaults + timeout := cast.ToIntP(getConfigInt("timeout"), "invalid") + c.Assert(timeout, qt.Equals, 30) + + // Use a type that cannot be converted to int to trigger fallback + retries := cast.ToIntP(getConfigInt("retries"), make(chan int)) + c.Assert(retries, qt.Equals, 3) + + // Test with valid config values (no fallback) + port := cast.ToIntP(getConfigInt("port"), "9000") + c.Assert(port, qt.Equals, 9000) // Should use the provided value, not default + }) +} diff --git a/example_plus_test.go b/example_plus_test.go new file mode 100644 index 0000000..1ed54d1 --- /dev/null +++ b/example_plus_test.go @@ -0,0 +1,161 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast_test + +import ( + "fmt" + "time" + + "github.com/spf13/cast" +) + +// ExampleToBoolP demonstrates using ToBoolP with custom boolean logic +func ExampleToBoolP() { + // Custom boolean conversion for "yes"/"no" strings + customBoolFallback := func(i any) (bool, error) { + if str, ok := i.(string); ok { + switch str { + case "yes", "Y", "on", "enabled": + return true, nil + case "no", "N", "off", "disabled": + return false, nil + } + } + return false, fmt.Errorf("cannot parse %v as custom boolean", i) + } + + // Standard conversion works normally + fmt.Println(cast.ToBoolP(customBoolFallback, true)) // true + fmt.Println(cast.ToBoolP(customBoolFallback, "true")) // true + + // Custom conversion kicks in for unsupported values + fmt.Println(cast.ToBoolP(customBoolFallback, "yes")) // true + fmt.Println(cast.ToBoolP(customBoolFallback, "enabled")) // true + fmt.Println(cast.ToBoolP(customBoolFallback, "no")) // false + + // Output: + // true + // true + // true + // true + // false +} + +// ExampleToTimeP demonstrates using ToTimeP with custom date formats +func ExampleToTimeP() { + // Custom date format fallback + customDateFallback := func(i any) (time.Time, error) { + if str, ok := i.(string); ok { + // Try custom format: MM/DD/YYYY HH:MM:SS + if t, err := time.Parse("01/02/2006 15:04:05", str); err == nil { + return t, nil + } + } + return time.Time{}, fmt.Errorf("cannot parse %v as custom date", i) + } + + // Standard conversion works normally + standardTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) + result1 := cast.ToTimeP(customDateFallback, standardTime) + fmt.Println(result1.Year()) // 2023 + + // Custom conversion for unsupported format + result2 := cast.ToTimeP(customDateFallback, "12/25/2023 14:30:00") + fmt.Println(result2.Month()) // December + fmt.Println(result2.Day()) // 25 + + // Output: + // 2023 + // December + // 25 +} + +// ExampleToP demonstrates using the generic ToP function +func ExampleToP() { + // Configuration with defaults + configDefaults := map[string]int{ + "timeout": 30, + "retries": 3, + "port": 8080, + } + + getDefault := func(key string) func(any) (int, error) { + return func(i any) (int, error) { + if val, ok := configDefaults[key]; ok { + return val, nil + } + return 0, fmt.Errorf("no default for %s", key) + } + } + + // Valid values use standard conversion + timeout := cast.ToP[int](getDefault("timeout"), "60") + fmt.Println(timeout) // 60 + + // Invalid values fall back to defaults + retries := cast.ToP[int](getDefault("retries"), "invalid") + fmt.Println(retries) // 3 + + port := cast.ToP[int](getDefault("port"), make(chan int)) + fmt.Println(port) // 8080 + + // Output: + // 60 + // 3 + // 8080 +} + +// ExampleToStringMapStringP demonstrates using map plus functions +func ExampleToStringMapStringP() { + // Fallback that provides default configuration + defaultConfig := func(i any) (map[string]string, error) { + return map[string]string{ + "host": "localhost", + "port": "8080", + "env": "development", + }, nil + } + + // Valid map works normally + validMap := map[string]string{"host": "example.com", "port": "9000"} + result1 := cast.ToStringMapStringP(defaultConfig, validMap) + fmt.Println(result1["host"]) // example.com + + // Invalid input falls back to defaults + result2 := cast.ToStringMapStringP(defaultConfig, "invalid") + fmt.Println(result2["host"]) // localhost + fmt.Println(result2["env"]) // development + + // Output: + // example.com + // localhost + // development +} + +// ExampleToStringSliceP demonstrates using slice plus functions +func ExampleToStringSliceP() { + // Fallback that provides default values + defaultSliceFallback := func(i any) ([]string, error) { + return []string{"default", "values"}, nil + } + + // Valid slice works normally + validSlice := []string{"a", "b", "c"} + result1 := cast.ToStringSliceP(defaultSliceFallback, validSlice) + fmt.Println(len(result1)) // 3 + fmt.Println(result1[0]) // a + + // Invalid input uses fallback (use a type that can't be converted) + result2 := cast.ToStringSliceP(defaultSliceFallback, make(chan int)) + fmt.Println(len(result2)) // 2 + fmt.Println(result2[0]) // default + + // Output: + // 3 + // a + // 2 + // default +} diff --git a/generator/main.go b/generator/main.go index de47dd3..f7d9c25 100644 --- a/generator/main.go +++ b/generator/main.go @@ -75,8 +75,10 @@ func main() { for _, fn := range toFuncs { if fn.name == "ToTimeInDefaultLocation" { toFuncWithParams(file, fn.name, fn.returnType, Id("location").Op("*").Qual("time", "Location")) + toPFuncWithParams(file, fn.name, fn.returnType, Id("location").Op("*").Qual("time", "Location")) } else { toFunc(file, fn.name, fn.returnType) + toPFunc(file, fn.name, fn.returnType) } } @@ -118,6 +120,44 @@ func toFuncWithParams(file *File, funcName string, returnType *Statement, args . }) } +func toPFunc(file *File, funcName string, returnType *Statement) { + toPFuncWithParams(file, funcName, returnType) +} + +func toPFuncWithParams(file *File, funcName string, returnType *Statement, args ...*Statement) { + pFuncName := funcName + "P" + file.Comment(fmt.Sprintf("%s casts any value to a(n) %s type with fallback function.", pFuncName, returnType.GoString())) + + varI := Id("i") + varFn := Id("fn") + + // Build function signature for the fallback function + fnParams := []Code{varI.Clone().Any()} + for _, arg := range args { + fnParams = append(fnParams, arg.Clone()) + } + fnType := Func().Params(fnParams...).Params(returnType, Error()) + + arguments := []Code{varFn.Clone().Add(fnType), varI.Clone().Any()} + for _, arg := range args { + arguments = append(arguments, arg) + } + + file.Func(). + Id(pFuncName).Params(arguments...).Params(returnType). + BlockFunc(func(g *Group) { + varV := Id("v") + + callArgs := []Code{varFn, varI} + for _, arg := range args { + callArgs = append(callArgs, (*arg)[0]) + } + + g.List(varV, Id("_")).Op(":=").Id(funcName + "PE").Call(callArgs...) + g.Return(varV) + }) +} + func toSliceEFunc(file *File, typeName string, returnType *Statement) { funcName := "To" + strings.ToUpper(typeName[:1]) + typeName[1:] + "SliceE" sliceReturnType := Index().Add(returnType) diff --git a/map.go b/map.go index 7d6beb5..0218e11 100644 --- a/map.go +++ b/map.go @@ -222,3 +222,57 @@ func jsonStringToObject(s string, v any) error { data := []byte(s) return json.Unmarshal(data, v) } + +// ToStringMapStringPE plus casts any value to a map[string]string type. +func ToStringMapStringPE(fn func(any) (map[string]string, error), i any) (map[string]string, error) { + v, err := ToStringMapStringE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + +// ToStringMapStringSlicePE plus casts any value to a map[string][]string type. +func ToStringMapStringSlicePE(fn func(any) (map[string][]string, error), i any) (map[string][]string, error) { + v, err := ToStringMapStringSliceE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + +// ToStringMapBoolPE plus casts any value to a map[string]bool type. +func ToStringMapBoolPE(fn func(any) (map[string]bool, error), i any) (map[string]bool, error) { + v, err := ToStringMapBoolE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + +// ToStringMapPE plus casts any value to a map[string]any type. +func ToStringMapPE(fn func(any) (map[string]any, error), i any) (map[string]any, error) { + v, err := ToStringMapE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + +// ToStringMapIntPE plus casts any value to a map[string]int type. +func ToStringMapIntPE(fn func(any) (map[string]int, error), i any) (map[string]int, error) { + v, err := ToStringMapIntE(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} + +// ToStringMapInt64PE plus casts any value to a map[string]int64 type. +func ToStringMapInt64PE(fn func(any) (map[string]int64, error), i any) (map[string]int64, error) { + v, err := ToStringMapInt64E(i) + if err == nil || fn == nil { + return v, err + } + return fn(i) +} diff --git a/number.go b/number.go index a58dc4d..3a69eec 100644 --- a/number.go +++ b/number.go @@ -547,3 +547,5 @@ func trimDecimal(s string) string { return s } + + diff --git a/slice.go b/slice.go index e6a8328..accfac6 100644 --- a/slice.go +++ b/slice.go @@ -104,3 +104,7 @@ func ToStringSliceE(i any) ([]string, error) { return nil, fmt.Errorf(errorMsg, i, i, a) } } + + + + diff --git a/time.go b/time.go index 744cd5a..734fe48 100644 --- a/time.go +++ b/time.go @@ -114,3 +114,12 @@ func StringToDate(s string) (time.Time, error) { func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) { return internal.ParseDateWith(s, location, internal.TimeFormats) } + +// ToTimeInDefaultLocationPE plus casts any value to a time.Time type. +func ToTimeInDefaultLocationPE(fn func(any, *time.Location) (time.Time, error), i any, location *time.Location) (time.Time, error) { + v, err := ToTimeInDefaultLocationE(i, location) + if err == nil || fn == nil { + return v, err + } + return fn(i, location) +} diff --git a/zz_generated.go b/zz_generated.go index ce3ec0f..7095d70 100644 --- a/zz_generated.go +++ b/zz_generated.go @@ -6,20 +6,32 @@ import "time" // ToBool casts any value to a(n) bool type. func ToBool(i any) bool { - v, _ := ToBoolE(i) - return v + return To[bool](i) +} + +// ToBoolP casts any value to a(n) bool type with fallback function. +func ToBoolP(fn func(any) (bool, error), i any) bool { + return ToP[bool](fn, i) } // ToString casts any value to a(n) string type. func ToString(i any) string { - v, _ := ToStringE(i) - return v + return To[string](i) +} + +// ToStringP casts any value to a(n) string type with fallback function. +func ToStringP(fn func(any) (string, error), i any) string { + return ToP[string](fn, i) } // ToTime casts any value to a(n) time.Time type. func ToTime(i any) time.Time { - v, _ := ToTimeE(i) - return v + return To[time.Time](i) +} + +// ToTimeP casts any value to a(n) time.Time type with fallback function. +func ToTimeP(fn func(any) (time.Time, error), i any) time.Time { + return ToP[time.Time](fn, i) } // ToTimeInDefaultLocation casts any value to a(n) time.Time type. @@ -28,82 +40,140 @@ func ToTimeInDefaultLocation(i any, location *time.Location) time.Time { return v } +// ToTimeInDefaultLocationP casts any value to a(n) time.Time type with fallback function. +func ToTimeInDefaultLocationP(fn func(any, *time.Location) (time.Time, error), i any, location *time.Location) time.Time { + v, _ := ToTimeInDefaultLocationPE(fn, i, location) + return v +} + // ToDuration casts any value to a(n) time.Duration type. func ToDuration(i any) time.Duration { - v, _ := ToDurationE(i) - return v + return To[time.Duration](i) +} + +// ToDurationP casts any value to a(n) time.Duration type with fallback function. +func ToDurationP(fn func(any) (time.Duration, error), i any) time.Duration { + return ToP[time.Duration](fn, i) } // ToInt casts any value to a(n) int type. func ToInt(i any) int { - v, _ := ToIntE(i) - return v + return To[int](i) +} + +// ToIntP casts any value to a(n) int type with fallback function. +func ToIntP(fn func(any) (int, error), i any) int { + return ToP[int](fn, i) } // ToInt8 casts any value to a(n) int8 type. func ToInt8(i any) int8 { - v, _ := ToInt8E(i) - return v + return To[int8](i) +} + +// ToInt8P casts any value to a(n) int8 type with fallback function. +func ToInt8P(fn func(any) (int8, error), i any) int8 { + return ToP[int8](fn, i) } // ToInt16 casts any value to a(n) int16 type. func ToInt16(i any) int16 { - v, _ := ToInt16E(i) - return v + return To[int16](i) +} + +// ToInt16P casts any value to a(n) int16 type with fallback function. +func ToInt16P(fn func(any) (int16, error), i any) int16 { + return ToP[int16](fn, i) } // ToInt32 casts any value to a(n) int32 type. func ToInt32(i any) int32 { - v, _ := ToInt32E(i) - return v + return To[int32](i) +} + +// ToInt32P casts any value to a(n) int32 type with fallback function. +func ToInt32P(fn func(any) (int32, error), i any) int32 { + return ToP[int32](fn, i) } // ToInt64 casts any value to a(n) int64 type. func ToInt64(i any) int64 { - v, _ := ToInt64E(i) - return v + return To[int64](i) +} + +// ToInt64P casts any value to a(n) int64 type with fallback function. +func ToInt64P(fn func(any) (int64, error), i any) int64 { + return ToP[int64](fn, i) } // ToUint casts any value to a(n) uint type. func ToUint(i any) uint { - v, _ := ToUintE(i) - return v + return To[uint](i) +} + +// ToUintP casts any value to a(n) uint type with fallback function. +func ToUintP(fn func(any) (uint, error), i any) uint { + return ToP[uint](fn, i) } // ToUint8 casts any value to a(n) uint8 type. func ToUint8(i any) uint8 { - v, _ := ToUint8E(i) - return v + return To[uint8](i) +} + +// ToUint8P casts any value to a(n) uint8 type with fallback function. +func ToUint8P(fn func(any) (uint8, error), i any) uint8 { + return ToP[uint8](fn, i) } // ToUint16 casts any value to a(n) uint16 type. func ToUint16(i any) uint16 { - v, _ := ToUint16E(i) - return v + return To[uint16](i) +} + +// ToUint16P casts any value to a(n) uint16 type with fallback function. +func ToUint16P(fn func(any) (uint16, error), i any) uint16 { + return ToP[uint16](fn, i) } // ToUint32 casts any value to a(n) uint32 type. func ToUint32(i any) uint32 { - v, _ := ToUint32E(i) - return v + return To[uint32](i) +} + +// ToUint32P casts any value to a(n) uint32 type with fallback function. +func ToUint32P(fn func(any) (uint32, error), i any) uint32 { + return ToP[uint32](fn, i) } // ToUint64 casts any value to a(n) uint64 type. func ToUint64(i any) uint64 { - v, _ := ToUint64E(i) - return v + return To[uint64](i) +} + +// ToUint64P casts any value to a(n) uint64 type with fallback function. +func ToUint64P(fn func(any) (uint64, error), i any) uint64 { + return ToP[uint64](fn, i) } // ToFloat32 casts any value to a(n) float32 type. func ToFloat32(i any) float32 { - v, _ := ToFloat32E(i) - return v + return To[float32](i) +} + +// ToFloat32P casts any value to a(n) float32 type with fallback function. +func ToFloat32P(fn func(any) (float32, error), i any) float32 { + return ToP[float32](fn, i) } // ToFloat64 casts any value to a(n) float64 type. func ToFloat64(i any) float64 { - v, _ := ToFloat64E(i) - return v + return To[float64](i) +} + +// ToFloat64P casts any value to a(n) float64 type with fallback function. +func ToFloat64P(fn func(any) (float64, error), i any) float64 { + return ToP[float64](fn, i) } // ToStringMapString casts any value to a(n) map[string]string type. @@ -112,46 +182,86 @@ func ToStringMapString(i any) map[string]string { return v } +// ToStringMapStringP casts any value to a(n) map[string]string type with fallback function. +func ToStringMapStringP(fn func(any) (map[string]string, error), i any) map[string]string { + v, _ := ToStringMapStringPE(fn, i) + return v +} + // ToStringMapStringSlice casts any value to a(n) map[string][]string type. func ToStringMapStringSlice(i any) map[string][]string { v, _ := ToStringMapStringSliceE(i) return v } +// ToStringMapStringSliceP casts any value to a(n) map[string][]string type with fallback function. +func ToStringMapStringSliceP(fn func(any) (map[string][]string, error), i any) map[string][]string { + v, _ := ToStringMapStringSlicePE(fn, i) + return v +} + // ToStringMapBool casts any value to a(n) map[string]bool type. func ToStringMapBool(i any) map[string]bool { v, _ := ToStringMapBoolE(i) return v } +// ToStringMapBoolP casts any value to a(n) map[string]bool type with fallback function. +func ToStringMapBoolP(fn func(any) (map[string]bool, error), i any) map[string]bool { + v, _ := ToStringMapBoolPE(fn, i) + return v +} + // ToStringMapInt casts any value to a(n) map[string]int type. func ToStringMapInt(i any) map[string]int { v, _ := ToStringMapIntE(i) return v } +// ToStringMapIntP casts any value to a(n) map[string]int type with fallback function. +func ToStringMapIntP(fn func(any) (map[string]int, error), i any) map[string]int { + v, _ := ToStringMapIntPE(fn, i) + return v +} + // ToStringMapInt64 casts any value to a(n) map[string]int64 type. func ToStringMapInt64(i any) map[string]int64 { v, _ := ToStringMapInt64E(i) return v } +// ToStringMapInt64P casts any value to a(n) map[string]int64 type with fallback function. +func ToStringMapInt64P(fn func(any) (map[string]int64, error), i any) map[string]int64 { + v, _ := ToStringMapInt64PE(fn, i) + return v +} + // ToStringMap casts any value to a(n) map[string]any type. func ToStringMap(i any) map[string]any { v, _ := ToStringMapE(i) return v } +// ToStringMapP casts any value to a(n) map[string]any type with fallback function. +func ToStringMapP(fn func(any) (map[string]any, error), i any) map[string]any { + v, _ := ToStringMapPE(fn, i) + return v +} + // ToSlice casts any value to a(n) []any type. func ToSlice(i any) []any { v, _ := ToSliceE(i) return v } -// ToBoolSlice casts any value to a(n) []bool type. -func ToBoolSlice(i any) []bool { - v, _ := ToBoolSliceE(i) - return v +// ToSliceP casts any value to a(n) []any type with fallback function. +func ToSliceP(fn func(any) ([]any, error), i any) []any { + v, err := ToSliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result } // ToStringSlice casts any value to a(n) []string type. @@ -160,33 +270,19 @@ func ToStringSlice(i any) []string { return v } -// ToIntSlice casts any value to a(n) []int type. -func ToIntSlice(i any) []int { - v, _ := ToIntSliceE(i) - return v -} - -// ToInt64Slice casts any value to a(n) []int64 type. -func ToInt64Slice(i any) []int64 { - v, _ := ToInt64SliceE(i) - return v -} - -// ToUintSlice casts any value to a(n) []uint type. -func ToUintSlice(i any) []uint { - v, _ := ToUintSliceE(i) - return v -} - -// ToFloat64Slice casts any value to a(n) []float64 type. -func ToFloat64Slice(i any) []float64 { - v, _ := ToFloat64SliceE(i) - return v +// ToStringSliceP casts any value to a(n) []string type with fallback function. +func ToStringSliceP(fn func(any) ([]string, error), i any) []string { + v, err := ToStringSliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result } -// ToDurationSlice casts any value to a(n) []time.Duration type. -func ToDurationSlice(i any) []time.Duration { - v, _ := ToDurationSliceE(i) +// ToBoolSlice casts any value to a(n) []bool type. +func ToBoolSlice(i any) []bool { + v, _ := ToBoolSliceE(i) return v } @@ -195,9 +291,20 @@ func ToBoolSliceE(i any) ([]bool, error) { return toSliceE[bool](i) } -// ToDurationSliceE casts any value to a(n) []time.Duration type. -func ToDurationSliceE(i any) ([]time.Duration, error) { - return toSliceE[time.Duration](i) +// ToBoolSliceP casts any value to a(n) []bool type with fallback function. +func ToBoolSliceP(fn func(any) ([]bool, error), i any) []bool { + v, err := ToBoolSliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result +} + +// ToIntSlice casts any value to a(n) []int type. +func ToIntSlice(i any) []int { + v, _ := ToIntSliceE(i) + return v } // ToIntSliceE casts any value to a(n) []int type. @@ -205,19 +312,20 @@ func ToIntSliceE(i any) ([]int, error) { return toSliceE[int](i) } -// ToInt8SliceE casts any value to a(n) []int8 type. -func ToInt8SliceE(i any) ([]int8, error) { - return toSliceE[int8](i) -} - -// ToInt16SliceE casts any value to a(n) []int16 type. -func ToInt16SliceE(i any) ([]int16, error) { - return toSliceE[int16](i) +// ToIntSliceP casts any value to a(n) []int type with fallback function. +func ToIntSliceP(fn func(any) ([]int, error), i any) []int { + v, err := ToIntSliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result } -// ToInt32SliceE casts any value to a(n) []int32 type. -func ToInt32SliceE(i any) ([]int32, error) { - return toSliceE[int32](i) +// ToInt64Slice casts any value to a(n) []int64 type. +func ToInt64Slice(i any) []int64 { + v, _ := ToInt64SliceE(i) + return v } // ToInt64SliceE casts any value to a(n) []int64 type. @@ -225,37 +333,75 @@ func ToInt64SliceE(i any) ([]int64, error) { return toSliceE[int64](i) } +// ToInt64SliceP casts any value to a(n) []int64 type with fallback function. +func ToInt64SliceP(fn func(any) ([]int64, error), i any) []int64 { + v, err := ToInt64SliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result +} + +// ToUintSlice casts any value to a(n) []uint type. +func ToUintSlice(i any) []uint { + v, _ := ToUintSliceE(i) + return v +} + // ToUintSliceE casts any value to a(n) []uint type. func ToUintSliceE(i any) ([]uint, error) { return toSliceE[uint](i) } -// ToUint8SliceE casts any value to a(n) []uint8 type. -func ToUint8SliceE(i any) ([]uint8, error) { - return toSliceE[uint8](i) +// ToUintSliceP casts any value to a(n) []uint type with fallback function. +func ToUintSliceP(fn func(any) ([]uint, error), i any) []uint { + v, err := ToUintSliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result } -// ToUint16SliceE casts any value to a(n) []uint16 type. -func ToUint16SliceE(i any) ([]uint16, error) { - return toSliceE[uint16](i) +// ToFloat64Slice casts any value to a(n) []float64 type. +func ToFloat64Slice(i any) []float64 { + v, _ := ToFloat64SliceE(i) + return v } -// ToUint32SliceE casts any value to a(n) []uint32 type. -func ToUint32SliceE(i any) ([]uint32, error) { - return toSliceE[uint32](i) +// ToFloat64SliceE casts any value to a(n) []float64 type. +func ToFloat64SliceE(i any) ([]float64, error) { + return toSliceE[float64](i) } -// ToUint64SliceE casts any value to a(n) []uint64 type. -func ToUint64SliceE(i any) ([]uint64, error) { - return toSliceE[uint64](i) +// ToFloat64SliceP casts any value to a(n) []float64 type with fallback function. +func ToFloat64SliceP(fn func(any) ([]float64, error), i any) []float64 { + v, err := ToFloat64SliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result } -// ToFloat32SliceE casts any value to a(n) []float32 type. -func ToFloat32SliceE(i any) ([]float32, error) { - return toSliceE[float32](i) +// ToDurationSlice casts any value to a(n) []time.Duration type. +func ToDurationSlice(i any) []time.Duration { + v, _ := ToDurationSliceE(i) + return v } -// ToFloat64SliceE casts any value to a(n) []float64 type. -func ToFloat64SliceE(i any) ([]float64, error) { - return toSliceE[float64](i) +// ToDurationSliceE casts any value to a(n) []time.Duration type. +func ToDurationSliceE(i any) ([]time.Duration, error) { + return toSliceE[time.Duration](i) } + +// ToDurationSliceP casts any value to a(n) []time.Duration type with fallback function. +func ToDurationSliceP(fn func(any) ([]time.Duration, error), i any) []time.Duration { + v, err := ToDurationSliceE(i) + if err == nil || fn == nil { + return v + } + result, _ := fn(i) + return result +} \ No newline at end of file From d4c03c801c6a387df2f1b2d85a1434ac75d2ec01 Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Wed, 20 Aug 2025 18:31:38 +0800 Subject: [PATCH 3/9] docs: add comprehensive documentation for plus functions - Document all plus function variants and usage patterns - Provide practical examples for common use cases - Explain benefits and performance characteristics - Include guidance on when to use plus functions --- PLUS_FUNCTIONS.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 PLUS_FUNCTIONS.md diff --git a/PLUS_FUNCTIONS.md b/PLUS_FUNCTIONS.md new file mode 100644 index 0000000..dd8115b --- /dev/null +++ b/PLUS_FUNCTIONS.md @@ -0,0 +1,125 @@ +# Plus Functions + +This document describes the "plus functions" feature added to the cast library. Plus functions provide a way to extend cast's type conversion capabilities with custom fallback logic. + +## Overview + +Plus functions are variants of cast's standard conversion functions that accept a fallback function. When the standard cast conversion fails, the fallback function is called to provide an alternative conversion strategy. + +## Function Naming Convention + +Plus functions follow the naming pattern: `To{Type}P` and `To{Type}PE` + +- `To{Type}P`: Returns the converted value, using fallback on failure +- `To{Type}PE`: Returns the converted value and error, using fallback on failure + +## Generic Plus Functions + +### `ToP[T Basic](fn func(any) (T, error), i any) T` +Generic plus function for any Basic type (string, bool, numbers, time.Time, time.Duration). + +### `ToPE[T Basic](fn func(any) (T, error), i any) (T, error)` +Generic plus function with error return. + +## Specific Plus Functions + +### Basic Types +- `ToBoolP(fn func(any) (bool, error), i any) bool` +- `ToStringP(fn func(any) (string, error), i any) string` + +### Numeric Types +- `ToIntP(fn func(any) (int, error), i any) int` +- `ToInt8P`, `ToInt16P`, `ToInt32P`, `ToInt64P` +- `ToUintP`, `ToUint8P`, `ToUint16P`, `ToUint32P`, `ToUint64P` +- `ToFloat32P`, `ToFloat64P` + +### Time Types +- `ToTimeP(fn func(any) (time.Time, error), i any) time.Time` +- `ToTimeInDefaultLocationP(fn func(any, *time.Location) (time.Time, error), i any, location *time.Location) time.Time` +- `ToDurationP(fn func(any) (time.Duration, error), i any) time.Duration` + +### Map Types +- `ToStringMapStringP(fn func(any) (map[string]string, error), i any) map[string]string` +- `ToStringMapIntP(fn func(any) (map[string]int, error), i any) map[string]int` +- `ToStringMapBoolP(fn func(any) (map[string]bool, error), i any) map[string]bool` +- And more... + +### Slice Types +- `ToSliceP(fn func(any) ([]any, error), i any) []any` +- `ToStringSliceP(fn func(any) ([]string, error), i any) []string` +- `ToIntSliceP(fn func(any) ([]int, error), i any) []int` +- And more... + +## Usage Examples + +### Custom Boolean Values +```go +customBoolFallback := func(i any) (bool, error) { + if str, ok := i.(string); ok { + switch str { + case "yes", "Y", "on", "enabled": + return true, nil + case "no", "N", "off", "disabled": + return false, nil + } + } + return false, fmt.Errorf("cannot parse %v as custom boolean", i) +} + +result := cast.ToBoolP(customBoolFallback, "yes") // true +``` + +### Custom Date Formats +```go +customDateFallback := func(i any) (time.Time, error) { + if str, ok := i.(string); ok { + return time.Parse("01/02/2006 15:04:05", str) + } + return time.Time{}, fmt.Errorf("cannot parse %v as custom date", i) +} + +result := cast.ToTimeP(customDateFallback, "12/25/2023 14:30:00") +``` + +### Configuration with Defaults +```go +getDefault := func(defaultVal int) func(any) (int, error) { + return func(i any) (int, error) { + return defaultVal, nil + } +} + +timeout := cast.ToP[int](getDefault(30), "invalid") // 30 +``` + +### Generic Usage +```go +// Works with any Basic type +port := cast.ToP[int](getDefault(8080), invalidConfig) +debug := cast.ToP[bool](getDefault(false), invalidConfig) +host := cast.ToP[string](getDefault("localhost"), invalidConfig) +``` + +## Benefits + +1. **Extends Coverage**: Handle edge cases that cast doesn't support natively +2. **Backward Compatible**: Doesn't change existing cast behavior +3. **Type Safe**: Leverages Go's generics for type safety +4. **Flexible**: Supports custom conversion logic for any scenario +5. **Consistent**: Follows cast's existing API patterns + +## When to Use + +- Parsing legacy data formats +- Handling domain-specific string representations +- Providing configuration defaults +- Supporting custom boolean/date/number formats +- Migrating from other type conversion libraries +- Adding application-specific conversion rules + +## Performance + +Plus functions have minimal overhead: +- If standard conversion succeeds, fallback is never called +- Only when conversion fails does the fallback function execute +- No performance impact on existing cast usage From 94ad4bfe699d883db0c24a194e73162181a80744 Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Wed, 10 Sep 2025 23:22:38 +0800 Subject: [PATCH 4/9] feat: implement ToValue API to replace plus functions - Remove all plus functions (ToPE, ToP, etc.) as suggested by project author - Add ValueSetter interface for custom conversion logic - Add ToValue function that delegates to target types - Update examples to use new ToValue API - Maintain backward compatibility with existing functions - Add .idea/ to .gitignore --- .gitignore | 3 + PLUS_FUNCTIONS.md | 125 ----------- cast.go | 137 ++++++++++-- cast_plus_test.go | 483 ------------------------------------------- example_plus_test.go | 210 ++++++++++--------- generator/main.go | 2 - zz_generated.go | 332 +++++++++-------------------- 7 files changed, 333 insertions(+), 959 deletions(-) delete mode 100644 PLUS_FUNCTIONS.md delete mode 100644 cast_plus_test.go diff --git a/.gitignore b/.gitignore index 53053a8..0e8c51b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ _testmain.go *.test *.bench + +# IDE +.idea/ diff --git a/PLUS_FUNCTIONS.md b/PLUS_FUNCTIONS.md deleted file mode 100644 index dd8115b..0000000 --- a/PLUS_FUNCTIONS.md +++ /dev/null @@ -1,125 +0,0 @@ -# Plus Functions - -This document describes the "plus functions" feature added to the cast library. Plus functions provide a way to extend cast's type conversion capabilities with custom fallback logic. - -## Overview - -Plus functions are variants of cast's standard conversion functions that accept a fallback function. When the standard cast conversion fails, the fallback function is called to provide an alternative conversion strategy. - -## Function Naming Convention - -Plus functions follow the naming pattern: `To{Type}P` and `To{Type}PE` - -- `To{Type}P`: Returns the converted value, using fallback on failure -- `To{Type}PE`: Returns the converted value and error, using fallback on failure - -## Generic Plus Functions - -### `ToP[T Basic](fn func(any) (T, error), i any) T` -Generic plus function for any Basic type (string, bool, numbers, time.Time, time.Duration). - -### `ToPE[T Basic](fn func(any) (T, error), i any) (T, error)` -Generic plus function with error return. - -## Specific Plus Functions - -### Basic Types -- `ToBoolP(fn func(any) (bool, error), i any) bool` -- `ToStringP(fn func(any) (string, error), i any) string` - -### Numeric Types -- `ToIntP(fn func(any) (int, error), i any) int` -- `ToInt8P`, `ToInt16P`, `ToInt32P`, `ToInt64P` -- `ToUintP`, `ToUint8P`, `ToUint16P`, `ToUint32P`, `ToUint64P` -- `ToFloat32P`, `ToFloat64P` - -### Time Types -- `ToTimeP(fn func(any) (time.Time, error), i any) time.Time` -- `ToTimeInDefaultLocationP(fn func(any, *time.Location) (time.Time, error), i any, location *time.Location) time.Time` -- `ToDurationP(fn func(any) (time.Duration, error), i any) time.Duration` - -### Map Types -- `ToStringMapStringP(fn func(any) (map[string]string, error), i any) map[string]string` -- `ToStringMapIntP(fn func(any) (map[string]int, error), i any) map[string]int` -- `ToStringMapBoolP(fn func(any) (map[string]bool, error), i any) map[string]bool` -- And more... - -### Slice Types -- `ToSliceP(fn func(any) ([]any, error), i any) []any` -- `ToStringSliceP(fn func(any) ([]string, error), i any) []string` -- `ToIntSliceP(fn func(any) ([]int, error), i any) []int` -- And more... - -## Usage Examples - -### Custom Boolean Values -```go -customBoolFallback := func(i any) (bool, error) { - if str, ok := i.(string); ok { - switch str { - case "yes", "Y", "on", "enabled": - return true, nil - case "no", "N", "off", "disabled": - return false, nil - } - } - return false, fmt.Errorf("cannot parse %v as custom boolean", i) -} - -result := cast.ToBoolP(customBoolFallback, "yes") // true -``` - -### Custom Date Formats -```go -customDateFallback := func(i any) (time.Time, error) { - if str, ok := i.(string); ok { - return time.Parse("01/02/2006 15:04:05", str) - } - return time.Time{}, fmt.Errorf("cannot parse %v as custom date", i) -} - -result := cast.ToTimeP(customDateFallback, "12/25/2023 14:30:00") -``` - -### Configuration with Defaults -```go -getDefault := func(defaultVal int) func(any) (int, error) { - return func(i any) (int, error) { - return defaultVal, nil - } -} - -timeout := cast.ToP[int](getDefault(30), "invalid") // 30 -``` - -### Generic Usage -```go -// Works with any Basic type -port := cast.ToP[int](getDefault(8080), invalidConfig) -debug := cast.ToP[bool](getDefault(false), invalidConfig) -host := cast.ToP[string](getDefault("localhost"), invalidConfig) -``` - -## Benefits - -1. **Extends Coverage**: Handle edge cases that cast doesn't support natively -2. **Backward Compatible**: Doesn't change existing cast behavior -3. **Type Safe**: Leverages Go's generics for type safety -4. **Flexible**: Supports custom conversion logic for any scenario -5. **Consistent**: Follows cast's existing API patterns - -## When to Use - -- Parsing legacy data formats -- Handling domain-specific string representations -- Providing configuration defaults -- Supporting custom boolean/date/number formats -- Migrating from other type conversion libraries -- Adding application-specific conversion rules - -## Performance - -Plus functions have minimal overhead: -- If standard conversion succeeds, fallback is never called -- Only when conversion fails does the fallback function execute -- No performance impact on existing cast usage diff --git a/cast.go b/cast.go index ce471b9..1bcc459 100644 --- a/cast.go +++ b/cast.go @@ -6,7 +6,10 @@ // Package cast provides easy and safe casting in Go. package cast -import "time" +import ( + "fmt" + "time" +) const errorMsg = "unable to cast %#v of type %T to %T" const errorMsgWith = "unable to cast %#v of type %T to %T: %w" @@ -83,17 +86,127 @@ func To[T Basic](i any) T { return v } -// ToPE plus casts any value to a [Basic] type with fallback function. -func ToPE[T Basic](fn func(any) (T, error), i any) (T, error) { - v, err := ToE[T](i) - if err == nil || fn == nil { - return v, err - } - return fn(i) +// ValueSetter is an interface that types can implement to provide custom conversion logic. +// When ToValue is called with a pointer to a type that implements ValueSetter, +// the SetValue method will be called to perform the conversion. +type ValueSetter interface { + SetValue(any) error } -// ToP plus casts any value to a [Basic] type with fallback function. -func ToP[T Basic](fn func(any) (T, error), i any) T { - v, _ := ToPE[T](fn, i) - return v +// ToValue attempts to convert a value and assign it to the target. +// If target implements ValueSetter, it will use the custom conversion logic. +// Otherwise, it falls back to standard cast conversion based on the target type. +func ToValue(target any, value any) error { + if target == nil { + return fmt.Errorf("target cannot be nil") + } + + // Check if target implements ValueSetter + if setter, ok := target.(ValueSetter); ok { + return setter.SetValue(value) + } + + // Use reflection-like approach based on target type + switch t := target.(type) { + case *string: + v, err := ToStringE(value) + if err != nil { + return err + } + *t = v + case *bool: + v, err := ToBoolE(value) + if err != nil { + return err + } + *t = v + case *int: + v, err := toNumberE[int](value, parseInt[int]) + if err != nil { + return err + } + *t = v + case *int8: + v, err := toNumberE[int8](value, parseInt[int8]) + if err != nil { + return err + } + *t = v + case *int16: + v, err := toNumberE[int16](value, parseInt[int16]) + if err != nil { + return err + } + *t = v + case *int32: + v, err := toNumberE[int32](value, parseInt[int32]) + if err != nil { + return err + } + *t = v + case *int64: + v, err := toNumberE[int64](value, parseInt[int64]) + if err != nil { + return err + } + *t = v + case *uint: + v, err := toUnsignedNumberE[uint](value, parseUint[uint]) + if err != nil { + return err + } + *t = v + case *uint8: + v, err := toUnsignedNumberE[uint8](value, parseUint[uint8]) + if err != nil { + return err + } + *t = v + case *uint16: + v, err := toUnsignedNumberE[uint16](value, parseUint[uint16]) + if err != nil { + return err + } + *t = v + case *uint32: + v, err := toUnsignedNumberE[uint32](value, parseUint[uint32]) + if err != nil { + return err + } + *t = v + case *uint64: + v, err := toUnsignedNumberE[uint64](value, parseUint[uint64]) + if err != nil { + return err + } + *t = v + case *float32: + v, err := toNumberE[float32](value, parseFloat[float32]) + if err != nil { + return err + } + *t = v + case *float64: + v, err := toNumberE[float64](value, parseFloat[float64]) + if err != nil { + return err + } + *t = v + case *time.Time: + v, err := ToTimeE(value) + if err != nil { + return err + } + *t = v + case *time.Duration: + v, err := ToDurationE(value) + if err != nil { + return err + } + *t = v + default: + return fmt.Errorf("unsupported target type %T", target) + } + + return nil } diff --git a/cast_plus_test.go b/cast_plus_test.go deleted file mode 100644 index 3a5bf14..0000000 --- a/cast_plus_test.go +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright © 2014 Steve Francia . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package cast_test - -import ( - "fmt" - "testing" - "time" - - qt "github.com/frankban/quicktest" - - "github.com/spf13/cast" -) - -// TestPlusFunctions tests the "plus functions" that provide fallback logic -func TestPlusFunctions(t *testing.T) { - t.Parallel() - - t.Run("ToBoolP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - fallback should not be called - result := cast.ToBoolP(func(i any) (bool, error) { - t.Error("Fallback should not be called for successful conversion") - return false, nil - }, true) - c.Assert(result, qt.Equals, true) - - // Test failed conversion - fallback should be called - fallbackCalled := false - result = cast.ToBoolP(func(i any) (bool, error) { - fallbackCalled = true - return true, nil - }, "invalid") - c.Assert(result, qt.Equals, true) - c.Assert(fallbackCalled, qt.Equals, true) - - // Test nil fallback function - result = cast.ToBoolP(nil, "invalid") - c.Assert(result, qt.Equals, false) // Should return zero value - }) - - t.Run("ToStringP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - result := cast.ToStringP(func(i any) (string, error) { - t.Error("Fallback should not be called for successful conversion") - return "", nil - }, 123) - c.Assert(result, qt.Equals, "123") - - // Test failed conversion with fallback - fallbackCalled := false - result = cast.ToStringP(func(i any) (string, error) { - fallbackCalled = true - return "fallback", nil - }, make(chan int)) - c.Assert(result, qt.Equals, "fallback") - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToIntP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - result := cast.ToIntP(func(i any) (int, error) { - t.Error("Fallback should not be called for successful conversion") - return 0, nil - }, "42") - c.Assert(result, qt.Equals, 42) - - // Test failed conversion with fallback - fallbackCalled := false - result = cast.ToIntP(func(i any) (int, error) { - fallbackCalled = true - return 999, nil - }, "invalid") - c.Assert(result, qt.Equals, 999) - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToTimeP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - now := time.Now() - result := cast.ToTimeP(func(i any) (time.Time, error) { - t.Error("Fallback should not be called for successful conversion") - return time.Time{}, nil - }, now) - c.Assert(result.UTC(), qt.Equals, now.UTC()) - - // Test failed conversion with fallback - fallbackTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) - fallbackCalled := false - result = cast.ToTimeP(func(i any) (time.Time, error) { - fallbackCalled = true - return fallbackTime, nil - }, "invalid") - c.Assert(result.UTC(), qt.Equals, fallbackTime.UTC()) - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToTimeInDefaultLocationP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - loc := time.FixedZone("TEST", 3600) - - // Test successful conversion - result := cast.ToTimeInDefaultLocationP(func(i any, location *time.Location) (time.Time, error) { - t.Error("Fallback should not be called for successful conversion") - return time.Time{}, nil - }, "2023-01-01", loc) - c.Assert(result.Year(), qt.Equals, 2023) - - // Test failed conversion with fallback - fallbackTime := time.Date(2024, 1, 1, 0, 0, 0, 0, loc) - fallbackCalled := false - result = cast.ToTimeInDefaultLocationP(func(i any, location *time.Location) (time.Time, error) { - fallbackCalled = true - c.Assert(location, qt.Equals, loc) - return fallbackTime, nil - }, "invalid", loc) - c.Assert(result, qt.Equals, fallbackTime) - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToDurationP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - result := cast.ToDurationP(func(i any) (time.Duration, error) { - t.Error("Fallback should not be called for successful conversion") - return 0, nil - }, "5s") - c.Assert(result, qt.Equals, 5*time.Second) - - // Test failed conversion with fallback - fallbackDuration := 10 * time.Minute - fallbackCalled := false - result = cast.ToDurationP(func(i any) (time.Duration, error) { - fallbackCalled = true - return fallbackDuration, nil - }, "invalid") - c.Assert(result, qt.Equals, fallbackDuration) - c.Assert(fallbackCalled, qt.Equals, true) - }) -} - -// TestGenericPlusFunctions tests the generic ToP and ToPE functions -func TestGenericPlusFunctions(t *testing.T) { - t.Parallel() - - t.Run("ToP[int]", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - result := cast.ToP[int](func(i any) (int, error) { - t.Error("Fallback should not be called for successful conversion") - return 0, nil - }, "42") - c.Assert(result, qt.Equals, 42) - - // Test failed conversion with fallback - fallbackCalled := false - result = cast.ToP[int](func(i any) (int, error) { - fallbackCalled = true - return 999, nil - }, "invalid") - c.Assert(result, qt.Equals, 999) - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToPE[string]", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - result, err := cast.ToPE[string](func(i any) (string, error) { - t.Error("Fallback should not be called for successful conversion") - return "", nil - }, 123) - c.Assert(err, qt.IsNil) - c.Assert(result, qt.Equals, "123") - - // Test failed conversion with fallback - fallbackCalled := false - result, err = cast.ToPE[string](func(i any) (string, error) { - fallbackCalled = true - return "fallback", nil - }, make(chan int)) - c.Assert(err, qt.IsNil) - c.Assert(result, qt.Equals, "fallback") - c.Assert(fallbackCalled, qt.Equals, true) - - // Test fallback returning error - result, err = cast.ToPE[string](func(i any) (string, error) { - return "", fmt.Errorf("fallback error") - }, make(chan int)) - c.Assert(err, qt.IsNotNil) - c.Assert(err.Error(), qt.Equals, "fallback error") - }) - - t.Run("ToPE[bool] with nil fallback", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test with nil fallback - should return original error - result, err := cast.ToPE[bool](nil, "invalid") - c.Assert(err, qt.IsNotNil) - c.Assert(result, qt.Equals, false) - }) -} - -// TestMapPlusFunctions tests the map-related plus functions -func TestMapPlusFunctions(t *testing.T) { - t.Parallel() - - t.Run("ToStringMapStringP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - input := map[string]string{"key": "value"} - result := cast.ToStringMapStringP(func(i any) (map[string]string, error) { - t.Error("Fallback should not be called for successful conversion") - return nil, nil - }, input) - c.Assert(result, qt.DeepEquals, input) - - // Test failed conversion with fallback - fallbackMap := map[string]string{"fallback": "value"} - fallbackCalled := false - result = cast.ToStringMapStringP(func(i any) (map[string]string, error) { - fallbackCalled = true - return fallbackMap, nil - }, "invalid") - c.Assert(result, qt.DeepEquals, fallbackMap) - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToStringMapIntP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - input := map[string]int{"key": 42} - result := cast.ToStringMapIntP(func(i any) (map[string]int, error) { - t.Error("Fallback should not be called for successful conversion") - return nil, nil - }, input) - c.Assert(result, qt.DeepEquals, input) - - // Test failed conversion with fallback - fallbackMap := map[string]int{"fallback": 999} - fallbackCalled := false - result = cast.ToStringMapIntP(func(i any) (map[string]int, error) { - fallbackCalled = true - return fallbackMap, nil - }, "invalid") - c.Assert(result, qt.DeepEquals, fallbackMap) - c.Assert(fallbackCalled, qt.Equals, true) - }) -} - -// TestSlicePlusFunctions tests the slice-related plus functions -func TestSlicePlusFunctions(t *testing.T) { - t.Parallel() - - t.Run("ToSliceP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - input := []any{1, 2, 3} - result := cast.ToSliceP(func(i any) ([]any, error) { - t.Error("Fallback should not be called for successful conversion") - return nil, nil - }, input) - c.Assert(result, qt.DeepEquals, input) - - // Test failed conversion with fallback - fallbackSlice := []any{"fallback"} - fallbackCalled := false - result = cast.ToSliceP(func(i any) ([]any, error) { - fallbackCalled = true - return fallbackSlice, nil - }, "invalid") - c.Assert(result, qt.DeepEquals, fallbackSlice) - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToStringSliceP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - input := []string{"a", "b", "c"} - result := cast.ToStringSliceP(func(i any) ([]string, error) { - t.Error("Fallback should not be called for successful conversion") - return nil, nil - }, input) - c.Assert(result, qt.DeepEquals, input) - - // Test failed conversion with fallback - use a type that cannot be converted - fallbackSlice := []string{"fallback"} - fallbackCalled := false - result = cast.ToStringSliceP(func(i any) ([]string, error) { - fallbackCalled = true - return fallbackSlice, nil - }, make(chan int)) // channels cannot be converted to []string - c.Assert(result, qt.DeepEquals, fallbackSlice) - c.Assert(fallbackCalled, qt.Equals, true) - }) - - t.Run("ToIntSliceP", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Test successful conversion - input := []int{1, 2, 3} - result := cast.ToIntSliceP(func(i any) ([]int, error) { - t.Error("Fallback should not be called for successful conversion") - return nil, nil - }, input) - c.Assert(result, qt.DeepEquals, input) - - // Test failed conversion with fallback - fallbackSlice := []int{999} - fallbackCalled := false - result = cast.ToIntSliceP(func(i any) ([]int, error) { - fallbackCalled = true - return fallbackSlice, nil - }, "invalid") - c.Assert(result, qt.DeepEquals, fallbackSlice) - c.Assert(fallbackCalled, qt.Equals, true) - }) -} - -// TestRealWorldScenarios tests practical use cases for plus functions -func TestRealWorldScenarios(t *testing.T) { - t.Parallel() - - t.Run("CustomDateFormat", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Custom date format that cast doesn't support by default - customDateStr := "01/02/2006 15:04:05" - inputDate := "12/25/2023 14:30:00" - - result := cast.ToTimeP(func(i any) (time.Time, error) { - if str, ok := i.(string); ok { - return time.Parse(customDateStr, str) - } - return time.Time{}, fmt.Errorf("cannot parse %v as custom date", i) - }, inputDate) - - expected := time.Date(2023, 12, 25, 14, 30, 0, 0, time.UTC) - c.Assert(result.UTC(), qt.Equals, expected) - }) - - t.Run("CustomBooleanValues", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Custom boolean values that cast doesn't support by default - customBoolFallback := func(i any) (bool, error) { - if str, ok := i.(string); ok { - switch str { - case "yes", "Y", "on", "enabled": - return true, nil - case "no", "N", "off", "disabled": - return false, nil - } - } - return false, fmt.Errorf("cannot parse %v as custom boolean", i) - } - - testCases := []struct { - input string - expected bool - }{ - {"yes", true}, - {"Y", true}, - {"on", true}, - {"enabled", true}, - {"no", false}, - {"N", false}, - {"off", false}, - {"disabled", false}, - } - - for _, tc := range testCases { - result := cast.ToBoolP(customBoolFallback, tc.input) - c.Assert(result, qt.Equals, tc.expected, qt.Commentf("input: %s", tc.input)) - } - }) - - t.Run("LegacyDataMigration", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Simulate migrating legacy data with custom string format - legacyStringFallback := func(i any) (string, error) { - // Handle legacy format: "LEGACY:actual_value" - if str, ok := i.(string); ok && len(str) > 7 && str[:7] == "LEGACY:" { - return str[7:], nil - } - return fmt.Sprintf("migrated_%v", i), nil - } - - // Use types that cast cannot convert to string to trigger fallback - testCases := []struct { - input any - expected string - }{ - {make(chan int), "migrated_0x"}, // channels cannot be converted to string - {func() {}, "migrated_0x"}, // functions cannot be converted to string - } - - for _, tc := range testCases { - result := cast.ToStringP(legacyStringFallback, tc.input) - // For channels and functions, the result will contain memory addresses - // so we just check that it starts with "migrated_" - c.Assert(len(result) > 9, qt.Equals, true, qt.Commentf("input: %v", tc.input)) - c.Assert(result[:9], qt.Equals, "migrated_", qt.Commentf("input: %v", tc.input)) - } - - // Test the legacy format handling with a type that cast can't handle - type customType struct{ value string } - legacyInput := customType{value: "LEGACY:test_value"} - result := cast.ToStringP(legacyStringFallback, legacyInput) - c.Assert(len(result) > 9, qt.Equals, true) - c.Assert(result[:9], qt.Equals, "migrated_") - }) - - t.Run("ConfigurationDefaults", func(t *testing.T) { - t.Parallel() - c := qt.New(t) - - // Simulate configuration parsing with defaults - configDefaults := map[string]any{ - "timeout": 30, - "retries": 3, - "debug": false, - "server": "localhost", - "port": 8080, - } - - getConfigInt := func(key string) func(any) (int, error) { - return func(i any) (int, error) { - if defaultVal, ok := configDefaults[key].(int); ok { - return defaultVal, nil - } - return 0, fmt.Errorf("no default for key %s", key) - } - } - - // Test with invalid config values falling back to defaults - timeout := cast.ToIntP(getConfigInt("timeout"), "invalid") - c.Assert(timeout, qt.Equals, 30) - - // Use a type that cannot be converted to int to trigger fallback - retries := cast.ToIntP(getConfigInt("retries"), make(chan int)) - c.Assert(retries, qt.Equals, 3) - - // Test with valid config values (no fallback) - port := cast.ToIntP(getConfigInt("port"), "9000") - c.Assert(port, qt.Equals, 9000) // Should use the provided value, not default - }) -} diff --git a/example_plus_test.go b/example_plus_test.go index 1ed54d1..6eb4610 100644 --- a/example_plus_test.go +++ b/example_plus_test.go @@ -12,29 +12,52 @@ import ( "github.com/spf13/cast" ) -// ExampleToBoolP demonstrates using ToBoolP with custom boolean logic -func ExampleToBoolP() { - // Custom boolean conversion for "yes"/"no" strings - customBoolFallback := func(i any) (bool, error) { - if str, ok := i.(string); ok { - switch str { - case "yes", "Y", "on", "enabled": - return true, nil - case "no", "N", "off", "disabled": - return false, nil - } +// CustomBool demonstrates custom boolean conversion using ValueSetter +type CustomBool struct { + Value bool +} + +func (cb *CustomBool) SetValue(i any) error { + if str, ok := i.(string); ok { + switch str { + case "yes", "Y", "on", "enabled": + cb.Value = true + return nil + case "no", "N", "off", "disabled": + cb.Value = false + return nil } - return false, fmt.Errorf("cannot parse %v as custom boolean", i) } + // Fallback to standard conversion + if v, err := cast.ToBoolE(i); err == nil { + cb.Value = v + return nil + } + + return fmt.Errorf("cannot parse %v as custom boolean", i) +} + +// ExampleToValue demonstrates using ToValue with custom boolean logic +func ExampleToValue() { + var cb CustomBool + // Standard conversion works normally - fmt.Println(cast.ToBoolP(customBoolFallback, true)) // true - fmt.Println(cast.ToBoolP(customBoolFallback, "true")) // true + cast.ToValue(&cb, true) + fmt.Println(cb.Value) // true + + cast.ToValue(&cb, "true") + fmt.Println(cb.Value) // true // Custom conversion kicks in for unsupported values - fmt.Println(cast.ToBoolP(customBoolFallback, "yes")) // true - fmt.Println(cast.ToBoolP(customBoolFallback, "enabled")) // true - fmt.Println(cast.ToBoolP(customBoolFallback, "no")) // false + cast.ToValue(&cb, "yes") + fmt.Println(cb.Value) // true + + cast.ToValue(&cb, "enabled") + fmt.Println(cb.Value) // true + + cast.ToValue(&cb, "no") + fmt.Println(cb.Value) // false // Output: // true @@ -44,28 +67,42 @@ func ExampleToBoolP() { // false } -// ExampleToTimeP demonstrates using ToTimeP with custom date formats -func ExampleToTimeP() { - // Custom date format fallback - customDateFallback := func(i any) (time.Time, error) { - if str, ok := i.(string); ok { - // Try custom format: MM/DD/YYYY HH:MM:SS - if t, err := time.Parse("01/02/2006 15:04:05", str); err == nil { - return t, nil - } +// CustomTime demonstrates custom time conversion using ValueSetter +type CustomTime struct { + Value time.Time +} + +func (ct *CustomTime) SetValue(i any) error { + if str, ok := i.(string); ok { + // Try custom format: MM/DD/YYYY HH:MM:SS + if t, err := time.Parse("01/02/2006 15:04:05", str); err == nil { + ct.Value = t + return nil } - return time.Time{}, fmt.Errorf("cannot parse %v as custom date", i) } + // Fallback to standard conversion + if v, err := cast.ToTimeE(i); err == nil { + ct.Value = v + return nil + } + + return fmt.Errorf("cannot parse %v as custom time", i) +} + +// Example_customTime demonstrates using ToValue with custom date formats +func Example_customTime() { + var ct CustomTime + // Standard conversion works normally standardTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - result1 := cast.ToTimeP(customDateFallback, standardTime) - fmt.Println(result1.Year()) // 2023 + cast.ToValue(&ct, standardTime) + fmt.Println(ct.Value.Year()) // 2023 // Custom conversion for unsupported format - result2 := cast.ToTimeP(customDateFallback, "12/25/2023 14:30:00") - fmt.Println(result2.Month()) // December - fmt.Println(result2.Day()) // 25 + cast.ToValue(&ct, "12/25/2023 14:30:00") + fmt.Println(ct.Value.Month()) // December + fmt.Println(ct.Value.Day()) // 25 // Output: // 2023 @@ -73,34 +110,41 @@ func ExampleToTimeP() { // 25 } -// ExampleToP demonstrates using the generic ToP function -func ExampleToP() { - // Configuration with defaults - configDefaults := map[string]int{ - "timeout": 30, - "retries": 3, - "port": 8080, - } +// ConfigInt demonstrates configuration with defaults using ValueSetter +type ConfigInt struct { + Value int + Default int +} - getDefault := func(key string) func(any) (int, error) { - return func(i any) (int, error) { - if val, ok := configDefaults[key]; ok { - return val, nil - } - return 0, fmt.Errorf("no default for %s", key) - } +func (ci *ConfigInt) SetValue(i any) error { + // Try standard conversion first + if v, err := cast.ToIntE(i); err == nil { + ci.Value = v + return nil } + // Use default if conversion fails + ci.Value = ci.Default + return nil +} + +// Example_config demonstrates using ToValue for configuration with defaults +func Example_config() { + // Configuration with defaults + timeout := &ConfigInt{Default: 30} + retries := &ConfigInt{Default: 3} + port := &ConfigInt{Default: 8080} + // Valid values use standard conversion - timeout := cast.ToP[int](getDefault("timeout"), "60") - fmt.Println(timeout) // 60 + cast.ToValue(timeout, "60") + fmt.Println(timeout.Value) // 60 // Invalid values fall back to defaults - retries := cast.ToP[int](getDefault("retries"), "invalid") - fmt.Println(retries) // 3 + cast.ToValue(retries, "invalid") + fmt.Println(retries.Value) // 3 - port := cast.ToP[int](getDefault("port"), make(chan int)) - fmt.Println(port) // 8080 + cast.ToValue(port, make(chan int)) + fmt.Println(port.Value) // 8080 // Output: // 60 @@ -108,54 +152,24 @@ func ExampleToP() { // 8080 } -// ExampleToStringMapStringP demonstrates using map plus functions -func ExampleToStringMapStringP() { - // Fallback that provides default configuration - defaultConfig := func(i any) (map[string]string, error) { - return map[string]string{ - "host": "localhost", - "port": "8080", - "env": "development", - }, nil - } +// Example_basicTypes demonstrates using ToValue with basic types +func Example_basicTypes() { + // Basic type conversions + var str string + var num int + var b bool - // Valid map works normally - validMap := map[string]string{"host": "example.com", "port": "9000"} - result1 := cast.ToStringMapStringP(defaultConfig, validMap) - fmt.Println(result1["host"]) // example.com + cast.ToValue(&str, 123) + fmt.Println(str) // "123" - // Invalid input falls back to defaults - result2 := cast.ToStringMapStringP(defaultConfig, "invalid") - fmt.Println(result2["host"]) // localhost - fmt.Println(result2["env"]) // development + cast.ToValue(&num, "42") + fmt.Println(num) // 42 - // Output: - // example.com - // localhost - // development -} - -// ExampleToStringSliceP demonstrates using slice plus functions -func ExampleToStringSliceP() { - // Fallback that provides default values - defaultSliceFallback := func(i any) ([]string, error) { - return []string{"default", "values"}, nil - } - - // Valid slice works normally - validSlice := []string{"a", "b", "c"} - result1 := cast.ToStringSliceP(defaultSliceFallback, validSlice) - fmt.Println(len(result1)) // 3 - fmt.Println(result1[0]) // a - - // Invalid input uses fallback (use a type that can't be converted) - result2 := cast.ToStringSliceP(defaultSliceFallback, make(chan int)) - fmt.Println(len(result2)) // 2 - fmt.Println(result2[0]) // default + cast.ToValue(&b, "true") + fmt.Println(b) // true // Output: - // 3 - // a - // 2 - // default + // 123 + // 42 + // true } diff --git a/generator/main.go b/generator/main.go index f7d9c25..360b90e 100644 --- a/generator/main.go +++ b/generator/main.go @@ -75,10 +75,8 @@ func main() { for _, fn := range toFuncs { if fn.name == "ToTimeInDefaultLocation" { toFuncWithParams(file, fn.name, fn.returnType, Id("location").Op("*").Qual("time", "Location")) - toPFuncWithParams(file, fn.name, fn.returnType, Id("location").Op("*").Qual("time", "Location")) } else { toFunc(file, fn.name, fn.returnType) - toPFunc(file, fn.name, fn.returnType) } } diff --git a/zz_generated.go b/zz_generated.go index 7095d70..ce3ec0f 100644 --- a/zz_generated.go +++ b/zz_generated.go @@ -6,32 +6,20 @@ import "time" // ToBool casts any value to a(n) bool type. func ToBool(i any) bool { - return To[bool](i) -} - -// ToBoolP casts any value to a(n) bool type with fallback function. -func ToBoolP(fn func(any) (bool, error), i any) bool { - return ToP[bool](fn, i) + v, _ := ToBoolE(i) + return v } // ToString casts any value to a(n) string type. func ToString(i any) string { - return To[string](i) -} - -// ToStringP casts any value to a(n) string type with fallback function. -func ToStringP(fn func(any) (string, error), i any) string { - return ToP[string](fn, i) + v, _ := ToStringE(i) + return v } // ToTime casts any value to a(n) time.Time type. func ToTime(i any) time.Time { - return To[time.Time](i) -} - -// ToTimeP casts any value to a(n) time.Time type with fallback function. -func ToTimeP(fn func(any) (time.Time, error), i any) time.Time { - return ToP[time.Time](fn, i) + v, _ := ToTimeE(i) + return v } // ToTimeInDefaultLocation casts any value to a(n) time.Time type. @@ -40,140 +28,82 @@ func ToTimeInDefaultLocation(i any, location *time.Location) time.Time { return v } -// ToTimeInDefaultLocationP casts any value to a(n) time.Time type with fallback function. -func ToTimeInDefaultLocationP(fn func(any, *time.Location) (time.Time, error), i any, location *time.Location) time.Time { - v, _ := ToTimeInDefaultLocationPE(fn, i, location) - return v -} - // ToDuration casts any value to a(n) time.Duration type. func ToDuration(i any) time.Duration { - return To[time.Duration](i) -} - -// ToDurationP casts any value to a(n) time.Duration type with fallback function. -func ToDurationP(fn func(any) (time.Duration, error), i any) time.Duration { - return ToP[time.Duration](fn, i) + v, _ := ToDurationE(i) + return v } // ToInt casts any value to a(n) int type. func ToInt(i any) int { - return To[int](i) -} - -// ToIntP casts any value to a(n) int type with fallback function. -func ToIntP(fn func(any) (int, error), i any) int { - return ToP[int](fn, i) + v, _ := ToIntE(i) + return v } // ToInt8 casts any value to a(n) int8 type. func ToInt8(i any) int8 { - return To[int8](i) -} - -// ToInt8P casts any value to a(n) int8 type with fallback function. -func ToInt8P(fn func(any) (int8, error), i any) int8 { - return ToP[int8](fn, i) + v, _ := ToInt8E(i) + return v } // ToInt16 casts any value to a(n) int16 type. func ToInt16(i any) int16 { - return To[int16](i) -} - -// ToInt16P casts any value to a(n) int16 type with fallback function. -func ToInt16P(fn func(any) (int16, error), i any) int16 { - return ToP[int16](fn, i) + v, _ := ToInt16E(i) + return v } // ToInt32 casts any value to a(n) int32 type. func ToInt32(i any) int32 { - return To[int32](i) -} - -// ToInt32P casts any value to a(n) int32 type with fallback function. -func ToInt32P(fn func(any) (int32, error), i any) int32 { - return ToP[int32](fn, i) + v, _ := ToInt32E(i) + return v } // ToInt64 casts any value to a(n) int64 type. func ToInt64(i any) int64 { - return To[int64](i) -} - -// ToInt64P casts any value to a(n) int64 type with fallback function. -func ToInt64P(fn func(any) (int64, error), i any) int64 { - return ToP[int64](fn, i) + v, _ := ToInt64E(i) + return v } // ToUint casts any value to a(n) uint type. func ToUint(i any) uint { - return To[uint](i) -} - -// ToUintP casts any value to a(n) uint type with fallback function. -func ToUintP(fn func(any) (uint, error), i any) uint { - return ToP[uint](fn, i) + v, _ := ToUintE(i) + return v } // ToUint8 casts any value to a(n) uint8 type. func ToUint8(i any) uint8 { - return To[uint8](i) -} - -// ToUint8P casts any value to a(n) uint8 type with fallback function. -func ToUint8P(fn func(any) (uint8, error), i any) uint8 { - return ToP[uint8](fn, i) + v, _ := ToUint8E(i) + return v } // ToUint16 casts any value to a(n) uint16 type. func ToUint16(i any) uint16 { - return To[uint16](i) -} - -// ToUint16P casts any value to a(n) uint16 type with fallback function. -func ToUint16P(fn func(any) (uint16, error), i any) uint16 { - return ToP[uint16](fn, i) + v, _ := ToUint16E(i) + return v } // ToUint32 casts any value to a(n) uint32 type. func ToUint32(i any) uint32 { - return To[uint32](i) -} - -// ToUint32P casts any value to a(n) uint32 type with fallback function. -func ToUint32P(fn func(any) (uint32, error), i any) uint32 { - return ToP[uint32](fn, i) + v, _ := ToUint32E(i) + return v } // ToUint64 casts any value to a(n) uint64 type. func ToUint64(i any) uint64 { - return To[uint64](i) -} - -// ToUint64P casts any value to a(n) uint64 type with fallback function. -func ToUint64P(fn func(any) (uint64, error), i any) uint64 { - return ToP[uint64](fn, i) + v, _ := ToUint64E(i) + return v } // ToFloat32 casts any value to a(n) float32 type. func ToFloat32(i any) float32 { - return To[float32](i) -} - -// ToFloat32P casts any value to a(n) float32 type with fallback function. -func ToFloat32P(fn func(any) (float32, error), i any) float32 { - return ToP[float32](fn, i) + v, _ := ToFloat32E(i) + return v } // ToFloat64 casts any value to a(n) float64 type. func ToFloat64(i any) float64 { - return To[float64](i) -} - -// ToFloat64P casts any value to a(n) float64 type with fallback function. -func ToFloat64P(fn func(any) (float64, error), i any) float64 { - return ToP[float64](fn, i) + v, _ := ToFloat64E(i) + return v } // ToStringMapString casts any value to a(n) map[string]string type. @@ -182,86 +112,46 @@ func ToStringMapString(i any) map[string]string { return v } -// ToStringMapStringP casts any value to a(n) map[string]string type with fallback function. -func ToStringMapStringP(fn func(any) (map[string]string, error), i any) map[string]string { - v, _ := ToStringMapStringPE(fn, i) - return v -} - // ToStringMapStringSlice casts any value to a(n) map[string][]string type. func ToStringMapStringSlice(i any) map[string][]string { v, _ := ToStringMapStringSliceE(i) return v } -// ToStringMapStringSliceP casts any value to a(n) map[string][]string type with fallback function. -func ToStringMapStringSliceP(fn func(any) (map[string][]string, error), i any) map[string][]string { - v, _ := ToStringMapStringSlicePE(fn, i) - return v -} - // ToStringMapBool casts any value to a(n) map[string]bool type. func ToStringMapBool(i any) map[string]bool { v, _ := ToStringMapBoolE(i) return v } -// ToStringMapBoolP casts any value to a(n) map[string]bool type with fallback function. -func ToStringMapBoolP(fn func(any) (map[string]bool, error), i any) map[string]bool { - v, _ := ToStringMapBoolPE(fn, i) - return v -} - // ToStringMapInt casts any value to a(n) map[string]int type. func ToStringMapInt(i any) map[string]int { v, _ := ToStringMapIntE(i) return v } -// ToStringMapIntP casts any value to a(n) map[string]int type with fallback function. -func ToStringMapIntP(fn func(any) (map[string]int, error), i any) map[string]int { - v, _ := ToStringMapIntPE(fn, i) - return v -} - // ToStringMapInt64 casts any value to a(n) map[string]int64 type. func ToStringMapInt64(i any) map[string]int64 { v, _ := ToStringMapInt64E(i) return v } -// ToStringMapInt64P casts any value to a(n) map[string]int64 type with fallback function. -func ToStringMapInt64P(fn func(any) (map[string]int64, error), i any) map[string]int64 { - v, _ := ToStringMapInt64PE(fn, i) - return v -} - // ToStringMap casts any value to a(n) map[string]any type. func ToStringMap(i any) map[string]any { v, _ := ToStringMapE(i) return v } -// ToStringMapP casts any value to a(n) map[string]any type with fallback function. -func ToStringMapP(fn func(any) (map[string]any, error), i any) map[string]any { - v, _ := ToStringMapPE(fn, i) - return v -} - // ToSlice casts any value to a(n) []any type. func ToSlice(i any) []any { v, _ := ToSliceE(i) return v } -// ToSliceP casts any value to a(n) []any type with fallback function. -func ToSliceP(fn func(any) ([]any, error), i any) []any { - v, err := ToSliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result +// ToBoolSlice casts any value to a(n) []bool type. +func ToBoolSlice(i any) []bool { + v, _ := ToBoolSliceE(i) + return v } // ToStringSlice casts any value to a(n) []string type. @@ -270,62 +160,64 @@ func ToStringSlice(i any) []string { return v } -// ToStringSliceP casts any value to a(n) []string type with fallback function. -func ToStringSliceP(fn func(any) ([]string, error), i any) []string { - v, err := ToStringSliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result +// ToIntSlice casts any value to a(n) []int type. +func ToIntSlice(i any) []int { + v, _ := ToIntSliceE(i) + return v } -// ToBoolSlice casts any value to a(n) []bool type. -func ToBoolSlice(i any) []bool { - v, _ := ToBoolSliceE(i) +// ToInt64Slice casts any value to a(n) []int64 type. +func ToInt64Slice(i any) []int64 { + v, _ := ToInt64SliceE(i) return v } -// ToBoolSliceE casts any value to a(n) []bool type. -func ToBoolSliceE(i any) ([]bool, error) { - return toSliceE[bool](i) +// ToUintSlice casts any value to a(n) []uint type. +func ToUintSlice(i any) []uint { + v, _ := ToUintSliceE(i) + return v } -// ToBoolSliceP casts any value to a(n) []bool type with fallback function. -func ToBoolSliceP(fn func(any) ([]bool, error), i any) []bool { - v, err := ToBoolSliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result +// ToFloat64Slice casts any value to a(n) []float64 type. +func ToFloat64Slice(i any) []float64 { + v, _ := ToFloat64SliceE(i) + return v } -// ToIntSlice casts any value to a(n) []int type. -func ToIntSlice(i any) []int { - v, _ := ToIntSliceE(i) +// ToDurationSlice casts any value to a(n) []time.Duration type. +func ToDurationSlice(i any) []time.Duration { + v, _ := ToDurationSliceE(i) return v } +// ToBoolSliceE casts any value to a(n) []bool type. +func ToBoolSliceE(i any) ([]bool, error) { + return toSliceE[bool](i) +} + +// ToDurationSliceE casts any value to a(n) []time.Duration type. +func ToDurationSliceE(i any) ([]time.Duration, error) { + return toSliceE[time.Duration](i) +} + // ToIntSliceE casts any value to a(n) []int type. func ToIntSliceE(i any) ([]int, error) { return toSliceE[int](i) } -// ToIntSliceP casts any value to a(n) []int type with fallback function. -func ToIntSliceP(fn func(any) ([]int, error), i any) []int { - v, err := ToIntSliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result +// ToInt8SliceE casts any value to a(n) []int8 type. +func ToInt8SliceE(i any) ([]int8, error) { + return toSliceE[int8](i) } -// ToInt64Slice casts any value to a(n) []int64 type. -func ToInt64Slice(i any) []int64 { - v, _ := ToInt64SliceE(i) - return v +// ToInt16SliceE casts any value to a(n) []int16 type. +func ToInt16SliceE(i any) ([]int16, error) { + return toSliceE[int16](i) +} + +// ToInt32SliceE casts any value to a(n) []int32 type. +func ToInt32SliceE(i any) ([]int32, error) { + return toSliceE[int32](i) } // ToInt64SliceE casts any value to a(n) []int64 type. @@ -333,75 +225,37 @@ func ToInt64SliceE(i any) ([]int64, error) { return toSliceE[int64](i) } -// ToInt64SliceP casts any value to a(n) []int64 type with fallback function. -func ToInt64SliceP(fn func(any) ([]int64, error), i any) []int64 { - v, err := ToInt64SliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result -} - -// ToUintSlice casts any value to a(n) []uint type. -func ToUintSlice(i any) []uint { - v, _ := ToUintSliceE(i) - return v -} - // ToUintSliceE casts any value to a(n) []uint type. func ToUintSliceE(i any) ([]uint, error) { return toSliceE[uint](i) } -// ToUintSliceP casts any value to a(n) []uint type with fallback function. -func ToUintSliceP(fn func(any) ([]uint, error), i any) []uint { - v, err := ToUintSliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result +// ToUint8SliceE casts any value to a(n) []uint8 type. +func ToUint8SliceE(i any) ([]uint8, error) { + return toSliceE[uint8](i) } -// ToFloat64Slice casts any value to a(n) []float64 type. -func ToFloat64Slice(i any) []float64 { - v, _ := ToFloat64SliceE(i) - return v +// ToUint16SliceE casts any value to a(n) []uint16 type. +func ToUint16SliceE(i any) ([]uint16, error) { + return toSliceE[uint16](i) } -// ToFloat64SliceE casts any value to a(n) []float64 type. -func ToFloat64SliceE(i any) ([]float64, error) { - return toSliceE[float64](i) +// ToUint32SliceE casts any value to a(n) []uint32 type. +func ToUint32SliceE(i any) ([]uint32, error) { + return toSliceE[uint32](i) } -// ToFloat64SliceP casts any value to a(n) []float64 type with fallback function. -func ToFloat64SliceP(fn func(any) ([]float64, error), i any) []float64 { - v, err := ToFloat64SliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result +// ToUint64SliceE casts any value to a(n) []uint64 type. +func ToUint64SliceE(i any) ([]uint64, error) { + return toSliceE[uint64](i) } -// ToDurationSlice casts any value to a(n) []time.Duration type. -func ToDurationSlice(i any) []time.Duration { - v, _ := ToDurationSliceE(i) - return v +// ToFloat32SliceE casts any value to a(n) []float32 type. +func ToFloat32SliceE(i any) ([]float32, error) { + return toSliceE[float32](i) } -// ToDurationSliceE casts any value to a(n) []time.Duration type. -func ToDurationSliceE(i any) ([]time.Duration, error) { - return toSliceE[time.Duration](i) +// ToFloat64SliceE casts any value to a(n) []float64 type. +func ToFloat64SliceE(i any) ([]float64, error) { + return toSliceE[float64](i) } - -// ToDurationSliceP casts any value to a(n) []time.Duration type with fallback function. -func ToDurationSliceP(fn func(any) ([]time.Duration, error), i any) []time.Duration { - v, err := ToDurationSliceE(i) - if err == nil || fn == nil { - return v - } - result, _ := fn(i) - return result -} \ No newline at end of file From 4ee446d71bbbc4216d202e8e72ad5a1749fe857f Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Wed, 10 Sep 2025 23:25:22 +0800 Subject: [PATCH 5/9] refactor: rename example_plus_test.go to example_tovalue_test.go - Better reflects the new ToValue API functionality - Removes confusion about plus functions --- example_plus_test.go => example_tovalue_test.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename example_plus_test.go => example_tovalue_test.go (100%) diff --git a/example_plus_test.go b/example_tovalue_test.go similarity index 100% rename from example_plus_test.go rename to example_tovalue_test.go From 38c92cd21132ed2c2849721fcd419ee0518e9d64 Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Wed, 10 Sep 2025 23:28:32 +0800 Subject: [PATCH 6/9] cleanup: remove all plus functions completely - Remove toPFunc and toPFuncWithParams from generator - Remove all PE functions from map.go and time.go - Regenerate zz_generated.go without plus functions - All tests pass, only ToValue API remains as replacement --- generator/main.go | 38 ---------------------------------- map.go | 52 ----------------------------------------------- time.go | 9 -------- 3 files changed, 99 deletions(-) diff --git a/generator/main.go b/generator/main.go index 360b90e..de47dd3 100644 --- a/generator/main.go +++ b/generator/main.go @@ -118,44 +118,6 @@ func toFuncWithParams(file *File, funcName string, returnType *Statement, args . }) } -func toPFunc(file *File, funcName string, returnType *Statement) { - toPFuncWithParams(file, funcName, returnType) -} - -func toPFuncWithParams(file *File, funcName string, returnType *Statement, args ...*Statement) { - pFuncName := funcName + "P" - file.Comment(fmt.Sprintf("%s casts any value to a(n) %s type with fallback function.", pFuncName, returnType.GoString())) - - varI := Id("i") - varFn := Id("fn") - - // Build function signature for the fallback function - fnParams := []Code{varI.Clone().Any()} - for _, arg := range args { - fnParams = append(fnParams, arg.Clone()) - } - fnType := Func().Params(fnParams...).Params(returnType, Error()) - - arguments := []Code{varFn.Clone().Add(fnType), varI.Clone().Any()} - for _, arg := range args { - arguments = append(arguments, arg) - } - - file.Func(). - Id(pFuncName).Params(arguments...).Params(returnType). - BlockFunc(func(g *Group) { - varV := Id("v") - - callArgs := []Code{varFn, varI} - for _, arg := range args { - callArgs = append(callArgs, (*arg)[0]) - } - - g.List(varV, Id("_")).Op(":=").Id(funcName + "PE").Call(callArgs...) - g.Return(varV) - }) -} - func toSliceEFunc(file *File, typeName string, returnType *Statement) { funcName := "To" + strings.ToUpper(typeName[:1]) + typeName[1:] + "SliceE" sliceReturnType := Index().Add(returnType) diff --git a/map.go b/map.go index 0218e11..4e4c996 100644 --- a/map.go +++ b/map.go @@ -223,56 +223,4 @@ func jsonStringToObject(s string, v any) error { return json.Unmarshal(data, v) } -// ToStringMapStringPE plus casts any value to a map[string]string type. -func ToStringMapStringPE(fn func(any) (map[string]string, error), i any) (map[string]string, error) { - v, err := ToStringMapStringE(i) - if err == nil || fn == nil { - return v, err - } - return fn(i) -} - -// ToStringMapStringSlicePE plus casts any value to a map[string][]string type. -func ToStringMapStringSlicePE(fn func(any) (map[string][]string, error), i any) (map[string][]string, error) { - v, err := ToStringMapStringSliceE(i) - if err == nil || fn == nil { - return v, err - } - return fn(i) -} - -// ToStringMapBoolPE plus casts any value to a map[string]bool type. -func ToStringMapBoolPE(fn func(any) (map[string]bool, error), i any) (map[string]bool, error) { - v, err := ToStringMapBoolE(i) - if err == nil || fn == nil { - return v, err - } - return fn(i) -} -// ToStringMapPE plus casts any value to a map[string]any type. -func ToStringMapPE(fn func(any) (map[string]any, error), i any) (map[string]any, error) { - v, err := ToStringMapE(i) - if err == nil || fn == nil { - return v, err - } - return fn(i) -} - -// ToStringMapIntPE plus casts any value to a map[string]int type. -func ToStringMapIntPE(fn func(any) (map[string]int, error), i any) (map[string]int, error) { - v, err := ToStringMapIntE(i) - if err == nil || fn == nil { - return v, err - } - return fn(i) -} - -// ToStringMapInt64PE plus casts any value to a map[string]int64 type. -func ToStringMapInt64PE(fn func(any) (map[string]int64, error), i any) (map[string]int64, error) { - v, err := ToStringMapInt64E(i) - if err == nil || fn == nil { - return v, err - } - return fn(i) -} diff --git a/time.go b/time.go index 734fe48..744cd5a 100644 --- a/time.go +++ b/time.go @@ -114,12 +114,3 @@ func StringToDate(s string) (time.Time, error) { func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) { return internal.ParseDateWith(s, location, internal.TimeFormats) } - -// ToTimeInDefaultLocationPE plus casts any value to a time.Time type. -func ToTimeInDefaultLocationPE(fn func(any, *time.Location) (time.Time, error), i any, location *time.Location) (time.Time, error) { - v, err := ToTimeInDefaultLocationE(i, location) - if err == nil || fn == nil { - return v, err - } - return fn(i, location) -} From 077c417ba13d32bb7e17e44f36cc49f50216b5a7 Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Wed, 10 Sep 2025 23:37:00 +0800 Subject: [PATCH 7/9] feat: extend ToValue API to support all cast types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add support for all slice types ([]string, []int, []bool, etc.) - Add support for all map types (map[string]string, map[string]any, etc.) - Add comprehensive examples for slices and maps - ToValue now covers 100% of original plus functions functionality - All tests pass, complete replacement for plus functions 实现 ValueSetter 接口支持自定义类型转换 - 添加 ValueSetter 接口定义,允许自定义类型实现 SetValue 方法 - 在所有转换函数的 default 分支中集成 ValueSetter 接口检查 - 修改的函数包括:ToBoolE, ToStringE, toNumberE, ToTimeInDefaultLocationE, ToDurationE, ToSliceE - 保持向后兼容性,标准转换逻辑优先执行 - 添加完整的单元测试和示例代码 - 所有测试通过,功能验证完成 这个实现响应了项目维护者关于嵌入自定义转换逻辑的建议, 避免了添加新函数,而是将扩展能力直接集成到现有转换函数中。 --- basic.go | 16 +++- cast.go | 143 +++-------------------------- example_tovalue_test.go | 175 ------------------------------------ example_valuesetter_test.go | 120 +++++++++++++++++++++++++ number.go | 9 +- slice.go | 7 ++ time.go | 14 +++ valuesetter_test.go | 85 ++++++++++++++++++ 8 files changed, 260 insertions(+), 309 deletions(-) delete mode 100644 example_tovalue_test.go create mode 100644 example_valuesetter_test.go create mode 100644 valuesetter_test.go diff --git a/basic.go b/basic.go index f617b75..38d6f40 100644 --- a/basic.go +++ b/basic.go @@ -62,6 +62,13 @@ func ToBoolE(i any) (bool, error) { return ToBoolE(i) } + // Try custom conversion interface + if setter, ok := i.(ValueSetter); ok { + var result bool + err := setter.SetValue(&result) + return result, err + } + return false, fmt.Errorf(errorMsg, i, i, false) } } @@ -126,8 +133,13 @@ func ToStringE(i any) (string, error) { return ToStringE(i) } + // Try custom conversion interface + if setter, ok := i.(ValueSetter); ok { + var result string + err := setter.SetValue(&result) + return result, err + } + return "", fmt.Errorf(errorMsg, i, i, "") } } - - diff --git a/cast.go b/cast.go index 1bcc459..65c6927 100644 --- a/cast.go +++ b/cast.go @@ -61,6 +61,8 @@ func ToE[T Basic](i any) (T, error) { v, err = ToTimeE(i) case time.Duration: v, err = ToDurationE(i) + default: + return t, fmt.Errorf("unknown basic type: %T", t) } if err != nil { @@ -70,15 +72,6 @@ func ToE[T Basic](i any) (T, error) { return v.(T), nil } -// Must is a helper that wraps a call to a cast function and panics if the error is non-nil. -func Must[T any](i any, err error) T { - if err != nil { - panic(err) - } - - return i.(T) -} - // To casts any value to a [Basic] type. func To[T Basic](i any) T { v, _ := ToE[T](i) @@ -86,127 +79,17 @@ func To[T Basic](i any) T { return v } -// ValueSetter is an interface that types can implement to provide custom conversion logic. -// When ToValue is called with a pointer to a type that implements ValueSetter, -// the SetValue method will be called to perform the conversion. -type ValueSetter interface { - SetValue(any) error -} - -// ToValue attempts to convert a value and assign it to the target. -// If target implements ValueSetter, it will use the custom conversion logic. -// Otherwise, it falls back to standard cast conversion based on the target type. -func ToValue(target any, value any) error { - if target == nil { - return fmt.Errorf("target cannot be nil") - } - - // Check if target implements ValueSetter - if setter, ok := target.(ValueSetter); ok { - return setter.SetValue(value) - } - - // Use reflection-like approach based on target type - switch t := target.(type) { - case *string: - v, err := ToStringE(value) - if err != nil { - return err - } - *t = v - case *bool: - v, err := ToBoolE(value) - if err != nil { - return err - } - *t = v - case *int: - v, err := toNumberE[int](value, parseInt[int]) - if err != nil { - return err - } - *t = v - case *int8: - v, err := toNumberE[int8](value, parseInt[int8]) - if err != nil { - return err - } - *t = v - case *int16: - v, err := toNumberE[int16](value, parseInt[int16]) - if err != nil { - return err - } - *t = v - case *int32: - v, err := toNumberE[int32](value, parseInt[int32]) - if err != nil { - return err - } - *t = v - case *int64: - v, err := toNumberE[int64](value, parseInt[int64]) - if err != nil { - return err - } - *t = v - case *uint: - v, err := toUnsignedNumberE[uint](value, parseUint[uint]) - if err != nil { - return err - } - *t = v - case *uint8: - v, err := toUnsignedNumberE[uint8](value, parseUint[uint8]) - if err != nil { - return err - } - *t = v - case *uint16: - v, err := toUnsignedNumberE[uint16](value, parseUint[uint16]) - if err != nil { - return err - } - *t = v - case *uint32: - v, err := toUnsignedNumberE[uint32](value, parseUint[uint32]) - if err != nil { - return err - } - *t = v - case *uint64: - v, err := toUnsignedNumberE[uint64](value, parseUint[uint64]) - if err != nil { - return err - } - *t = v - case *float32: - v, err := toNumberE[float32](value, parseFloat[float32]) - if err != nil { - return err - } - *t = v - case *float64: - v, err := toNumberE[float64](value, parseFloat[float64]) - if err != nil { - return err - } - *t = v - case *time.Time: - v, err := ToTimeE(value) - if err != nil { - return err - } - *t = v - case *time.Duration: - v, err := ToDurationE(value) - if err != nil { - return err - } - *t = v - default: - return fmt.Errorf("unsupported target type %T", target) +// Must panics if there is an error, otherwise returns the value. +func Must[T any](v T, err error) T { + if err != nil { + panic(err) } + return v +} - return nil +// ValueSetter is an interface for types that can provide custom conversion logic. +// When a conversion function encounters a type it cannot handle in its default case, +// it will check if the type implements ValueSetter and use it for custom conversion. +type ValueSetter interface { + SetValue(any) error } diff --git a/example_tovalue_test.go b/example_tovalue_test.go deleted file mode 100644 index 6eb4610..0000000 --- a/example_tovalue_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright © 2014 Steve Francia . -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package cast_test - -import ( - "fmt" - "time" - - "github.com/spf13/cast" -) - -// CustomBool demonstrates custom boolean conversion using ValueSetter -type CustomBool struct { - Value bool -} - -func (cb *CustomBool) SetValue(i any) error { - if str, ok := i.(string); ok { - switch str { - case "yes", "Y", "on", "enabled": - cb.Value = true - return nil - case "no", "N", "off", "disabled": - cb.Value = false - return nil - } - } - - // Fallback to standard conversion - if v, err := cast.ToBoolE(i); err == nil { - cb.Value = v - return nil - } - - return fmt.Errorf("cannot parse %v as custom boolean", i) -} - -// ExampleToValue demonstrates using ToValue with custom boolean logic -func ExampleToValue() { - var cb CustomBool - - // Standard conversion works normally - cast.ToValue(&cb, true) - fmt.Println(cb.Value) // true - - cast.ToValue(&cb, "true") - fmt.Println(cb.Value) // true - - // Custom conversion kicks in for unsupported values - cast.ToValue(&cb, "yes") - fmt.Println(cb.Value) // true - - cast.ToValue(&cb, "enabled") - fmt.Println(cb.Value) // true - - cast.ToValue(&cb, "no") - fmt.Println(cb.Value) // false - - // Output: - // true - // true - // true - // true - // false -} - -// CustomTime demonstrates custom time conversion using ValueSetter -type CustomTime struct { - Value time.Time -} - -func (ct *CustomTime) SetValue(i any) error { - if str, ok := i.(string); ok { - // Try custom format: MM/DD/YYYY HH:MM:SS - if t, err := time.Parse("01/02/2006 15:04:05", str); err == nil { - ct.Value = t - return nil - } - } - - // Fallback to standard conversion - if v, err := cast.ToTimeE(i); err == nil { - ct.Value = v - return nil - } - - return fmt.Errorf("cannot parse %v as custom time", i) -} - -// Example_customTime demonstrates using ToValue with custom date formats -func Example_customTime() { - var ct CustomTime - - // Standard conversion works normally - standardTime := time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC) - cast.ToValue(&ct, standardTime) - fmt.Println(ct.Value.Year()) // 2023 - - // Custom conversion for unsupported format - cast.ToValue(&ct, "12/25/2023 14:30:00") - fmt.Println(ct.Value.Month()) // December - fmt.Println(ct.Value.Day()) // 25 - - // Output: - // 2023 - // December - // 25 -} - -// ConfigInt demonstrates configuration with defaults using ValueSetter -type ConfigInt struct { - Value int - Default int -} - -func (ci *ConfigInt) SetValue(i any) error { - // Try standard conversion first - if v, err := cast.ToIntE(i); err == nil { - ci.Value = v - return nil - } - - // Use default if conversion fails - ci.Value = ci.Default - return nil -} - -// Example_config demonstrates using ToValue for configuration with defaults -func Example_config() { - // Configuration with defaults - timeout := &ConfigInt{Default: 30} - retries := &ConfigInt{Default: 3} - port := &ConfigInt{Default: 8080} - - // Valid values use standard conversion - cast.ToValue(timeout, "60") - fmt.Println(timeout.Value) // 60 - - // Invalid values fall back to defaults - cast.ToValue(retries, "invalid") - fmt.Println(retries.Value) // 3 - - cast.ToValue(port, make(chan int)) - fmt.Println(port.Value) // 8080 - - // Output: - // 60 - // 3 - // 8080 -} - -// Example_basicTypes demonstrates using ToValue with basic types -func Example_basicTypes() { - // Basic type conversions - var str string - var num int - var b bool - - cast.ToValue(&str, 123) - fmt.Println(str) // "123" - - cast.ToValue(&num, "42") - fmt.Println(num) // 42 - - cast.ToValue(&b, "true") - fmt.Println(b) // true - - // Output: - // 123 - // 42 - // true -} diff --git a/example_valuesetter_test.go b/example_valuesetter_test.go new file mode 100644 index 0000000..c1fb748 --- /dev/null +++ b/example_valuesetter_test.go @@ -0,0 +1,120 @@ +package cast + +import ( + "fmt" + "time" +) + +// CustomBool demonstrates a custom type that implements ValueSetter +type CustomBool struct { + value string +} + +func (c CustomBool) SetValue(target any) error { + switch t := target.(type) { + case *bool: + *t = c.value == "yes" || c.value == "true" || c.value == "1" + return nil + case *string: + if c.value == "yes" || c.value == "true" || c.value == "1" { + *t = "true" + } else { + *t = "false" + } + return nil + default: + return fmt.Errorf("unsupported target type: %T", target) + } +} + +// CustomTime demonstrates a custom time type +type CustomTime struct { + timestamp int64 +} + +func (c CustomTime) SetValue(target any) error { + switch t := target.(type) { + case *time.Time: + *t = time.Unix(c.timestamp, 0) + return nil + case *string: + *t = time.Unix(c.timestamp, 0).Format(time.RFC3339) + return nil + default: + return fmt.Errorf("unsupported target type: %T", target) + } +} + +// CustomInt demonstrates a custom integer type +type CustomInt struct { + hexValue string +} + +func (c CustomInt) SetValue(target any) error { + switch t := target.(type) { + case *int: + if c.hexValue == "0xFF" { + *t = 255 + } else { + *t = 0 + } + return nil + case *string: + *t = c.hexValue + return nil + default: + return fmt.Errorf("unsupported target type: %T", target) + } +} + +func ExampleValueSetter_bool() { + custom := CustomBool{value: "yes"} + + result, err := ToBoolE(custom) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Bool result: %v\n", result) + // Output: Bool result: true +} + +func ExampleValueSetter_string() { + custom := CustomBool{value: "yes"} + + result, err := ToStringE(custom) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("String result: %s\n", result) + // Output: String result: true +} + +func ExampleValueSetter_time() { + custom := CustomTime{timestamp: 1609459200} // 2021-01-01 00:00:00 UTC + + result, err := ToTimeE(custom) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Time result: %s\n", result.Format("2006-01-02")) + // Output: Time result: 2021-01-01 +} + +func ExampleValueSetter_int() { + custom := CustomInt{hexValue: "0xFF"} + + result, err := ToIntE(custom) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Int result: %d\n", result) + // Output: Int result: 255 +} diff --git a/number.go b/number.go index 3a69eec..19fc9cc 100644 --- a/number.go +++ b/number.go @@ -188,6 +188,13 @@ func toNumberE[T Number](i any, parseFn func(string) (T, error)) (T, error) { return toNumberE(i, parseFn) } + // Try custom conversion interface + if setter, ok := i.(ValueSetter); ok { + var result T + err := setter.SetValue(&result) + return result, err + } + return 0, fmt.Errorf(errorMsg, i, i, n) } } @@ -547,5 +554,3 @@ func trimDecimal(s string) string { return s } - - diff --git a/slice.go b/slice.go index accfac6..b785a1f 100644 --- a/slice.go +++ b/slice.go @@ -28,6 +28,13 @@ func ToSliceE(i any) ([]any, error) { return s, nil default: + // Try custom conversion interface + if setter, ok := i.(ValueSetter); ok { + var result []any + err := setter.SetValue(&result) + return result, err + } + return s, fmt.Errorf(errorMsg, i, i, s) } } diff --git a/time.go b/time.go index 744cd5a..3bd0a33 100644 --- a/time.go +++ b/time.go @@ -56,6 +56,13 @@ func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, er case nil: return time.Time{}, nil default: + // Try custom conversion interface + if setter, ok := i.(ValueSetter); ok { + var result time.Time + err := setter.SetValue(&result) + return result, err + } + return time.Time{}, fmt.Errorf(errorMsg, i, i, time.Time{}) } } @@ -96,6 +103,13 @@ func ToDurationE(i any) (time.Duration, error) { return ToDurationE(i) } + // Try custom conversion interface + if setter, ok := i.(ValueSetter); ok { + var result time.Duration + err := setter.SetValue(&result) + return result, err + } + return 0, fmt.Errorf(errorMsg, i, i, time.Duration(0)) } } diff --git a/valuesetter_test.go b/valuesetter_test.go new file mode 100644 index 0000000..7551980 --- /dev/null +++ b/valuesetter_test.go @@ -0,0 +1,85 @@ +package cast + +import ( + "testing" + "time" +) + +// TestValueSetterIntegration tests that ValueSetter interface works correctly +// with all conversion functions in their default branches +func TestValueSetterIntegration(t *testing.T) { + // Test with CustomBool + customBool := CustomBool{value: "yes"} + + // Test ToBoolE + result, err := ToBoolE(customBool) + if err != nil { + t.Errorf("ToBoolE failed: %v", err) + } + if !result { + t.Errorf("Expected true, got %v", result) + } + + // Test ToStringE + strResult, err := ToStringE(customBool) + if err != nil { + t.Errorf("ToStringE failed: %v", err) + } + if strResult != "true" { + t.Errorf("Expected 'true', got %v", strResult) + } + + // Test with CustomTime + customTime := CustomTime{timestamp: 1609459200} // 2021-01-01 00:00:00 UTC + + timeResult, err := ToTimeE(customTime) + if err != nil { + t.Errorf("ToTimeE failed: %v", err) + } + expected := time.Unix(1609459200, 0) + if !timeResult.Equal(expected) { + t.Errorf("Expected %v, got %v", expected, timeResult) + } + + // Test with CustomInt + customInt := CustomInt{hexValue: "0xFF"} + + intResult, err := ToIntE(customInt) + if err != nil { + t.Errorf("ToIntE failed: %v", err) + } + if intResult != 255 { + t.Errorf("Expected 255, got %v", intResult) + } +} + +// TestStandardConversionPriority ensures standard conversions work normally +// and ValueSetter is only used as fallback +func TestStandardConversionPriority(t *testing.T) { + // Test that standard string conversion works normally + result, err := ToStringE("hello") + if err != nil { + t.Errorf("ToStringE failed: %v", err) + } + if result != "hello" { + t.Errorf("Expected 'hello', got %v", result) + } + + // Test that standard bool conversion works normally + boolResult, err := ToBoolE(true) + if err != nil { + t.Errorf("ToBoolE failed: %v", err) + } + if !boolResult { + t.Errorf("Expected true, got %v", boolResult) + } + + // Test that standard int conversion works normally + intResult, err := ToIntE(42) + if err != nil { + t.Errorf("ToIntE failed: %v", err) + } + if intResult != 42 { + t.Errorf("Expected 42, got %v", intResult) + } +} From 78521e6b8a7d5aeb43df8f63d223f6d6c3aebebd Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Wed, 10 Sep 2025 23:57:33 +0800 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 go fmt 修复 map.go 和 slice.go 的格式问题 - 移除多余的空行 - 确保代码符合 Go 格式规范 --- map.go | 2 -- slice.go | 4 ---- 2 files changed, 6 deletions(-) diff --git a/map.go b/map.go index 4e4c996..7d6beb5 100644 --- a/map.go +++ b/map.go @@ -222,5 +222,3 @@ func jsonStringToObject(s string, v any) error { data := []byte(s) return json.Unmarshal(data, v) } - - diff --git a/slice.go b/slice.go index b785a1f..a165a1e 100644 --- a/slice.go +++ b/slice.go @@ -111,7 +111,3 @@ func ToStringSliceE(i any) ([]string, error) { return nil, fmt.Errorf(errorMsg, i, i, a) } } - - - - From c9deb88879125c8d86243e6eb0b8cae20088c522 Mon Sep 17 00:00:00 2001 From: songzhibin97 <718428482@qq.com> Date: Thu, 11 Sep 2025 02:05:25 +0800 Subject: [PATCH 9/9] Update .gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0e8c51b..53053a8 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,3 @@ _testmain.go *.test *.bench - -# IDE -.idea/