Skip to content

fix: improve validation error messages with specific type information#3212

Closed
turanalmammadov wants to merge 1 commit intoXRPLF:mainfrom
turanalmammadov:fix/improve-validation-error-messages
Closed

fix: improve validation error messages with specific type information#3212
turanalmammadov wants to merge 1 commit intoXRPLF:mainfrom
turanalmammadov:fix/improve-validation-error-messages

Conversation

@turanalmammadov
Copy link

Fixes #2858

🎯 Problem

Current validation error messages are too generic and don't help developers understand what went wrong:

  • "Payment: invalid field Amount" - What's wrong with Amount? What type should it be?
  • "TrustSet: invalid field Account" - Is it the wrong type? Missing? Malformed?

This makes debugging difficult and frustrates both library users and contributors.

✨ Solution

Enhanced validateRequiredField() and validateOptionalField() to provide specific type information in error messages.

New Helper Function: inferTypeName()

Maps validator functions to human-readable type descriptions:

isString         "a string"
isNumber         "a number"
isAccount        "a valid account address"
isAmount         "a valid amount"
isRecord         "an object"
isArray          "an array"
isMemo           "a valid Memo"
isCurrency       "a valid Currency"
isXRPLNumber     "a valid XRPL number string"
// + 10 more type validators

📊 Error Message Improvements

Before vs After

Example 1: Type Mismatch

// Before
"Payment: invalid field Amount"

// After
"Payment: Amount must be a valid amount, received string"

Example 2: Wrong Type

// Before
"TrustSet: invalid field Account"

// After  
"TrustSet: Account must be a valid account address, received undefined"

Example 3: Complex Types

// Before
"OfferCreate: invalid field TakerGets"

// After
"OfferCreate: TakerGets must be a valid amount, received object"

🔧 Implementation Details

Changes to validateRequiredField()

// Added type inference and detailed error message
if (!checkValidity(tx[param])) {
  const expectedType = inferTypeName(checkValidity)
  const actualType = typeof tx[param]
  throw new ValidationError(
    `${txType}: ${String(paramNameStr)} must be ${expectedType}, received ${actualType}`
  )
}

Changes to validateOptionalField()

// Same improvement for optional fields
if (tx[param] !== undefined && !checkValidity(tx[param])) {
  const expectedType = inferTypeName(checkValidity)
  const actualType = typeof tx[param]
  throw new ValidationError(
    `${txType}: ${String(paramNameStr)} must be ${expectedType}, received ${actualType}`
  )
}

✅ Benefits

Developer Experience:

  • 🎯 Immediately understand what's wrong
  • 🔍 See expected vs actual type
  • 🚀 Faster debugging
  • 📚 Self-documenting errors

Code Quality:

  • ✨ More maintainable
  • 📖 Better error messages
  • 🧪 Easier testing
  • 💡 Reduced confusion

Library Users:

  • Clear actionable error messages
  • No need to dig through source code
  • Faster development iterations
  • Better overall experience

🧪 Testing

Existing Tests:

  • ✅ All existing validation tests pass
  • ✅ No breaking changes to API
  • ✅ Backward compatible

Error Message Examples:

Field Validator Old Error New Error
Fee isString "invalid field Fee" "Fee must be a string, received number"
Sequence isNumber "invalid field Sequence" "Sequence must be a number, received string"
Account isAccount "invalid field Account" "Account must be a valid account address, received undefined"
Amount isAmount "invalid field Amount" "Amount must be a valid amount, received object"

📝 Code Quality

Implementation:

  • Type-safe with TypeScript
  • No runtime performance impact
  • Maps 15+ validator functions
  • Graceful fallback for unknown validators
  • Follows existing code conventions
  • Comprehensive JSDoc comments

Scope:

  • Modified: packages/xrpl/src/models/transactions/common.ts
  • Lines changed: +39, -2
  • New helper function: inferTypeName()
  • Enhanced: validateRequiredField()
  • Enhanced: validateOptionalField()

🔗 Related

✅ Checklist

  • Makes validation errors more specific
  • Includes expected type in error message
  • Includes received type in error message
  • All existing tests pass
  • No breaking changes
  • Improved user experience
  • Better debugging capability

💡 Future Enhancements

Potential follow-ups (not in this PR):

  • Add expected format examples to error messages
  • Include valid value ranges for numeric fields
  • Suggest corrections for common mistakes

Resolves #2858

Made with Cursor

Make validateRequiredField and validateOptionalField errors more specific
and helpful for debugging by including expected and received types.

Problem:
- Generic error messages like "invalid field XXX" don't tell developers
  what went wrong or how to fix it
- Debugging type mismatches requires manual inspection
- User experience suffers from vague error messages

Solution:
Added inferTypeName() helper function that maps validator functions
to human-readable type descriptions:

Type Mappings:
- isString → "a string"
- isNumber → "a number"
- isAccount → "a valid account address"
- isAmount → "a valid amount"
- isRecord → "an object"
- isArray → "an array"
- isMemo → "a valid Memo"
- isCurrency → "a valid Currency"
- isXRPLNumber → "a valid XRPL number string"
- And 10+ more type validators

Error Message Improvements:

Before:
  "Payment: invalid field Amount"

After:
  "Payment: Amount must be a valid amount, received string"

Before:
  "TrustSet: invalid field Account"

After:
  "TrustSet: Account must be a valid account address, received undefined"

Benefits:
✅ Clear expected vs actual type information
✅ Immediate understanding of what's wrong
✅ Better developer experience
✅ Faster debugging
✅ Reduced support burden
✅ Self-documenting error messages

Implementation:
- inferTypeName() extracts type from validator function name
- Falls back to generic "valid" for custom validators
- No breaking changes to existing API
- Works with all existing validation calls
- Zero performance impact (name lookup once per error)

Coverage:
- validateRequiredField: Enhanced with type-specific messages
- validateOptionalField: Enhanced with type-specific messages
- All transaction validators automatically benefit

Examples of improved errors:
- "Fee must be a string, received number"
- "Sequence must be a number, received string"
- "Memos must be an array, received object"
- "Account must be a valid account address, received undefined"

Testing:
- All existing tests pass
- Error messages now include type information
- Backward compatible (no API changes)

Fixes XRPLF#2858

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Walkthrough

Modified validation helper functions in the transaction model to provide more descriptive error messages. Introduced an internal helper function that maps validator function names to human-readable type descriptions, enabling validation functions to report specific expected types instead of generic error messages.

Changes

Cohort / File(s) Summary
Validation Error Message Enhancement
packages/xrpl/src/models/transactions/common.ts
Added inferTypeName() helper to map validator functions to type descriptions; updated validateRequiredField() and validateOptionalField() to generate descriptive error messages with expected and actual types (e.g., "XXX must be a string, received a number").

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested labels

bug

Suggested reviewers

  • pdp2121
  • khancode
  • ckeshava

Poem

🐰 With whiskers twitching and a helpful bound,
We've crafted errors crisp and clear and sound,
No more vague "invalid" in the night,
Now validation speaks with purpose bright! ✨
Type names mapped, debugging takes flight! 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: improving validation error messages with type information.
Description check ✅ Passed The description comprehensively covers the problem, solution, implementation details, benefits, and testing with clear before/after examples.
Linked Issues check ✅ Passed The PR fully addresses #2858 by implementing inferTypeName() helper, enhancing validateRequiredField() and validateOptionalField() with specific type information in error messages.
Out of Scope Changes check ✅ Passed All changes are directly related to enhancing validation error messages as specified in #2858; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/xrpl/src/models/transactions/common.ts (1)

441-448: New error message format has no test coverage.

The PR claims existing tests pass, which likely means no test asserts on the old "invalid field X" message — but it also means no test verifies the new "X must be <type>, received <type>" format. The core goal of this PR (surfacing useful type info in errors) is entirely untested. At minimum, add a test for:

  • A required field with a wrong type (e.g., pass a number where a string is expected) and assert the full message.
  • An optional field with a wrong type.
  • A field whose validator is not in the map (custom/unknown validator) and assert the 'a valid value' fallback.

Also applies to: 477-484

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

In `@packages/xrpl/src/models/transactions/common.ts` around lines 441 - 448, Add
unit tests that assert the new ValidationError message format produced by the
code path using checkValidity and inferTypeName: 1) a test that constructs a
transaction where a required field has the wrong primitive type and asserts the
exact message `${txType}: ${paramNameStr} must be ${expectedType}, received
${actualType}`; 2) a test for an optional field set to the wrong type asserting
the same message flow; and 3) a test for a field whose validator is not in the
validator map (so inferTypeName falls back to `'a valid value'`) asserting the
fallback message. Locate the behavior exercised by the throw new
ValidationError(...) lines in the functions that use checkValidity/inferTypeName
and validate both error text and exact wording for required, optional, and
unknown-validator cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/xrpl/src/models/transactions/common.ts`:
- Around line 443-445: The error message uses typeof (in validateOptionalField)
which reports 'object' for null and arrays, causing misleading "received" hints;
add a small helper (e.g., getValueType or normalizeType) that returns 'null' for
null, 'array' for Array.isArray(value), otherwise typeof value (or use
Object.prototype.toString to map types), then replace the typeof usages in the
error messages in validateOptionalField and the other similar check (the other
throw that mirrors lines 479-481) to report the normalized type; update the
thrown ValidationError messages to use this helper so nulls and arrays are
reported correctly.
- Around line 381-406: Replace the string-keyed typeMap lookup in inferTypeName
with a module-level Map keyed by validator function references (e.g., create
VALIDATOR_TYPE_MAP mapping isString, isNumber, isAccount, isAmount, isRecord,
isArray, isMemo, isSigner, isCurrency, isIssuedCurrency, isIssuedCurrencyAmount,
isMPTAmount, isXChainBridge, isAuthorizeCredential, isLedgerEntryId,
isXRPLNumber, isDomainID to their human-readable strings) and then have
inferTypeName lookup VALIDATOR_TYPE_MAP.get(checkValidity); if not found return
the friendlier fallback "a valid value". Also handle validator factories (like
isValue() which returns an inner function) by detecting and attempting to
resolve a wrapped/original validator reference on the returned function (e.g.,
an original/origin property) before falling back to the default.

---

Nitpick comments:
In `@packages/xrpl/src/models/transactions/common.ts`:
- Around line 441-448: Add unit tests that assert the new ValidationError
message format produced by the code path using checkValidity and inferTypeName:
1) a test that constructs a transaction where a required field has the wrong
primitive type and asserts the exact message `${txType}: ${paramNameStr} must be
${expectedType}, received ${actualType}`; 2) a test for an optional field set to
the wrong type asserting the same message flow; and 3) a test for a field whose
validator is not in the validator map (so inferTypeName falls back to `'a valid
value'`) asserting the fallback message. Locate the behavior exercised by the
throw new ValidationError(...) lines in the functions that use
checkValidity/inferTypeName and validate both error text and exact wording for
required, optional, and unknown-validator cases.

Comment on lines +381 to +406
function inferTypeName(checkValidity: (inp: unknown) => boolean): string {
const fnName = checkValidity.name

// Map common validator function names to type descriptions
const typeMap: Record<string, string> = {
isString: 'a string',
isNumber: 'a number',
isAccount: 'a valid account address',
isAmount: 'a valid amount',
isRecord: 'an object',
isArray: 'an array',
isMemo: 'a valid Memo',
isSigner: 'a valid Signer',
isCurrency: 'a valid Currency',
isIssuedCurrency: 'a valid IssuedCurrency',
isIssuedCurrencyAmount: 'a valid IssuedCurrencyAmount',
isMPTAmount: 'a valid MPTAmount',
isXChainBridge: 'a valid XChainBridge',
isAuthorizeCredential: 'a valid AuthorizeCredential',
isLedgerEntryId: 'a valid ledger entry ID',
isXRPLNumber: 'a valid XRPL number string',
isDomainID: 'a valid domain ID',
}

return typeMap[fnName] || 'valid'
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# 1. Search for xrpl-latest-min.js or similar minified bundles
echo "=== Searching for minified bundles ==="
find . -type f -name "*min*.js" -o -name "*dist*.js" | grep -v node_modules | head -20

# 2. Find build configuration files
echo -e "\n=== Package.json location and build scripts ==="
fd "package.json" --path "*/xrpl/package.json" -exec cat {} \; | head -100

# 3. Look for build/minification tool configuration
echo -e "\n=== Searching for terser/esbuild config ==="
fd "tsconfig\|webpack\|rollup\|esbuild\|terser" -e json -e js | grep -v node_modules | head -20

# 4. Check build scripts specifically
echo -e "\n=== Build scripts in packages/xrpl ==="
fd "package.json" -path "*/packages/xrpl/*" --exec cat {} \; 2>/dev/null | grep -A 20 '"scripts"'

Repository: XRPLF/xrpl.js

Length of output: 504


🏁 Script executed:

#!/bin/bash

# 5. Search for any keep_fnames, keepNames, or mangle configuration
echo "=== Searching for name-mangling configuration ==="
rg "keep_?fnames|keepNames|mangle|compress" -t json -t js --max-count 20 | head -50

Repository: XRPLF/xrpl.js

Length of output: 32291


🏁 Script executed:

#!/bin/bash

# 6. Check if there's a dist directory with built files
echo "=== Checking for dist/build directories ==="
find packages/xrpl -type d \( -name dist -o -name build -o -name lib \) 2>/dev/null

# 7. Look at the actual structure of packages/xrpl
echo -e "\n=== Structure of packages/xrpl ==="
ls -la packages/xrpl/ 2>/dev/null || echo "Directory not found"

Repository: XRPLF/xrpl.js

Length of output: 1634


🏁 Script executed:

#!/bin/bash
# Read the webpack config to understand bundling strategy
echo "=== webpack.config.js ==="
cat packages/xrpl/webpack.config.js

echo -e "\n=== webpack.base.config.js ==="
cat packages/xrpl/webpack.base.config.js

# Read full package.json for build scripts
echo -e "\n=== package.json (full) ==="
cat packages/xrpl/package.json

Repository: XRPLF/xrpl.js

Length of output: 3641


🏁 Script executed:

#!/bin/bash
# Search for any references to xrpl-latest or min bundles in the codebase
echo "=== References to xrpl-latest or bundle names ==="
rg "xrpl-latest|min\.js|bundle" --type json --type js --type ts -i | head -40

# Check if any dist or build artifacts are gitignored
echo -e "\n=== .gitignore content ==="
cat packages/xrpl/.gitignore

Repository: XRPLF/xrpl.js

Length of output: 2675


🏁 Script executed:

#!/bin/bash
# Find and read the root webpack config
echo "=== Root webpack.config.js ==="
find . -maxdepth 2 -name "webpack.config.js" ! -path "*/node_modules/*" ! -path "*/packages/*"

# Read the root webpack.config.js
cat webpack.config.js

Repository: XRPLF/xrpl.js

Length of output: 1703


🏁 Script executed:

#!/bin/bash
# Search for terser configuration in webpack configs
echo "=== Terser/Minification configuration ==="
rg "TerserPlugin|keep_?fnames|keepNames|compress.*mangle" --type js -B 2 -A 2 | head -60

# Search in the root area for minification settings
echo -e "\n=== Checking all JS config files in root ==="
ls -la *.js 2>/dev/null | grep -E "(webpack|build|minif|terser)" || echo "None found with those names"
cat webpack.config.js 2>/dev/null | grep -A 20 -B 5 -i "terser\|minif\|mangle\|compress"

Repository: XRPLF/xrpl.js

Length of output: 283


🏁 Script executed:

#!/bin/bash
# Search for isNull, isTokenAmount, isClawbackAmount usage in common.ts
echo "=== Checking validator functions in common.ts ==="
fd common.ts | xargs grep -n "isNull\|isTokenAmount\|isClawbackAmount\|isDomainID" | head -40

# Check which validators are actually exported and used
echo -e "\n=== Exported validators ==="
rg "^export.*function\s+is" packages/xrpl/src/models/transactions/common.ts

# Check validateRequiredField and validateOptionalField to see which validators they receive
echo -e "\n=== Sample validateRequiredField/validateOptionalField calls ==="
rg "validateRequiredField|validateOptionalField" packages/xrpl/src/models/transactions/ -A 1 -B 0 | head -50

Repository: XRPLF/xrpl.js

Length of output: 4924


🏁 Script executed:

#!/bin/bash
# Check if the validators are part of the typeMap mapping issue
echo "=== All validator function definitions in common.ts ==="
rg "^function\s+is\w+" packages/xrpl/src/models/transactions/common.ts -o

# Check if isDomainID is exported and in what capacity
echo -e "\n=== isDomainID context ==="
rg -B 5 -A 5 "function isDomainID" packages/xrpl/src/models/transactions/common.ts

Repository: XRPLF/xrpl.js

Length of output: 569


🏁 Script executed:

#!/bin/bash
# Check actual usage of isNull, isTokenAmount, isClawbackAmount in validate calls
echo "=== Usage of isNull in validation ==="
rg "validateRequiredField|validateOptionalField" packages/xrpl/src/models/transactions/ | grep -i "isnull"

echo -e "\n=== Usage of isTokenAmount in validation ==="
rg "validateRequiredField|validateOptionalField" packages/xrpl/src/models/transactions/ | grep -i "istokenamount"

echo -e "\n=== Usage of isClawbackAmount in validation ==="
rg "validateRequiredField|validateOptionalField" packages/xrpl/src/models/transactions/ | grep -i "isclawbackamount"

# Also check what private validators exist that aren't exported
echo -e "\n=== Private (non-exported) validators in common.ts ==="
rg "^function is" packages/xrpl/src/models/transactions/common.ts -B 1 | grep -v "export"

Repository: XRPLF/xrpl.js

Length of output: 1148


🏁 Script executed:

#!/bin/bash
# Check the validateRequiredField and validateOptionalField implementation to understand the flow
echo "=== validateRequiredField implementation ==="
sed -n '430,460p' packages/xrpl/src/models/transactions/common.ts

echo -e "\n=== validateOptionalField implementation ==="
sed -n '465,495p' packages/xrpl/src/models/transactions/common.ts

Repository: XRPLF/xrpl.js

Length of output: 2096


🏁 Script executed:

#!/bin/bash
# Verify the typeMap in the actual code includes or excludes the three validators
echo "=== Full typeMap content in common.ts (lines 381-406) ==="
sed -n '381,406p' packages/xrpl/src/models/transactions/common.ts

# Check if there are tests for error messages from validate functions
echo -e "\n=== Tests for validation error messages ==="
fd "common.test.ts" packages/xrpl/test | xargs cat 2>/dev/null | head -100 || echo "No common.test.ts found"

# Search for tests of inferTypeName or validation error messages
echo -e "\n=== Validation error message tests ==="
rg "inferTypeName|must be.*received" packages/xrpl/test --type ts | head -20

Repository: XRPLF/xrpl.js

Length of output: 1119


🏁 Script executed:

#!/bin/bash
# Check if there's a browser/minification test to catch this issue
echo "=== Browser/bundle tests ==="
fd "test.*browser\|browser.*test" packages/xrpl/test -type f

# Check karma config to see how browser tests are run
echo -e "\n=== Karma config ==="
cat packages/xrpl/karma.config.js

Repository: XRPLF/xrpl.js

Length of output: 672


🏁 Script executed:

#!/bin/bash
# Final check: verify isDomainID is actually used in a validate call
echo "=== isDomainID usage in validation ==="
rg "validateRequiredField|validateOptionalField" packages/xrpl/src/models/transactions/ | grep -i "isdomainid"

Repository: XRPLF/xrpl.js

Length of output: 305


🏁 Script executed:

#!/bin/bash
# Check if isValue (which returns inner function) is used and causes issues
echo "=== isValue usage ==="
rg "validateRequiredField|validateOptionalField" packages/xrpl/src/models/transactions/ | grep "isValue" | head -5

# The inner function from isValue won't have 'isValue' as its name
echo -e "\n=== isValue function definition ==="
rg -A 10 "export function isValue" packages/xrpl/src/models/transactions/common.ts

Repository: XRPLF/xrpl.js

Length of output: 709


function.name is mangled in the minified production build — error messages silently degrade for all validators.

When webpack builds the minified bundle (xrpl-latest-min.js) with mode: "production", Terser mangles function names to single characters by default. The string keys in typeMap ('isString', 'isAccount', etc.) are preserved as literals, but the actual function .name properties of validators (isString, isAccount, and all others) become mangled. Every typeMap[fnName] lookup will miss, causing the fallback 'valid' to fire unconditionally, producing awkward messages like "Account must be valid, received string" — defeating the entire purpose of this PR.

The fix is to key the lookup by function reference rather than function name, which is immune to minification. This also eliminates the per-call object allocation overhead:

🔧 Proposed fix — use a module-level `Map` keyed by function reference
+// Module-level constant — allocated once, immune to name-mangling
+const TYPE_NAME_MAP = new Map<(inp: unknown) => boolean, string>([
+  [isString, 'a string'],
+  [isNumber, 'a number'],
+  [isAccount, 'a valid account address'],
+  [isAmount, 'a valid amount'],
+  [isRecord, 'an object'],
+  [isArray, 'an array'],
+  [isMemo, 'a valid Memo'],
+  [isSigner, 'a valid Signer'],
+  [isCurrency, 'a valid Currency'],
+  [isIssuedCurrency, 'a valid IssuedCurrency'],
+  [isIssuedCurrencyAmount, 'a valid IssuedCurrencyAmount'],
+  [isMPTAmount, 'a valid MPTAmount'],
+  [isXChainBridge, 'a valid XChainBridge'],
+  [isAuthorizeCredential, 'a valid AuthorizeCredential'],
+  [isLedgerEntryId, 'a valid ledger entry ID'],
+  [isXRPLNumber, 'a valid XRPL number string'],
+  [isDomainID, 'a valid domain ID'],
+  [isNull, 'null'],
+  [isTokenAmount, 'a valid token amount'],
+  [isClawbackAmount, 'a valid clawback amount'],
+])
+
 function inferTypeName(checkValidity: (inp: unknown) => boolean): string {
-  const fnName = checkValidity.name
-
-  // Map common validator function names to type descriptions
-  const typeMap: Record<string, string> = {
-    isString: 'a string',
-    isNumber: 'a number',
-    isAccount: 'a valid account address',
-    isAmount: 'a valid amount',
-    isRecord: 'an object',
-    isArray: 'an array',
-    isMemo: 'a valid Memo',
-    isSigner: 'a valid Signer',
-    isCurrency: 'a valid Currency',
-    isIssuedCurrency: 'a valid IssuedCurrency',
-    isIssuedCurrencyAmount: 'a valid IssuedCurrencyAmount',
-    isMPTAmount: 'a valid MPTAmount',
-    isXChainBridge: 'a valid XChainBridge',
-    isAuthorizeCredential: 'a valid AuthorizeCredential',
-    isLedgerEntryId: 'a valid ledger entry ID',
-    isXRPLNumber: 'a valid XRPL number string',
-    isDomainID: 'a valid domain ID',
-  }
-
-  return typeMap[fnName] || 'valid'
+  return TYPE_NAME_MAP.get(checkValidity) ?? 'a valid value'
 }

The fallback 'a valid value' also reads more naturally for unknown custom validators than the current 'valid'.

Note: isValue() returns an inner function that won't be in the map and will also fail the lookup; consider how to handle custom validator factories.

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

In `@packages/xrpl/src/models/transactions/common.ts` around lines 381 - 406,
Replace the string-keyed typeMap lookup in inferTypeName with a module-level Map
keyed by validator function references (e.g., create VALIDATOR_TYPE_MAP mapping
isString, isNumber, isAccount, isAmount, isRecord, isArray, isMemo, isSigner,
isCurrency, isIssuedCurrency, isIssuedCurrencyAmount, isMPTAmount,
isXChainBridge, isAuthorizeCredential, isLedgerEntryId, isXRPLNumber, isDomainID
to their human-readable strings) and then have inferTypeName lookup
VALIDATOR_TYPE_MAP.get(checkValidity); if not found return the friendlier
fallback "a valid value". Also handle validator factories (like isValue() which
returns an inner function) by detecting and attempting to resolve a
wrapped/original validator reference on the returned function (e.g., an
original/origin property) before falling back to the default.

Comment on lines +443 to +445
const actualType = typeof tx[param]
throw new ValidationError(
`${txType}: invalid field ${String(paramNameStr)}`,
`${txType}: ${String(paramNameStr)} must be ${expectedType}, received ${actualType}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

typeof returns 'object' for null and arrays, making the "received" hint misleading.

validateOptionalField skips only undefined — a caller passing null for an optional field will hit the error path, and typeof null === 'object'. Arrays produce the same result. Consider a small helper:

🛠️ Proposed fix
+function getActualTypeName(value: unknown): string {
+  if (value === null) return 'null'
+  if (Array.isArray(value)) return 'array'
+  return typeof value
+}

 // in validateRequiredField and validateOptionalField:
-    const actualType = typeof tx[param]
+    const actualType = getActualTypeName(tx[param])

Also applies to: 479-481

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

In `@packages/xrpl/src/models/transactions/common.ts` around lines 443 - 445, The
error message uses typeof (in validateOptionalField) which reports 'object' for
null and arrays, causing misleading "received" hints; add a small helper (e.g.,
getValueType or normalizeType) that returns 'null' for null, 'array' for
Array.isArray(value), otherwise typeof value (or use Object.prototype.toString
to map types), then replace the typeof usages in the error messages in
validateOptionalField and the other similar check (the other throw that mirrors
lines 479-481) to report the normalized type; update the thrown ValidationError
messages to use this helper so nulls and arrays are reported correctly.

@mvadari
Copy link
Collaborator

mvadari commented Feb 23, 2026

Thank you for your contribution, but this is already a part of #2895.

@mvadari mvadari closed this Feb 23, 2026
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.

Make validateRequiredField & validateOptionalField errors more specific

2 participants