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}