aboutsummaryrefslogtreecommitdiff
path: root/dynamicid/encoding.go
blob: 2dd216dc3e2b78cfc804f10d1e22754e513fb102 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package dynamicid

import (
	"encoding/csv"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

var (
	stringReflectType = reflect.TypeFor[string]()
	intReflectType    = reflect.TypeFor[int]()
	uintReflectType   = reflect.TypeFor[uint]()
	boolReflectType   = reflect.TypeFor[bool]()
)

// UnmarshallCSV record into a struct in-place
func UnmarshallCSV(data string, v any) error {
	r := csv.NewReader(strings.NewReader(data))
	record, err := r.Read()
	if err != nil {
		return err
	}
	s := reflect.ValueOf(v).Elem()
	t := s.Type()
	if s.NumField() != len(record) {
		return &ErrFieldMismatch{s.NumField(), len(record)}
	}
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		if t.Field(i).Tag.Get("cid") != "-" {
			switch f.Type() {
			case stringReflectType:
				f.SetString(record[i])
			case intReflectType:
				v, err := strconv.ParseInt(record[i], 10, 0)
				if err != nil {
					return err
				}
				f.SetInt(v)
			case uintReflectType:
				v, err := strconv.ParseUint(record[i], 10, 0)
				if err != nil {
					return err
				}
				f.SetUint(v)
			case boolReflectType:
				switch record[i] {
				case "0":
					f.SetBool(false)
				case "1":
					f.SetBool(true)
				default:
					return &ErrUnreadable{"boolean", record[i]}
				}
			default:
				return &ErrUnsupportedType{Type: f.Type().String()}
			}
		}
	}
	return nil
}

// MarshallCSV from a struct
func MarshallCSV(v any) string {
	s := reflect.ValueOf(v)
	r := make([]string, 0)
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		switch f.Type() {
		case stringReflectType:
			r = append(r, f.String())
		case intReflectType:
			r = append(r, strconv.FormatInt(f.Int(), 10))
		case uintReflectType:
			r = append(r, strconv.FormatUint(f.Uint(), 10))
		case boolReflectType:
			if f.Bool() {
				r = append(r, "1")
			} else {
				r = append(r, "0")
			}
		}
	}
	b := new(strings.Builder)
	w := csv.NewWriter(b)
	w.Write(r)
	w.Flush()
	return b.String()
}

type ErrFieldMismatch struct {
	Expected, Found int
}

func (e *ErrFieldMismatch) Error() string {
	return fmt.Sprintf("CSV line fields mismatch. Expected %d found %d", e.Expected, e.Found)
}

type ErrUnreadable struct {
	Format, Found string
}

func (e *ErrUnreadable) Error() string {
	return fmt.Sprintf("Unreadable value as %s. Found %s", e.Format, e.Found)
}

type ErrUnsupportedType struct {
	Type string
}

func (e *ErrUnsupportedType) Error() string {
	return "Unsupported type: " + e.Type
}