func.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package reader
  2. import (
  3. "context"
  4. "io"
  5. )
  6. // nextFunc produces the next item for the FuncReader to read.
  7. //
  8. // It should return:
  9. // 1. (item, nil) when an item is available and more may remain
  10. // 2. (zero, io.EOF) when there are no more items remaining
  11. // 3. (zero, err) when there is an error getting the next item
  12. //
  13. // io.EOF is the end-of-stream signal; any other error is a failure. Any error
  14. // is terminal. Once a non-nil error is returned it is latched and re-returned
  15. // by Read.
  16. //
  17. // Do NOT return an item with io.EOF, as it will NOT be read.
  18. type nextFunc[T any] func() (item T, err error)
  19. // FuncReader adapts an arbitrary "next item" function into a Reader[T]. It
  20. // optionally accepts an io.Closer, and will close it in Close(). If a
  21. // terminal error is returned from next() it will hold that error and return
  22. // it on Read() indefinitely (e.g. io.EOF when the next() source is exhausted).
  23. type FuncReader[T any] struct {
  24. next nextFunc[T]
  25. closer io.Closer
  26. err error
  27. }
  28. // NewFuncReader returns a Reader[T] driven by next. If a non-nil closer is
  29. // provided, it will be closed by Close().
  30. func NewFuncReader[T any](next nextFunc[T], closer io.Closer) *FuncReader[T] {
  31. return &FuncReader[T]{next: next, closer: closer}
  32. }
  33. // Read fills dst with up to len(dst) items pulled from next() func, returning
  34. // the number of items read. Returns io.EOF with the final batch, and will
  35. // continue to return n=0 and the terminal error on subsequent reads.
  36. func (r *FuncReader[T]) Read(ctx context.Context, dst []T) (int, error) {
  37. if err := ctx.Err(); err != nil {
  38. return 0, err
  39. }
  40. if r.err != nil {
  41. return 0, r.err
  42. }
  43. n := 0
  44. for n < len(dst) {
  45. // Re-check cancellation each iteration.
  46. if err := ctx.Err(); err != nil {
  47. return n, err
  48. }
  49. item, err := r.next()
  50. if err != nil {
  51. // Terminal error (io.EOF for normal exhaustion, or a real failure).
  52. // Latch it and fold it into the batch gathered so far.
  53. r.err = err
  54. return n, err
  55. }
  56. // An item is available and more may remain.
  57. dst[n] = item
  58. n++
  59. }
  60. // dst is full, but more items may remain
  61. return n, nil
  62. }
  63. // Close closes the underlying source if it implements io.Closer, and is a no-op
  64. // otherwise. It does not disturb the reader's terminal state, so it is safe to
  65. // Close early to abandon a partially-read stream.
  66. func (r *FuncReader[T]) Close() error {
  67. if r.closer == nil {
  68. return nil
  69. }
  70. return r.closer.Close()
  71. }