Add exceptions for metadata modules (#6070)

Class exception for each metadata module + handler to map on graphql
error

TODO left :
- find a way to call handler on auto-resolvers nestjs query (probably
interceptors)
- discuss what should be done for pre-hooks errors
- discuss what should be done for Unauthorized exception
This commit is contained in:
Thomas Trompette
2024-07-01 13:49:17 +02:00
committed by GitHub
parent 4599f43b6c
commit a15884ea0a
48 changed files with 815 additions and 199 deletions

View File

@ -1,5 +1,4 @@
import { BadRequestException } from '@nestjs/common';
import { FieldMetadataException } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
describe('serializeDefaultValue', () => {
@ -15,8 +14,10 @@ describe('serializeDefaultValue', () => {
expect(serializeDefaultValue('now')).toBe('now()');
});
it('should throw BadRequestException for invalid dynamic default value type', () => {
expect(() => serializeDefaultValue('invalid')).toThrow(BadRequestException);
it('should throw FieldMetadataException for invalid dynamic default value type', () => {
expect(() => serializeDefaultValue('invalid')).toThrow(
FieldMetadataException,
);
});
it('should handle string static default value', () => {

View File

@ -4,6 +4,10 @@ import { CompositeProperty } from 'src/engine/metadata-modules/field-metadata/in
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
type ComputeColumnNameOptions = { isForeignKey?: boolean };
@ -29,8 +33,9 @@ export function computeColumnName<T extends FieldMetadataType | 'default'>(
}
if (isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) {
throw new Error(
`Cannot compute column name for composite field metadata type: ${fieldMetadataOrFieldName.type}`,
throw new FieldMetadataException(
`Cannot compute composite column name for non-composite field metadata type: ${fieldMetadataOrFieldName.type}`,
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
@ -61,8 +66,9 @@ export function computeCompositeColumnName<
}
if (!isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) {
throw new Error(
throw new FieldMetadataException(
`Cannot compute composite column name for non-composite field metadata type: ${fieldMetadataOrFieldName.type}`,
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}

View File

@ -0,0 +1,32 @@
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import {
UserInputError,
ForbiddenError,
ConflictError,
InternalServerError,
NotFoundError,
} from 'src/engine/utils/graphql-errors.util';
export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
if (error instanceof FieldMetadataException) {
switch (error.code) {
case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND:
throw new NotFoundError(error.message);
case FieldMetadataExceptionCode.INVALID_FIELD_INPUT:
throw new UserInputError(error.message);
case FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED:
throw new ForbiddenError(error.message);
case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS:
throw new ConflictError(error.message);
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
default:
throw new InternalServerError(error.message);
}
}
throw error;
};

View File

@ -1,7 +1,9 @@
import { BadRequestException } from '@nestjs/common';
import { FieldMetadataDefaultSerializableValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { isFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/is-function-default-value.util';
import { serializeFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-function-default-value.util';
@ -18,7 +20,10 @@ export const serializeDefaultValue = (
serializeFunctionDefaultValue(defaultValue);
if (!serializedTypeDefaultValue) {
throw new BadRequestException('Invalid default value');
throw new FieldMetadataException(
'Invalid default value',
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
return serializedTypeDefaultValue;
@ -51,5 +56,8 @@ export const serializeDefaultValue = (
return `'${JSON.stringify(defaultValue)}'`;
}
throw new BadRequestException(`Invalid default value "${defaultValue}"`);
throw new FieldMetadataException(
`Invalid default value "${defaultValue}"`,
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
};

View File

@ -8,6 +8,10 @@ import {
FieldMetadataComplexOption,
FieldMetadataDefaultOption,
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { isEnumFieldMetadataType } from './is-enum-field-metadata-type.util';
@ -24,7 +28,10 @@ export const validateOptionsForType = (
if (options === null) return true;
if (!Array.isArray(options)) {
throw new Error('Options must be an array');
throw new FieldMetadataException(
'Options must be an array',
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
if (!isEnumFieldMetadataType(type)) {
@ -39,7 +46,10 @@ export const validateOptionsForType = (
// Check if all options are unique
if (new Set(values).size !== options.length) {
throw new Error('Options must be unique');
throw new FieldMetadataException(
'Options must be unique',
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
const validators = optionsValidatorsMap[type];