Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughClient error handling updated to parse and route nested JSON error payloads (including Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/xrpl/HISTORY.mdpackages/xrpl/src/client/connection.tspackages/xrpl/test/connection.test.tspackages/xrpl/test/createMockRippled.ts
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: ckeshava
Repo: XRPLF/xrpl.js PR: 2874
File: packages/xrpl/test/integration/transactions/permissionedDomain.test.ts:25-80
Timestamp: 2025-01-08T02:12:28.489Z
Learning: The rippled C++ implementation (PR #5161) includes comprehensive test coverage for PermissionedDomain (XLS-80d) error cases. The JS SDK tests focus on the happy path since the error cases are already validated at the rippled level, following the principle of not duplicating complex validation testing across SDK implementations.
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts:29-118
Timestamp: 2024-12-06T19:25:15.376Z
Learning: In the XRPLF/xrpl.js TypeScript client library, when writing tests (e.g., in `packages/xrpl/test/integration/transactions/`), we generally do not need to test rippled server behaviors, because those behaviors are covered by rippled's own integration and unit tests.
📚 Learning: 2024-12-06T19:25:15.376Z
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts:29-118
Timestamp: 2024-12-06T19:25:15.376Z
Learning: In the XRPLF/xrpl.js TypeScript client library, when writing tests (e.g., in `packages/xrpl/test/integration/transactions/`), we generally do not need to test rippled server behaviors, because those behaviors are covered by rippled's own integration and unit tests.
Applied to files:
packages/xrpl/test/createMockRippled.tspackages/xrpl/src/client/connection.tspackages/xrpl/test/connection.test.ts
📚 Learning: 2024-12-06T19:27:11.147Z
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/integration/transactions/clawback.test.ts:165-178
Timestamp: 2024-12-06T19:27:11.147Z
Learning: In the integration tests for `clawback.test.ts`, it's acceptable to use `ts-expect-error` to bypass type checking when verifying ledger entries, and no additional type safety improvements are needed.
Applied to files:
packages/xrpl/test/createMockRippled.tspackages/xrpl/test/connection.test.ts
📚 Learning: 2024-12-06T18:44:55.095Z
Learnt from: shawnxie999
Repo: XRPLF/xrpl.js PR: 2661
File: packages/xrpl/test/models/MPTokenAuthorize.test.ts:60-71
Timestamp: 2024-12-06T18:44:55.095Z
Learning: In the XRPL.js library's TypeScript test file `packages/xrpl/test/models/MPTokenAuthorize.test.ts`, negative test cases for invalid `Account` address format, invalid `Holder` address format, invalid `MPTokenIssuanceID` format, and invalid flag combinations are not necessary.
Applied to files:
packages/xrpl/test/connection.test.ts
📚 Learning: 2025-01-08T02:12:28.489Z
Learnt from: ckeshava
Repo: XRPLF/xrpl.js PR: 2874
File: packages/xrpl/test/integration/transactions/permissionedDomain.test.ts:25-80
Timestamp: 2025-01-08T02:12:28.489Z
Learning: The rippled C++ implementation (PR #5161) includes comprehensive test coverage for PermissionedDomain (XLS-80d) error cases. The JS SDK tests focus on the happy path since the error cases are already validated at the rippled level, following the principle of not duplicating complex validation testing across SDK implementations.
Applied to files:
packages/xrpl/test/connection.test.ts
📚 Learning: 2025-02-12T23:28:55.377Z
Learnt from: mvadari
Repo: XRPLF/xrpl.js PR: 2895
File: packages/xrpl/test/models/clawback.test.ts:0-0
Timestamp: 2025-02-12T23:28:55.377Z
Learning: The `validate` function in xrpl.js is synchronous and should be tested using `assert.doesNotThrow` rather than async assertions.
Applied to files:
packages/xrpl/test/connection.test.ts
🧬 Code graph analysis (2)
packages/xrpl/test/createMockRippled.ts (2)
packages/xrpl/src/client/connection.ts (1)
request(291-319)packages/xrpl/src/client/index.ts (1)
request(340-359)
packages/xrpl/test/connection.test.ts (3)
packages/xrpl/src/client/connection.ts (1)
request(291-319)packages/xrpl/src/client/index.ts (1)
request(340-359)packages/xrpl/src/models/methods/index.ts (1)
AccountInfoRequest(523-523)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (4)
packages/xrpl/HISTORY.md (1)
7-9: LGTM!The changelog entry accurately documents the error handling improvement and follows the existing format.
packages/xrpl/test/createMockRippled.ts (1)
24-29: LGTM!The logic correctly injects the request ID into the error value payload to support the new error handling path in connection.ts. Since this is test utility code with controlled inputs, the implementation is appropriate.
packages/xrpl/src/client/connection.ts (1)
354-354: LGTM!Good improvement to provide a fallback when
error_messageis missing, ensuring errors likeslowDowndisplay a meaningful message even whenerror_messageis not set.packages/xrpl/test/connection.test.ts (1)
1033-1050: LGTM!Good test coverage for the new
jsonInvaliderror handling path. The test properly validates that the error is correctly propagated with the expected name and message.
| if (data.type === 'error' && data.value != null) { | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed | ||
| const parsedValue: Record<string, unknown> = JSON.parse( | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| data.value as string, | ||
| ) | ||
| if (parsedValue.id != null) { | ||
| this.requestManager.reject( | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| parsedValue.id as string | number, | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| new Error(data.error as string), | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Wrap JSON.parse in a try-catch to handle malformed data.value.
If data.value contains invalid JSON, JSON.parse will throw an uncaught exception. While rippled should always send valid JSON, defensive error handling would prevent the client from crashing on unexpected malformed responses.
🛡️ Proposed fix
if (data.type === 'error' && data.value != null) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
- const parsedValue: Record<string, unknown> = JSON.parse(
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
- data.value as string,
- )
- if (parsedValue.id != null) {
- this.requestManager.reject(
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
- parsedValue.id as string | number,
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
- new Error(data.error as string),
- )
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed
+ const parsedValue: Record<string, unknown> = JSON.parse(
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
+ data.value as string,
+ )
+ if (parsedValue.id != null) {
+ this.requestManager.reject(
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
+ parsedValue.id as string | number,
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true
+ new Error(data.error as string),
+ )
+ }
+ } catch (error) {
+ if (error instanceof Error) {
+ this.emit('error', 'badMessage', error.message, data)
+ }
}
}📝 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.
| if (data.type === 'error' && data.value != null) { | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed | |
| const parsedValue: Record<string, unknown> = JSON.parse( | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| data.value as string, | |
| ) | |
| if (parsedValue.id != null) { | |
| this.requestManager.reject( | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| parsedValue.id as string | number, | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| new Error(data.error as string), | |
| ) | |
| } | |
| } | |
| if (data.type === 'error' && data.value != null) { | |
| try { | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed | |
| const parsedValue: Record<string, unknown> = JSON.parse( | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| data.value as string, | |
| ) | |
| if (parsedValue.id != null) { | |
| this.requestManager.reject( | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| parsedValue.id as string | number, | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| new Error(data.error as string), | |
| ) | |
| } | |
| } catch (error) { | |
| if (error instanceof Error) { | |
| this.emit('error', 'badMessage', error.message, data) | |
| } | |
| } | |
| } |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/xrpl/HISTORY.md (1)
5-15:⚠️ Potential issue | 🟡 MinorChangelog entry belongs under
## Unreleased, not## 4.6.0.The new
### Fixedblock was added inside the already-released4.6.0section (tagged 2026-02-12), while the## Unreleasedsection directly above it is empty. Because this PR is still open, any code it introduces will ship in a future release, not4.6.0. Misplacing it here will cause the entry to be missed or duplicated when the next version is cut.📝 Proposed fix
## Unreleased +### Fixed +* Better error handling in the `Client` for edge case errors + ## 4.6.0 (2026-02-12) ### Added * Add `faucetProtocol` (http or https) option to `fundWallet` method. Makes `fundWallet` work with locally running faucet servers. * Add `signLoanSetByCounterparty` and `combineLoanSetCounterpartySigners` helper functions to sign and combine LoanSet transactions signed by the counterparty. * Add newly added fields to `Loan`, `LoanBroker` and `Vault` ledger objects and lending protocol related transaction types. -### Fixed -* Better error handling in the `Client` for edge case errors - ## 4.5.0 (2025-12-16)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/xrpl/HISTORY.md` around lines 5 - 15, Move the misplaced changelog entry "Better error handling in the `Client` for edge case errors" from the released section `## 4.6.0 (2026-02-12)` into the `## Unreleased` section: locate the `### Fixed` block currently under `## 4.6.0 (2026-02-12)` in HISTORY.md and cut that bullet (or the entire `### Fixed` subsection if it's only the one entry) and paste it under the `## Unreleased` header, preserving the `### Fixed` heading if needed so the entry appears in the next release notes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@packages/xrpl/HISTORY.md`:
- Around line 5-15: Move the misplaced changelog entry "Better error handling in
the `Client` for edge case errors" from the released section `## 4.6.0
(2026-02-12)` into the `## Unreleased` section: locate the `### Fixed` block
currently under `## 4.6.0 (2026-02-12)` in HISTORY.md and cut that bullet (or
the entire `### Fixed` subsection if it's only the one entry) and paste it under
the `## Unreleased` header, preserving the `### Fixed` heading if needed so the
entry appears in the next release notes.
There was a problem hiding this comment.
Pull request overview
Improves how xrpl.js handles certain edge-case rippled error frames (e.g., jsonInvalid) so requests don’t appear to hang waiting for a standard type: "response" envelope.
Changes:
- Add special handling for
type: "error"frames containing a JSON-encodedvaluepayload to reject the pending request. - Update the mock rippled server to inject request ids into
error.valuepayloads for tests. - Add a unit test covering
jsonInvalidbehavior and updateHISTORY.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| packages/xrpl/src/client/connection.ts | Adds type: "error" + value parsing to reject pending requests; tweaks emitted error message fallback. |
| packages/xrpl/test/createMockRippled.ts | Adjusts mock response creation to place id inside error.value JSON payload. |
| packages/xrpl/test/connection.test.ts | Adds a unit test asserting jsonInvalid errors reject instead of hanging. |
| packages/xrpl/HISTORY.md | Adds an unreleased changelog entry; also modifies a 4.5.0 bullet. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed | ||
| const parsedValue: Record<string, unknown> = JSON.parse( | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| data.value as string, | ||
| ) |
There was a problem hiding this comment.
data.value is JSON.parsed without a try/catch or even checking that it's a string. If rippled ever sends a non-JSON string (or an object) in value, this will throw and can crash the WS message handler. Please guard with a type check and wrap the parse in error handling that emits a structured 'badMessage' error (similar to the existing top-level JSON.parse handling).
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed | |
| const parsedValue: Record<string, unknown> = JSON.parse( | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| data.value as string, | |
| ) | |
| if (typeof data.value !== 'string') { | |
| this.emit( | |
| 'error', | |
| 'badMessage', | |
| 'Expected error value to be a JSON string', | |
| data, | |
| ) | |
| return | |
| } | |
| let parsedValue: Record<string, unknown> | |
| try { | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- needed | |
| parsedValue = JSON.parse(data.value) as Record<string, unknown> | |
| } catch (error) { | |
| if (error instanceof Error) { | |
| this.emit('error', 'badMessage', error.message, data.value) | |
| } else { | |
| this.emit('error', 'badMessage', error, error) | |
| } | |
| return | |
| } |
| this.requestManager.reject( | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| parsedValue.id as string | number, | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| new Error(data.error as string), | ||
| ) |
There was a problem hiding this comment.
this.requestManager.reject(...) can throw (it throws XrplError when there is no pending promise with that id). In this new data.type === 'error' path that exception is currently unhandled, so an unexpected/late error frame could take down the message handler. Wrap the reject call in a try/catch and surface failures via this.emit('error', 'badMessage', ...) or similar.
| this.requestManager.reject( | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| parsedValue.id as string | number, | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| new Error(data.error as string), | |
| ) | |
| try { | |
| this.requestManager.reject( | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| parsedValue.id as string | number, | |
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | |
| new Error(data.error as string), | |
| ) | |
| } catch (error) { | |
| if (error instanceof Error) { | |
| this.emit('error', 'badMessage', error.message, message) | |
| } else { | |
| this.emit('error', 'badMessage', error, error) | |
| } | |
| } |
| this.requestManager.reject( | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| parsedValue.id as string | number, | ||
| // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be true | ||
| new Error(data.error as string), | ||
| ) |
There was a problem hiding this comment.
This code rejects the pending request with new Error(...), but the codebase documents that errors thrown by xrpl.js should be XrplErrors (and request failures typically reject with RippledError). Using the built-in Error here changes the error type contract and drops useful context. Consider rejecting with new RippledError(data.error_message ?? data.error, data) (or another XrplError subtype) so callers get consistent error types and access to the original frame.
| const value = JSON.parse(response.value as string) | ||
| value.id = request.id | ||
| response.value = JSON.stringify(value) | ||
| return JSON.stringify(response) |
There was a problem hiding this comment.
createResponse mutates the provided response object (response.value = ...). Since mocked responses are stored and can be reused across multiple requests, this can leak an earlier request’s id into later responses. Also, JSON.parse(response.value as string) is unguarded and will throw if value isn’t valid JSON. Prefer creating a new response object (no mutation) and handle/propagate parse failures with a clearer test error.
| const value = JSON.parse(response.value as string) | |
| value.id = request.id | |
| response.value = JSON.stringify(value) | |
| return JSON.stringify(response) | |
| if (typeof response.value !== 'string') { | |
| throw new XrplError( | |
| `Bad error response format. \`value\` must be a JSON string. ${JSON.stringify( | |
| response, | |
| )}`, | |
| ) | |
| } | |
| let value: Record<string, unknown> | |
| try { | |
| value = JSON.parse(response.value) as Record<string, unknown> | |
| } catch (error) { | |
| throw new XrplError( | |
| `Bad error response format. \`value\` must be valid JSON. ${JSON.stringify( | |
| response, | |
| )}`, | |
| ) | |
| } | |
| return JSON.stringify({ | |
| ...response, | |
| value: JSON.stringify({ ...value, id: request.id }), | |
| }) |
| await clientContext.client.request(request).catch((error) => { | ||
| assert.strictEqual(error.name, 'Error') | ||
| assert.strictEqual(error.message, 'jsonInvalid') | ||
| }) |
There was a problem hiding this comment.
This test will silently pass if the request unexpectedly resolves (because the assertions are only in .catch). It should explicitly fail on success (e.g., await assertRejects(...) or try { await ...; assert.fail(...) } catch (e) { ... }). Also, once the production code returns an XrplError/RippledError for this case, the assertions should verify that type instead of the built-in 'Error' name.
| await clientContext.client.request(request).catch((error) => { | |
| assert.strictEqual(error.name, 'Error') | |
| assert.strictEqual(error.message, 'jsonInvalid') | |
| }) | |
| await assertRejects( | |
| clientContext.client.request(request), | |
| Error, | |
| 'jsonInvalid', | |
| ) |
| * Export signing and binary co | ||
| * dec utilities. |
There was a problem hiding this comment.
The release note bullet under 4.5.0 was accidentally split and truncated ("binary co" / "dec utilities"). Please restore it to a single bullet so the changelog renders correctly.
| * Export signing and binary co | |
| * dec utilities. | |
| * Export signing and binary codec utilities. |
High Level Overview of Change
This PR does better error handling for certain edge-case errors (such as
jsonInvalid) in responses from rippled.Context of Change
I was working on some tests for rippled (XRPLF/rippled#6206) and discovered that the xrpl.js client hangs when some errors are received.
Type of Change
Did you update HISTORY.md?
Test Plan
I wasn't able to write a good integration test for some reason but I did write a unit test and test by hand against a standalone node.