Fix fieldMetadata sync validation exceptions caught in exception handler (#10789)
## Context Field metadata service was reusing validators from validate-**OBJECT**-metadata-input which were throwing ObjectMetadata exceptions not handled in fieldMetadataGraphqlApiExceptionHandler and were going to Sentry. To solve the issue since this validator is associated with both fields and objects I'm moving the util to the root utils folder of metadata module and throwing a common metadata user input exception
This commit is contained in:
@ -36,14 +36,15 @@ import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-
|
||||
import { isSelectFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-select-field-metadata-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||
import {
|
||||
RelationMetadataEntity,
|
||||
RelationMetadataType,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception';
|
||||
import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
|
||||
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||
import {
|
||||
@ -59,7 +60,6 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { ViewService } from 'src/modules/view/services/view.service';
|
||||
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
|
||||
import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception';
|
||||
|
||||
import { FieldMetadataValidationService } from './field-metadata-validation.service';
|
||||
import { FieldMetadataEntity } from './field-metadata.entity';
|
||||
|
||||
@ -9,8 +9,13 @@ import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof InvalidMetadataException) {
|
||||
throw new UserInputError(error.message);
|
||||
}
|
||||
|
||||
if (error instanceof FieldMetadataException) {
|
||||
switch (error.code) {
|
||||
case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND:
|
||||
|
||||
@ -27,12 +27,12 @@ import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/objec
|
||||
import { buildDefaultFieldsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util';
|
||||
import {
|
||||
validateLowerCasedAndTrimmedStringsAreDifferentOrThrow,
|
||||
validateNameAndLabelAreSyncOrThrow,
|
||||
validateObjectMetadataInputLabelsOrThrow,
|
||||
validateObjectMetadataInputNamesOrThrow,
|
||||
} from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
|
||||
import { SearchService } from 'src/engine/metadata-modules/search/search.service';
|
||||
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
|
||||
@ -9,8 +9,13 @@ import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof InvalidMetadataException) {
|
||||
throw new UserInputError(error.message);
|
||||
}
|
||||
|
||||
if (error instanceof ObjectMetadataException) {
|
||||
switch (error.code) {
|
||||
case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { slugify } from 'transliteration';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
@ -11,7 +10,6 @@ import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/
|
||||
import { validateMetadataNameIsNotTooLongOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils';
|
||||
import { validateMetadataNameIsNotTooShortOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { camelCase } from 'src/utils/camel-case';
|
||||
|
||||
export const validateObjectMetadataInputNamesOrThrow = <
|
||||
T extends UpdateObjectPayload | CreateObjectInput,
|
||||
@ -71,50 +69,6 @@ const validateObjectMetadataInputLabelOrThrow = (name: string): void => {
|
||||
}
|
||||
};
|
||||
|
||||
export const computeMetadataNameFromLabel = (label: string): string => {
|
||||
if (!isDefined(label)) {
|
||||
throw new ObjectMetadataException(
|
||||
'Label is required',
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const prefixedLabel = /^\d/.test(label) ? `n${label}` : label;
|
||||
|
||||
if (prefixedLabel === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const formattedString = slugify(prefixedLabel, {
|
||||
trim: true,
|
||||
separator: '_',
|
||||
allowedChars: 'a-zA-Z0-9',
|
||||
});
|
||||
|
||||
if (formattedString === '') {
|
||||
throw new ObjectMetadataException(
|
||||
`Invalid label: "${label}"`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
return camelCase(formattedString);
|
||||
};
|
||||
|
||||
export const validateNameAndLabelAreSyncOrThrow = (
|
||||
label: string,
|
||||
name: string,
|
||||
) => {
|
||||
const computedName = computeMetadataNameFromLabel(label);
|
||||
|
||||
if (name !== computedName) {
|
||||
throw new ObjectMetadataException(
|
||||
`Name is not synced with label. Expected name: "${computedName}", got ${name}`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
type ValidateLowerCasedAndTrimmedStringAreDifferentOrThrowArgs = {
|
||||
inputs: [string, string];
|
||||
message: string;
|
||||
|
||||
@ -8,8 +8,13 @@ import {
|
||||
RelationMetadataException,
|
||||
RelationMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception';
|
||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const relationMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||
if (error instanceof InvalidMetadataException) {
|
||||
throw new UserInputError(error.message);
|
||||
}
|
||||
|
||||
if (error instanceof RelationMetadataException) {
|
||||
switch (error.code) {
|
||||
case RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND:
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import { slugify } from 'transliteration';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const computeMetadataNameFromLabel = (label: string): string => {
|
||||
if (!isDefined(label)) {
|
||||
throw new InvalidMetadataException('Label is required');
|
||||
}
|
||||
|
||||
const prefixedLabel = /^\d/.test(label) ? `n${label}` : label;
|
||||
|
||||
if (prefixedLabel === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const formattedString = slugify(prefixedLabel, {
|
||||
trim: true,
|
||||
separator: '_',
|
||||
allowedChars: 'a-zA-Z0-9',
|
||||
});
|
||||
|
||||
if (formattedString === '') {
|
||||
throw new InvalidMetadataException(`Invalid label: "${label}"`);
|
||||
}
|
||||
|
||||
return camelCase(formattedString);
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export class InvalidMetadataException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import { slugify } from 'transliteration';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const validateNameAndLabelAreSyncOrThrow = (
|
||||
label: string,
|
||||
name: string,
|
||||
) => {
|
||||
const computedName = computeMetadataNameFromLabel(label);
|
||||
|
||||
if (name !== computedName) {
|
||||
throw new InvalidMetadataException(
|
||||
`Name is not synced with label. Expected name: "${computedName}", got ${name}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const computeMetadataNameFromLabel = (label: string): string => {
|
||||
if (!isDefined(label)) {
|
||||
throw new InvalidMetadataException('Label is required');
|
||||
}
|
||||
|
||||
const prefixedLabel = /^\d/.test(label) ? `n${label}` : label;
|
||||
|
||||
if (prefixedLabel === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const formattedString = slugify(prefixedLabel, {
|
||||
trim: true,
|
||||
separator: '_',
|
||||
allowedChars: 'a-zA-Z0-9',
|
||||
});
|
||||
|
||||
if (formattedString === '') {
|
||||
throw new InvalidMetadataException(`Invalid label: "${label}"`);
|
||||
}
|
||||
|
||||
return camelCase(formattedString);
|
||||
};
|
||||
Reference in New Issue
Block a user