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:
+7
@@ -19,4 +19,11 @@ export const FieldMetadataExceptionCode = appendCommonExceptionCode({
|
||||
'LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND',
|
||||
UNCOVERED_FIELD_METADATA_TYPE_VALIDATION:
|
||||
'UNCOVERED_FIELD_METADATA_TYPE_VALIDATION',
|
||||
RESERVED_KEYWORD: 'RESERVED_KEYWORD',
|
||||
NOT_AVAILABLE: 'NOT_AVAILABLE',
|
||||
NAME_NOT_SYNCED_WITH_LABEL: 'NAME_NOT_SYNCED_WITH_LABEL',
|
||||
} as const);
|
||||
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export type FieldMetadataExceptionCode =
|
||||
(typeof FieldMetadataExceptionCode)[keyof typeof FieldMetadataExceptionCode];
|
||||
|
||||
+6
-2
@@ -34,8 +34,9 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
|
||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||
import { ViewModule } from 'src/modules/view/view.module';
|
||||
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||
import { ViewModule } from 'src/modules/view/view.module';
|
||||
import { WorkspaceMigrationBuilderExceptionV2Interceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-exception-v2.interceptor';
|
||||
|
||||
import { FieldMetadataEntity } from './field-metadata.entity';
|
||||
|
||||
@@ -100,7 +101,10 @@ import { FieldMetadataService } from './services/field-metadata.service';
|
||||
},
|
||||
delete: { disabled: true },
|
||||
guards: [WorkspaceAuthGuard],
|
||||
interceptors: [FieldMetadataGraphqlApiExceptionInterceptor],
|
||||
interceptors: [
|
||||
WorkspaceMigrationBuilderExceptionV2Interceptor,
|
||||
FieldMetadataGraphqlApiExceptionInterceptor,
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
+55
-36
@@ -26,6 +26,7 @@ import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadat
|
||||
import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.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 { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
@@ -98,19 +99,25 @@ export class FieldMetadataServiceV2 {
|
||||
flatObjectMetadataMapsWithImpactedObject,
|
||||
);
|
||||
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: true,
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: true,
|
||||
},
|
||||
fromFlatObjectMetadataMaps: flatObjectMetadataMapsWithImpactedObject,
|
||||
toFlatObjectMetadataMaps,
|
||||
workspaceId,
|
||||
},
|
||||
fromFlatObjectMetadataMaps: flatObjectMetadataMapsWithImpactedObject,
|
||||
toFlatObjectMetadataMaps,
|
||||
workspaceId,
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while deleting field',
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while deleting field',
|
||||
);
|
||||
}
|
||||
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(
|
||||
flatFieldMetadatasToDelete[0],
|
||||
@@ -153,19 +160,25 @@ export class FieldMetadataServiceV2 {
|
||||
flatFieldMetadata: optimisticiallyUpdatedFlatFieldMetadata,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
workspaceId,
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while updating field',
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating field',
|
||||
);
|
||||
}
|
||||
|
||||
return this.fieldMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
@@ -236,19 +249,25 @@ export class FieldMetadataServiceV2 {
|
||||
}),
|
||||
);
|
||||
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
},
|
||||
workspaceId,
|
||||
},
|
||||
workspaceId,
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while creating fields',
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderExceptionV2(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while creating fields',
|
||||
);
|
||||
}
|
||||
|
||||
return this.fieldMetadataRepository.find({
|
||||
where: {
|
||||
|
||||
+3
@@ -33,6 +33,9 @@ export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
||||
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
|
||||
case FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION:
|
||||
case FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND:
|
||||
case FieldMetadataExceptionCode.RESERVED_KEYWORD:
|
||||
case FieldMetadataExceptionCode.NOT_AVAILABLE:
|
||||
case FieldMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL:
|
||||
throw error;
|
||||
default: {
|
||||
return assertUnreachable(error.code);
|
||||
|
||||
Reference in New Issue
Block a user