diff --git a/net/smsgw/cmd/sendsms/main.go b/net/smsgw/cmd/sendsms/main.go index 4deee9b..4893078 100644 --- a/net/smsgw/cmd/sendsms/main.go +++ b/net/smsgw/cmd/sendsms/main.go @@ -14,6 +14,7 @@ import ( "github.com/joho/godotenv" "github.com/therootcompany/golib/net/smsgw" "github.com/therootcompany/golib/net/smsgw/androidsmsgateway" + "github.com/therootcompany/golib/net/smsgw/smscsv" ) type MainConfig struct { @@ -122,7 +123,7 @@ func main() { csvr := csv.NewReader(file) csvr.FieldsPerRecord = -1 - messages, warns, err := cfg.LaxParseCSV(csvr) + messages, warns, err := smscsv.ReadOrIgnoreAll(csvr) if err != nil { fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err) os.Exit(1) diff --git a/net/smsgw/cmd/sendsms/csv.go b/net/smsgw/smscsv/smscsv.go similarity index 69% rename from net/smsgw/cmd/sendsms/csv.go rename to net/smsgw/smscsv/smscsv.go index 09bb33b..8763122 100644 --- a/net/smsgw/cmd/sendsms/csv.go +++ b/net/smsgw/smscsv/smscsv.go @@ -1,22 +1,15 @@ -package main +package smscsv import ( - "encoding/csv" "fmt" "io" "slices" "strings" - - "github.com/therootcompany/golib/net/smsgw" ) -func GetFieldIndex(header []string, name string) int { - for i, h := range header { - if strings.EqualFold(strings.TrimSpace(h), name) { - return i - } - } - return -1 +type Reader interface { + Read() ([]string, error) + // ReadAll() ([][]string, error) } type CSVWarn struct { @@ -30,7 +23,36 @@ func (w CSVWarn) Error() string { return w.Message } -func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message, warns []CSVWarn, err error) { +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) @@ -55,16 +77,8 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message, return nil, nil, fmt.Errorf("failed to parse row %d (and all following rows): %w", rowIndex, err) } - 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 - } - + // 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 { @@ -78,7 +92,17 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message, } } - message := smsgw.Message{ + 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]), @@ -86,20 +110,17 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message, Vars: vars, } - message.Number = smsgw.StripFormatting(message.Number) - message.Number, err = smsgw.PrefixUS10Digit(message.Number) - if err != nil { - warns = append(warns, CSVWarn{ - Index: rowIndex, - Code: "PhoneInvalid", - Message: fmt.Sprintf("ignoring row %d (%s): %s", rowIndex, message.Name, err.Error()), - Record: rec, - }) - continue - } - 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 +} diff --git a/net/smsgw/smsgw.go b/net/smsgw/smsgw.go index b581ad2..59c3cf9 100644 --- a/net/smsgw/smsgw.go +++ b/net/smsgw/smsgw.go @@ -5,19 +5,6 @@ import ( "strings" ) -type Gateway interface { - CurlString(to, text string) string - Send(to, text string) error -} - -type Message struct { - Name string - Number string - Template string - Vars map[string]string - Text string -} - var ErrInvalidClockFormat = fmt.Errorf("invalid clock time, ex: '06:00 PM', '6pm', or '18:00' (space and case insensitive)") var ErrInvalidClockTime = fmt.Errorf("invalid hour or minute, for example '27:63 p' would not be valid") var ErrPhoneEmpty = fmt.Errorf("no phone number") @@ -25,6 +12,11 @@ var ErrPhoneInvalid11 = fmt.Errorf("invalid 11-digit number (does not start with var ErrPhoneInvalid12 = fmt.Errorf("invalid 12-digit number (does not start with +1)") var ErrPhoneInvalidLength = fmt.Errorf("invalid number length (should be 10 digits or 12 with +1 prefix)") +type Gateway interface { + CurlString(to, text string) string + Send(to, text string) error +} + // Strips away symbols, non-printing characters copied from HTML, etc, // leaving only a possible leading '+' and digits. // Does not leave *, # or comma. diff --git a/net/smsgw/smstmpl/smstmpl.go b/net/smsgw/smstmpl/smstmpl.go index 661f204..5a8ed45 100644 --- a/net/smsgw/smstmpl/smstmpl.go +++ b/net/smsgw/smstmpl/smstmpl.go @@ -8,11 +8,15 @@ import ( "strings" "github.com/therootcompany/golib/net/smsgw" + "github.com/therootcompany/golib/net/smsgw/smscsv" ) var reUnmatchedVars = regexp.MustCompile(`(\{[^}]+\})`) -func RenderMessages(messages []smsgw.Message) ([]smsgw.Message, error) { +func RenderAll(messages []smscsv.Message) ([]smscsv.Message, error) { + var err error + var warns []smscsv.CSVWarn + for i, message := range messages { rowIndex := i + 1 @@ -24,8 +28,20 @@ func RenderMessages(messages []smsgw.Message) ([]smsgw.Message, error) { message.Text = ReplaceVar(message.Text, key, val) } + message.Number = smsgw.StripFormatting(message.Number) + message.Number, err = smsgw.PrefixUS10Digit(message.Number) + if err != nil { + warns = append(warns, smscsv.CSVWarn{ + Index: rowIndex, + Code: "PhoneInvalid", + Message: fmt.Sprintf("ignoring row %d (%s): %s", rowIndex, message.Name, err.Error()), + // Record: rec, + }) + continue + } + if tmpls := reUnmatchedVars.FindAllString(message.Text, -1); len(tmpls) != 0 { - return nil, &CSVWarn{ + return nil, &smscsv.CSVWarn{ Index: rowIndex, Code: "UnmatchedVars", Message: fmt.Sprintf(