1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

Return details arrays on all errors. (#3630)

We used to use the `details` property to return a list of errors on a
lot of our errors, but the new format doesn't do this anymore. However,
some of the admin UI still expects this to be present, even when the
data could go into `message`. So for now, the solution is to duplicate
the data and put it both in `message` and in the first element of the
`details` list. If the error has its own details lust (such as openapi
errors etc), then they will overwrite this default `details` property.
This commit is contained in:
Thomas Heartman 2023-04-26 15:41:43 +02:00 committed by GitHub
parent 33dce40773
commit a7213bf70b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 32 deletions

View File

@ -1,9 +1,47 @@
import { ErrorObject } from 'ajv'; import { ErrorObject } from 'ajv';
import { import {
ApiErrorSchema, ApiErrorSchema,
fromLegacyError,
fromOpenApiValidationError, fromOpenApiValidationError,
fromOpenApiValidationErrors, fromOpenApiValidationErrors,
UnleashApiErrorNameWithoutExtraData,
UnleashApiErrorTypes,
UnleashError,
} from './api-error'; } from './api-error';
import BadDataError from './bad-data-error';
describe('v5 deprecation: backwards compatibility', () => {
it.each(UnleashApiErrorTypes)(
'Adds details to error type: "%s"',
(name: UnleashApiErrorNameWithoutExtraData) => {
const message = `Error type: ${name}`;
const error = new UnleashError({ name, message }).toJSON();
expect(error.message).toBe(message);
expect(error.details).toStrictEqual([
{
message,
description: message,
},
]);
},
);
});
describe('Standard/legacy error conversion', () => {
it('Moves message to the details list for baddataerror', () => {
const message = `: message!`;
const result = fromLegacyError(new BadDataError(message)).toJSON();
expect(result.message.includes('`details`'));
expect(result.details).toStrictEqual([
{
message,
description: message,
},
]);
});
});
describe('OpenAPI error conversion', () => { describe('OpenAPI error conversion', () => {
it('Gives useful error messages for missing properties', () => { it('Gives useful error messages for missing properties', () => {

View File

@ -2,32 +2,32 @@ import { v4 as uuidV4 } from 'uuid';
import { FromSchema } from 'json-schema-to-ts'; import { FromSchema } from 'json-schema-to-ts';
import { ErrorObject } from 'ajv'; import { ErrorObject } from 'ajv';
const UnleashApiErrorTypes = [ export const UnleashApiErrorTypes = [
'OwaspValidationError', 'ContentTypeError',
'PasswordUndefinedError', 'DisabledError',
'NoAccessError',
'UsedTokenError',
'InvalidOperationError',
'IncompatibleProjectError',
'OperationDeniedError',
'NotFoundError',
'NameExistsError',
'FeatureHasTagError', 'FeatureHasTagError',
'RoleInUseError', 'IncompatibleProjectError',
'ProjectWithoutOwnerError', 'InvalidOperationError',
'UnknownError', 'MinimumOneEnvironmentError',
'NameExistsError',
'NoAccessError',
'NotFoundError',
'NotImplementedError',
'OperationDeniedError',
'OwaspValidationError',
'PasswordMismatch', 'PasswordMismatch',
'PasswordMismatchError', 'PasswordMismatchError',
'DisabledError', 'PasswordUndefinedError',
'ContentTypeError', 'ProjectWithoutOwnerError',
'NotImplementedError', 'RoleInUseError',
'UnknownError',
'UsedTokenError',
// server errors; not the end user's fault // server errors; not the end user's fault
'InternalError', 'InternalError',
] as const; ] as const;
const UnleashApiErrorTypesWithExtraData = [ const UnleashApiErrorTypesWithExtraData = [
'MinimumOneEnvironmentError',
'BadDataError', 'BadDataError',
'BadRequestError', 'BadRequestError',
'ValidationError', 'ValidationError',
@ -42,10 +42,8 @@ const AllUnleashApiErrorTypes = [
] as const; ] as const;
type UnleashApiErrorName = typeof AllUnleashApiErrorTypes[number]; type UnleashApiErrorName = typeof AllUnleashApiErrorTypes[number];
type UnleashApiErrorNameWithoutExtraData = Exclude< export type UnleashApiErrorNameWithoutExtraData =
UnleashApiErrorName, typeof UnleashApiErrorTypes[number];
typeof UnleashApiErrorTypesWithExtraData[number]
>;
const statusCode = (errorName: UnleashApiErrorName): number => { const statusCode = (errorName: UnleashApiErrorName): number => {
switch (errorName) { switch (errorName) {
@ -174,6 +172,7 @@ export class UnleashError extends Error {
id: this.id, id: this.id,
name: this.name, name: this.name,
message: this.message, message: this.message,
details: [{ message: this.message, description: this.message }],
...this.additionalParameters, ...this.additionalParameters,
}; };
} }
@ -228,11 +227,10 @@ export const fromLegacyError = (e: Error): UnleashError => {
if ( if (
[ [
'ValidationError',
'BadRequestError',
'BadDataError', 'BadDataError',
'BadRequestError',
'InvalidTokenError', 'InvalidTokenError',
'MinimumOneEnvironmentError', 'ValidationError',
].includes(name) ].includes(name)
) { ) {
return new UnleashError({ return new UnleashError({
@ -240,10 +238,9 @@ export const fromLegacyError = (e: Error): UnleashError => {
| 'ValidationError' | 'ValidationError'
| 'BadRequestError' | 'BadRequestError'
| 'BadDataError' | 'BadDataError'
| 'InvalidTokenError' | 'InvalidTokenError',
| 'MinimumOneEnvironmentError',
message: message:
'Your request body failed to validate. Refer to the `details` list to see what happened.', 'Request validation failed: your request body failed to validate. Refer to the `details` list to see what happened.',
details: [{ description: e.message, message: e.message }], details: [{ description: e.message, message: e.message }],
}); });
} }

View File

@ -26,14 +26,11 @@ export const handleErrors: (
logger: Logger, logger: Logger,
error: Error, error: Error,
) => void = (res, logger, error) => { ) => void = (res, logger, error) => {
logger.warn(error.message);
// @ts-expect-error
// eslint-disable-next-line no-param-reassign
error.isJoi = true;
const finalError = const finalError =
error instanceof UnleashError ? error : fromLegacyError(error); error instanceof UnleashError ? error : fromLegacyError(error);
logger.warn(finalError.id, finalError.message);
if (['InternalError', 'UnknownError'].includes(finalError.name)) { if (['InternalError', 'UnknownError'].includes(finalError.name)) {
logger.error('Server failed executing request', error); logger.error('Server failed executing request', error);
} }