feat(npm): streampub top-level field handlers (part 3/8)
📦 What this MR does
Part 3/8 of the stack splitting the streampub streaming publish-envelope library (S11 Step 13). See Part 1/8 (!432) for the stack overview and the byte-identity contract against the reference, !420 (closed).
- Targets
10io/npm-local-step-13-part2of8(Part 2/8). GitLab retargets tomainas the parent merges. - Depends on Part 2/8 (set as a merge-request dependency).
This part adds the top-level envelope field handlers and routes the walk to them. The five envelope memory bounds and the streaming attachment pipeline still arrive in later parts.
walker.go—dispatchTopLevel+ thename/versions/dist-tagskey constants;runnow dispatches per key.readName(npm name-rule validation) andreadDistTags(captures the tag→version map, no count bound yet)._attachmentsstill falls through toskipValueuntil the attachment part.name.go— the npm package-name rules (scoped/unscoped segments, reserved names,encodeURIComponent-safe charset).versions.go—readVersions(exactly one entry; aborts on the second key before reading its value, S11 AC 47) andreadVersionValue(captures the per-version object as normalized JSON via ajsontext.Encoder). TheMaxEnvelopePackageJSONSizebyte-span bound is added with the other bounds in a later part.token.go— restoresreadStringValue(now called byreadName/readDistTags) and addsabortErr(used by the versions second-key abort; the bounds reuse it later).error.go— addserrPackageName+CodePackageNameInvalid, referenced for the first time here.
🧪 Tests
TestProcess_PackageNameInvalid(S11 AC 39, streaming-unit half): uppercase, leading dot/underscore, reserved name, non-URL-safe char, and over-214-byte names all surfacepackage_name_invalid.TestProcess_CapturesVersionPackageJSON: the per-version object is captured as normalized JSON.- Plus the shared envelope/tarball builders these need (
buildEnvelope,versionObject,buildTarball,wellFormedTarball).
Both exercise behavior the current code implements fully (name validation and version capture run before the not-yet-present attachment/shape stages), so no throwaway tests.
golangci-lint(repo-pinned v2.12) → 0 issuesGOEXPERIMENT=jsonv2 go test -race -count=1 ./internal/format/npm/streampub/...→ pass
🚩 Deviations from the reference (!420 (closed))
Carries forward the Part 1/8 error.go API improvements (type ProcessError, unexported code / wrapped) and the Part 2/8 six-cap validate. Every identifier uses *ProcessError, and abortErr returns *ProcessError.
-
Test label fix: the
TestProcess_PackageNameInvalidcase for"weird(name)"is relabelled from "non-url-safe paren" to "forbidden rune paren" — parentheses are URL-safe perencodeURIComponentand are rejected by theforbiddenNameRunescheck, noturlComponentSafe, so the original label was misleading. Test behavior is unchanged. -
Added test coverage (
a162eec,d566ad9):TestProcess_VersionsShapeInvalidcovers four versions-object shape failures, each assertingpublish_envelope_invalid(the two abort cases also assertingAbortOffset > 0):- empty-versions (
versions.go:22) and non-object-version (versions.go:58) — error returns that exist but are untested in the reference (400a21f'sTestProcess_EnvelopeShapeInvalidcovers missing name/versions/attachments and an attachment-key mismatch, but neither of these two). - the single-version-rule abort (AC 47): a two-version-entry case (the second-key abort; also drives the previously-unused
extraVersionsbuilder) and a duplicate-version-key case that pins the classification guarantee — the streaming abort fires before the decoder's duplicate-name check, so dup keys surface aspublish_envelope_invalid, notbad_request. The reference'sTestProcess_TwoVersionsAbortsEarly(part 4) covers the distinct-key size-abort but not the duplicate-key classification.
Surfaced in review of this part; test-only, no production change.
- empty-versions (
🔗 References
- Reference MR (read-only): !420 (closed)
- Related work item: #131 (closed) (not closed by this MR)