FieldMetadataServiceV2.updateOne (#13784)
# Introduction Implementing the `updateOne` service handler following workspace migration v2 style
This commit is contained in:
+13
-1
@@ -4,8 +4,20 @@ import { IsJSON, IsOptional, IsString } from 'class-validator';
|
||||
import { GraphQLJSON } from 'graphql-type-json';
|
||||
import { type APP_LOCALES } from 'twenty-shared/translations';
|
||||
|
||||
import { FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
|
||||
|
||||
export const fieldMetadataStandardOverridesProperties = [
|
||||
'label',
|
||||
'description',
|
||||
'icon',
|
||||
] as const satisfies FlatFieldMetadataPropertiesToCompare[];
|
||||
|
||||
export type FieldMetadataStandardOverridesProperties =
|
||||
(typeof fieldMetadataStandardOverridesProperties)[number];
|
||||
@ObjectType('StandardOverrides')
|
||||
export class FieldStandardOverridesDTO {
|
||||
export class FieldStandardOverridesDTO
|
||||
implements Partial<Record<FieldMetadataStandardOverridesProperties, unknown>>
|
||||
{
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field(() => String, { nullable: true })
|
||||
|
||||
+17
@@ -87,6 +87,23 @@ export class FieldMetadataResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@Context() context: I18nContext,
|
||||
) {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
try {
|
||||
return await this.fieldMetadataServiceV2.updateOne({
|
||||
updateFieldInput: { ...input.update, id: input.id },
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
fieldMetadataGraphqlApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedInput = (await this.beforeUpdateOneField.run(input, {
|
||||
workspaceId,
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-module
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||
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 { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metad
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
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 { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
|
||||
type ValidateFieldMetadataArgs = {
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
|
||||
+112
-26
@@ -11,6 +11,7 @@ import { In, Repository } from 'typeorm';
|
||||
import { MultipleMetadataValidationErrors } from 'src/engine/core-modules/error/multiple-metadata-validation-errors';
|
||||
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
@@ -21,6 +22,7 @@ import { type FailedFlatFieldMetadataValidationExceptions } from 'src/engine/met
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util';
|
||||
import { fromDeleteFieldInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util';
|
||||
import { fromUpdateFieldInputToFlatFieldMetadataToUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata-to-update.util';
|
||||
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { throwOnFieldInputTranspilationsError } from 'src/engine/metadata-modules/flat-field-metadata/utils/throw-on-field-input-transpilations-error.util';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
@@ -29,6 +31,7 @@ import { addFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metadat
|
||||
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util';
|
||||
import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util';
|
||||
import { getSubFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps.util';
|
||||
import { replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
||||
@@ -105,33 +108,40 @@ export class FieldMetadataServiceV2 {
|
||||
);
|
||||
}
|
||||
|
||||
const flatObjectMetadataMapsWithImpactedObject =
|
||||
getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: flatFieldMetadatasToDelete.map(
|
||||
(flatFieldMetadataToDelete) =>
|
||||
flatFieldMetadataToDelete.objectMetadataId,
|
||||
),
|
||||
try {
|
||||
const flatObjectMetadataMapsWithImpactedObject =
|
||||
getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: flatFieldMetadatasToDelete.map(
|
||||
(flatFieldMetadataToDelete) =>
|
||||
flatFieldMetadataToDelete.objectMetadataId,
|
||||
),
|
||||
});
|
||||
|
||||
const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete.reduce(
|
||||
(flatObjectMetadataMaps, flatFieldMetadataToDelete) =>
|
||||
deleteFieldFromFlatObjectMetadataMapsOrThrow({
|
||||
fieldMetadataId: flatFieldMetadataToDelete.id,
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId: flatFieldMetadataToDelete.objectMetadataId,
|
||||
}),
|
||||
flatObjectMetadataMapsWithImpactedObject,
|
||||
);
|
||||
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
fromFlatObjectMetadataMaps: flatObjectMetadataMapsWithImpactedObject,
|
||||
toFlatObjectMetadataMaps,
|
||||
inferDeletionFromMissingObjectFieldIndex: true,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete.reduce(
|
||||
(flatObjectMetadataMaps, flatFieldMetadataToDelete) =>
|
||||
deleteFieldFromFlatObjectMetadataMapsOrThrow({
|
||||
fieldMetadataId: flatFieldMetadataToDelete.id,
|
||||
flatObjectMetadataMaps,
|
||||
objectMetadataId: flatFieldMetadataToDelete.objectMetadataId,
|
||||
}),
|
||||
flatObjectMetadataMapsWithImpactedObject,
|
||||
);
|
||||
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
fromFlatObjectMetadataMaps: flatObjectMetadataMapsWithImpactedObject,
|
||||
toFlatObjectMetadataMaps,
|
||||
inferDeletionFromMissingObjectFieldIndex: true,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
} catch {
|
||||
throw new FieldMetadataException(
|
||||
'Workspace migration failed to run',
|
||||
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO to be discussed with coco regarding storing dates in flat in order to build dtos
|
||||
// return flatFieldMetadatasToDelete[0];
|
||||
@@ -188,6 +198,82 @@ export class FieldMetadataServiceV2 {
|
||||
});
|
||||
}
|
||||
|
||||
async updateOne({
|
||||
updateFieldInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
updateFieldInput: UpdateFieldInput;
|
||||
workspaceId: string;
|
||||
}): Promise<FieldMetadataEntity> {
|
||||
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const inputTranspilationResult =
|
||||
fromUpdateFieldInputToFlatFieldMetadataToUpdate({
|
||||
existingFlatObjectMetadataMaps,
|
||||
updateFieldInput,
|
||||
});
|
||||
|
||||
if (inputTranspilationResult.status === 'fail') {
|
||||
throw inputTranspilationResult.error;
|
||||
}
|
||||
|
||||
const optimisticiallyUpdatedFlatFieldMetadata =
|
||||
inputTranspilationResult.result;
|
||||
|
||||
const validationErrors =
|
||||
await this.flatFieldMetadataValidatorService.validateFlatFieldMetadataUpdate(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate: optimisticiallyUpdatedFlatFieldMetadata,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
throw new MultipleMetadataValidationErrors(
|
||||
validationErrors,
|
||||
'Multiple validation errors occurred while updating field',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: [
|
||||
optimisticiallyUpdatedFlatFieldMetadata.objectMetadataId,
|
||||
],
|
||||
});
|
||||
const toFlatObjectMetadataMaps =
|
||||
replaceFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: fromFlatObjectMetadataMaps,
|
||||
flatFieldMetadata: optimisticiallyUpdatedFlatFieldMetadata,
|
||||
});
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
|
||||
return this.fieldMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
id: optimisticiallyUpdatedFlatFieldMetadata.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
throw new FieldMetadataException(
|
||||
'Workspace migration failed to run',
|
||||
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async createMany({
|
||||
fieldMetadataInputs,
|
||||
workspaceId,
|
||||
@@ -240,7 +326,7 @@ export class FieldMetadataServiceV2 {
|
||||
});
|
||||
|
||||
const validationErrors =
|
||||
await this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata(
|
||||
await this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadataCreation(
|
||||
{
|
||||
existingFlatObjectMetadataMaps: optimisticFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate: flatFieldMetadataToCreate,
|
||||
|
||||
+38
-1
@@ -1,11 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { ValidateOneFieldMetadataArgs } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
|
||||
import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { type FlatFieldMetadataTypeValidator } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
|
||||
import { isEnumValidateOneFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-validate-one-field-metadata-args.util';
|
||||
import { validateEnumSelectFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/validate-enum-flat-field-metadata.validator';
|
||||
@@ -14,7 +19,7 @@ import { validateRelationFlatFieldMetadata } from 'src/engine/metadata-modules/f
|
||||
export class FlatFieldMetadataTypeValidatorService {
|
||||
constructor(private readonly featureFlagService: FeatureFlagService) {}
|
||||
|
||||
public readonly FIELD_METADATA_TYPE_VALIDATOR_HASHMAP: FlatFieldMetadataTypeValidator =
|
||||
private readonly FIELD_METADATA_TYPE_VALIDATOR_HASHMAP: FlatFieldMetadataTypeValidator =
|
||||
{
|
||||
ACTOR: async (_args) => {
|
||||
return [];
|
||||
@@ -126,4 +131,36 @@ export class FlatFieldMetadataTypeValidatorService {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
public async validateFlatFieldMetadataTypeSpecificities<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
>({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
otherFlatObjectMetadataMapsToValidate,
|
||||
}: ValidateOneFieldMetadataArgs<T>): Promise<
|
||||
FailedFlatFieldMetadataValidationExceptions[]
|
||||
> {
|
||||
const fieldMetadataTypeValidator =
|
||||
this.FIELD_METADATA_TYPE_VALIDATOR_HASHMAP[
|
||||
flatFieldMetadataToValidate.type
|
||||
];
|
||||
|
||||
if (!isDefined(fieldMetadataTypeValidator)) {
|
||||
return [
|
||||
new FieldMetadataException(
|
||||
'Unsupported field metadata type',
|
||||
FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return await fieldMetadataTypeValidator({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
otherFlatObjectMetadataMapsToValidate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+160
-34
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
@@ -11,8 +11,14 @@ import {
|
||||
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
|
||||
import { type FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import {
|
||||
compareTwoFlatFieldMetadata,
|
||||
type FlatFieldMetadataPropertiesToCompare,
|
||||
} from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
|
||||
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { validateFlatFieldMetadataNameAvailability } from 'src/engine/metadata-modules/flat-field-metadata/validators/validate-flat-field-metadata-name-availability.validator';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
@@ -21,7 +27,7 @@ import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
|
||||
export type ValidateOneFieldMetadataArgs<
|
||||
@@ -39,6 +45,143 @@ export class FlatFieldMetadataValidatorService {
|
||||
private readonly flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService,
|
||||
) {}
|
||||
|
||||
async validateFlatFieldMetadataUpdate<T extends FieldMetadataType>({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate: updatedFlatFieldMetadata,
|
||||
workspaceId,
|
||||
}: ValidateOneFieldMetadataArgs<T>): Promise<
|
||||
FailedFlatFieldMetadataValidationExceptions[]
|
||||
> {
|
||||
const errors: FailedFlatFieldMetadataValidationExceptions[] = [];
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
existingFlatObjectMetadataMaps.byId[
|
||||
updatedFlatFieldMetadata.objectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
|
||||
return [
|
||||
new FieldMetadataException(
|
||||
'field metadata to update object metadata not found',
|
||||
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
),
|
||||
];
|
||||
}
|
||||
if (
|
||||
!isDefined(
|
||||
flatObjectMetadataWithFlatFieldMaps.labelIdentifierFieldMetadataId,
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
'Label identifier field metadata id does not exist',
|
||||
FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const existingFlatFieldMetadataToUpdate =
|
||||
flatObjectMetadataWithFlatFieldMaps.fieldsById[
|
||||
updatedFlatFieldMetadata.id
|
||||
];
|
||||
|
||||
if (!isDefined(existingFlatFieldMetadataToUpdate)) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
'field metadata to update not found',
|
||||
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const updates = compareTwoFlatFieldMetadata({
|
||||
from: existingFlatFieldMetadataToUpdate,
|
||||
to: updatedFlatFieldMetadata,
|
||||
});
|
||||
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
updatedFlatFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
updatedFlatFieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
const relationEditableFields = [
|
||||
'label',
|
||||
'description',
|
||||
'isActive',
|
||||
] as const satisfies FlatFieldMetadataPropertiesToCompare[];
|
||||
const relationNonEditableUpdatedProperties = updates.flatMap(
|
||||
({ property }) =>
|
||||
!relationEditableFields.includes(
|
||||
property as (typeof relationEditableFields)[number],
|
||||
)
|
||||
? property
|
||||
: [],
|
||||
);
|
||||
|
||||
if (relationNonEditableUpdatedProperties.length > 0) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
`Forbidden updated properties for relation field metadata: ${relationNonEditableUpdatedProperties.join(', ')}`,
|
||||
FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const flatObjectMetadata =
|
||||
fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
|
||||
flatObjectMetadataWithFlatFieldMaps,
|
||||
);
|
||||
|
||||
if (updates.some((update) => update.property === 'name')) {
|
||||
errors.push(
|
||||
...[
|
||||
validateMetadataName(updatedFlatFieldMetadata.name),
|
||||
validateFlatFieldMetadataNameAvailability({
|
||||
name: updatedFlatFieldMetadata.name,
|
||||
flatObjectMetadata: flatObjectMetadata,
|
||||
}),
|
||||
].filter(isDefined),
|
||||
);
|
||||
}
|
||||
|
||||
if (updatedFlatFieldMetadata.isLabelSyncedWithName) {
|
||||
const computedName = computeMetadataNameFromLabel(
|
||||
updatedFlatFieldMetadata.label,
|
||||
);
|
||||
|
||||
if (updatedFlatFieldMetadata.name !== computedName) {
|
||||
errors.push(
|
||||
new InvalidMetadataException(
|
||||
`Name is not synced with label. Expected name: "${computedName}", got ${updatedFlatFieldMetadata.name}`,
|
||||
InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fieldMetadataTypeValidationErrors =
|
||||
await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate: updatedFlatFieldMetadata,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (fieldMetadataTypeValidationErrors.length > 0) {
|
||||
errors.push(...fieldMetadataTypeValidationErrors);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
validateFlatFieldMetadataDeletion({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToDelete,
|
||||
@@ -98,7 +241,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
return errors;
|
||||
}
|
||||
|
||||
async validateOneFlatFieldMetadata<
|
||||
async validateOneFlatFieldMetadataCreation<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
>({
|
||||
existingFlatObjectMetadataMaps,
|
||||
@@ -140,7 +283,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
const failedNameAvailabilityValidation =
|
||||
validateFlatFieldMetadataNameAvailability({
|
||||
name: flatFieldMetadataToValidate.name,
|
||||
objectMetadata: parentFlatObjectMetadata,
|
||||
flatObjectMetadata: parentFlatObjectMetadata,
|
||||
});
|
||||
|
||||
if (isDefined(failedNameAvailabilityValidation)) {
|
||||
@@ -163,43 +306,26 @@ export class FlatFieldMetadataValidatorService {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
validateMetadataNameOrThrow(flatFieldMetadataToValidate.name);
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
error.message,
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
{
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
},
|
||||
),
|
||||
);
|
||||
const nameValidationResult = validateMetadataName(
|
||||
flatFieldMetadataToValidate.name,
|
||||
);
|
||||
|
||||
if (isDefined(nameValidationResult)) {
|
||||
errors.push(nameValidationResult);
|
||||
}
|
||||
|
||||
const fieldMetadataTypeValidator =
|
||||
this.flatFieldMetadataTypeValidatorService
|
||||
.FIELD_METADATA_TYPE_VALIDATOR_HASHMAP[
|
||||
flatFieldMetadataToValidate.type
|
||||
];
|
||||
|
||||
if (!isDefined(fieldMetadataTypeValidator)) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
'Unsupported field metadata type',
|
||||
FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const fieldMetadataTypeValidatorExceptions =
|
||||
await fieldMetadataTypeValidator({
|
||||
const fieldMetadataTypeValidationErrors =
|
||||
await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
otherFlatObjectMetadataMapsToValidate,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
errors.push(...fieldMetadataTypeValidatorExceptions);
|
||||
if (fieldMetadataTypeValidationErrors.length > 0) {
|
||||
errors.push(...fieldMetadataTypeValidationErrors);
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
||||
+23
-8
@@ -3,11 +3,10 @@ import { FieldMetadataType, type FromTo } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
|
||||
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
|
||||
|
||||
const flatFieldMetadataPropertiesToCompare = [
|
||||
export const flatFieldMetadataPropertiesToCompare = [
|
||||
'defaultValue',
|
||||
'description',
|
||||
'icon',
|
||||
@@ -24,11 +23,14 @@ const flatFieldMetadataPropertiesToCompare = [
|
||||
export type FlatFieldMetadataPropertiesToCompare =
|
||||
(typeof flatFieldMetadataPropertiesToCompare)[number];
|
||||
|
||||
const fieldMetadataPropertiesToStringify = [
|
||||
export const flatFieldMetadataEntityJsonbProperties = [
|
||||
'defaultValue',
|
||||
'standardOverrides',
|
||||
'options',
|
||||
'settings',
|
||||
] as const satisfies FlatFieldMetadataPropertiesToCompare[];
|
||||
'standardOverrides',
|
||||
] as const satisfies (keyof FlatFieldMetadata)[];
|
||||
export type FlatFieldMetadataEntityJsonbProperties =
|
||||
(typeof flatFieldMetadataEntityJsonbProperties)[number];
|
||||
|
||||
const shouldNotOverrideDefaultValue = (type: FieldMetadataType) => {
|
||||
return [
|
||||
@@ -73,7 +75,8 @@ export const compareTwoFlatFieldMetadata = ({
|
||||
// Remove below assertion when we authorize relation edition, see https://github.com/twentyhq/twenty/commit/39f6f3c4bb101272a9014e142a842d0801a3c33b
|
||||
if (
|
||||
isDefined(fieldMetadata.type) &&
|
||||
isRelationFieldMetadataType(fieldMetadata.type) &&
|
||||
(fieldMetadata.type === FieldMetadataType.RELATION ||
|
||||
fieldMetadata.type === FieldMetadataType.MORPH_RELATION) &&
|
||||
!['label', 'description', 'isActive'].includes(property)
|
||||
) {
|
||||
return true;
|
||||
@@ -81,7 +84,7 @@ export const compareTwoFlatFieldMetadata = ({
|
||||
|
||||
return false;
|
||||
},
|
||||
propertiesToStringify: fieldMetadataPropertiesToStringify,
|
||||
propertiesToStringify: flatFieldMetadataEntityJsonbProperties,
|
||||
};
|
||||
const fromCompare = transformMetadataForComparison(
|
||||
from,
|
||||
@@ -100,11 +103,23 @@ export const compareTwoFlatFieldMetadata = ({
|
||||
switch (difference.type) {
|
||||
case 'CHANGE': {
|
||||
const { oldValue, path, value } = difference;
|
||||
const property = path[0] as FlatFieldMetadataPropertiesToCompare;
|
||||
const isJsonb = flatFieldMetadataEntityJsonbProperties.includes(
|
||||
property as FlatFieldMetadataEntityJsonbProperties,
|
||||
);
|
||||
|
||||
if (isJsonb) {
|
||||
return {
|
||||
from: isDefined(oldValue) ? JSON.parse(oldValue) : oldValue,
|
||||
to: isDefined(value) ? JSON.parse(value) : value,
|
||||
property,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
from: oldValue,
|
||||
to: value,
|
||||
property: path[0] as FlatFieldMetadataPropertiesToCompare,
|
||||
property,
|
||||
};
|
||||
}
|
||||
case 'CREATE':
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
import {
|
||||
extractAndSanitizeObjectStringFields,
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
fieldMetadataStandardOverridesProperties,
|
||||
type FieldMetadataStandardOverridesProperties,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
|
||||
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import {
|
||||
flatFieldMetadataPropertiesToCompare,
|
||||
type FlatFieldMetadataPropertiesToCompare,
|
||||
} from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
|
||||
const fieldMetadataEditableProperties =
|
||||
flatFieldMetadataPropertiesToCompare.filter(
|
||||
(
|
||||
property,
|
||||
): property is Exclude<
|
||||
FlatFieldMetadataPropertiesToCompare,
|
||||
'standardOverrides'
|
||||
> => property !== 'standardOverrides',
|
||||
);
|
||||
|
||||
type FromUpdateFieldInputToFlatFieldMetadataToUpdateArgs = {
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
updateFieldInput: UpdateFieldInput;
|
||||
};
|
||||
export const fromUpdateFieldInputToFlatFieldMetadataToUpdate = ({
|
||||
existingFlatObjectMetadataMaps,
|
||||
updateFieldInput: rawUpdateFieldInput,
|
||||
}: FromUpdateFieldInputToFlatFieldMetadataToUpdateArgs): FieldInputTranspilationResult<FlatFieldMetadata> => {
|
||||
const updateFieldInputInformalProperties =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawUpdateFieldInput,
|
||||
['objectMetadataId', 'id'],
|
||||
);
|
||||
const updatedEditableFields = extractAndSanitizeObjectStringFields(
|
||||
rawUpdateFieldInput,
|
||||
fieldMetadataEditableProperties,
|
||||
);
|
||||
|
||||
const relatedFlatFieldMetadata =
|
||||
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
|
||||
fieldMetadataId: updateFieldInputInformalProperties.id,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(relatedFlatFieldMetadata)) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: new FieldMetadataException(
|
||||
'Field metadata to update not found',
|
||||
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
existingFlatObjectMetadataMaps.byId[
|
||||
relatedFlatFieldMetadata.objectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: new FieldMetadataException(
|
||||
'Field metadata to update object metadata not found',
|
||||
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (flatObjectMetadataWithFlatFieldMaps.isRemote) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: new ObjectMetadataException(
|
||||
'Remote objects are read-only',
|
||||
ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED,
|
||||
),
|
||||
};
|
||||
}
|
||||
const isStandardField =
|
||||
relatedFlatFieldMetadata.standardId !== null &&
|
||||
!relatedFlatFieldMetadata.isCustom;
|
||||
|
||||
if (isStandardField) {
|
||||
const invalidUpdatedProperties = Object.keys(updatedEditableFields).filter(
|
||||
(property) =>
|
||||
fieldMetadataStandardOverridesProperties.includes(
|
||||
property as FieldMetadataStandardOverridesProperties,
|
||||
),
|
||||
);
|
||||
|
||||
if (invalidUpdatedProperties.length > 0) {
|
||||
return {
|
||||
status: 'fail',
|
||||
error: new FieldMetadataException(
|
||||
`Cannot edit standard field metadata properties: ${invalidUpdatedProperties.join(', ')}`,
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
const updatedStandardFlatFieldMetadata =
|
||||
fieldMetadataStandardOverridesProperties.reduce((acc, property) => {
|
||||
const isPropertyUpdated = updatedEditableFields[property] !== undefined;
|
||||
|
||||
return {
|
||||
...acc,
|
||||
standardOverrides: {
|
||||
...acc.standardOverrides,
|
||||
...(isPropertyUpdated
|
||||
? { [property]: updatedEditableFields[property] }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
}, relatedFlatFieldMetadata);
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
result: updatedStandardFlatFieldMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
const updatedFlatFieldMetadata = fieldMetadataEditableProperties.reduce(
|
||||
(acc, property) => {
|
||||
const isPropertyUpdated = updatedEditableFields[property] !== undefined;
|
||||
|
||||
return {
|
||||
...acc,
|
||||
...(isPropertyUpdated
|
||||
? { [property]: updatedEditableFields[property] }
|
||||
: {}),
|
||||
};
|
||||
},
|
||||
relatedFlatFieldMetadata,
|
||||
);
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
result: updatedFlatFieldMetadata,
|
||||
};
|
||||
};
|
||||
+4
-4
@@ -37,16 +37,16 @@ const getReservedCompositeFieldNames = (
|
||||
|
||||
export const validateFlatFieldMetadataNameAvailability = ({
|
||||
name,
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
}: {
|
||||
name: string;
|
||||
objectMetadata: FlatObjectMetadata;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
}): FailedFlatFieldMetadataValidationExceptions | undefined => {
|
||||
const reservedCompositeFieldsNames =
|
||||
getReservedCompositeFieldNames(objectMetadata);
|
||||
getReservedCompositeFieldNames(flatObjectMetadata);
|
||||
|
||||
if (
|
||||
objectMetadata.flatFieldMetadatas.some(
|
||||
flatObjectMetadata.flatFieldMetadatas.some(
|
||||
(field) =>
|
||||
field.name === name ||
|
||||
(field.type === FieldMetadataType.RELATION && // Question: Should we also look for MORPH_RELATION field types ?
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import {
|
||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
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 { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
|
||||
export const validateObjectMetadataInputNamesOrThrow = <
|
||||
T extends UpdateObjectPayload | CreateObjectInput,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
|
||||
type ValidateMetadataNameTestContext = EachTestingContext<{
|
||||
input: string;
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { validateMetadataNameIsCamelCaseOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils';
|
||||
import { validateMetadataNameIsNotReservedKeywordOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword';
|
||||
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 { validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils';
|
||||
|
||||
export const validateMetadataNameOrThrow = (name: string): void => {
|
||||
const validators = [
|
||||
validateMetadataNameIsNotTooLongOrThrow,
|
||||
validateMetadataNameIsNotTooShortOrThrow,
|
||||
validateMetadataNameIsCamelCaseOrThrow,
|
||||
validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow,
|
||||
validateMetadataNameIsNotReservedKeywordOrThrow,
|
||||
];
|
||||
|
||||
validators.forEach((validator) => validator(name));
|
||||
};
|
||||
+19
-15
@@ -1,17 +1,21 @@
|
||||
import { validateMetadataNameIsCamelCaseOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils';
|
||||
import { validateMetadataNameIsNotReservedKeywordOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword';
|
||||
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 { validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
|
||||
export const validateMetadataNameOrThrow = (name: string): void => {
|
||||
const validators = [
|
||||
validateMetadataNameIsNotTooLongOrThrow,
|
||||
validateMetadataNameIsNotTooShortOrThrow,
|
||||
validateMetadataNameIsCamelCaseOrThrow,
|
||||
validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow,
|
||||
validateMetadataNameIsNotReservedKeywordOrThrow,
|
||||
];
|
||||
|
||||
validators.forEach((validator) => validator(name));
|
||||
export const validateMetadataName = (
|
||||
name: string,
|
||||
): undefined | FieldMetadataException => {
|
||||
try {
|
||||
validateMetadataNameOrThrow(name);
|
||||
} catch (error) {
|
||||
return new FieldMetadataException(
|
||||
error.message,
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
{
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user