mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-21 17:15: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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { logger } from './logger';
|
||||||
|
|
||||||
export class TransactionError extends Error {
|
export class TransactionError extends Error {
|
||||||
transactionId: string;
|
transactionId: string;
|
||||||
|
|
||||||
|
|
@ -43,3 +45,91 @@ export class AssetNotFoundError extends TransactionError {
|
||||||
this.name = 'AssetNotFoundError';
|
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());
|
expect(result.toString()).toBe(mockPayload.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it('throws an AssetExistsError an asset already exists error occurs', async () => {
|
||||||
'the asset GOCHAINCODE already exists',
|
mockTransaction.evaluate.mockRejectedValue(
|
||||||
'Asset JAVACHAINCODE already exists',
|
new Error('The asset JSCHAINCODE 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));
|
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
||||||
}).rejects.toThrow(AssetExistsError);
|
}).rejects.toThrow(AssetExistsError);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
it.each([
|
it('throws an AssetNotFoundError if an asset does not exist error occurs', async () => {
|
||||||
'the asset GOCHAINCODE does not exist',
|
mockTransaction.evaluate.mockRejectedValue(
|
||||||
'Asset JAVACHAINCODE does not exist',
|
new Error('The asset JSCHAINCODE 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));
|
|
||||||
|
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
await evatuateTransaction(mockContract, 'txn', 'arga', 'argb');
|
||||||
}).rejects.toThrow(AssetNotFoundError);
|
}).rejects.toThrow(AssetNotFoundError);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
it('throws a TransactionNotFoundError if a transaction not found error occurs', async () => {
|
it('throws a TransactionNotFoundError if a transaction not found error occurs', async () => {
|
||||||
mockTransaction.evaluate.mockRejectedValue(
|
mockTransaction.evaluate.mockRejectedValue(
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,7 @@ import {
|
||||||
incrementRetryCount,
|
incrementRetryCount,
|
||||||
TransactionDetails,
|
TransactionDetails,
|
||||||
} from './redis';
|
} from './redis';
|
||||||
import {
|
import { handleError, isDuplicateTransactionError } from './errors';
|
||||||
AssetExistsError,
|
|
||||||
AssetNotFoundError,
|
|
||||||
TransactionError,
|
|
||||||
TransactionNotFoundError,
|
|
||||||
} from './errors';
|
|
||||||
import protos from 'fabric-protos';
|
import protos from 'fabric-protos';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -247,63 +242,6 @@ export const submitTransaction = async (
|
||||||
return txnId;
|
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
|
* 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
|
* Block event listener to handle successful transactions
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue