Skip to content

fix(cmd): validate --format before any network call (CONTRACT §4 spirit)#39

Merged
DTTerastar merged 1 commit into
mainfrom
feat/validate-format-hermetic
May 19, 2026
Merged

fix(cmd): validate --format before any network call (CONTRACT §4 spirit)#39
DTTerastar merged 1 commit into
mainfrom
feat/validate-format-hermetic

Conversation

@Terastar-Paperclip
Copy link
Copy Markdown
Contributor

Closes QUA-30. Follow-up to #36.

Why

PR #36 wired withings-export-cli into compat/formats, including the flagValidationIsHermetic subtest. It passed, but for the wrong reason: each data-producing subcommand's RunE called client.New() and the Withings HTTP path before validateFormat(). Under the compat test's HTTP_PROXY=127.0.0.1:1 env that HTTP round-trip failed with a dial-timeout, so the binary exited non-zero — making the subtest happy even though it never proved a parse-time failure was network-free.

The contract's §4 spirit (and the framework comment on the subtest) is "a flag-validation failure must not have already opened a connection by the time it exits non-zero." Today's withings behavior violates that. This PR closes the gap.

What

In each of cmd/{activity,intraday,measurements,sleep,workouts}.go, the first statement of RunE is now:

format, err := validateFormat(<sub>FormatFlag)
if err != nil {
    return err
}

The previous late-binding validateFormat call near the codec switch is removed; the format variable from the top is reused there. No other logic moves. No new dependencies. No changes to flag declarations, output shapes, or the help string.

Acceptance check (per the QUA-30 description)

Built the binary and ran every data-producing subcommand under the same no-network proxy env the compat suite uses:

HTTP_PROXY=http://127.0.0.1:1 HTTPS_PROXY=http://127.0.0.1:1
http_proxy=http://127.0.0.1:1 https_proxy=http://127.0.0.1:1
NO_PROXY= no_proxy= TZ=UTC HOME=<empty>
./withings-export <sub> --format obviously-not-a-format

Every subcommand now exits with:

  • exit code 1
  • empty stdout (§4 "stdout is data only")
  • stderr Error: unknown --format "obviously-not-a-format" (use markdown, json, or csv)

No dial-timeout error, no auth error — the parse fails first and that's all that runs.

Local go test -tags=compat -run TestContractFormats ./... still green: all 30 subtests (6 contract subtests × 5 subcommands) PASS, including the now-genuinely-hermetic FlagValidationIsHermetic. go vet ./... and go test ./... clean.

Re: the optional WITHINGS_API_BASE drop

The QUA-30 description proposed optionally dropping WITHINGS_API_BASE from the compat-test runner once parse failure was hermetic. Skipping that here — the happy-path subtests (JSONIsArray, CSVHasHeader, DefaultIsMarkdown) run the binary without HTTP_PROXY override, and they still need the stub origin via WITHINGS_API_BASE to reach a successful HTTP response. Removing it would break the data-path subtests. The hermetic claim is now enforced at the code level by the validate-first ordering, not by the env shape.

🤖 Generated with Claude Code

Each data-producing subcommand (activity, intraday, measurements,
sleep, workouts) was instantiating client.New() and hitting the
Withings API before calling validateFormat() on --format. An invalid
value like --format obviously-not-a-format would only fail after the
HTTP round-trip, so the compat/formats.flagValidationIsHermetic
subtest was passing for the wrong reason (network error masking the
parse failure under the no-net proxy env).

Move validateFormat() to the top of each RunE, before client.New().
Now an unknown --format exits non-zero with an empty stdout and the
real "unknown --format" message on stderr, with no auth load and no
dial attempt — verified per-subcommand under HTTP_PROXY=127.0.0.1:1.

Refs: quantcli/common QUA-30, CONTRACT §4 (output format).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@DTTerastar DTTerastar enabled auto-merge (squash) May 19, 2026 11:51
@DTTerastar DTTerastar merged commit d19480c into main May 19, 2026
7 checks passed
@DTTerastar DTTerastar deleted the feat/validate-format-hermetic branch May 19, 2026 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants