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:
Paul Rastoin
2025-08-20 19:33:15 +02:00
committed by GitHub
parent 4752970f79
commit e99c67d6b5
53 changed files with 2589 additions and 810 deletions
@@ -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;
@@ -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>;
};
@@ -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;
};
@@ -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'>
>;
@@ -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,
});
@@ -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,
);
};
@@ -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,
},
);
}
};
@@ -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 [];
});
};
@@ -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`,
});
}
}
@@ -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;
@@ -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;