mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
Move error related functions to errors.ts
Signed-off-by: James Taylor <jamest@uk.ibm.com>
This commit is contained in:
parent
fd269237d4
commit
0456a9e94c
4 changed files with 211 additions and 111 deletions
102
asset-transfer-basic/rest-api-typescript/src/errors.spec.ts
Normal file
102
asset-transfer-basic/rest-api-typescript/src/errors.spec.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
AssetExistsError,
|
||||
AssetNotFoundError,
|
||||
TransactionError,
|
||||
TransactionNotFoundError,
|
||||
handleError,
|
||||
isDuplicateTransactionError,
|
||||
} from './errors';
|
||||
|
||||
describe('Errors', () => {
|
||||
describe('isDuplicateTransactionError', () => {
|
||||
it('returns true for an error with duplicate transaction endorsement details', () => {
|
||||
const mockDuplicateTransactionError = {
|
||||
errors: [
|
||||
{
|
||||
endorsements: [
|
||||
{
|
||||
details: 'duplicate transaction found',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(isDuplicateTransactionError(mockDuplicateTransactionError)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false for an error without duplicate transaction endorsement details', () => {
|
||||
const mockDuplicateTransactionError = {
|
||||
errors: [
|
||||
{
|
||||
endorsements: [
|
||||
{
|
||||
details: 'mock endorsement details',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(isDuplicateTransactionError(mockDuplicateTransactionError)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleError', () => {
|
||||
it.each([
|
||||
'the asset GOCHAINCODE already exists',
|
||||
'Asset JAVACHAINCODE already exists',
|
||||
'The asset JSCHAINCODE already exists',
|
||||
])(
|
||||
'returns an AssetExistsError for errors with an asset already exists message: %s',
|
||||
(msg) => {
|
||||
expect(handleError('txn1', new Error(msg))).toStrictEqual(
|
||||
new AssetExistsError(msg, 'txn1')
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
'the asset GOCHAINCODE does not exist',
|
||||
'Asset JAVACHAINCODE does not exist',
|
||||
'The asset JSCHAINCODE does not exist',
|
||||
])(
|
||||
'returns an AssetNotFoundError for errors with an asset does not exist message: %s',
|
||||
(msg) => {
|
||||
expect(handleError('txn1', new Error(msg))).toStrictEqual(
|
||||
new AssetNotFoundError(msg, 'txn1')
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('returns a TransactionNotFoundError for errors with a transaction not found message', () => {
|
||||
expect(
|
||||
handleError(
|
||||
'txn1',
|
||||
new Error(
|
||||
'Failed to get transaction with id txn, error Entry not found in index'
|
||||
)
|
||||
)
|
||||
).toStrictEqual(
|
||||
new TransactionNotFoundError(
|
||||
'Failed to get transaction with id txn, error Entry not found in index',
|
||||
'txn1'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns a TransactionError for errors with other messages', () => {
|
||||
expect(handleError('txn1', new Error('MOCK ERROR'))).toStrictEqual(
|
||||
new TransactionError('Transaction error', 'txn1')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { logger } from './logger';
|
||||
|
||||
export class TransactionError extends Error {
|
||||
transactionId: string;
|
||||
|
||||
|
|
@ -43,3 +45,91 @@ export class AssetNotFoundError extends TransactionError {
|
|||
this.name = 'AssetNotFoundError';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks whether an error was caused by a duplicate transaction.
|
||||
*
|
||||
* Checking error strings like this is not ideal, unfortunately it appears to
|
||||
* be the only option. In this case it would be better to check for the
|
||||
* DUPLICATE_TXID TxValidationCode somehow but that does not seem to be
|
||||
* possible.
|
||||
*/
|
||||
export const isDuplicateTransactionError = (error: {
|
||||
errors: { endorsements: { details: string }[] }[];
|
||||
}): boolean => {
|
||||
try {
|
||||
const isDuplicateTxn = error?.errors?.some((err) =>
|
||||
err?.endorsements?.some((endorsement) =>
|
||||
endorsement?.details?.startsWith('duplicate transaction found')
|
||||
)
|
||||
);
|
||||
|
||||
return isDuplicateTxn;
|
||||
} catch (err) {
|
||||
logger.warn(err, 'Error checking for duplicate transaction');
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
* Handles errors from evaluating and submitting transactions.
|
||||
*
|
||||
* As with duplicate transaction errors, checking error strings like this is
|
||||
* not ideal. Unfortunately the chaincode samples do not use error codes so
|
||||
* again it's the only option. The error message text is not even the same for
|
||||
* the Go, Java, and Javascript implementations of the chaincode!
|
||||
*/
|
||||
export const handleError = (transactionId: string, err: Error): Error => {
|
||||
// This regex needs to match the following error messages:
|
||||
// "the asset %s already exists"
|
||||
// "The asset ${id} already exists"
|
||||
// "Asset %s already exists"
|
||||
const assetAlreadyExistsRegex = /([tT]he )?[aA]sset \w* already exists/g;
|
||||
const assetAlreadyExistsMatch = err.message.match(assetAlreadyExistsRegex);
|
||||
logger.debug(
|
||||
{ message: err.message, result: assetAlreadyExistsMatch },
|
||||
'Checking for asset already exists message'
|
||||
);
|
||||
if (assetAlreadyExistsMatch) {
|
||||
return new AssetExistsError(assetAlreadyExistsMatch[0], transactionId);
|
||||
}
|
||||
|
||||
// This regex needs to match the following error messages:
|
||||
// "the asset %s does not exist"
|
||||
// "The asset ${id} does not exist"
|
||||
// "Asset %s does not exist"
|
||||
const assetDoesNotExistRegex = /([tT]he )?[aA]sset \w* does not exist/g;
|
||||
const assetDoesNotExistMatch = err.message.match(assetDoesNotExistRegex);
|
||||
logger.debug(
|
||||
{ message: err.message, result: assetDoesNotExistMatch },
|
||||
'Checking for asset does not exist message'
|
||||
);
|
||||
if (assetDoesNotExistMatch) {
|
||||
return new AssetNotFoundError(assetDoesNotExistMatch[0], transactionId);
|
||||
}
|
||||
|
||||
// This regex needs to match the following error messages:
|
||||
// "Failed to get transaction with id %s, error Entry not found in index"
|
||||
const transactionDoesNotExistRegex =
|
||||
/Failed to get transaction with id [^,]*, error Entry not found in index/g;
|
||||
const transactionDoesNotExistMatch = err.message.match(
|
||||
transactionDoesNotExistRegex
|
||||
);
|
||||
logger.debug(
|
||||
{ message: err.message, result: transactionDoesNotExistMatch },
|
||||
'Checking for transaction does not exist message'
|
||||
);
|
||||
if (transactionDoesNotExistMatch) {
|
||||
return new TransactionNotFoundError(
|
||||
transactionDoesNotExistMatch[0],
|
||||
transactionId
|
||||
);
|
||||
}
|
||||
|
||||
logger.error(
|
||||
{ transactionId: transactionId, error: err },
|
||||
'Unhandled transaction error'
|
||||
);
|
||||
return new TransactionError('Transaction error', transactionId);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -320,35 +320,25 @@ describe('Fabric', () => {
|
|||
expect(result.toString()).toBe(mockPayload.toString());
|
||||
});
|
||||
|
||||
it.each([
|
||||
'the asset GOCHAINCODE already exists',
|
||||
'Asset JAVACHAINCODE already exists',
|
||||
'The asset JSCHAINCODE already exists',
|
||||
])(
|
||||
'throws an AssetExistsError an asset already exists error occurs: %s',
|
||||
async (msg) => {
|
||||
mockTransaction.evaluate.mockRejectedValue(new Error(msg));
|
||||
it('throws an AssetExistsError an asset already exists error occurs', async () => {
|
||||
mockTransaction.evaluate.mockRejectedValue(
|
||||
new Error('The asset JSCHAINCODE already exists')
|
||||
);
|
||||
|
||||
await expect(async () => {
|
||||
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
||||
}).rejects.toThrow(AssetExistsError);
|
||||
}
|
||||
);
|
||||
await expect(async () => {
|
||||
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
||||
}).rejects.toThrow(AssetExistsError);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'the asset GOCHAINCODE does not exist',
|
||||
'Asset JAVACHAINCODE does not exist',
|
||||
'The asset JSCHAINCODE does not exist',
|
||||
])(
|
||||
'throws an AssetNotFoundError if an asset does not exist error occurs: %s',
|
||||
async (msg) => {
|
||||
mockTransaction.evaluate.mockRejectedValue(new Error(msg));
|
||||
it('throws an AssetNotFoundError if an asset does not exist error occurs', async () => {
|
||||
mockTransaction.evaluate.mockRejectedValue(
|
||||
new Error('The asset JSCHAINCODE does not exist')
|
||||
);
|
||||
|
||||
await expect(async () => {
|
||||
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
||||
}).rejects.toThrow(AssetNotFoundError);
|
||||
}
|
||||
);
|
||||
await expect(async () => {
|
||||
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
||||
}).rejects.toThrow(AssetNotFoundError);
|
||||
});
|
||||
|
||||
it('throws a TransactionNotFoundError if a transaction not found error occurs', async () => {
|
||||
mockTransaction.evaluate.mockRejectedValue(
|
||||
|
|
|
|||
|
|
@ -25,12 +25,7 @@ import {
|
|||
incrementRetryCount,
|
||||
TransactionDetails,
|
||||
} from './redis';
|
||||
import {
|
||||
AssetExistsError,
|
||||
AssetNotFoundError,
|
||||
TransactionError,
|
||||
TransactionNotFoundError,
|
||||
} from './errors';
|
||||
import { handleError, isDuplicateTransactionError } from './errors';
|
||||
import protos from 'fabric-protos';
|
||||
|
||||
/*
|
||||
|
|
@ -247,63 +242,6 @@ export const submitTransaction = async (
|
|||
return txnId;
|
||||
};
|
||||
|
||||
// Unfortunately the chaincode samples do not use error codes, and the error
|
||||
// message text is not the same for each implementation
|
||||
// TODO move to errors.ts?
|
||||
const handleError = (transactionId: string, err: Error): Error => {
|
||||
// This regex needs to match the following error messages:
|
||||
// "the asset %s already exists"
|
||||
// "The asset ${id} already exists"
|
||||
// "Asset %s already exists"
|
||||
const assetAlreadyExistsRegex = /([tT]he )?[aA]sset \w* already exists/g;
|
||||
const assetAlreadyExistsMatch = err.message.match(assetAlreadyExistsRegex);
|
||||
logger.debug(
|
||||
{ message: err.message, result: assetAlreadyExistsMatch },
|
||||
'Checking for asset already exists message'
|
||||
);
|
||||
if (assetAlreadyExistsMatch) {
|
||||
return new AssetExistsError(assetAlreadyExistsMatch[0], transactionId);
|
||||
}
|
||||
|
||||
// This regex needs to match the following error messages:
|
||||
// "the asset %s does not exist"
|
||||
// "The asset ${id} does not exist"
|
||||
// "Asset %s does not exist"
|
||||
const assetDoesNotExistRegex = /([tT]he )?[aA]sset \w* does not exist/g;
|
||||
const assetDoesNotExistMatch = err.message.match(assetDoesNotExistRegex);
|
||||
logger.debug(
|
||||
{ message: err.message, result: assetDoesNotExistMatch },
|
||||
'Checking for asset does not exist message'
|
||||
);
|
||||
if (assetDoesNotExistMatch) {
|
||||
return new AssetNotFoundError(assetDoesNotExistMatch[0], transactionId);
|
||||
}
|
||||
|
||||
// This regex needs to match the following error messages:
|
||||
// "Failed to get transaction with id %s, error Entry not found in index"
|
||||
const transactionDoesNotExistRegex =
|
||||
/Failed to get transaction with id [^,]*, error Entry not found in index/g;
|
||||
const transactionDoesNotExistMatch = err.message.match(
|
||||
transactionDoesNotExistRegex
|
||||
);
|
||||
logger.debug(
|
||||
{ message: err.message, result: transactionDoesNotExistMatch },
|
||||
'Checking for transaction does not exist message'
|
||||
);
|
||||
if (transactionDoesNotExistMatch) {
|
||||
return new TransactionNotFoundError(
|
||||
transactionDoesNotExistMatch[0],
|
||||
transactionId
|
||||
);
|
||||
}
|
||||
|
||||
logger.error(
|
||||
{ transactionId: transactionId, error: err },
|
||||
'Unhandled transaction error'
|
||||
);
|
||||
return new TransactionError('Transaction error', transactionId);
|
||||
};
|
||||
|
||||
/*
|
||||
* Retry a transaction
|
||||
*
|
||||
|
|
@ -357,26 +295,6 @@ const retryTransaction = async (
|
|||
}
|
||||
};
|
||||
|
||||
// TODO move to errors.ts?
|
||||
const isDuplicateTransactionError = (error: {
|
||||
errors: { endorsements: { details: string }[] }[];
|
||||
}) => {
|
||||
// TODO this is horrible! Isn't it possible to check for TxValidationCode DUPLICATE_TXID somehow?
|
||||
try {
|
||||
const isDuplicateTxn = error?.errors?.some((err) =>
|
||||
err?.endorsements?.some((endorsement) =>
|
||||
endorsement?.details?.startsWith('duplicate transaction found')
|
||||
)
|
||||
);
|
||||
|
||||
return isDuplicateTxn;
|
||||
} catch (err) {
|
||||
logger.warn(err, 'Error checking for duplicate transaction');
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
* Block event listener to handle successful transactions
|
||||
*
|
||||
|
|
@ -409,9 +327,9 @@ export const blockEventHandler = (redis: Redis): BlockListener => {
|
|||
|
||||
/*
|
||||
* Get the current block height
|
||||
*
|
||||
*
|
||||
* This example of using a system contract is used for the liveness REST
|
||||
* endpoint
|
||||
* endpoint
|
||||
*/
|
||||
export const getBlockHeight = async (
|
||||
qscc: Contract
|
||||
|
|
|
|||
Loading…
Reference in a new issue