feat(csv): read a csv the way the caller says to - #667
Merged
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1c21594d32
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
andiwand
force-pushed
the
feat/csv-probe
branch
from
August 9, 2026 08:17
528cb2f to
8e08e93
Compare
andiwand
force-pushed
the
feat/csv-options
branch
from
August 9, 2026 08:17
1c21594 to
9250683
Compare
andiwand
force-pushed
the
feat/csv-probe
branch
from
August 9, 2026 08:33
8e08e93 to
e1e29b7
Compare
andiwand
force-pushed
the
feat/csv-options
branch
from
August 9, 2026 08:33
9250683 to
08fad77
Compare
andiwand
force-pushed
the
feat/csv-probe
branch
2 times, most recently
from
August 9, 2026 10:46
7b05289 to
76b0591
Compare
andiwand
force-pushed
the
feat/csv-options
branch
from
August 9, 2026 10:49
08fad77 to
8026aa2
Compare
Detection is a guess, and a caller who knows the file should not have to argue with it. `CsvOptions` carries an encoding, a separator and a quote, each optional: unset means detect, set means take as given. The shape follows `decrypt` — an immutable handle deriving another handle, narrowed on the concrete type — so `odr::open` is untouched, no new overload across six `open` signatures and six `DecodedFile` constructors, and the bindings will have the call pattern already. `CsvFile::options()` returns every field resolved, so a caller can show what was detected and offer to change it, which is the flow this is for: open, see it is wrong, adjust, reopen. What it does *not* copy is `decrypt`'s state machine. An encrypted file cannot be rendered at all until the password arrives, and `capabilities()` masks it accordingly; a csv without options always has a guess, so there is no state where the object cannot be derived, and inventing one would force every caller to handle a case that never occurs. Nor does the failure map. A wrong password leaves nothing to hand back, whereas a declared separator makes the parser total: a one-column file, prose, an empty file and a truncated quoted field all read as some csv. What is left to fail is an incoherent dialect — a separator equal to the quote, or a line break as a separator — which is a caller mistake and an `invalid_argument`. `NoCsvFile` is now purely a detection failure, which is what its name always claimed. `CsvFile::from_file` is the way in for a file detection would have refused; without it the only handle able to override a verdict would be one you could not obtain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSgWdLTSLCWDeFvbVwZDVU
andiwand
force-pushed
the
feat/csv-options
branch
from
August 9, 2026 10:50
8026aa2 to
90dfbac
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
Third of five stacked PRs. Based on #666 (which is based on #665). Plan:
src/odr/internal/csv/PLAN.md.Detection is a guess. A caller who knows the file should not have to argue with it.
Unset means detect; set means take as given.
Shaped like
decryptAn immutable handle deriving another handle, narrowed on the concrete type —
CsvFile::with_options()next toDocumentFile::decrypt(). That keepsodr::openuntouched: no new overload across sixopensignatures and sixDecodedFileconstructors, and the bindings already have the call pattern fromdecrypt(wasm_file.cpp:86,jni_file.cpp:162).options()returns every field resolved, so a caller can show "detected;, UTF-8" and offer to change it. That is the flow this exists for: open → see it is wrong → adjust → reopen.What it deliberately does not copy
The state machine.
EncryptionStategates rendering because an encrypted file cannot be rendered at all (file.cpp:173). A csv without options always has a guess, so there is no state where the object cannot be derived. Anoptions_required()would invent a gate the format does not have and force every caller to handle a case that never occurs.The failure. A wrong password leaves nothing to hand back. A declared separator makes the parser total — a one-column file, prose, an empty file, a truncated quoted field all read as some csv:
One column is no evidence of a csv, and a perfectly good csv once someone says so. What is left to fail is an incoherent dialect — separator equal to the quote, or a line break as a separator — an
invalid_argument, a caller mistake rather than bad input.NoCsvFileis now purely a detection failure, which is what its name always claimed.from_fileWithout it, the only handle able to override a verdict would be one you could not obtain — you cannot
with_optionsa file that refused to open.DocumentFile::from_disk/from_memoryare the precedent for a static factory.Not here
header_row— would be inert until a sheet exists to mark a header on. Arrives with stage 4, or not at all.CsvFileis the handle stage 4 hangsdocument()off, and stage 4 also settles the file category; binding now means binding twice. One pass after the shape settles, tracked as its own stage.Full suite green (566 unit, 234 reference-output).