Skip to content

Commit

Permalink
[OGUI-1596] Update grpcErrorToNativeError to provide option for devel…
Browse files Browse the repository at this point in the history
…oper to use either message or details (#2711)

* allows usage of either `details` or `message` for the custom error class
* this is due to the fact that the `message` includes also the error key and code which is not needed as the custom error class already describes that but it might be useful to developers
  • Loading branch information
graduta authored Jan 8, 2025
1 parent 62b8f39 commit 11e0413
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 28 deletions.
23 changes: 23 additions & 0 deletions Framework/Backend/errors/grpcErrorCodes.enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

const GrpcErrorCodes = Object.freeze({
UNKNOWN: 2,
INVALID_INPUT: 3,
TIMEOUT: 4,
NOT_FOUND: 5,
UNAUTHORIZED_ACCESS: 7,
SERVICE_UNAVAILABLE: 14,
});

exports.GrpcErrorCodes = GrpcErrorCodes;
33 changes: 18 additions & 15 deletions Framework/Backend/errors/grpcErrorToNativeError.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ const { NotFoundError } = require('./NotFoundError.js');
const { ServiceUnavailableError } = require('./ServiceUnavailableError.js');
const { TimeoutError } = require('./TimeoutError.js');
const { UnauthorizedAccessError } = require('./UnauthorizedAccessError.js');
const { GrpcErrorCodes } = require('./grpcErrorCodes.enum.js');

/**
* @typedef GrpcError
* also known as gRPC Status Object
* also known as gRPC Status Object https://grpc.github.io/grpc/node/grpc.html#~StatusObject
*
* @property {number} code - code of the gRPC Status object
* @property {GrpcErrorCodes} code - code of the gRPC Status object
* @property {string} details - details of the gRPC Status object
* @property {string} message - message of the gRPC Status object
*/

Expand All @@ -30,24 +32,25 @@ const { UnauthorizedAccessError } = require('./UnauthorizedAccessError.js');
* Code List source: https://grpc.github.io/grpc/core/md_doc_statuscodes.html
*
* @param {GrpcError} error - error object from gRPC Client library
* @param {Boolean} includeStatusCode - whether to error message field or details field
* @returns {Error}
*/
const grpcErrorToNativeError = (error) => {
const { code, message } = error;
const grpcErrorToNativeError = (error, includeStatusCode = false) => {
const { code, details, message } = error;

switch (code) {
case 3:
return new InvalidInputError(message);
case 4:
return new TimeoutError(message);
case 5:
return new NotFoundError(message);
case 7:
return new UnauthorizedAccessError(message);
case 14:
return new ServiceUnavailableError(message);
case GrpcErrorCodes.INVALID_INPUT:
return new InvalidInputError(includeStatusCode ? message : details);
case GrpcErrorCodes.TIMEOUT:
return new TimeoutError(includeStatusCode ? message : details);
case GrpcErrorCodes.NOT_FOUND:
return new NotFoundError(includeStatusCode ? message : details);
case GrpcErrorCodes.UNAUTHORIZED_ACCESS:
return new UnauthorizedAccessError(includeStatusCode ? message : details);
case GrpcErrorCodes.SERVICE_UNAVAILABLE:
return new ServiceUnavailableError(includeStatusCode ? message : details);
default:
return new Error(message);
return new Error(includeStatusCode ? message : details);
}
};

Expand Down
3 changes: 3 additions & 0 deletions Framework/Backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const { ServiceUnavailableError } = require('./errors/ServiceUnavailableError.js
const { TimeoutError } = require('./errors/TimeoutError.js');
const { UnauthorizedAccessError } = require('./errors/UnauthorizedAccessError.js');
const { grpcErrorToNativeError } = require('./errors/grpcErrorToNativeError.js');
const { GrpcErrorCodes } = require('./errors/grpcErrorCodes.enum.js');
const {
updateAndSendExpressResponseFromNativeError,
} = require('./errors/updateAndSendExpressResponseFromNativeError.js');
Expand Down Expand Up @@ -79,6 +80,8 @@ exports.TimeoutError = TimeoutError;

exports.UnauthorizedAccessError = UnauthorizedAccessError;

exports.GrpcErrorCodes = GrpcErrorCodes;

exports.grpcErrorToNativeError = grpcErrorToNativeError;

exports.updateAndSendExpressResponseFromNativeError = updateAndSendExpressResponseFromNativeError;
81 changes: 68 additions & 13 deletions Framework/Backend/test/errors/mocha-grpcErrorToNativeError.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,81 @@
* or submit itself to any jurisdiction.
*/

const assert = require('assert');

const { grpcErrorToNativeError } = require('../../errors/grpcErrorToNativeError.js');
const { GrpcErrorCodes: {
INVALID_INPUT, TIMEOUT, NOT_FOUND, UNAUTHORIZED_ACCESS, SERVICE_UNAVAILABLE, UNKNOWN,
} } = require('../../errors/grpcErrorCodes.enum.js');
const { InvalidInputError } = require('../../errors/InvalidInputError.js');
const { NotFoundError } = require('../../errors/NotFoundError.js');
const { ServiceUnavailableError } = require('../../errors/ServiceUnavailableError.js');
const { TimeoutError } = require('../../errors/TimeoutError.js');
const { UnauthorizedAccessError } = require('../../errors/UnauthorizedAccessError.js');

const assert = require('assert');

describe('\'grpcErrorToNativeError\' test suite', () => {
it('should successfully convert gRPC errors to native errors', () => {
assert.deepStrictEqual(grpcErrorToNativeError({ code: 3, message: 'invalid' }), new InvalidInputError('invalid'));
assert.deepStrictEqual(grpcErrorToNativeError({ code: 4, message: 'timeout' }), new TimeoutError('timeout'));
assert.deepStrictEqual(grpcErrorToNativeError({ code: 5, message: 'not-found' }), new NotFoundError('not-found'));
assert.deepStrictEqual(grpcErrorToNativeError({ code: 7, message: 'unauthorized' }), new UnauthorizedAccessError('unauthorized'));
assert.deepStrictEqual(
grpcErrorToNativeError({ code: 14, message: 'service-unavailable' }),
new ServiceUnavailableError('service-unavailable'),
);
assert.deepStrictEqual(grpcErrorToNativeError({ code: 100, message: 'standard-error' }), new Error('standard-error'));
assert.deepStrictEqual(grpcErrorToNativeError({ message: 'standard-error' }), new Error('standard-error'));
const testCases = [
{
grpcError: { code: UNKNOWN, message: `${UNKNOWN}: Error is unknown`, details: 'Error is unknown' },
expectedError: Error,
expectedMessage: `${UNKNOWN}: Error is unknown`,
expectedDetails: 'Error is unknown',
},
{
grpcError: { code: INVALID_INPUT, message: `${INVALID_INPUT}: Invalid input details`, details: 'Invalid input details' },
expectedError: InvalidInputError,
expectedMessage: `${INVALID_INPUT}: Invalid input details`,
expectedDetails: 'Invalid input details',
},
{
grpcError: { code: TIMEOUT, message: `${TIMEOUT}: Timeout occurred`, details: 'Timeout occurred' },
expectedError: TimeoutError,
expectedMessage: `${TIMEOUT}: Timeout occurred`,
expectedDetails: 'Timeout occurred',
},
{
grpcError: { code: NOT_FOUND, message: `${NOT_FOUND}: Not found`, details: 'Not found' },
expectedError: NotFoundError,
expectedMessage: `${NOT_FOUND}: Not found`,
expectedDetails: 'Not found',
},
{
grpcError: { code: UNAUTHORIZED_ACCESS, message: `${UNAUTHORIZED_ACCESS}: Unauthorized access`, details: 'Unauthorized access details' },
expectedError: UnauthorizedAccessError,
expectedMessage: `${UNAUTHORIZED_ACCESS}: Unauthorized access`,
expectedDetails: 'Unauthorized access details',
},
{
grpcError: { code: SERVICE_UNAVAILABLE, message: `${SERVICE_UNAVAILABLE}: Service unavailable`, details: 'Service unavailable details' },
expectedError: ServiceUnavailableError,
expectedMessage: `${SERVICE_UNAVAILABLE}: Service unavailable`,
expectedDetails: 'Service unavailable details',
},
{
grpcError: { code: 100, message: '100: Unknown Code and Error', details: 'Unknown Code and Error' },
expectedError: Error,
expectedMessage: '100: Unknown Code and Error',
expectedDetails: 'Unknown Code and Error',
},
];

testCases.forEach(({ grpcError: { code, details }, expectedError, expectedDetails }) => {
it(`should convert gRPC error with code ${code} and pass DETAILS in error of type ${expectedError}`, () => {
assert.deepStrictEqual(grpcErrorToNativeError({ code, details }), new expectedError(expectedDetails));
});
});

testCases.forEach(({ grpcError: { code, message }, expectedError, expectedMessage }) => {
it(`should convert gRPC error with code ${code} and pass MESSAGE in error of type ${expectedError.name}`, () => {
assert.deepStrictEqual(grpcErrorToNativeError({ code, message }, true), new expectedError(expectedMessage));
});
});

it('should handle gRPC error with unknown code gracefully', () => {
assert.deepStrictEqual(grpcErrorToNativeError({ code: 999, details: 'unknown-error' }), new Error('unknown-error'));
});

it('should handle gRPC error without message gracefully', () => {
assert.deepStrictEqual(grpcErrorToNativeError({ code: 3 }), new InvalidInputError(''));
});
});

0 comments on commit 11e0413

Please sign in to comment.