Skip to content

Commit eabbfaa

Browse files
author
Stefan Tudose
committed
improve names and documentation
1 parent b780b46 commit eabbfaa

File tree

10 files changed

+206
-200
lines changed

10 files changed

+206
-200
lines changed

convert.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func convertAssignValue(dest interface{}, src string) error {
2626
}
2727

2828
// check if the destination implements the Decoder interface
29-
if decoder, ok := dest.(Decoder); ok {
29+
if decoder, ok := dest.(Interface); ok {
3030
return decoder.DecodeRecord(src)
3131
}
3232

decoder.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package csvdecoder
2+
3+
import (
4+
"encoding/csv"
5+
"errors"
6+
"fmt"
7+
"io"
8+
)
9+
10+
type Decoder struct {
11+
reader *csv.Reader
12+
config Config
13+
currentRowValues []string
14+
lastErr error
15+
}
16+
17+
// Config is a type that can be used to configure a decoder.
18+
type Config struct {
19+
Comma rune // the character that separates values. Default value is comma.
20+
IgnoreHeaders bool // if set to true, the first line will be ignored
21+
IgnoreUnmatchingFields bool // if set to true, the number of records and scan targets are allowed to be different
22+
}
23+
24+
// New returns a new CSV decoder that reads from r.
25+
// The decoder can be given a custom configuration.
26+
func NewWithConfig(r io.Reader, config Config) (*Decoder, error) {
27+
return newDecoder(r, config)
28+
}
29+
30+
// New returns a new CSV decoder that reads from r
31+
func New(r io.Reader) (*Decoder, error) {
32+
return newDecoder(r, Config{})
33+
}
34+
35+
func newDecoder(reader io.Reader, config Config) (*Decoder, error) {
36+
p := &Decoder{
37+
reader: csv.NewReader(reader),
38+
config: config,
39+
}
40+
41+
if config.Comma != 0 {
42+
p.reader.Comma = config.Comma
43+
}
44+
45+
p.reader.LazyQuotes = true
46+
p.reader.FieldsPerRecord = -1
47+
48+
if config.IgnoreHeaders {
49+
// consume the first line
50+
_, _ = p.reader.Read()
51+
}
52+
53+
return p, nil
54+
}
55+
56+
// Scan copies the values in the current row into the values pointed
57+
// at by dest.
58+
// With the default behavior, it will throw an error if the number of values in dest
59+
// is different from the number of values. If the `IgnoreUnmatchingFields` flag is
60+
// set, it will ignore the records and the arguments that have no match.
61+
//
62+
// Scan converts columns read from the source into the following
63+
// types:
64+
// *string
65+
// *int, *int8, *int16, *int32, *int64
66+
// *uint, *uint8, *uint16, *uint32, *uint64
67+
// *bool
68+
// *float32, *float64
69+
// a pointer to any type implementing Decoder interface
70+
// a slice of values that can be decoded from a JSON array by the JSON Decoder
71+
// an array of values that can be decoded from a JSON array by the JSON Decoder
72+
//
73+
// Scan must not be called concurrently.
74+
func (p *Decoder) Scan(dest ...interface{}) error {
75+
switch {
76+
case errors.Is(p.lastErr, ErrEOF):
77+
return ErrEOF
78+
case p.lastErr != nil:
79+
return ErrReadingOccurred
80+
case p.currentRowValues == nil:
81+
return ErrNextNotCalled
82+
case !p.config.IgnoreUnmatchingFields && len(p.currentRowValues) != len(dest):
83+
return fmt.Errorf("%w: got %d scan targets and %d records",
84+
ErrScanTargetsNotMatch,
85+
len(dest),
86+
len(p.currentRowValues),
87+
)
88+
}
89+
for i, val := range p.currentRowValues {
90+
if i >= len(dest) {
91+
// ignore the remaining records as they have no scan target
92+
break
93+
}
94+
err := convertAssignValue(dest[i], val)
95+
if err != nil {
96+
return fmt.Errorf("scan error on value index %d: %w", i, err)
97+
}
98+
}
99+
return nil
100+
}
101+
102+
// Next prepares the next result row for reading with the Scan method. It
103+
// returns nil on success, or false if there is no next result row or an error
104+
// happened while preparing it. Err should be consulted to distinguish between
105+
// the two cases.
106+
//
107+
// Every call to Scan, even the first one, must be preceded by a call to Next.
108+
// Next must not be called concurrently.
109+
func (p *Decoder) Next() bool {
110+
var err error
111+
p.currentRowValues, err = p.reader.Read()
112+
if err != nil {
113+
if err.Error() == "EOF" {
114+
p.lastErr = ErrEOF
115+
return false
116+
}
117+
p.lastErr = fmt.Errorf("error while reading: %w", err)
118+
return false
119+
}
120+
return true
121+
}
122+
123+
// Err returns the reading error, if any, that was encountered during iteration.
124+
func (p *Decoder) Err() error {
125+
if p.lastErr != nil && p.lastErr != ErrEOF {
126+
return p.lastErr
127+
}
128+
return nil
129+
}
Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,20 @@ func TestDecoderStruct(t *testing.T) {
3939
} {
4040
tc := tc
4141
t.Run(tc.name, func(t *testing.T) {
42-
parser, err := NewParserWithConfig(strings.NewReader(tc.data), ParserConfig{IgnoreHeaders: false, Comma: '\t'})
42+
d, err := NewWithConfig(strings.NewReader(tc.data), Config{IgnoreHeaders: false, Comma: '\t'})
4343
if err != nil {
44-
t.Fatalf("could not create parser: %w", err)
44+
t.Fatalf("could not create d: %w", err)
4545
}
4646

47-
for parser.Next() {
48-
if err := parser.Scan(&tc.dest); err != nil {
47+
for d.Next() {
48+
if err := d.Scan(&tc.dest); err != nil {
4949
t.Error(err)
5050
}
5151
if tc.dest.DecodedValue() != tc.expected {
5252
t.Errorf("expected value '%s' got '%s'", tc.expected, tc.dest.DecodedValue())
5353
}
5454
}
55-
if parser.Err() != nil {
55+
if d.Err() != nil {
5656
t.Error(err)
5757
}
5858
})
@@ -75,20 +75,20 @@ func TestDecoderPointer(t *testing.T) {
7575
} {
7676
tc := tc
7777
t.Run(tc.name, func(t *testing.T) {
78-
parser, err := NewParserWithConfig(strings.NewReader(tc.data), ParserConfig{IgnoreHeaders: false, Comma: '\t'})
78+
d, err := NewWithConfig(strings.NewReader(tc.data), Config{IgnoreHeaders: false, Comma: '\t'})
7979
if err != nil {
80-
t.Fatalf("could not create parser: %w", err)
80+
t.Fatalf("could not create d: %w", err)
8181
}
8282

83-
for parser.Next() {
84-
if err := parser.Scan(&tc.dest); err != nil {
83+
for d.Next() {
84+
if err := d.Scan(&tc.dest); err != nil {
8585
t.Error(err)
8686
}
8787
if tc.dest.DecodedValue() != tc.expected {
8888
t.Errorf("expected value '%s' got '%s'", tc.expected, tc.dest.DecodedValue())
8989
}
9090
}
91-
if parser.Err() != nil {
91+
if d.Err() != nil {
9292
t.Error(err)
9393
}
9494
})
@@ -112,20 +112,20 @@ func TestDecoderDoublePointer(t *testing.T) {
112112
} {
113113
tc := tc
114114
t.Run(tc.name, func(t *testing.T) {
115-
parser, err := NewParserWithConfig(strings.NewReader(tc.data), ParserConfig{IgnoreHeaders: false, Comma: '\t'})
115+
d, err := NewWithConfig(strings.NewReader(tc.data), Config{IgnoreHeaders: false, Comma: '\t'})
116116
if err != nil {
117-
t.Fatalf("could not create parser: %w", err)
117+
t.Fatalf("could not create d: %w", err)
118118
}
119119

120-
for parser.Next() {
121-
if err := parser.Scan(&tc.dest); err != nil {
120+
for d.Next() {
121+
if err := d.Scan(&tc.dest); err != nil {
122122
t.Error(err)
123123
}
124124
if (*tc.dest).DecodedValue() != tc.expected {
125125
t.Errorf("expected value '%s' got '%s'", tc.expected, (*tc.dest).DecodedValue())
126126
}
127127
}
128-
if parser.Err() != nil {
128+
if d.Err() != nil {
129129
t.Error(err)
130130
}
131131
})
@@ -135,7 +135,7 @@ func TestDecoderDoublePointer(t *testing.T) {
135135
func TestDecoderInterface(t *testing.T) {
136136
for _, tc := range []struct {
137137
name string
138-
dest Decoder
138+
dest Interface
139139
data string
140140
expected string
141141
}{
@@ -148,20 +148,20 @@ func TestDecoderInterface(t *testing.T) {
148148
} {
149149
tc := tc
150150
t.Run(tc.name, func(t *testing.T) {
151-
parser, err := NewParserWithConfig(strings.NewReader(tc.data), ParserConfig{IgnoreHeaders: false})
151+
d, err := NewWithConfig(strings.NewReader(tc.data), Config{IgnoreHeaders: false})
152152
if err != nil {
153-
t.Fatalf("could not create parser: %w", err)
153+
t.Fatalf("could not create d: %w", err)
154154
}
155155

156-
for parser.Next() {
157-
if err := parser.Scan(tc.dest); err != nil {
156+
for d.Next() {
157+
if err := d.Scan(tc.dest); err != nil {
158158
t.Error(err)
159159
}
160160
if tc.dest.(*MyDecoder).DecodedValue() != tc.expected {
161161
t.Errorf("expected value '%s' got '%s'", tc.expected, tc.dest.(*MyDecoder).DecodedValue())
162162
}
163163
}
164-
if parser.Err() != nil {
164+
if d.Err() != nil {
165165
t.Error(err)
166166
}
167167
})
Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"testing"
77
)
88

9-
func TestIntArray(t *testing.T) {
9+
func TestIntSlice(t *testing.T) {
1010
type TestRow struct {
1111
Field []int
1212
}
@@ -44,13 +44,13 @@ func TestIntArray(t *testing.T) {
4444
} {
4545
tc := tc
4646
t.Run(tc.name, func(t *testing.T) {
47-
parser, err := NewParserWithConfig(strings.NewReader(tc.data), ParserConfig{IgnoreHeaders: false, Comma: '\t'})
47+
d, err := NewWithConfig(strings.NewReader(tc.data), Config{IgnoreHeaders: false, Comma: '\t'})
4848
if err != nil {
49-
t.Fatalf("could not create parser: %w", err)
49+
t.Fatalf("could not create d: %w", err)
5050
}
5151

52-
for parser.Next() {
53-
err := parser.Scan(&tc.RowStruct.Field)
52+
for d.Next() {
53+
err := d.Scan(&tc.RowStruct.Field)
5454
if err != nil {
5555
t.Error(err)
5656
}
@@ -59,14 +59,14 @@ func TestIntArray(t *testing.T) {
5959
t.Errorf("expected value '%v' got '%v'", tc.expected, tc.RowStruct.Field)
6060
}
6161
}
62-
if parser.Err() != nil {
63-
t.Errorf("parser error: %w", err)
62+
if d.Err() != nil {
63+
t.Errorf("d error: %w", err)
6464
}
6565
})
6666
}
6767
}
6868

69-
func TestMultiLevelIntArray(t *testing.T) {
69+
func TestMultiLevelIntSlice(t *testing.T) {
7070
type TestRow struct {
7171
Field [][][]int
7272
}
@@ -104,13 +104,13 @@ func TestMultiLevelIntArray(t *testing.T) {
104104
} {
105105
tc := tc
106106
t.Run(tc.name, func(t *testing.T) {
107-
parser, err := NewParserWithConfig(strings.NewReader(tc.data), ParserConfig{IgnoreHeaders: false, Comma: '\t'})
107+
d, err := NewWithConfig(strings.NewReader(tc.data), Config{IgnoreHeaders: false, Comma: '\t'})
108108
if err != nil {
109-
t.Fatalf("could not create parser: %w", err)
109+
t.Fatalf("could not create d: %w", err)
110110
}
111111

112-
for parser.Next() {
113-
err := parser.Scan(&tc.RowStruct.Field)
112+
for d.Next() {
113+
err := d.Scan(&tc.RowStruct.Field)
114114
if err != nil {
115115
t.Error(err)
116116
}
@@ -119,14 +119,14 @@ func TestMultiLevelIntArray(t *testing.T) {
119119
t.Errorf("expected value '%v' got '%v'", tc.expected, tc.RowStruct.Field)
120120
}
121121
}
122-
if parser.Err() != nil {
123-
t.Errorf("parser error: %w", err)
122+
if d.Err() != nil {
123+
t.Errorf("d error: %w", err)
124124
}
125125
})
126126
}
127127
}
128128

129-
func TestStructArray(t *testing.T) {
129+
func TestStructSlice(t *testing.T) {
130130
type MyStruct struct {
131131
A int `json:"a"`
132132
B int32 `json:"b"`
@@ -189,13 +189,13 @@ func TestStructArray(t *testing.T) {
189189
} {
190190
tc := tc
191191
t.Run(tc.name, func(t *testing.T) {
192-
parser, err := NewParserWithConfig(strings.NewReader(tc.data), ParserConfig{IgnoreHeaders: false, Comma: '\t'})
192+
d, err := NewWithConfig(strings.NewReader(tc.data), Config{IgnoreHeaders: false, Comma: '\t'})
193193
if err != nil {
194-
t.Fatalf("could not create parser: %w", err)
194+
t.Fatalf("could not create d: %w", err)
195195
}
196196

197-
for parser.Next() {
198-
err := parser.Scan(&tc.RowStruct.Field)
197+
for d.Next() {
198+
err := d.Scan(&tc.RowStruct.Field)
199199
if err != nil {
200200
t.Error(err)
201201
}
@@ -204,8 +204,8 @@ func TestStructArray(t *testing.T) {
204204
t.Errorf("expected value '%v' got '%v'", tc.expected, tc.RowStruct.Field)
205205
}
206206
}
207-
if parser.Err() != nil {
208-
t.Errorf("parser error: %w", err)
207+
if d.Err() != nil {
208+
t.Errorf("d error: %w", err)
209209
}
210210
})
211211
}

0 commit comments

Comments
 (0)