Search Apps Documentation Source Content File Folder Download Copy Actions Download

typeutil.gno

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