Skip to content

feat:[AH-2685]: fix npm error handling while upload#90

Open
abhinavcode wants to merge 1 commit into
mainfrom
AH-2685
Open

feat:[AH-2685]: fix npm error handling while upload#90
abhinavcode wants to merge 1 commit into
mainfrom
AH-2685

Conversation

@abhinavcode

@abhinavcode abhinavcode commented Feb 23, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling and messaging for NPM package uploads, providing clearer and more consistent feedback when validation fails.
  • Performance

    • Optimized attachment processing with streamlined data handling for improved upload efficiency.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Standardized error handling in NPM package parsing logic by replacing fmt.Errorf calls with usererror.BadRequestf for JSON parsing failures, updated error wrapping format from %w to %s, and introduced optimized attachment processing functions with aligned error handling patterns.

Changes

Cohort / File(s) Summary
Error Handling Standardization
registry/app/pkg/npm/local.go
Replaced fmt.Errorf with usererror.BadRequestf across parseAndUploadNPMPackage JSON field parsing error paths (_id, name, description, dist-tags, versions, readme, maintainers, etc.), updated error wrapping format from %w to %s, introduced processAttachmentsOptimized and processBase64DataOptimized functions with consistent BadRequestf error handling.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hops through error paths so bright,
Trading fmt.Errorf for BadRequest tonight,
With optimized attachment streams that flow,
Each error now caught with a proper hello!
Consistency reigns in this parse-and-upload show! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: improving error handling in NPM package upload functionality by replacing error types and adjusting error messaging.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch AH-2685

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
registry/app/pkg/npm/local.go (1)

483-483: ⚠️ Potential issue | 🟠 Major

processAttachmentsOptimized errors are fmt.Errorf, defeating the PR's standardisation goal

The PR converts all parseAndUploadNPMPackage field-parse failures to usererror.BadRequestf to ensure client-input errors surface as HTTP 400. However:

  1. Line 483 still uses fmt.Errorf("failed to process attachments: %w", err) instead of usererror.BadRequestf.
  2. Every error path inside the newly-introduced processAttachmentsOptimized (lines 512, 515, 526, 533, 539, 542, 553, 571, 577) uses plain fmt.Errorf.

Because the HTTP error middleware likely inspects the error chain via errors.As for *usererror.Error, errors that originate inside processAttachmentsOptimized (e.g., "expected '{' at start of _attachments", "no attachment data found") will be classified as server errors and return HTTP 500 to the client instead of HTTP 400.

Only errors propagated through processBase64DataOptimized (which does use usererror.BadRequestf) survive as 400 because of the %w chain at line 483.

🔧 Proposed fix — convert `processAttachmentsOptimized` errors to `usererror.BadRequestf`
-	return types.FileInfo{}, fmt.Errorf("failed to parse _attachments: %w", err)
+	return types.FileInfo{}, usererror.BadRequestf("failed to parse _attachments: %s", err.Error())
 ...
-	return types.FileInfo{}, fmt.Errorf("expected '{' at start of _attachments")
+	return types.FileInfo{}, usererror.BadRequestf("expected '{' at start of _attachments")
 ...
-	return types.FileInfo{}, fmt.Errorf("failed to parse JSON: %w", err)
+	return types.FileInfo{}, usererror.BadRequestf("failed to parse JSON: %s", err.Error())
 ...
-	return types.FileInfo{}, fmt.Errorf("expected string key in _attachments")
+	return types.FileInfo{}, usererror.BadRequestf("expected string key in _attachments")
 ...
-	return types.FileInfo{}, fmt.Errorf("failed to parse attachment %s: %w", attachmentKey, err)
+	return types.FileInfo{}, usererror.BadRequestf("failed to parse attachment %s: %s", attachmentKey, err.Error())
 ...
-	return types.FileInfo{}, fmt.Errorf("expected '{' for attachment %s", attachmentKey)
+	return types.FileInfo{}, usererror.BadRequestf("expected '{' for attachment %s", attachmentKey)
 ...
-	return types.FileInfo{}, fmt.Errorf("failed to parse attachment %s fields: %w", attachmentKey, err)
+	return types.FileInfo{}, usererror.BadRequestf("failed to parse attachment %s fields: %s", attachmentKey, err.Error())
 ...
-	return types.FileInfo{}, fmt.Errorf("failed to skip field %s: %w", field, err)
+	return types.FileInfo{}, usererror.BadRequestf("failed to skip field %s: %s", field, err.Error())
 ...
-	return types.FileInfo{}, fmt.Errorf("no attachment data found")
+	return types.FileInfo{}, usererror.BadRequestf("no attachment data found")

And line 483 can be simplified to direct propagation since all inner errors will already be usererror:

-			return types.FileInfo{}, fmt.Errorf("failed to process attachments: %w", err)
+			return types.FileInfo{}, err

Also applies to: 512-577

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@registry/app/pkg/npm/local.go` at line 483, The errors produced inside
processAttachmentsOptimized (and any fmt.Errorf wrappers returned from
parseAndUploadNPMPackage) must be converted to usererror.BadRequestf so client
input failures surface as HTTP 400: replace all fmt.Errorf(...) usages inside
processAttachmentsOptimized (the internal messages like "expected '{' at start
of _attachments", "no attachment data found", etc.) with usererror.BadRequestf
and ensure processBase64DataOptimized continues to return usererror errors; then
simplify the upstream return in parseAndUploadNPMPackage that currently wraps
with fmt.Errorf("failed to process attachments: %w", err) to just propagate the
error (or return err) since the inner errors will already be
usererror.BadRequestf.
🧹 Nitpick comments (1)
registry/app/pkg/npm/local.go (1)

523-523: Inconsistent io.EOF comparison — prefer errors.Is

Lines 523 and 550 use err == io.EOF (and line 651 in NewOptimizedJSONStringStreamReader uses err != io.EOF), while line 406 correctly uses errors.Is(err, io.EOF). Direct == comparisons miss wrapped EOF errors and are non-idiomatic in modern Go.

♻️ Proposed fix
-		if err == io.EOF || strings.Contains(err.Error(), "EOF") {
+		if errors.Is(err, io.EOF) || strings.Contains(err.Error(), "EOF") {

Apply the same change at line 550, and in NewOptimizedJSONStringStreamReader:

-		if err != nil && err != io.EOF {
+		if err != nil && !errors.Is(err, io.EOF) {
 ...
-		if err == io.EOF {
+		if errors.Is(err, io.EOF) {

Also applies to: 550-550

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@registry/app/pkg/npm/local.go` at line 523, Several EOF checks use direct
equality (err == io.EOF / err != io.EOF) which misses wrapped errors; replace
these with errors.Is(err, io.EOF) and !errors.Is(err, io.EOF) respectively
(e.g., the comparisons inside the stream-reading logic and in
NewOptimizedJSONStringStreamReader), and add the standard "errors" import if
missing so wrapped EOFs are detected consistently and idiomatically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@registry/app/pkg/npm/local.go`:
- Around line 596-614: The code reading raw bytes from decoder.Buffered() (via
streamReader.ReadByte()) after decoder.Token() doesn't tolerate optional JSON
whitespace; change the logic around the streamReader.ReadByte() calls (the block
that checks for ':' and then '"' using streamReader) to first loop-reading and
discarding ASCII whitespace (space, tab, CR, LF) until a non-whitespace byte is
found, then assert that the first non-whitespace is ':'; after consuming ':',
again skip any whitespace before asserting the next non-whitespace is '"' for
the string start; keep the same usererror.BadRequestf messages but report the
actual non-whitespace byte when expectations fail and reference attachmentKey in
the error as before.

---

Outside diff comments:
In `@registry/app/pkg/npm/local.go`:
- Line 483: The errors produced inside processAttachmentsOptimized (and any
fmt.Errorf wrappers returned from parseAndUploadNPMPackage) must be converted to
usererror.BadRequestf so client input failures surface as HTTP 400: replace all
fmt.Errorf(...) usages inside processAttachmentsOptimized (the internal messages
like "expected '{' at start of _attachments", "no attachment data found", etc.)
with usererror.BadRequestf and ensure processBase64DataOptimized continues to
return usererror errors; then simplify the upstream return in
parseAndUploadNPMPackage that currently wraps with fmt.Errorf("failed to process
attachments: %w", err) to just propagate the error (or return err) since the
inner errors will already be usererror.BadRequestf.

---

Nitpick comments:
In `@registry/app/pkg/npm/local.go`:
- Line 523: Several EOF checks use direct equality (err == io.EOF / err !=
io.EOF) which misses wrapped errors; replace these with errors.Is(err, io.EOF)
and !errors.Is(err, io.EOF) respectively (e.g., the comparisons inside the
stream-reading logic and in NewOptimizedJSONStringStreamReader), and add the
standard "errors" import if missing so wrapped EOFs are detected consistently
and idiomatically.
ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63620ab and 831a3fd.

📒 Files selected for processing (1)
  • registry/app/pkg/npm/local.go

Comment on lines 596 to 614
startByte, err := streamReader.ReadByte()
if err != nil {
return types.FileInfo{}, fmt.Errorf("failed to upload attachment %s: Error while reading : character: %w",
attachmentKey, err)
return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: Error while reading : character: %s",
attachmentKey, err.Error())
}
if startByte != ':' {
return types.FileInfo{}, fmt.Errorf("failed to upload attachment %s: Expected : character, got %c",
return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: Expected : character, got %c",
attachmentKey, startByte)
}

// Now expecting `"`, marking start of JSON string
startByte, err = streamReader.ReadByte()
if err != nil {
return types.FileInfo{}, fmt.Errorf("failed to upload"+
" attachment %s: Error while reading \" character: %w", attachmentKey, err)
return types.FileInfo{}, usererror.BadRequestf("failed to upload"+
" attachment %s: Error while reading \" character: %s", attachmentKey, err.Error())
}
if startByte != '"' {
return types.FileInfo{}, fmt.Errorf("failed to upload"+
return types.FileInfo{}, usererror.BadRequestf("failed to upload"+
" attachment %s: Expected \" character, got %c", attachmentKey, startByte)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Raw byte parsing after decoder.Buffered() doesn't tolerate whitespace in the JSON stream

After decoder.Token() returns the key "data", decoder.Buffered() returns a reader starting at the first byte after the key's closing ". Per RFC 8259, insignificant whitespace is legal around : and before the value. If the client sends pretty-printed JSON (e.g., "data" : "base64..."), streamReader.ReadByte() at line 596 returns a space character, not :, and the upload fails with a misleading usererror.BadRequestf("...Expected : character, got ").

Standard npm clients emit compact JSON, so this is low-risk in practice, but a defensive fix is straightforward:

🛡️ Proposed fix — skip optional whitespace before `:` and `"`
-	// Expecting `:` character first
-	startByte, err := streamReader.ReadByte()
-	if err != nil {
-		return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: Error while reading : character: %s",
-			attachmentKey, err.Error())
-	}
-	if startByte != ':' {
-		return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: Expected : character, got %c",
-			attachmentKey, startByte)
-	}
-
-	// Now expecting `"`, marking start of JSON string
-	startByte, err = streamReader.ReadByte()
-	if err != nil {
-		return types.FileInfo{}, usererror.BadRequestf("failed to upload"+
-			" attachment %s: Error while reading \" character: %s", attachmentKey, err.Error())
-	}
-	if startByte != '"' {
-		return types.FileInfo{}, usererror.BadRequestf("failed to upload"+
-			" attachment %s: Expected \" character, got %c", attachmentKey, startByte)
-	}
+	// Skip optional whitespace, then expect `:`
+	for {
+		startByte, err := streamReader.ReadByte()
+		if err != nil {
+			return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: error reading colon separator: %s",
+				attachmentKey, err.Error())
+		}
+		if startByte == ':' {
+			break
+		}
+		if startByte != ' ' && startByte != '\t' && startByte != '\n' && startByte != '\r' {
+			return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: expected ':' separator, got %c",
+				attachmentKey, startByte)
+		}
+	}
+	// Skip optional whitespace, then expect `"`
+	for {
+		startByte, err := streamReader.ReadByte()
+		if err != nil {
+			return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: error reading opening quote: %s",
+				attachmentKey, err.Error())
+		}
+		if startByte == '"' {
+			break
+		}
+		if startByte != ' ' && startByte != '\t' && startByte != '\n' && startByte != '\r' {
+			return types.FileInfo{}, usererror.BadRequestf("failed to upload attachment %s: expected '\"' opening quote, got %c",
+				attachmentKey, startByte)
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@registry/app/pkg/npm/local.go` around lines 596 - 614, The code reading raw
bytes from decoder.Buffered() (via streamReader.ReadByte()) after
decoder.Token() doesn't tolerate optional JSON whitespace; change the logic
around the streamReader.ReadByte() calls (the block that checks for ':' and then
'"' using streamReader) to first loop-reading and discarding ASCII whitespace
(space, tab, CR, LF) until a non-whitespace byte is found, then assert that the
first non-whitespace is ':'; after consuming ':', again skip any whitespace
before asserting the next non-whitespace is '"' for the string start; keep the
same usererror.BadRequestf messages but report the actual non-whitespace byte
when expectations fail and reference attachmentKey in the error as before.

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