Skip to content

Throw WebServiceError instances and preserve error causes#1902

Merged
oschwald merged 2 commits into
mainfrom
greg/stf-803
Jun 24, 2026
Merged

Throw WebServiceError instances and preserve error causes#1902
oschwald merged 2 commits into
mainfrom
greg/stf-803

Conversation

@oschwald

@oschwald oschwald commented Jun 24, 2026

Copy link
Copy Markdown
Member

Summary

The web service client rejected with plain objects ({ code, error, status?, url }), which meant:

  • Errors were not Error instances (err instanceof Error was false, no stack trace).
  • The underlying error's cause was discarded. A Node fetch failure throws TypeError: fetch failed whose real reason (DNS failure, ECONNREFUSED, TLS error, etc.) lives in error.cause, but the client surfaced only FETCH_ERROR: TypeError - fetch failed.

This introduces a WebServiceError class that extends Error and carries code/error/status/url as own enumerable properties (so existing field access and JSON.stringify output are preserved), plus the standard cause. The cause is now captured at every throw site (fetch failure, timeout, response-body parse, bad-server-response parse). WebServiceError, ArgumentError, and the WebServiceClientError type are now exported from the package.

Backward compatibility

  • Preserved: err.code, err.error, err.status, err.url, and existing JSON.stringify(err) keys.
  • Additive: err instanceof Error, err.message (=== err.error), err.cause, a stack trace.
  • Breaking (already part of the unreleased 9.0.0): the rejected value is now an Error instance rather than a plain object.

Testing

  • Added src/errors.spec.ts covering the WebServiceError class.
  • Updated the client error tests to assert instanceof WebServiceError, the preserved fields, and the propagated cause.
  • npm run lint, full jest suite (183 tests), and prettier all pass.

A parallel PR applies the same change to @maxmind/geoip2-node.

Part of STF-803.

Summary by CodeRabbit

  • New Features
    • Request failures now reject Promises with WebServiceError (extends Error) using consistent fields: code, error (mapped to message), optional status, optional url, and optional underlying cause.
    • Exposes WebServiceError, ArgumentError, and the WebServiceClientError type for clearer consumer handling.
  • Bug Fixes
    • Preserves the original network/parsing error as cause, including for invalid or unexpected server payloads.
    • Standardizes error behavior across timeout, server errors, and invalid responses.
  • Documentation
    • Updated README and changelog to describe the new rejection behavior and error shapes.
  • Tests
    • Added/expanded coverage to validate error properties, naming, and serialization expectations.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@oschwald, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 26 minutes and 47 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ee8e4ed3-c208-4b48-a509-fd04bee18c15

📥 Commits

Reviewing files that changed from the base of the PR and between a0c1b69 and 717ed80.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • src/errors.spec.ts
  • src/errors.ts
  • src/index.ts
  • src/types.ts
  • src/webServiceClient.spec.ts
  • src/webServiceClient.ts
📝 Walkthrough

Walkthrough

Adds WebServiceError as the client error type, extends WebServiceClientError with cause, updates client failure paths to throw structured error instances, and refreshes exports, tests, and documentation.

Changes

WebServiceError Error Class

Layer / File(s) Summary
Error interface, WebServiceError class, and exports
src/types.ts, src/errors.ts, src/index.ts
WebServiceClientError gains cause?: unknown. WebServiceError extends Error implements WebServiceClientError is added with readonly code, error, status?, and url fields and a structured constructor. ArgumentError, WebServiceError, and WebServiceClientError are re-exported from the package index.
WebServiceClient throws WebServiceError instances
src/webServiceClient.ts
WebServiceClient now throws WebServiceError for timeout, fetch, JSON parse, and server-response failures, with cause preserved where available and validated error bodies required for server errors.
Error class unit tests and client spec updates
src/errors.spec.ts, src/webServiceClient.spec.ts
Adds tests for WebServiceError inheritance, field mapping, message/error coupling, cause, and serialization, plus client-spec assertions that expect standardized WebServiceError rejections.
README and CHANGELOG documentation
README.md, CHANGELOG.md
Updates the error-handling docs and changelog entry to describe WebServiceError, exported error types, and the cause field.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A fluffy bug hopped off the page,
And WebServiceError took the stage.
With code and cause, it lands just right,
A tidy bounce in error flight.
The burrow cheers — typed errors feel so bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: switching to WebServiceError and preserving underlying causes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch greg/stf-803

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request updates the web service client to throw errors as WebServiceError instances (which extend the built-in Error class) instead of plain objects, preserving the original error properties and adding support for the standard cause property to hold underlying errors. The WebServiceError and ArgumentError classes, along with the WebServiceClientError type, are now exported. The documentation and test suites have been updated accordingly. One piece of feedback suggests explicitly setting this.name to 'WebServiceError' instead of using this.constructor.name to avoid issues when the code is minified or bundled.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/errors.ts Outdated
options?: { cause?: unknown }
) {
super(properties.error, options);
this.name = this.constructor.name;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using this.constructor.name to set this.name is prone to breaking when the code is minified or bundled (e.g., using Webpack, Terser, or esbuild), as the class name WebServiceError may be renamed to a short, single-character name (like e or t). This can break error-handling logic that relies on checking error.name === 'WebServiceError'.\n\nIt is safer and more robust to explicitly assign the string literal 'WebServiceError' to this.name.

Suggested change
this.name = this.constructor.name;
this.name = 'WebServiceError';

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 163-167: The per-code `cause` note in the error code list is
incomplete and can mislead readers into thinking it only applies to
`FETCH_ERROR`. Update the list near the documented error codes so
`NETWORK_TIMEOUT` and `INVALID_RESPONSE_BODY` are also annotated with the
preserved `cause`, or remove the single-code inline comment and rely on the
broader note already given earlier in the README.

In `@src/errors.ts`:
- Around line 49-53: The WebServiceError constructor currently assigns
`this.name` directly, which makes `name` an own enumerable property and changes
`JSON.stringify` output. Update `WebServiceError` to keep `name` non-enumerable
when setting it, and apply the same fix consistently in `ArgumentError` if it
uses the same pattern, so serialized errors remain limited to `code`, `error`,
`status`, and `url`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3b493e53-15e4-4cfa-b6fd-bd8937343228

📥 Commits

Reviewing files that changed from the base of the PR and between 5a30629 and ebbf975.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • src/errors.spec.ts
  • src/errors.ts
  • src/index.ts
  • src/types.ts
  • src/webServiceClient.spec.ts
  • src/webServiceClient.ts

Comment thread README.md
Comment thread src/errors.ts Outdated

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/errors.ts`:
- Around line 49-52: The WebServiceClientError constructor is always defining an
own status property via this.status = properties.status, which makes status
present even when it should be omitted at runtime for FETCH_ERROR and
NETWORK_TIMEOUT. Update the WebServiceClientError initialization logic in
src/errors.ts so status is only assigned when a real value exists, keeping the
observable shape aligned with the optional contract while preserving the
existing handling of code, error, and url.
- Around line 4-9: Expose the structured error contract on ArgumentError in
src/errors.ts before it is re-exported through src/index.ts: update the
ArgumentError class so instances include the expected code and error fields, and
add an optional url field when applicable, alongside the existing name/message
setup. Make the constructor initialize these public properties consistently so
consumers can reliably inspect the error shape after importing ArgumentError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8d291b52-4e7d-4263-b500-89e59079f423

📥 Commits

Reviewing files that changed from the base of the PR and between ebbf975 and a0c1b69.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • src/errors.spec.ts
  • src/errors.ts
  • src/index.ts
  • src/types.ts
  • src/webServiceClient.spec.ts
  • src/webServiceClient.ts

Comment thread src/errors.ts
Comment on lines 4 to 9
export class ArgumentError extends Error {
constructor(message: string) {
super(message);
this.name = this.constructor.name;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Expose structured fields on ArgumentError before exporting it publicly.

src/index.ts now re-exports this class, but it still only exposes name and message. That leaves library consumers without the code / error contract this file is supposed to provide for errors.

Proposed fix
 export class ArgumentError extends Error {
+  public readonly code: string;
+  public readonly error: string;
+
   constructor(message: string) {
     super(message);
     this.name = this.constructor.name;
+    this.code = 'INVALID_ARGUMENT';
+    this.error = message;
   }
 }

As per coding guidelines, src/errors.ts: Ensure error objects include code, error, and optional url properties for proper error handling by library consumers.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export class ArgumentError extends Error {
constructor(message: string) {
super(message);
this.name = this.constructor.name;
}
}
export class ArgumentError extends Error {
public readonly code: string;
public readonly error: string;
constructor(message: string) {
super(message);
this.name = this.constructor.name;
this.code = 'INVALID_ARGUMENT';
this.error = message;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/errors.ts` around lines 4 - 9, Expose the structured error contract on
ArgumentError in src/errors.ts before it is re-exported through src/index.ts:
update the ArgumentError class so instances include the expected code and error
fields, and add an optional url field when applicable, alongside the existing
name/message setup. Make the constructor initialize these public properties
consistently so consumers can reliably inspect the error shape after importing
ArgumentError.

Source: Coding guidelines

Comment thread src/errors.ts
The web service client rejected with plain objects, which meant errors
were not Error instances and the underlying cause (for example, the
network error behind a FETCH_ERROR) was discarded.

Introduce a WebServiceError class that extends Error and carries the
existing code/error/status/url properties as own enumerable properties,
plus the standard cause. Capture the cause at every throw site. Export
WebServiceError, ArgumentError, and the WebServiceClientError type.

STF-803

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A FETCH_ERROR previously surfaced only "TypeError - fetch failed", with
the real reason (DNS failure, refused connection, TLS error, etc.) hidden
in the cause. Consumers that log only `code`/`error` never saw it.

Append the underlying cause's message to the error string so the reason
is visible without inspecting `cause`, which is still attached.

STF-803

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@mm-jpoole mm-jpoole left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Noice!

@oschwald oschwald merged commit d25fe89 into main Jun 24, 2026
16 checks passed
@oschwald oschwald deleted the greg/stf-803 branch June 24, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants