Skip to content

Commit 783ae06

Browse files
feat(valueparser): Add custom type support
1 parent 169cf9e commit 783ae06

5 files changed

Lines changed: 185 additions & 25 deletions

File tree

valueparser/array_parser.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package valueparser
22

33
import (
44
"fmt"
5+
"reflect"
56
"strings"
67

78
"github.com/YaCodeDev/GoYaCodeDevUtils/yaerrors"
@@ -21,6 +22,45 @@ import (
2122
func ParseArray[T ParsableType](
2223
str string,
2324
separator *string,
25+
) ([]T, yaerrors.Error) {
26+
return ParseArrayWithCustomType[T](str, separator, reflect.TypeOf(new(T)).Elem())
27+
}
28+
29+
// ParseArrayWithCustomType is a generic function that splits a string by 'separator'
30+
// and parses each part into T using the provided type for conversion.
31+
// If the string is empty, it returns an empty slice.
32+
// If 'separator' is nil, it defaults to DefaultEntrySeparator.
33+
// It is useful when you need to specify a custom type for parsing.
34+
//
35+
// Example usage:
36+
//
37+
// type YourCustomType uint64
38+
//
39+
// func (s *YourCustomType) Unmarshal(data string) error {
40+
// if s == nil {
41+
// return fmt.Errorf("nil pointer to YourCustomType")
42+
// }
43+
//
44+
// switch data {
45+
// case "FIRST":
46+
// *s = 1
47+
// case "SECOND":
48+
// *s = 2
49+
// default:
50+
// return fmt.Errorf("unknown value: %s", data)
51+
// }
52+
//
53+
// return nil
54+
// }
55+
//
56+
// customValue, err := ParseArrayWithCustomType[uint64]("FIRST,SECOND", nil, reflect.TypeOf(YourCustomType(0)))
57+
// if err != nil {
58+
// // Handle error
59+
// }
60+
func ParseArrayWithCustomType[T ParsableType](
61+
str string,
62+
separator *string,
63+
vType reflect.Type,
2464
) ([]T, yaerrors.Error) {
2565
if str == "" {
2666
return []T{}, nil
@@ -41,7 +81,7 @@ func ParseArray[T ParsableType](
4181

4282
for _, part := range parts {
4383
trimmed := strings.TrimSpace(part)
44-
if parsed, err = ParseValue[T](trimmed); err == nil {
84+
if parsed, err = ParseValueWithCustomType[T](trimmed, vType); err == nil {
4585
result = append(result, parsed)
4686
} else {
4787
return nil, err.Wrap(

valueparser/errors.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
)
66

77
var (
8-
ErrUnknownType = errors.New("unknown type")
9-
ErrInvalidValue = errors.New("invalid value")
10-
ErrInvalidType = errors.New("invalid type")
11-
ErrUnparsableValue = errors.New("unparsable value")
12-
ErrInvalidEntry = errors.New("invalid entry")
8+
ErrUnknownType = errors.New("unknown type")
9+
ErrInvalidValue = errors.New("invalid value")
10+
ErrUnconvertibleType = errors.New("unconvertible type")
11+
ErrInvalidType = errors.New("invalid type")
12+
ErrUnparsableValue = errors.New("unparsable value")
13+
ErrInvalidEntry = errors.New("invalid entry")
1314
)

valueparser/map_parser.go

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package valueparser
33
import (
44
"fmt"
55
"net/http"
6+
"reflect"
67
"strings"
78

89
"github.com/YaCodeDev/GoYaCodeDevUtils/yaerrors"
@@ -25,6 +26,61 @@ func ParseMap[K ParsableComparableType, V ParsableType](
2526
str string,
2627
entrySeparator *string,
2728
kvSeparator *string,
29+
) (map[K]V, yaerrors.Error) {
30+
return ParseMapWithCustomType[K, V](
31+
str,
32+
entrySeparator,
33+
kvSeparator,
34+
reflect.TypeOf(new(K)).Elem(),
35+
reflect.TypeOf(new(V)).Elem(),
36+
)
37+
}
38+
39+
// ParseMapWithCustomType is a generic function that parses a string into a map[K]V
40+
// using the provided separators and types for keys and values.
41+
// It splits the string by 'entrySeparator' and each entry by 'kvSeparator'.
42+
// If 'entrySeparator' is nil, it defaults to DefaultEntrySeparator.
43+
// If 'kvSeparator' is nil, it defaults to DefaultKVSeparator.
44+
// If the string is empty, it returns an empty map.
45+
// It is useful when you need to specify custom types for parsing keys and/or values.
46+
//
47+
// Example usage:
48+
//
49+
// type YourCustomType uint64
50+
//
51+
// func (s *YourCustomType) Unmarshal(data string) error {
52+
// if s == nil {
53+
// return fmt.Errorf("nil pointer to YourCustomType")
54+
// }
55+
//
56+
// switch data {
57+
// case "FIRST":
58+
// *s = 1
59+
// case "SECOND":
60+
// *s = 2
61+
// default:
62+
// return fmt.Errorf("unknown value: %s", data)
63+
// }
64+
//
65+
// return nil
66+
// }
67+
//
68+
// customMap, err := ParseMapWithCustomType[uint64, int](
69+
// "FIRST:1,SECOND:2",
70+
// nil,
71+
// nil,
72+
// reflect.TypeOf(YourCustomType(0)),
73+
// reflect.TypeOf(0),
74+
// )
75+
// if err != nil {
76+
// // Handle error
77+
// }
78+
func ParseMapWithCustomType[K ParsableComparableType, V ParsableType](
79+
str string,
80+
entrySeparator *string,
81+
kvSeparator *string,
82+
kType reflect.Type,
83+
vType reflect.Type,
2884
) (map[K]V, yaerrors.Error) {
2985
result := make(map[K]V)
3086

@@ -52,7 +108,7 @@ func ParseMap[K ParsableComparableType, V ParsableType](
52108
for item := range entries {
53109
parts := strings.Split(item, *kvSeparator)
54110
if len(parts) == MapPartsCount {
55-
k, err = ParseValue[K](strings.TrimSpace(parts[0]))
111+
k, err = ParseValueWithCustomType[K](strings.TrimSpace(parts[0]), kType)
56112
if err != nil {
57113
return nil, yaerrors.FromError(
58114
http.StatusInternalServerError,
@@ -64,7 +120,7 @@ func ParseMap[K ParsableComparableType, V ParsableType](
64120
)
65121
}
66122

67-
v, err = ParseValue[V](strings.TrimSpace(parts[1]))
123+
v, err = ParseValueWithCustomType[V](strings.TrimSpace(parts[1]), vType)
68124
if err != nil {
69125
return nil, yaerrors.FromError(
70126
http.StatusInternalServerError,

valueparser/value_convertor.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package valueparser
22

33
import (
4+
"fmt"
45
"net/http"
56
"reflect"
67

@@ -11,19 +12,35 @@ import (
1112
// It checks if the value is valid and convertible to the target type.
1213
// If the value is valid and convertible, it returns the converted value.
1314
// If the value is invalid, it returns a zero value of the target type.
14-
// If the value is valid but not convertible, it panics with an error message.
15+
// If the value is valid but not convertible, an error is returned.
16+
//
17+
// Example usage:
18+
//
19+
// converted, err := ConvertValue(reflect.ValueOf(42), reflect.TypeOf(float64(0)))
20+
//
21+
// if err != nil {
22+
// // handle error
23+
// }
1524
func ConvertValue(val reflect.Value, targetType reflect.Type) (reflect.Value, yaerrors.Error) {
1625
if !val.IsValid() {
17-
return reflect.Zero(targetType), nil
26+
return reflect.Zero(targetType), yaerrors.FromError(
27+
http.StatusInternalServerError,
28+
ErrInvalidValue,
29+
"convert value: value is invalid",
30+
)
1831
}
1932

2033
if val.Type().ConvertibleTo(targetType) {
2134
return val.Convert(targetType), nil
2235
}
2336

24-
return reflect.Value{}, yaerrors.FromError(
37+
return reflect.Zero(targetType), yaerrors.FromError(
2538
http.StatusInternalServerError,
26-
ErrInvalidValue,
27-
"convert value: value is not convertible to target type",
39+
ErrUnconvertibleType,
40+
fmt.Sprintf(
41+
"convert value: %s is not convertible to %s",
42+
val.Type().String(),
43+
targetType.String(),
44+
),
2845
)
2946
}

valueparser/value_parser.go

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,55 @@ import (
1919
// // Handle error
2020
// }
2121
func ParseValue[T ParsableType](value string) (T, yaerrors.Error) {
22+
return ParseValueWithCustomType[T](value, reflect.TypeOf(new(T)).Elem())
23+
}
24+
25+
// ParseValueWithCustomType is a generic function that converts a string value to the specified type T,
26+
// using the provided valueType for conversion. It returns the converted value and an error if the conversion fails.
27+
// This function is useful when you need to specify a custom type for parsing, such as when using a custom unmarshal.
28+
//
29+
// Example usage:
30+
//
31+
// type YourCustomType uint64
32+
//
33+
// func (s *YourCustomType) Unmarshal(data string) error {
34+
// if s == nil {
35+
// return fmt.Errorf("nil pointer to YourCustomType")
36+
// }
37+
//
38+
// switch data {
39+
// case "FIRST":
40+
// *s = 1
41+
// case "SECOND":
42+
// *s = 2
43+
// default:
44+
// return fmt.Errorf("unknown value: %s", data)
45+
// }
46+
//
47+
// return nil
48+
// }
49+
//
50+
// customValue, err := ParseValueWithCustomType[uint64]("FIRST", reflect.TypeOf(YourCustomType(0)))
51+
//
52+
// if err != nil {
53+
// // Handle error
54+
// }
55+
func ParseValueWithCustomType[T ParsableType](
56+
value string,
57+
valueType reflect.Type,
58+
) (T, yaerrors.Error) {
2259
var zero T
2360

24-
typ := reflect.TypeOf(zero)
61+
zeroType := reflect.TypeOf(zero)
2562

26-
switch typ.Kind() {
63+
switch valueType.Kind() {
2764
case reflect.String:
2865
if val, ok := any(value).(T); ok {
66+
unmarshaled, err := TryUnmarshal[T](value, valueType)
67+
if err == nil {
68+
return unmarshaled, nil
69+
}
70+
2971
return val, nil
3072
}
3173

@@ -37,34 +79,39 @@ func ParseValue[T ParsableType](value string) (T, yaerrors.Error) {
3779

3880
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
3981
if intValue, err := strconv.ParseInt(value, 10, 64); err == nil {
40-
if val, ok := reflect.ValueOf(intValue).Convert(typ).Interface().(T); ok {
82+
if val, ok := reflect.ValueOf(intValue).Convert(zeroType).Interface().(T); ok {
4183
return val, nil
4284
}
4385
}
4486

45-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
87+
case reflect.Uint,
88+
reflect.Uint8,
89+
reflect.Uint16,
90+
reflect.Uint32,
91+
reflect.Uint64,
92+
reflect.Uintptr:
4693
if uintValue, err := strconv.ParseUint(value, 10, 64); err == nil {
47-
if val, ok := reflect.ValueOf(uintValue).Convert(typ).Interface().(T); ok {
94+
if val, ok := reflect.ValueOf(uintValue).Convert(zeroType).Interface().(T); ok {
4895
return val, nil
4996
}
5097
}
5198

5299
case reflect.Float32, reflect.Float64:
53100
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
54-
if val, ok := reflect.ValueOf(floatValue).Convert(typ).Interface().(T); ok {
101+
if val, ok := reflect.ValueOf(floatValue).Convert(zeroType).Interface().(T); ok {
55102
return val, nil
56103
}
57104
}
58105

59106
case reflect.Bool:
60107
if boolValue, err := strconv.ParseBool(value); err == nil {
61-
if val, ok := reflect.ValueOf(boolValue).Convert(typ).Interface().(T); ok {
108+
if val, ok := reflect.ValueOf(boolValue).Convert(zeroType).Interface().(T); ok {
62109
return val, nil
63110
}
64111
}
65112

66113
case reflect.Slice:
67-
if typ.Elem().Kind() == reflect.Uint8 {
114+
if valueType.Elem().Kind() == reflect.Uint8 {
68115
if val, ok := any([]byte(value)).(T); ok {
69116
return val, nil
70117
}
@@ -77,21 +124,20 @@ func ParseValue[T ParsableType](value string) (T, yaerrors.Error) {
77124
reflect.Map,
78125
reflect.Ptr,
79126
reflect.Struct,
80-
reflect.Uintptr,
81127
reflect.Complex64,
82128
reflect.Complex128,
83129
reflect.Array,
84130
reflect.UnsafePointer:
85131
return zero, yaerrors.FromError(
86132
http.StatusInternalServerError,
87133
ErrInvalidValue,
88-
"parse value: unsupported type "+typ.String(),
134+
"parse value: unsupported type "+valueType.String(),
89135
)
90136
}
91137

92-
val, err := TryUnmarshal[T](value, typ)
138+
val, err := TryUnmarshal[T](value, valueType)
93139
if err != nil {
94-
return zero, err
140+
return zero, err.Wrap("parse value: failed to unmarshal")
95141
}
96142

97143
return val, nil

0 commit comments

Comments
 (0)