Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}