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}