Skip to content

Commit

Permalink
add generics to chan and callback-using functions
Browse files Browse the repository at this point in the history
  • Loading branch information
lzambarda committed Feb 9, 2024
1 parent b87c2d0 commit 9f70be1
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 94 deletions.
143 changes: 57 additions & 86 deletions csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ func MarshalWithoutHeaders(in interface{}, out io.Writer) (err error) {
}

// MarshalChan returns the CSV read from the channel.
func MarshalChan(c <-chan interface{}, out CSVWriter) error {
func MarshalChan[T any](c <-chan T, out CSVWriter) error {
return writeFromChan(out, c, false)
}

// MarshalChanWithoutHeaders returns the CSV read from the channel.
func MarshalChanWithoutHeaders(c <-chan interface{}, out CSVWriter) error {
func MarshalChanWithoutHeaders[T any](c <-chan T, out CSVWriter) error {
return writeFromChan(out, c, true)
}

Expand Down Expand Up @@ -278,24 +278,23 @@ func UnmarshalCSVToMap(in CSVReader, out interface{}) error {

// UnmarshalToChan parses the CSV from the reader and send each value in the chan c.
// The channel must have a concrete type.
func UnmarshalToChan(in io.Reader, c interface{}) error {
func UnmarshalToChan[T any](in io.Reader, c chan<- T) error {
if c == nil {
return fmt.Errorf("goscv: channel is %v", c)
}
return readEach(newSimpleDecoderFromReader(in), nil, c)
}

// UnmarshalToChanWithErrorHandler parses the CSV from the reader in the interface.
func UnmarshalToChanWithErrorHandler(in io.Reader, errorHandler ErrorHandler, c interface{}) error {
// UnmarshalToChanWithErrorHandler parses the CSV from the reader in the channel.
func UnmarshalToChanWithErrorHandler[T any](in io.Reader, errorHandler ErrorHandler, c chan<- T) error {
if c == nil {
return fmt.Errorf("goscv: channel is %v", c)
}
return readEach(newSimpleDecoderFromReader(in), errorHandler, c)
}

// UnmarshalToChanWithoutHeaders parses the CSV from the reader and send each value in the chan c.
// The channel must have a concrete type.
func UnmarshalToChanWithoutHeaders(in io.Reader, c interface{}) error {
func UnmarshalToChanWithoutHeaders[T any](in io.Reader, c chan<- T) error {
if c == nil {
return fmt.Errorf("goscv: channel is %v", c)
}
Expand All @@ -304,125 +303,99 @@ func UnmarshalToChanWithoutHeaders(in io.Reader, c interface{}) error {

// UnmarshalDecoderToChan parses the CSV from the decoder and send each value in the chan c.
// The channel must have a concrete type.
func UnmarshalDecoderToChan(in SimpleDecoder, c interface{}) error {
func UnmarshalDecoderToChan[T any](in SimpleDecoder, c chan<- T) error {
if c == nil {
return fmt.Errorf("goscv: channel is %v", c)
}
return readEach(in, nil, c)
}

// UnmarshalStringToChan parses the CSV from the string and send each value in the chan c.
// The channel must have a concrete type.
func UnmarshalStringToChan(in string, c interface{}) error {
// UnmarshalStringToChan parses the CSV from the string and send each value in
// the chan c.
func UnmarshalStringToChan[T any](in string, c chan<- T) error {
return UnmarshalToChan(strings.NewReader(in), c)
}

// UnmarshalBytesToChan parses the CSV from the bytes and send each value in the chan c.
// The channel must have a concrete type.
func UnmarshalBytesToChan(in []byte, c interface{}) error {
// UnmarshalBytesToChan parses the CSV from the bytes and send each value in the
// chan c.
func UnmarshalBytesToChan[T any](in []byte, c chan<- T) error {
return UnmarshalToChan(bytes.NewReader(in), c)
}

// UnmarshalToCallback parses the CSV from the reader and send each value to the given func f.
// The func must look like func(Struct).
func UnmarshalToCallback(in io.Reader, f interface{}) error {
valueFunc := reflect.ValueOf(f)
t := reflect.TypeOf(f)
if t.NumIn() != 1 {
return fmt.Errorf("the given function must have exactly one parameter")
}
// UnmarshalToCallback parses the CSV from the reader and send each value to the
// given func callback.
func UnmarshalToCallback[T any](in io.Reader, callback func(T) error) error {
cerr := make(chan error)
c := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, t.In(0)), 0)
c := make(chan T)
go func() {
cerr <- UnmarshalToChan(in, c.Interface())
cerr <- UnmarshalToChan(in, c)
}()
for {
select {
case err := <-cerr:
return err
default:
}
v, notClosed := c.Recv()
if !notClosed || v.Interface() == nil {
v, notClosed := <-c
if !notClosed {
break
}
callResults := valueFunc.Call([]reflect.Value{v})
// if last returned value from Call() is an error, return it
if len(callResults) > 0 {
if err, ok := callResults[len(callResults)-1].Interface().(error); ok {
return err
}
err := callback(v)
if err != nil {
return err
}
}
return <-cerr
}

// UnmarshalDecoderToCallback parses the CSV from the decoder and send each value to the given func f.
// The func must look like func(Struct).
func UnmarshalDecoderToCallback(in SimpleDecoder, f interface{}) error {
valueFunc := reflect.ValueOf(f)
t := reflect.TypeOf(f)
if t.NumIn() != 1 {
return fmt.Errorf("the given function must have exactly one parameter")
}
// UnmarshalDecoderToCallback parses the CSV from the decoder and send each value to the given func callback.
func UnmarshalDecoderToCallback[T any](in SimpleDecoder, callback func(T) error) error {
cerr := make(chan error)
c := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, t.In(0)), 0)
c := make(chan T)
go func() {
cerr <- UnmarshalDecoderToChan(in, c.Interface())
cerr <- UnmarshalDecoderToChan(in, c)
}()
for {
select {
case err := <-cerr:
return err
default:
}
v, notClosed := c.Recv()
if !notClosed || v.Interface() == nil {
v, notClosed := <-c
if !notClosed {
break
}
valueFunc.Call([]reflect.Value{v})
err := callback(v)
if err != nil {
return err
}
}
return <-cerr
}

// UnmarshalBytesToCallback parses the CSV from the bytes and send each value to the given func f.
// The func must look like func(Struct).
func UnmarshalBytesToCallback(in []byte, f interface{}) error {
return UnmarshalToCallback(bytes.NewReader(in), f)
// UnmarshalBytesToCallback parses the CSV from the bytes and send each value to
// the given func callback.
func UnmarshalBytesToCallback[T any](in []byte, callback func(T) error) error {
return UnmarshalToCallback(bytes.NewReader(in), callback)
}

// UnmarshalStringToCallback parses the CSV from the string and send each value to the given func f.
// The func must look like func(Struct).
func UnmarshalStringToCallback(in string, c interface{}) (err error) {
return UnmarshalToCallback(strings.NewReader(in), c)
// UnmarshalStringToCallback parses the CSV from the string and send each value
// to the given func callback.
func UnmarshalStringToCallback[T any](in string, callback func(T) error) error {
return UnmarshalToCallback(strings.NewReader(in), callback)
}

// UnmarshalToCallbackWithError parses the CSV from the reader and
// send each value to the given func f.
// send each value to the given func callback.
//
// If func returns error, it will stop processing, drain the
// parser and propagate the error to caller.
//
// The func must look like func(Struct) error.
func UnmarshalToCallbackWithError(in io.Reader, f interface{}) error {
valueFunc := reflect.ValueOf(f)
t := reflect.TypeOf(f)
if t.NumIn() != 1 {
return fmt.Errorf("the given function must have exactly one parameter")
}
if t.NumOut() != 1 {
return fmt.Errorf("the given function must have exactly one return value")
}
if !isErrorType(t.Out(0)) {
return fmt.Errorf("the given function must only return error")
}

func UnmarshalToCallbackWithError[T any](in io.Reader, callback func(T) error) error {
cerr := make(chan error)
c := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, t.In(0)), 0)
c := make(chan T)
go func() {
cerr <- UnmarshalToChan(in, c.Interface())
cerr <- UnmarshalToChan(in, c)
}()

var fErr error
for {
select {
Expand All @@ -433,39 +406,37 @@ func UnmarshalToCallbackWithError(in io.Reader, f interface{}) error {
return fErr
default:
}
v, notClosed := c.Recv()
if !notClosed || v.Interface() == nil {
v, notClosed := <-c
if !notClosed {
if err := <-cerr; err != nil {
fErr = err
}
break
}

// callback f has already returned an error, stop processing but keep draining the chan c
// callback has already returned an error, stop processing but keep draining the chan c
if fErr != nil {
continue
}

results := valueFunc.Call([]reflect.Value{v})

// If the callback f returns an error, stores it and returns it in future.
errValue := results[0].Interface()
if errValue != nil {
fErr = errValue.(error)
// If the callback returns an error, stores it and returns it in future.
err := callback(v)
if err != nil {
fErr = err
}
}
return fErr
}

// UnmarshalBytesToCallbackWithError parses the CSV from the bytes and
// send each value to the given func f.
// send each value to the given func callback.
//
// If func returns error, it will stop processing, drain the
// parser and propagate the error to caller.
//
// The func must look like func(Struct) error.
func UnmarshalBytesToCallbackWithError(in []byte, f interface{}) error {
return UnmarshalToCallbackWithError(bytes.NewReader(in), f)
func UnmarshalBytesToCallbackWithError[T any](in []byte, callback func(T) error) error {
return UnmarshalToCallbackWithError(bytes.NewReader(in), callback)
}

// UnmarshalStringToCallbackWithError parses the CSV from the string and
Expand All @@ -475,8 +446,8 @@ func UnmarshalBytesToCallbackWithError(in []byte, f interface{}) error {
// parser and propagate the error to caller.
//
// The func must look like func(Struct) error.
func UnmarshalStringToCallbackWithError(in string, c interface{}) (err error) {
return UnmarshalToCallbackWithError(strings.NewReader(in), c)
func UnmarshalStringToCallbackWithError[T any](in string, callback func(T) error) error {
return UnmarshalToCallbackWithError(strings.NewReader(in), callback)
}

// CSVToMap creates a simple map from a CSV of 2 columns.
Expand Down
6 changes: 3 additions & 3 deletions csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (

func TestUnmarshalToCallback_ReaderError(t *testing.T) {
type Dummy struct{}
var reader = &errorReader{}
reader := &errorReader{}

err := UnmarshalToCallback(reader, func(Dummy) {})
err := UnmarshalToCallback(reader, func(Dummy) error { return nil })
if !errors.Is(err, readerErr) {
t.Error("UnmarshalToCallback should return first reader error")
}

err = UnmarshalDecoderToCallback(newSimpleDecoderFromReader(reader), func(Dummy) {})
err = UnmarshalDecoderToCallback(newSimpleDecoderFromReader(reader), func(Dummy) error { return nil })
if !errors.Is(err, readerErr) {
t.Error("UnmarshalDecoderToCallback should return first reader error")
}
Expand Down
6 changes: 2 additions & 4 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import (
"reflect"
)

var (
ErrChannelIsClosed = errors.New("channel is closed")
)
var ErrChannelIsClosed = errors.New("channel is closed")

type encoder struct {
out io.Writer
Expand All @@ -19,7 +17,7 @@ func newEncoder(out io.Writer) *encoder {
return &encoder{out}
}

func writeFromChan(writer CSVWriter, c <-chan interface{}, omitHeaders bool) error {
func writeFromChan[T any](writer CSVWriter, c <-chan T, omitHeaders bool) error {
// Get the first value. It wil determine the header structure.
firstValue, ok := <-c
if !ok {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/gocarina/gocsv

go 1.13
go 1.18

0 comments on commit 9f70be1

Please sign in to comment.