diff --git a/src/api/common/errors.js b/src/api/common/errors.js index e5194c07..2190793b 100644 --- a/src/api/common/errors.js +++ b/src/api/common/errors.js @@ -77,13 +77,13 @@ ApiError.prototype = new RippleError(); ApiError.prototype.name = 'ApiError'; module.exports = { - ValidationError: ValidationError, - NetworkError: NetworkError, - TransactionError: TransactionError, - RippledNetworkError: RippledNetworkError, - NotFoundError: NotFoundError, - MissingLedgerHistoryError: MissingLedgerHistoryError, - TimeOutError: TimeOutError, - ApiError: ApiError, - RippleError: RippleError + ValidationError, + NetworkError, + TransactionError, + RippledNetworkError, + NotFoundError, + MissingLedgerHistoryError, + TimeOutError, + ApiError, + RippleError }; diff --git a/src/api/common/index.js b/src/api/common/index.js index 5caedaa3..688736e8 100644 --- a/src/api/common/index.js +++ b/src/api/common/index.js @@ -12,6 +12,7 @@ module.exports = { generateAddress: utils.generateAddress, composeAsync: utils.composeAsync, wrapCatch: utils.wrapCatch, + convertErrors: utils.convertErrors, convertExceptions: utils.convertExceptions, convertKeysFromSnakeCaseToCamelCase: utils.convertKeysFromSnakeCaseToCamelCase, diff --git a/src/api/common/utils.js b/src/api/common/utils.js index b968ad50..8fff1f53 100644 --- a/src/api/common/utils.js +++ b/src/api/common/utils.js @@ -66,6 +66,16 @@ function composeAsync(wrapper: Wrapper, callback: Callback): Callback { }; } +function convertErrors(callback: () => void): () => void { + return function(error, data) { + if (error && !(error instanceof errors.RippleError)) { + callback(new errors.RippleError(error)); + } else { + callback(error, data); + } + }; +} + function convertExceptions(f: () => T): () => T { return function() { try { @@ -105,6 +115,7 @@ module.exports = { composeAsync, wrapCatch, convertExceptions, + convertErrors, convertKeysFromSnakeCaseToCamelCase, promisify }; diff --git a/src/api/ledger/accountinfo.js b/src/api/ledger/accountinfo.js index a2d19006..4fed0754 100644 --- a/src/api/ledger/accountinfo.js +++ b/src/api/ledger/accountinfo.js @@ -5,6 +5,7 @@ const utils = require('./utils'); const removeUndefined = require('./parse/utils').removeUndefined; const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; type AccountData = { Sequence: number, @@ -66,10 +67,10 @@ function getAccountInfoAsync(account: string, options: AccountInfoOptions, }; this.remote.requestAccountInfo(request, - composeAsync(formatAccountInfo, callback)); + composeAsync(formatAccountInfo, convertErrors(callback))); } -function getAccountInfo(account: string, options: AccountInfoOptions={} +function getAccountInfo(account: string, options: AccountInfoOptions = {} ): Promise { return utils.promisify(getAccountInfoAsync).call(this, account, options); } diff --git a/src/api/ledger/balances.js b/src/api/ledger/balances.js index 2fe155d5..d05704da 100644 --- a/src/api/ledger/balances.js +++ b/src/api/ledger/balances.js @@ -6,6 +6,7 @@ const utils = require('./utils'); const getTrustlines = require('./trustlines'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; function getTrustlineBalanceAmount(trustline) { return { @@ -39,10 +40,10 @@ function getBalancesAsync(account, options, callback) { async.parallel({ xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion), trustlines: _.partial(getTrustlinesAsync.bind(this), account, options) - }, composeAsync(formatBalances, callback)); + }, composeAsync(formatBalances, convertErrors(callback))); } -function getBalances(account: string, options={}) { +function getBalances(account: string, options = {}) { return utils.promisify(getBalancesAsync).call(this, account, options); } diff --git a/src/api/ledger/ledger.js b/src/api/ledger/ledger.js index ff62a032..61919ab9 100644 --- a/src/api/ledger/ledger.js +++ b/src/api/ledger/ledger.js @@ -3,6 +3,7 @@ const utils = require('./utils'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; const parseLedger = require('./parse/ledger'); function getLedgerAsync(options, callback) { @@ -16,10 +17,11 @@ function getLedgerAsync(options, callback) { }; this.remote.requestLedger(request, - composeAsync(response => parseLedger(response.ledger), callback)); + composeAsync(response => parseLedger(response.ledger), + convertErrors(callback))); } -function getLedger(options={}) { +function getLedger(options = {}) { return utils.promisify(getLedgerAsync).call(this, options); } diff --git a/src/api/ledger/orderbook.js b/src/api/ledger/orderbook.js index 62e2a903..61cb470b 100644 --- a/src/api/ledger/orderbook.js +++ b/src/api/ledger/orderbook.js @@ -3,9 +3,10 @@ const _ = require('lodash'); const async = require('async'); const utils = require('./utils'); -const parseOrderbookOrder = require('./parse/orderbook-order'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; +const parseOrderbookOrder = require('./parse/orderbook-order'); // account is to specify a "perspective", which affects which unfunded offers // are returned @@ -18,7 +19,7 @@ function getBookOffers(remote, account, ledgerVersion, limit, ledger: ledgerVersion || 'validated', limit: limit, taker: account - }), composeAsync(data => data.offers, callback)); + }), composeAsync(data => data.offers, convertErrors(callback))); } function isSameIssue(a, b) { @@ -77,7 +78,7 @@ function getOrderbookAsync(account, orderbook, options, callback) { callback)); } -function getOrderbook(account: string, orderbook: Object, options={}) { +function getOrderbook(account: string, orderbook: Object, options = {}) { return utils.promisify(getOrderbookAsync).call(this, account, orderbook, options); } diff --git a/src/api/ledger/orders.js b/src/api/ledger/orders.js index 95209a12..9a765237 100644 --- a/src/api/ledger/orders.js +++ b/src/api/ledger/orders.js @@ -4,10 +4,11 @@ const _ = require('lodash'); const utils = require('./utils'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; const parseAccountOrder = require('./parse/account-order'); -function requestAccountOffers(remote, address, ledgerVersion, options, - marker, limit, callback +function requestAccountOffers(remote, address, ledgerVersion, marker, limit, + callback ) { remote.requestAccountOffers({ account: address, @@ -18,7 +19,7 @@ function requestAccountOffers(remote, address, ledgerVersion, options, composeAsync((data) => ({ marker: data.marker, results: data.offers.map(_.partial(parseAccountOrder, address)) - }), callback)); + }), convertErrors(callback))); } function getOrdersAsync(account, options, callback) { @@ -28,13 +29,13 @@ function getOrdersAsync(account, options, callback) { const ledgerVersion = options.ledgerVersion || this.remote.getLedgerSequence(); const getter = _.partial(requestAccountOffers, this.remote, account, - ledgerVersion, options); + ledgerVersion); utils.getRecursive(getter, options.limit, composeAsync((orders) => _.sortBy(orders, (order) => order.properties.sequence), callback)); } -function getOrders(account: string, options={}) { +function getOrders(account: string, options = {}) { return utils.promisify(getOrdersAsync).call(this, account, options); } diff --git a/src/api/ledger/pathfind.js b/src/api/ledger/pathfind.js index 532c92e6..39bab1a2 100644 --- a/src/api/ledger/pathfind.js +++ b/src/api/ledger/pathfind.js @@ -8,6 +8,7 @@ const validate = utils.common.validate; const parsePathfind = require('./parse/pathfind'); const NotFoundError = utils.common.errors.NotFoundError; const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; type PathFindParams = { src_currencies?: Array, src_account: string, dst_amount: string, @@ -47,7 +48,7 @@ function requestPathFind(remote, pathfind: PathFind, callback) { } remote.createPathFind(params, - composeAsync(_.partial(addParams, params), callback)); + composeAsync(_.partial(addParams, params), convertErrors(callback))); } function addDirectXrpPath(paths, xrpBalance) { diff --git a/src/api/ledger/settings.js b/src/api/ledger/settings.js index 0215aac5..e04d285f 100644 --- a/src/api/ledger/settings.js +++ b/src/api/ledger/settings.js @@ -6,6 +6,7 @@ const validate = utils.common.validate; const parseFields = require('./parse/fields'); const composeAsync = utils.common.composeAsync; const AccountFlags = utils.common.constants.AccountFlags; +const convertErrors = utils.common.convertErrors; function parseFlags(value) { const settings = {}; @@ -34,10 +35,10 @@ function getSettingsAsync(account, options, callback) { }; this.remote.requestAccountInfo(request, - composeAsync(formatSettings, callback)); + composeAsync(formatSettings, convertErrors(callback))); } -function getSettings(account: string, options={}) { +function getSettings(account: string, options = {}) { return utils.promisify(getSettingsAsync).call(this, account, options); } diff --git a/src/api/ledger/transaction.js b/src/api/ledger/transaction.js index d9969d95..56d291ff 100644 --- a/src/api/ledger/transaction.js +++ b/src/api/ledger/transaction.js @@ -6,6 +6,7 @@ const utils = require('./utils'); const parseTransaction = require('./parse/transaction'); const validate = utils.common.validate; const errors = utils.common.errors; +const convertErrors = utils.common.convertErrors; const RippleError = require('../../core/rippleerror').RippleError; import type {Remote} from '../../core/remote'; @@ -69,9 +70,9 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, } else if (!error && tx && !isTransactionInRange(tx, options)) { callback(new errors.NotFoundError('Transaction not found')); } else if (error) { - callback(error); + convertErrors(callback)(error); } else if (!tx) { - callback(new Error('Internal error')); + callback(new errors.ApiError('Internal error')); } else { callback(error, parseTransaction(tx)); } @@ -85,7 +86,7 @@ function getTransactionAsync(identifier: string, options: TransactionOptions, } function getTransaction(identifier: string, - options: TransactionOptions={} + options: TransactionOptions = {} ): Promise { return utils.promisify(getTransactionAsync).call(this, identifier, options); } diff --git a/src/api/ledger/transactions.js b/src/api/ledger/transactions.js index df3e5474..8d2a4c14 100644 --- a/src/api/ledger/transactions.js +++ b/src/api/ledger/transactions.js @@ -7,6 +7,7 @@ const parseTransaction = require('./parse/transaction'); const getTransaction = require('./transaction'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; function parseAccountTxTransaction(tx) { // rippled uses a different response format for 'account_tx' than 'tx' @@ -55,6 +56,17 @@ function orderFilter(options, tx) { utils.compareTransactions(tx, options.startTx) < 0); } +function formatPartialResponse(address, options, data) { + return { + marker: data.marker, + results: data.transactions + .filter((tx) => tx.validated) + .map(parseAccountTxTransaction) + .filter(_.partial(transactionFilter, address, options)) + .filter(_.partial(orderFilter, options)) + }; +} + function getAccountTx(remote, address, options, marker, limit, callback) { const params = { account: address, @@ -66,16 +78,9 @@ function getAccountTx(remote, address, options, marker, limit, callback) { marker: marker }; - remote.requestAccountTx(params, (error, data) => { - return error ? callback(error) : callback(null, { - marker: data.marker, - results: data.transactions - .filter((tx) => tx.validated) - .map(parseAccountTxTransaction) - .filter(_.partial(transactionFilter, address, options)) - .filter(_.partial(orderFilter, options)) - }); - }); + remote.requestAccountTx(params, + composeAsync(_.partial(formatPartialResponse, address, options), + convertErrors(callback))); } function checkForLedgerGaps(remote, options, transactions) { @@ -131,7 +136,7 @@ function getTransactionsAsync(account, options, callback) { } } -function getTransactions(account: string, options={}) { +function getTransactions(account: string, options = {}) { return utils.promisify(getTransactionsAsync).call(this, account, options); } diff --git a/src/api/ledger/trustlines.js b/src/api/ledger/trustlines.js index 82584079..eefd1fd8 100644 --- a/src/api/ledger/trustlines.js +++ b/src/api/ledger/trustlines.js @@ -3,12 +3,22 @@ const _ = require('lodash'); const utils = require('./utils'); const validate = utils.common.validate; +const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; const parseAccountTrustline = require('./parse/account-trustline'); function currencyFilter(currency, trustline) { return currency === null || trustline.specification.currency === currency; } +function formatResponse(options, data) { + return { + marker: data.marker, + results: data.lines.map(parseAccountTrustline) + .filter(_.partial(currencyFilter, options.currency || null)) + }; +} + function getAccountLines(remote, address, ledgerVersion, options, marker, limit, callback ) { @@ -20,14 +30,9 @@ function getAccountLines(remote, address, ledgerVersion, options, marker, limit, peer: options.counterparty }; - remote.requestAccountLines(requestOptions, (error, data) => { - return error ? callback(error) : - callback(null, { - marker: data.marker, - results: data.lines.map(parseAccountTrustline) - .filter(_.partial(currencyFilter, options.currency || null)) - }); - }); + remote.requestAccountLines(requestOptions, + composeAsync(_.partial(formatResponse, options), + convertErrors(callback))); } function getTrustlinesAsync(account: string, options: {currency: string, @@ -44,7 +49,7 @@ function getTrustlinesAsync(account: string, options: {currency: string, utils.getRecursive(getter, options.limit, callback); } -function getTrustlines(account: string, options={}) { +function getTrustlines(account: string, options = {}) { return utils.promisify(getTrustlinesAsync).call(this, account, options); } diff --git a/src/api/transaction/submit.js b/src/api/transaction/submit.js index 7b702df4..57780263 100644 --- a/src/api/transaction/submit.js +++ b/src/api/transaction/submit.js @@ -3,16 +3,17 @@ const utils = require('./utils'); const validate = utils.common.validate; const Request = utils.common.core.Request; +const convertErrors = utils.common.convertErrors; -function submitAsync(txBlob: string, callback: (err: any, data: any) => void): - void { +function submitAsync(txBlob: string, callback: (err: any, data: any) => void +): void { validate.blob(txBlob); const request = new Request(this.remote, 'submit'); request.message.tx_blob = txBlob; request.request(null, utils.common.composeAsync( data => utils.common.convertKeysFromSnakeCaseToCamelCase(data), - callback)); + convertErrors(callback))); } function submit(txBlob: string) { diff --git a/src/core/remote.js b/src/core/remote.js index 957dd749..b03c8b1a 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -1816,10 +1816,7 @@ Remote.prototype.createPathFind = function(options, callback) { callback(null, data); } }); - pathFind.on('error', (error) => { - pathFind.close(); - callback(error); - }); + pathFind.on('error', callback); } this._cur_path_find = pathFind; diff --git a/src/core/request.js b/src/core/request.js index de6f3677..1f1bad7e 100644 --- a/src/core/request.js +++ b/src/core/request.js @@ -63,6 +63,10 @@ Request.prototype.request = function(servers, callback) { return this; }; +function isResponseNotError(res) { + return typeof res === 'object' && !res.hasOwnProperty('error'); +} + /** * Broadcast request to all servers, filter responses if a function is * provided. Return first response that satisfies the filter. Pre-filter @@ -74,16 +78,17 @@ Request.prototype.request = function(servers, callback) { * @param [Function] fn */ + Request.prototype.filter = Request.prototype.addFilter = -Request.prototype.broadcast = function(filterFn = Boolean) { +Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) { const self = this; if (!this.requested) { // Defer until requested, and prevent the normal request() from executing this.once('before', function() { self.requested = true; - self.broadcast(filterFn); + self.broadcast(isResponseSuccess); }); return this; } @@ -111,7 +116,7 @@ Request.prototype.broadcast = function(filterFn = Boolean) { // Listen for proxied success/error event and apply filter self.once('proposed', function(res) { lastResponse = res; - callback(filterFn(res)); + callback(isResponseSuccess(res)); }); return server._request(self); diff --git a/src/core/server.js b/src/core/server.js index 0b74c9c2..275f4976 100644 --- a/src/core/server.js +++ b/src/core/server.js @@ -1,6 +1,5 @@ 'use strict'; - const _ = require('lodash'); const assert = require('assert'); const util = require('util'); @@ -130,9 +129,8 @@ function Server(remote, opts_) { self._updateScore('ledgerclose', ledger); }); -/* eslint-disable no-unused-vars */ this.on('response_ping', function onPingResponse(message, request) { -/* eslint-enable no-unused-vars */ + _.noop(message); self._updateScore('response', request); }); diff --git a/test/api-test.js b/test/api-test.js index 8a308242..a8a50207 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -391,7 +391,7 @@ describe('RippleAPI', function() { return this.api.getTransactions(address, options).then(() => { assert(false, 'Should throw RippleError'); }).catch(error => { - assert(error instanceof common.core.RippleError); + assert(error instanceof this.api.errors.RippleError); }); }); @@ -552,6 +552,14 @@ describe('RippleAPI', function() { }); }); + it('getPaths - error: srcActNotFound', function() { + const pathfind = _.assign({}, requests.getPaths.normal, + {source: {address: addresses.NOTFOUND}}); + return this.api.getPaths(pathfind).catch(error => { + assert(error instanceof this.api.errors.RippleError); + }); + }); + it('getLedgerVersion', function() { assert.strictEqual(this.api.getLedgerVersion(), 8819951); }); diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index 27b3920e..d0e95b63 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -19,7 +19,8 @@ module.exports = { path_find: { generate: require('./path-find'), sendUSD: require('./path-find-send-usd'), - XrpToXrp: require('./path-find-xrp-to-xrp') + XrpToXrp: require('./path-find-xrp-to-xrp'), + srcActNotFound: require('./path-find-srcActNotFound') }, tx: { Payment: require('./tx/payment.json'), diff --git a/test/fixtures/api/rippled/path-find-srcActNotFound.json b/test/fixtures/api/rippled/path-find-srcActNotFound.json new file mode 100644 index 00000000..a1614ec3 --- /dev/null +++ b/test/fixtures/api/rippled/path-find-srcActNotFound.json @@ -0,0 +1,20 @@ +{ + "error": "srcActNotFound", + "error_code": 58, + "error_message": "Source account not found.", + "id": 0, + "request": { + "command": "path_find", + "destination_account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "destination_amount": { + "currency": "USD", + "issuer": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "value": "5" + }, + "id": 1, + "source_account": "rajTAg3hon5Lcu1RxQQPxTgHvqfhc1EaUS", + "subcommand": "create" + }, + "status": "error", + "type": "response" +} diff --git a/test/mock-rippled.js b/test/mock-rippled.js index c60515ef..598f1376 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -18,7 +18,7 @@ function isBTC(json) { return json === 'BTC' || json === '0000000000000000000000004254430000000000'; } -function createResponse(request, response, overrides={}) { +function createResponse(request, response, overrides = {}) { const result = _.assign({}, response.result, overrides); const change = response.result && !_.isEmpty(overrides) ? {id: request.id, result: result} : {id: request.id}; @@ -253,7 +253,9 @@ module.exports = function(port) { if (request.subcommand === 'close') { return; } - if (request.source_account === addresses.OTHER_ACCOUNT) { + if (request.source_account === addresses.NOTFOUND) { + response = createResponse(request, fixtures.path_find.srcActNotFound); + } else if (request.source_account === addresses.OTHER_ACCOUNT) { response = createResponse(request, fixtures.path_find.sendUSD); } else if (request.source_account === addresses.THIRD_ACCOUNT) { response = createResponse(request, fixtures.path_find.XrpToXrp, {