typeutil.gno

14.04 Kb ยท 715 lines
  1// Package typeutil provides utility functions for converting between different types
  2// and checking their states. It aims to provide consistent behavior across different
  3// types while remaining lightweight and dependency-free.
  4package typeutil
  5
  6import (
  7	"errors"
  8	"sort"
  9	"std"
 10	"strconv"
 11	"strings"
 12	"time"
 13)
 14
 15// stringer is the interface that wraps the String method.
 16type stringer interface {
 17	String() string
 18}
 19
 20// ToString converts any value to its string representation.
 21// It supports a wide range of Go types including:
 22//   - Basic: string, bool
 23//   - Numbers: int, int8-64, uint, uint8-64, float32, float64
 24//   - Special: time.Time, std.Address, []byte
 25//   - Slices: []T for most basic types
 26//   - Maps: map[string]string, map[string]any
 27//   - Interface: types implementing String() string
 28//
 29// Example usage:
 30//
 31//	str := typeutil.ToString(42)               // "42"
 32//	str = typeutil.ToString([]int{1, 2})      // "[1 2]"
 33//	str = typeutil.ToString(map[string]string{ // "map[a:1 b:2]"
 34//	    "a": "1",
 35//	    "b": "2",
 36//	})
 37func ToString(val any) string {
 38	if val == nil {
 39		return ""
 40	}
 41
 42	// First check if value implements Stringer interface
 43	if s, ok := val.(interface{ String() string }); ok {
 44		return s.String()
 45	}
 46
 47	switch v := val.(type) {
 48	// Pointer types - dereference and recurse
 49	case *string:
 50		if v == nil {
 51			return ""
 52		}
 53		return *v
 54	case *int:
 55		if v == nil {
 56			return ""
 57		}
 58		return strconv.Itoa(*v)
 59	case *bool:
 60		if v == nil {
 61			return ""
 62		}
 63		return strconv.FormatBool(*v)
 64	case *time.Time:
 65		if v == nil {
 66			return ""
 67		}
 68		return v.String()
 69	case *std.Address:
 70		if v == nil {
 71			return ""
 72		}
 73		return string(*v)
 74
 75	// String types
 76	case string:
 77		return v
 78	case stringer:
 79		return v.String()
 80
 81	// Special types
 82	case time.Time:
 83		return v.String()
 84	case std.Address:
 85		return string(v)
 86	case []byte:
 87		return string(v)
 88	case struct{}:
 89		return "{}"
 90
 91	// Integer types
 92	case int:
 93		return strconv.Itoa(v)
 94	case int8:
 95		return strconv.FormatInt(int64(v), 10)
 96	case int16:
 97		return strconv.FormatInt(int64(v), 10)
 98	case int32:
 99		return strconv.FormatInt(int64(v), 10)
100	case int64:
101		return strconv.FormatInt(v, 10)
102	case uint:
103		return strconv.FormatUint(uint64(v), 10)
104	case uint8:
105		return strconv.FormatUint(uint64(v), 10)
106	case uint16:
107		return strconv.FormatUint(uint64(v), 10)
108	case uint32:
109		return strconv.FormatUint(uint64(v), 10)
110	case uint64:
111		return strconv.FormatUint(v, 10)
112
113	// Float types
114	case float32:
115		return strconv.FormatFloat(float64(v), 'f', -1, 32)
116	case float64:
117		return strconv.FormatFloat(v, 'f', -1, 64)
118
119	// Boolean
120	case bool:
121		if v {
122			return "true"
123		}
124		return "false"
125
126	// Slice types
127	case []string:
128		return join(v)
129	case []int:
130		return join(v)
131	case []int32:
132		return join(v)
133	case []int64:
134		return join(v)
135	case []float32:
136		return join(v)
137	case []float64:
138		return join(v)
139	case []any:
140		return join(v)
141	case []time.Time:
142		return joinTimes(v)
143	case []stringer:
144		return join(v)
145	case []std.Address:
146		return joinAddresses(v)
147	case [][]byte:
148		return joinBytes(v)
149
150	// Map types with various key types
151	case map[any]any, map[string]any, map[string]string, map[string]int:
152		var b strings.Builder
153		b.WriteString("map[")
154		first := true
155
156		switch m := v.(type) {
157		case map[any]any:
158			// Convert all keys to strings for consistent ordering
159			keys := make([]string, 0)
160			keyMap := make(map[string]any)
161
162			for k := range m {
163				keyStr := ToString(k)
164				keys = append(keys, keyStr)
165				keyMap[keyStr] = k
166			}
167			sort.Strings(keys)
168
169			for _, keyStr := range keys {
170				if !first {
171					b.WriteString(" ")
172				}
173				origKey := keyMap[keyStr]
174				b.WriteString(keyStr)
175				b.WriteString(":")
176				b.WriteString(ToString(m[origKey]))
177				first = false
178			}
179
180		case map[string]any:
181			keys := make([]string, 0)
182			for k := range m {
183				keys = append(keys, k)
184			}
185			sort.Strings(keys)
186
187			for _, k := range keys {
188				if !first {
189					b.WriteString(" ")
190				}
191				b.WriteString(k)
192				b.WriteString(":")
193				b.WriteString(ToString(m[k]))
194				first = false
195			}
196
197		case map[string]string:
198			keys := make([]string, 0)
199			for k := range m {
200				keys = append(keys, k)
201			}
202			sort.Strings(keys)
203
204			for _, k := range keys {
205				if !first {
206					b.WriteString(" ")
207				}
208				b.WriteString(k)
209				b.WriteString(":")
210				b.WriteString(m[k])
211				first = false
212			}
213
214		case map[string]int:
215			keys := make([]string, 0)
216			for k := range m {
217				keys = append(keys, k)
218			}
219			sort.Strings(keys)
220
221			for _, k := range keys {
222				if !first {
223					b.WriteString(" ")
224				}
225				b.WriteString(k)
226				b.WriteString(":")
227				b.WriteString(strconv.Itoa(m[k]))
228				first = false
229			}
230		}
231		b.WriteString("]")
232		return b.String()
233
234	// Default
235	default:
236		return "<unknown>"
237	}
238}
239
240func join(slice any) string {
241	if IsZero(slice) {
242		return "[]"
243	}
244
245	items := ToInterfaceSlice(slice)
246	if items == nil {
247		return "[]"
248	}
249
250	var b strings.Builder
251	b.WriteString("[")
252	for i, item := range items {
253		if i > 0 {
254			b.WriteString(" ")
255		}
256		b.WriteString(ToString(item))
257	}
258	b.WriteString("]")
259	return b.String()
260}
261
262func joinTimes(slice []time.Time) string {
263	if len(slice) == 0 {
264		return "[]"
265	}
266	var b strings.Builder
267	b.WriteString("[")
268	for i, t := range slice {
269		if i > 0 {
270			b.WriteString(" ")
271		}
272		b.WriteString(t.String())
273	}
274	b.WriteString("]")
275	return b.String()
276}
277
278func joinAddresses(slice []std.Address) string {
279	if len(slice) == 0 {
280		return "[]"
281	}
282	var b strings.Builder
283	b.WriteString("[")
284	for i, addr := range slice {
285		if i > 0 {
286			b.WriteString(" ")
287		}
288		b.WriteString(string(addr))
289	}
290	b.WriteString("]")
291	return b.String()
292}
293
294func joinBytes(slice [][]byte) string {
295	if len(slice) == 0 {
296		return "[]"
297	}
298	var b strings.Builder
299	b.WriteString("[")
300	for i, bytes := range slice {
301		if i > 0 {
302			b.WriteString(" ")
303		}
304		b.WriteString(string(bytes))
305	}
306	b.WriteString("]")
307	return b.String()
308}
309
310// ToBool converts any value to a boolean based on common programming conventions.
311// For example:
312//   - Numbers: 0 is false, any other number is true
313//   - Strings: "", "0", "false", "f", "no", "n", "off" are false, others are true
314//   - Slices/Maps: empty is false, non-empty is true
315//   - nil: always false
316//   - bool: direct value
317func ToBool(val any) bool {
318	if IsZero(val) {
319		return false
320	}
321
322	// Handle special string cases
323	if str, ok := val.(string); ok {
324		str = strings.ToLower(strings.TrimSpace(str))
325		return str != "" && str != "0" && str != "false" && str != "f" && str != "no" && str != "n" && str != "off"
326	}
327
328	return true
329}
330
331// IsZero returns true if the value represents a "zero" or "empty" state for its type.
332// For example:
333//   - Numbers: 0
334//   - Strings: ""
335//   - Slices/Maps: empty
336//   - nil: true
337//   - bool: false
338//   - time.Time: IsZero()
339//   - std.Address: empty string
340func IsZero(val any) bool {
341	if val == nil {
342		return true
343	}
344
345	switch v := val.(type) {
346	// Pointer types - nil pointer is zero, otherwise check pointed value
347	case *bool:
348		return v == nil || !*v
349	case *string:
350		return v == nil || *v == ""
351	case *int:
352		return v == nil || *v == 0
353	case *time.Time:
354		return v == nil || v.IsZero()
355	case *std.Address:
356		return v == nil || string(*v) == ""
357
358	// Bool
359	case bool:
360		return !v
361
362	// String types
363	case string:
364		return v == ""
365	case stringer:
366		return v.String() == ""
367
368	// Integer types
369	case int:
370		return v == 0
371	case int8:
372		return v == 0
373	case int16:
374		return v == 0
375	case int32:
376		return v == 0
377	case int64:
378		return v == 0
379	case uint:
380		return v == 0
381	case uint8:
382		return v == 0
383	case uint16:
384		return v == 0
385	case uint32:
386		return v == 0
387	case uint64:
388		return v == 0
389
390	// Float types
391	case float32:
392		return v == 0
393	case float64:
394		return v == 0
395
396	// Special types
397	case []byte:
398		return len(v) == 0
399	case time.Time:
400		return v.IsZero()
401	case std.Address:
402		return string(v) == ""
403
404	// Slices (check if empty)
405	case []string:
406		return len(v) == 0
407	case []int:
408		return len(v) == 0
409	case []int32:
410		return len(v) == 0
411	case []int64:
412		return len(v) == 0
413	case []float32:
414		return len(v) == 0
415	case []float64:
416		return len(v) == 0
417	case []any:
418		return len(v) == 0
419	case []time.Time:
420		return len(v) == 0
421	case []std.Address:
422		return len(v) == 0
423	case [][]byte:
424		return len(v) == 0
425	case []stringer:
426		return len(v) == 0
427
428	// Maps (check if empty)
429	case map[string]string:
430		return len(v) == 0
431	case map[string]any:
432		return len(v) == 0
433
434	default:
435		return false // non-nil unknown types are considered non-zero
436	}
437}
438
439// ToInterfaceSlice converts various slice types to []any
440func ToInterfaceSlice(val any) []any {
441	switch v := val.(type) {
442	case []any:
443		return v
444	case []string:
445		result := make([]any, len(v))
446		for i, s := range v {
447			result[i] = s
448		}
449		return result
450	case []int:
451		result := make([]any, len(v))
452		for i, n := range v {
453			result[i] = n
454		}
455		return result
456	case []int32:
457		result := make([]any, len(v))
458		for i, n := range v {
459			result[i] = n
460		}
461		return result
462	case []int64:
463		result := make([]any, len(v))
464		for i, n := range v {
465			result[i] = n
466		}
467		return result
468	case []float32:
469		result := make([]any, len(v))
470		for i, n := range v {
471			result[i] = n
472		}
473		return result
474	case []float64:
475		result := make([]any, len(v))
476		for i, n := range v {
477			result[i] = n
478		}
479		return result
480	case []bool:
481		result := make([]any, len(v))
482		for i, b := range v {
483			result[i] = b
484		}
485		return result
486	default:
487		return nil
488	}
489}
490
491// ToMapStringInterface converts a map with string keys and any value type to map[string]any
492func ToMapStringInterface(m any) (map[string]any, error) {
493	result := make(map[string]any)
494
495	switch v := m.(type) {
496	case map[string]any:
497		return v, nil
498	case map[string]string:
499		for k, val := range v {
500			result[k] = val
501		}
502	case map[string]int:
503		for k, val := range v {
504			result[k] = val
505		}
506	case map[string]int64:
507		for k, val := range v {
508			result[k] = val
509		}
510	case map[string]float64:
511		for k, val := range v {
512			result[k] = val
513		}
514	case map[string]bool:
515		for k, val := range v {
516			result[k] = val
517		}
518	case map[string][]string:
519		for k, val := range v {
520			result[k] = ToInterfaceSlice(val)
521		}
522	case map[string][]int:
523		for k, val := range v {
524			result[k] = ToInterfaceSlice(val)
525		}
526	case map[string][]any:
527		for k, val := range v {
528			result[k] = val
529		}
530	case map[string]map[string]any:
531		for k, val := range v {
532			result[k] = val
533		}
534	case map[string]map[string]string:
535		for k, val := range v {
536			if converted, err := ToMapStringInterface(val); err == nil {
537				result[k] = converted
538			} else {
539				return nil, errors.New("failed to convert nested map at key: " + k)
540			}
541		}
542	default:
543		return nil, errors.New("unsupported map type: " + ToString(m))
544	}
545
546	return result, nil
547}
548
549// ToMapIntInterface converts a map with int keys and any value type to map[int]any
550func ToMapIntInterface(m any) (map[int]any, error) {
551	result := make(map[int]any)
552
553	switch v := m.(type) {
554	case map[int]any:
555		return v, nil
556	case map[int]string:
557		for k, val := range v {
558			result[k] = val
559		}
560	case map[int]int:
561		for k, val := range v {
562			result[k] = val
563		}
564	case map[int]int64:
565		for k, val := range v {
566			result[k] = val
567		}
568	case map[int]float64:
569		for k, val := range v {
570			result[k] = val
571		}
572	case map[int]bool:
573		for k, val := range v {
574			result[k] = val
575		}
576	case map[int][]string:
577		for k, val := range v {
578			result[k] = ToInterfaceSlice(val)
579		}
580	case map[int][]int:
581		for k, val := range v {
582			result[k] = ToInterfaceSlice(val)
583		}
584	case map[int][]any:
585		for k, val := range v {
586			result[k] = val
587		}
588	case map[int]map[string]any:
589		for k, val := range v {
590			result[k] = val
591		}
592	case map[int]map[int]any:
593		for k, val := range v {
594			result[k] = val
595		}
596	default:
597		return nil, errors.New("unsupported map type: " + ToString(m))
598	}
599
600	return result, nil
601}
602
603// ToStringSlice converts various slice types to []string
604func ToStringSlice(val any) []string {
605	switch v := val.(type) {
606	case []string:
607		return v
608	case []any:
609		result := make([]string, len(v))
610		for i, item := range v {
611			result[i] = ToString(item)
612		}
613		return result
614	case []int:
615		result := make([]string, len(v))
616		for i, n := range v {
617			result[i] = strconv.Itoa(n)
618		}
619		return result
620	case []int32:
621		result := make([]string, len(v))
622		for i, n := range v {
623			result[i] = strconv.FormatInt(int64(n), 10)
624		}
625		return result
626	case []int64:
627		result := make([]string, len(v))
628		for i, n := range v {
629			result[i] = strconv.FormatInt(n, 10)
630		}
631		return result
632	case []float32:
633		result := make([]string, len(v))
634		for i, n := range v {
635			result[i] = strconv.FormatFloat(float64(n), 'f', -1, 32)
636		}
637		return result
638	case []float64:
639		result := make([]string, len(v))
640		for i, n := range v {
641			result[i] = strconv.FormatFloat(n, 'f', -1, 64)
642		}
643		return result
644	case []bool:
645		result := make([]string, len(v))
646		for i, b := range v {
647			result[i] = strconv.FormatBool(b)
648		}
649		return result
650	case []time.Time:
651		result := make([]string, len(v))
652		for i, t := range v {
653			result[i] = t.String()
654		}
655		return result
656	case []std.Address:
657		result := make([]string, len(v))
658		for i, addr := range v {
659			result[i] = string(addr)
660		}
661		return result
662	case [][]byte:
663		result := make([]string, len(v))
664		for i, b := range v {
665			result[i] = string(b)
666		}
667		return result
668	case []stringer:
669		result := make([]string, len(v))
670		for i, s := range v {
671			result[i] = s.String()
672		}
673		return result
674	case []uint:
675		result := make([]string, len(v))
676		for i, n := range v {
677			result[i] = strconv.FormatUint(uint64(n), 10)
678		}
679		return result
680	case []uint8:
681		result := make([]string, len(v))
682		for i, n := range v {
683			result[i] = strconv.FormatUint(uint64(n), 10)
684		}
685		return result
686	case []uint16:
687		result := make([]string, len(v))
688		for i, n := range v {
689			result[i] = strconv.FormatUint(uint64(n), 10)
690		}
691		return result
692	case []uint32:
693		result := make([]string, len(v))
694		for i, n := range v {
695			result[i] = strconv.FormatUint(uint64(n), 10)
696		}
697		return result
698	case []uint64:
699		result := make([]string, len(v))
700		for i, n := range v {
701			result[i] = strconv.FormatUint(n, 10)
702		}
703		return result
704	default:
705		// Try to convert using reflection if it's a slice
706		if slice := ToInterfaceSlice(val); slice != nil {
707			result := make([]string, len(slice))
708			for i, item := range slice {
709				result[i] = ToString(item)
710			}
711			return result
712		}
713		return nil
714	}
715}