build-a-csv-parser / lesson-21.md
Lesson 21 · A streaming reader and headers

Draining the reader

The streaming reader and the whole-file Parse should never disagree, since they run the same engine. Today you add a drain-to-completion helper and prove the two agree on tricky input, the payoff that ties the streaming chapter to everything before it.

The goal

Add a read-all helper and show it produces exactly what Parse produces.

Start here - the target
TO DO
Scenario: Streaming and whole-file parsing agree
Giventhe input a,"b\nc"\nd,e, which has a quoted field with an embedded newline
Whena reader over it is drained to completion with a read-all helper
Thenthe result is [["a", "b\nc"], ["d", "e"]], identical to what Parse returns for the same input
Andthe helper stops cleanly at EOF and returns every record exactly once
Background

You now have two ways to read a CSV: Parse, which returns the whole table at once, and a Reader, which yields one record at a time. They must never disagree, because they are the same state machine consumed two different ways, and this lesson makes that guarantee explicit. Add a read-all helper that loops the reader to its EOF sentinel, collecting every record, so a caller who wants everything can get it from the streaming path too.

This is a payoff lesson: if the earlier chapters built the machine correctly, the helper needs almost no new logic and the equality with Parse simply holds, even on input with a quoted, multi-line field where the record boundary is not a plain newline. That agreement is the useful check, so assert it directly rather than manufacturing extra code. It also completes the reading half of the library: you can consume CSV in bulk or one row at a time, with or without a header, strictly or loosely. The next chapter turns the machine around and builds the writer, which you will hold to the same standard by round-tripping it against this reader.

Make it work
// ReadAll loops Read until the EOF sentinel, collecting records
func (r *Reader) ReadAll() ([][]string, error) {
// for { rec, err := r.Read(); if err == io.EOF { break }; ... append }
}
// the property to check: NewReader(s).ReadAll() deep-equals Parse(s)
CheckpointDONE
A read-all helper drains the reader and agrees with Parse on tricky input. Commit and stop here.