golib/net/smsgw/smscsv/smscsv.go

127 lines
2.7 KiB
Go

package smscsv
import (
"fmt"
"io"
"slices"
"strings"
)
type Reader interface {
Read() ([]string, error)
// ReadAll() ([][]string, error)
}
type CSVWarn struct {
Index int
Code string
Message string
Record []string
}
func (w CSVWarn) Error() string {
return w.Message
}
type Message struct {
header []string
indices map[string]int
fields []string
Name string
Number string
Template string
Vars map[string]string
Text string
}
func (m Message) Size() int {
return len(m.fields)
}
func (m Message) Get(key string) string {
index, ok := m.indices[key]
if !ok {
return ""
}
if len(m.fields) >= 1+index {
return m.fields[index]
}
return ""
}
// TODO XXX AJ pass in column name mapping
func ReadOrIgnoreAll(csvr Reader) (messages []Message, warns []CSVWarn, err error) {
header, err := csvr.Read()
if err != nil {
return nil, nil, fmt.Errorf("header could not be parsed: %w", err)
}
FIELD_NAME := GetFieldIndex(header, "Name")
FIELD_PHONE := GetFieldIndex(header, "Phone")
FIELD_MESSAGE := GetFieldIndex(header, "Message")
if FIELD_NAME == -1 || FIELD_PHONE == -1 || FIELD_MESSAGE == -1 {
return nil, nil, fmt.Errorf("header is missing one or more of 'Name', 'Phone', and/or 'Message'")
}
FIELD_MIN := 1 + slices.Max([]int{FIELD_NAME, FIELD_PHONE, FIELD_MESSAGE})
rowIndex := 1 // 1-index, start at header
for {
rowIndex++
rec, err := csvr.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, nil, fmt.Errorf("failed to parse row %d (and all following rows): %w", rowIndex, err)
}
// TODO XXX AJ create an abstraction around the header []string and the record []string
// the idea is to return the same thing for valid and invalid rows
vars := make(map[string]string)
n := min(len(header), len(rec))
for i := range n {
switch i {
case FIELD_NAME, FIELD_PHONE, FIELD_MESSAGE:
continue
default:
key := header[i]
val := rec[i]
vars[key] = val
}
}
if len(rec) < FIELD_MIN {
warns = append(warns, CSVWarn{
Index: rowIndex,
Code: "TooFewFields",
Message: fmt.Sprintf("ignoring row %d: too few fields (want %d, have %d)", rowIndex, FIELD_MIN, len(rec)),
Record: rec,
})
continue
}
message := Message{
// Index: rowIndex,
Name: strings.TrimSpace(rec[FIELD_NAME]),
Number: strings.TrimSpace(rec[FIELD_PHONE]),
Template: strings.TrimSpace(rec[FIELD_MESSAGE]),
Vars: vars,
}
messages = append(messages, message)
}
return messages, warns, nil
}
func GetFieldIndex(header []string, name string) int {
for i, h := range header {
if strings.EqualFold(strings.TrimSpace(h), name) {
return i
}
}
return -1
}