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:
Weiko
2025-03-11 18:41:29 +01:00
committed by GitHub
parent 9880114853
commit 4d0450069c
9 changed files with 94 additions and 49 deletions

View File

@ -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';

View File

@ -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:

View File

@ -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;