Fail slow + structured validator error response scoped by object and fields (#13980)
# Introduction
Example of returned response: ( snapshot copy don't mind jest expect any
values )
```json
{
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"fieldMetadata": [
{
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Name is too long",
"userFriendlyMessage": "Name is too long",
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
],
"id": Any<String>,
"name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"objectMetadataId": Any<String>,
"operation": "create_field",
},
{
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Name is too long",
"userFriendlyMessage": "Name is too long",
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
],
"id": Any<String>,
"name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"objectMetadataId": Any<String>,
"operation": "create_field",
},
{
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Name is too long",
"userFriendlyMessage": "Name is too long",
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
],
"id": Any<String>,
"name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"objectMetadataId": Any<String>,
"operation": "create_field",
},
{
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Name is too long",
"userFriendlyMessage": "Name is too long",
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
],
"id": Any<String>,
"name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"objectMetadataId": Any<String>,
"operation": "create_field",
},
{
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Name is too long",
"userFriendlyMessage": "Name is too long",
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
],
"id": Any<String>,
"name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"objectMetadataId": Any<String>,
"operation": "create_field",
},
],
"objectMetadata": [
{
"errors": [
{
"code": "INVALID_OBJECT_INPUT",
"message": "Name is too long",
"value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
],
"fields": [],
"id": Any<String>,
"namePlural": "listingas",
"nameSingular": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"operation": "create_object",
},
],
},
"message": "Validation failed for 1 object(s) and 5 field(s)",
"summary": {
"invalidFields": 5,
"invalidObjects": 1,
"totalErrors": 6,
},
"userFriendlyMessage": "Validation failed for 1 object(s) and 5 field(s)",
},
"message": "Multiple validation errors occurred while creating object",
"name": "GraphQLError",
}
```
## Note
We should put in place integrations tests on REST API too, in order to
ensure we receive the same response error
This commit is contained in:
+103
-82
@@ -4,23 +4,22 @@ import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FlatFieldMetadataValidatorService } 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 { FailedFlatFieldMetadataValidation } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { isRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util';
|
||||
import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util';
|
||||
import { FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { FailedFlatObjectMetadataValidation } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { areFlatObjectMetadataNamesSyncedWithLabels } from 'src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util';
|
||||
import { computeRelationTargetFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/compute-relation-target-flat-object-metadata-maps.util';
|
||||
import { getDefaultFailedFlatObjectMetadataValidation } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-default-failed-flat-object-metadata-validation.type';
|
||||
import { validateFlatObjectMetadataIdentifiers } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util';
|
||||
import { validateFlatObjectMetadataLabel } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-label.util';
|
||||
import { validateFlatObjectMetadataNames } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
|
||||
import { doesOtherObjectWithSameNameExists } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util';
|
||||
import { WorkspaceMigrationV2BuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
|
||||
@@ -37,32 +36,44 @@ export class FlatObjectMetadataValidatorService {
|
||||
}: {
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
updatedFlatObjectMetadata: FlatObjectMetadata;
|
||||
}) {
|
||||
}): FailedFlatObjectMetadataValidation {
|
||||
const validationResult = getDefaultFailedFlatObjectMetadataValidation({
|
||||
type: 'update_object',
|
||||
objectMinimalInformation: {
|
||||
id: updatedFlatObjectMetadata.id,
|
||||
},
|
||||
});
|
||||
|
||||
const existingFlatObjectMetadata =
|
||||
existingFlatObjectMetadataMaps.byId[updatedFlatObjectMetadata.id];
|
||||
|
||||
if (!isDefined(existingFlatObjectMetadata)) {
|
||||
return [
|
||||
new ObjectMetadataException(
|
||||
t`Object to update not found`,
|
||||
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
),
|
||||
];
|
||||
}
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
validationResult.objectLevelErrors.push({
|
||||
code: ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
message: t`Object to update not found`,
|
||||
userFriendlyMessage: t`Object to update not found`,
|
||||
});
|
||||
|
||||
errors.push(
|
||||
return validationResult;
|
||||
}
|
||||
validationResult.objectMinimalInformation = {
|
||||
id: existingFlatObjectMetadata.id,
|
||||
namePlural: existingFlatObjectMetadata.namePlural,
|
||||
nameSingular: existingFlatObjectMetadata.nameSingular,
|
||||
};
|
||||
|
||||
validationResult.objectLevelErrors.push(
|
||||
...this.validateFlatObjectMetadataNameAndLabels({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatObjectMetadataToValidate: updatedFlatObjectMetadata,
|
||||
}),
|
||||
);
|
||||
|
||||
errors.push(
|
||||
validationResult.objectLevelErrors.push(
|
||||
...validateFlatObjectMetadataIdentifiers(existingFlatObjectMetadata),
|
||||
);
|
||||
|
||||
return errors;
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatObjectMetadataDeletion({
|
||||
@@ -73,52 +84,60 @@ export class FlatObjectMetadataValidatorService {
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
objectMetadataToDeleteId: string;
|
||||
buildOptions: WorkspaceMigrationV2BuilderOptions;
|
||||
}) {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
}): FailedFlatObjectMetadataValidation {
|
||||
const validationResult = getDefaultFailedFlatObjectMetadataValidation({
|
||||
type: 'delete_object',
|
||||
objectMinimalInformation: {
|
||||
id: objectMetadataToDeleteId,
|
||||
},
|
||||
});
|
||||
|
||||
const flatObjectMetadataToDelete =
|
||||
existingFlatObjectMetadataMaps.byId[objectMetadataToDeleteId];
|
||||
|
||||
if (!isDefined(flatObjectMetadataToDelete)) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Object to delete not found`,
|
||||
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
validationResult.objectLevelErrors.push({
|
||||
code: ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
message: t`Object to delete not found`,
|
||||
userFriendlyMessage: t`Object to delete not found`,
|
||||
value: objectMetadataToDeleteId,
|
||||
});
|
||||
} else {
|
||||
validationResult.objectMinimalInformation = {
|
||||
id: flatObjectMetadataToDelete.id,
|
||||
namePlural: flatObjectMetadataToDelete.namePlural,
|
||||
nameSingular: flatObjectMetadataToDelete.nameSingular,
|
||||
};
|
||||
|
||||
if (flatObjectMetadataToDelete.isRemote) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Remote objects are not supported yet`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
validationResult.objectLevelErrors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message: t`Remote objects are not supported yet`,
|
||||
userFriendlyMessage: t`Remote objects are not supported yet`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!buildOptions.isSystemBuild &&
|
||||
isStandardMetadata(flatObjectMetadataToDelete)
|
||||
) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Standard objects cannot be deleted`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
validationResult.objectLevelErrors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message: t`Standard objects cannot be deleted`,
|
||||
userFriendlyMessage: t`Standard objects cannot be deleted`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!buildOptions.isSystemBuild && flatObjectMetadataToDelete.isActive) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Active objects cannot be deleted`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
validationResult.objectLevelErrors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message: t`Active objects cannot be deleted`,
|
||||
userFriendlyMessage: t`Active objects cannot be deleted`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public async validateFlatObjectMetadataCreation({
|
||||
@@ -129,8 +148,15 @@ export class FlatObjectMetadataValidatorService {
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
flatObjectMetadataToValidate: FlatObjectMetadata;
|
||||
otherFlatObjectMetadataMapsToValidate?: FlatObjectMetadataMaps;
|
||||
}) {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
}): Promise<FailedFlatObjectMetadataValidation> {
|
||||
const validationResult = getDefaultFailedFlatObjectMetadataValidation({
|
||||
type: 'create_object',
|
||||
objectMinimalInformation: {
|
||||
id: flatObjectMetadataToValidate.id,
|
||||
namePlural: flatObjectMetadataToValidate.namePlural,
|
||||
nameSingular: flatObjectMetadataToValidate.nameSingular,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
@@ -140,31 +166,29 @@ export class FlatObjectMetadataValidatorService {
|
||||
}),
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Object with same id already exists`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
validationResult.objectLevelErrors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message: t`Object with same id already exists`,
|
||||
userFriendlyMessage: t`Object with same id already exists`,
|
||||
});
|
||||
}
|
||||
|
||||
if (flatObjectMetadataToValidate.isRemote) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Remote objects are not supported yet`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
validationResult.objectLevelErrors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message: t`Remote objects are not supported yet`,
|
||||
userFriendlyMessage: t`Remote objects are not supported yet`,
|
||||
});
|
||||
}
|
||||
|
||||
errors.push(
|
||||
validationResult.objectLevelErrors.push(
|
||||
...this.validateFlatObjectMetadataNameAndLabels({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatObjectMetadataToValidate,
|
||||
}),
|
||||
);
|
||||
|
||||
const allFlatFieldMetadatasValidationErrors: FailedFlatFieldMetadataValidationExceptions[] =
|
||||
const allFlatFieldMetadatasValidationErrors: FailedFlatFieldMetadataValidation[] =
|
||||
[];
|
||||
let optimisticFlatObjectMetadataMaps =
|
||||
addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
|
||||
@@ -195,8 +219,8 @@ export class FlatObjectMetadataValidatorService {
|
||||
},
|
||||
);
|
||||
|
||||
if (flatFieldValidatorErrors.length > 0) {
|
||||
allFlatFieldMetadatasValidationErrors.push(...flatFieldValidatorErrors);
|
||||
if (flatFieldValidatorErrors.errors.length > 0) {
|
||||
allFlatFieldMetadatasValidationErrors.push(flatFieldValidatorErrors);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -208,10 +232,12 @@ export class FlatObjectMetadataValidatorService {
|
||||
}
|
||||
|
||||
if (allFlatFieldMetadatasValidationErrors.length > 0) {
|
||||
errors.push(...allFlatFieldMetadatasValidationErrors);
|
||||
validationResult.fieldLevelErrors.push(
|
||||
...allFlatFieldMetadatasValidationErrors,
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
private validateFlatObjectMetadataNameAndLabels({
|
||||
@@ -220,8 +246,8 @@ export class FlatObjectMetadataValidatorService {
|
||||
}: {
|
||||
flatObjectMetadataToValidate: FlatObjectMetadata;
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
}) {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
}): FlatObjectMetadataValidationError[] {
|
||||
const errors: FlatObjectMetadataValidationError[] = [];
|
||||
|
||||
errors.push(
|
||||
...validateFlatObjectMetadataNames({
|
||||
@@ -241,12 +267,11 @@ export class FlatObjectMetadataValidatorService {
|
||||
flatObjectMetadataToValidate.isLabelSyncedWithName &&
|
||||
!areFlatObjectMetadataNamesSyncedWithLabels(flatObjectMetadataToValidate)
|
||||
) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Names are not synced with labels`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
errors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message: t`Names are not synced with labels`,
|
||||
userFriendlyMessage: t`Names are not synced with labels`,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -257,15 +282,11 @@ export class FlatObjectMetadataValidatorService {
|
||||
existingObjectMetadataId: flatObjectMetadataToValidate.id,
|
||||
})
|
||||
) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
'Object already exists',
|
||||
ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
|
||||
{
|
||||
userFriendlyMessage: t`Object already exists`,
|
||||
},
|
||||
),
|
||||
);
|
||||
errors.push({
|
||||
code: ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
|
||||
message: 'Object already exists',
|
||||
userFriendlyMessage: t`Object already exists`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
||||
+10
-7
@@ -1,8 +1,11 @@
|
||||
import { type FieldMetadataException } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type ObjectMetadataException } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { type InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
import { type FailedFlatFieldMetadataValidation } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type';
|
||||
import { type ObjectMetadataMinimalInformation } from 'src/engine/metadata-modules/flat-object-metadata/types/object-metadata-minimal-information.type';
|
||||
import { type WorkspaceMigrationObjectActionTypeV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
|
||||
export type FailedFlatObjectMetadataValidationExceptions =
|
||||
| FieldMetadataException
|
||||
| ObjectMetadataException
|
||||
| InvalidMetadataException;
|
||||
export type FailedFlatObjectMetadataValidation = {
|
||||
type: WorkspaceMigrationObjectActionTypeV2;
|
||||
objectLevelErrors: FlatObjectMetadataValidationError[];
|
||||
fieldLevelErrors: FailedFlatFieldMetadataValidation[];
|
||||
objectMinimalInformation: Partial<ObjectMetadataMinimalInformation>;
|
||||
};
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { type ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
|
||||
export type FlatObjectMetadataValidationError = {
|
||||
code: ObjectMetadataExceptionCode;
|
||||
message: string;
|
||||
userFriendlyMessage?: string;
|
||||
value?: unknown;
|
||||
};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
export type ObjectMetadataMinimalInformation = Partial<
|
||||
Pick<FlatObjectMetadata, 'id' | 'namePlural' | 'nameSingular'>
|
||||
>;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { type FailedFlatObjectMetadataValidation } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
|
||||
export const getDefaultFailedFlatObjectMetadataValidation = (
|
||||
overrides: Partial<Omit<FailedFlatObjectMetadataValidation, 'type'>> &
|
||||
Pick<FailedFlatObjectMetadataValidation, 'type'>,
|
||||
): FailedFlatObjectMetadataValidation => ({
|
||||
fieldLevelErrors: [],
|
||||
objectLevelErrors: [],
|
||||
objectMinimalInformation: {},
|
||||
...overrides,
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { type FailedFlatFieldMetadataValidation } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { type FailedFlatObjectMetadataValidation } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import {
|
||||
WORKSPACE_MIGRATION_OBJECT_ACTION_TYPES,
|
||||
type WorkspaceMigrationObjectActionTypeV2,
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
|
||||
export const isFailedFlatObjectMetadataValidation = (
|
||||
failedValidation:
|
||||
| FailedFlatObjectMetadataValidation
|
||||
| FailedFlatFieldMetadataValidation,
|
||||
): failedValidation is FailedFlatObjectMetadataValidation => {
|
||||
return WORKSPACE_MIGRATION_OBJECT_ACTION_TYPES.includes(
|
||||
failedValidation.type as WorkspaceMigrationObjectActionTypeV2,
|
||||
);
|
||||
};
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type';
|
||||
|
||||
export const runFlatObjectMetadataValidator = <T>(
|
||||
elementToValidate: T,
|
||||
{ message, validator }: FlatMetadataValidator<T>,
|
||||
): FailedFlatObjectMetadataValidationExceptions | undefined => {
|
||||
const validationFailed = validator(elementToValidate);
|
||||
|
||||
if (validationFailed) {
|
||||
return new ObjectMetadataException(
|
||||
message,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
{
|
||||
userFriendlyMessage: message,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
+23
-13
@@ -1,15 +1,25 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { runFlatObjectMetadataValidator } from 'src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validator.util';
|
||||
import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type';
|
||||
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type';
|
||||
|
||||
export const runFlatObjectMetadataValidators = <T>(
|
||||
elementToValidate: T,
|
||||
validators: FlatMetadataValidator<T>[],
|
||||
): FailedFlatObjectMetadataValidationExceptions[] =>
|
||||
validators
|
||||
.map((validator) =>
|
||||
runFlatObjectMetadataValidator(elementToValidate, validator),
|
||||
)
|
||||
.filter(isDefined);
|
||||
export const runFlatObjectMetadataValidators = <T>({
|
||||
elementToValidate,
|
||||
validators,
|
||||
}: {
|
||||
elementToValidate: T;
|
||||
validators: FlatMetadataValidator<T>[];
|
||||
}): FlatObjectMetadataValidationError[] => {
|
||||
return validators.flatMap(({ validator, message }) => {
|
||||
const isInvalid = validator(elementToValidate);
|
||||
|
||||
if (isInvalid) {
|
||||
return {
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message,
|
||||
value: elementToValidate,
|
||||
};
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
};
|
||||
|
||||
+18
-29
@@ -4,16 +4,14 @@ import {
|
||||
isLabelIdentifierFieldMetadataTypes,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
|
||||
export const validateFlatObjectMetadataIdentifiers = (
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
) => {
|
||||
const errors: ObjectMetadataException[] = [];
|
||||
const errors: FlatObjectMetadataValidationError[] = [];
|
||||
|
||||
const { labelIdentifierFieldMetadataId, imageIdentifierFieldMetadataId } =
|
||||
flatObjectMetadata;
|
||||
@@ -25,25 +23,19 @@ export const validateFlatObjectMetadataIdentifiers = (
|
||||
);
|
||||
|
||||
if (!isDefined(flatFieldMetadata)) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
errors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message:
|
||||
'labelIdentifierFieldMetadataId validation failed: related field metadata not found',
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
{
|
||||
userFriendlyMessage: t`Field declared as label identifier not found`,
|
||||
},
|
||||
),
|
||||
);
|
||||
userFriendlyMessage: t`Field declared as label identifier not found`,
|
||||
});
|
||||
} else if (!isLabelIdentifierFieldMetadataTypes(flatFieldMetadata.type)) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
errors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message:
|
||||
'labelIdentifierFieldMetadataId validation failed: field type not compatible',
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
{
|
||||
userFriendlyMessage: t`Field cannot be used as label identifier`,
|
||||
},
|
||||
),
|
||||
);
|
||||
userFriendlyMessage: t`Field cannot be used as label identifier`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +46,12 @@ export const validateFlatObjectMetadataIdentifiers = (
|
||||
);
|
||||
|
||||
if (!isDefined(relatedFlatFieldMetadata)) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
errors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message:
|
||||
'imageIdentifierFieldMetadataId validation failed: related field metadata not found',
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
{
|
||||
userFriendlyMessage: t`Field declared as image identifier not found`,
|
||||
},
|
||||
),
|
||||
);
|
||||
userFriendlyMessage: t`Field declared as image identifier not found`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-17
@@ -1,12 +1,10 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type ObjectMetadataMinimalInformation } from 'src/engine/metadata-modules/flat-object-metadata/types/object-metadata-minimal-information.type';
|
||||
import { runFlatObjectMetadataValidators } from 'src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type';
|
||||
import {
|
||||
beneathDatabaseIdentifierMinimumLength,
|
||||
@@ -16,11 +14,9 @@ import {
|
||||
export const validateFlatObjectMetadataLabel = ({
|
||||
labelPlural,
|
||||
labelSingular,
|
||||
}: Pick<
|
||||
FlatObjectMetadata,
|
||||
'labelPlural' | 'labelSingular'
|
||||
>): FailedFlatObjectMetadataValidationExceptions[] => {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
}: Pick<FlatObjectMetadata, 'labelPlural' | 'labelSingular'> &
|
||||
ObjectMetadataMinimalInformation): FlatObjectMetadataValidationError[] => {
|
||||
const errors: FlatObjectMetadataValidationError[] = [];
|
||||
const validators: FlatMetadataValidator<string>[] = [
|
||||
{
|
||||
validator: (label) => beneathDatabaseIdentifierMinimumLength(label),
|
||||
@@ -34,7 +30,10 @@ export const validateFlatObjectMetadataLabel = ({
|
||||
|
||||
errors.push(
|
||||
...[labelSingular, labelPlural].flatMap((label) =>
|
||||
runFlatObjectMetadataValidators(label, validators),
|
||||
runFlatObjectMetadataValidators({
|
||||
elementToValidate: label,
|
||||
validators,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -42,12 +41,12 @@ export const validateFlatObjectMetadataLabel = ({
|
||||
labelSingular.trim().toLowerCase() === labelPlural.trim().toLowerCase();
|
||||
|
||||
if (labelsAreIdentical) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`The singular and plural labels cannot be the same for an object`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
errors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
userFriendlyMessage: `The singular and plural labels cannot be the same for an object`,
|
||||
message: t`The singular and plural labels cannot be the same for an object`,
|
||||
value: labelSingular,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
||||
+16
-14
@@ -1,23 +1,25 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { type FlatObjectMetadataValidationError } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-validation-error.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type ObjectMetadataMinimalInformation } from 'src/engine/metadata-modules/flat-object-metadata/types/object-metadata-minimal-information.type';
|
||||
import { runFlatObjectMetadataValidators } from 'src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { METADATA_NAME_VALIDATORS } from 'src/engine/metadata-modules/utils/constants/metadata-name-flat-metadata-validators.constants';
|
||||
|
||||
export const validateFlatObjectMetadataNames = ({
|
||||
namePlural,
|
||||
nameSingular,
|
||||
}: Pick<FlatObjectMetadata, 'nameSingular' | 'namePlural'>) => {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
}: Pick<FlatObjectMetadata, 'nameSingular' | 'namePlural'> &
|
||||
ObjectMetadataMinimalInformation) => {
|
||||
const errors: FlatObjectMetadataValidationError[] = [];
|
||||
|
||||
errors.push(
|
||||
...[nameSingular, namePlural].flatMap((name) =>
|
||||
runFlatObjectMetadataValidators(name, METADATA_NAME_VALIDATORS),
|
||||
runFlatObjectMetadataValidators({
|
||||
elementToValidate: name,
|
||||
validators: METADATA_NAME_VALIDATORS,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -25,12 +27,12 @@ export const validateFlatObjectMetadataNames = ({
|
||||
namePlural.trim().toLowerCase() === nameSingular.trim().toLowerCase();
|
||||
|
||||
if (namesAreIdentical) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`The singular and plural names cannot be the same for an object`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
errors.push({
|
||||
code: ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
message: `The singular and plural names cannot be the same for an object`,
|
||||
userFriendlyMessage: t`The singular and plural names cannot be the same for an object`,
|
||||
value: namePlural,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
||||
Reference in New Issue
Block a user