From d66b91e98d251e379ca99465ad2d897bdaa0c57c Mon Sep 17 00:00:00 2001
From: alban bertolini <albanb@forestadmin.com>
Date: Wed, 2 Oct 2024 08:29:15 +0200
Subject: [PATCH] fix: error

---
 .../operators-emulate/collection.ts           | 11 +---
 .../test/collection-customizer.test.ts        | 11 ++--
 .../decorators/computed/collection.test.ts    |  3 +-
 .../operators-emulate/collection.test.ts      |  6 +-
 .../decorators/publication/collection.test.ts |  4 +-
 .../decorators/relation/collection.test.ts    | 10 ++--
 .../rename-field/collection.test.ts           |  7 +--
 .../decorators/segment/collection.test.ts     |  6 +-
 .../sort-emulate/collection.test.ts           |  5 +-
 .../decorators/validation/collection.test.ts  | 12 +++-
 .../write-replace/collection_basics.test.ts   |  2 +-
 .../test/plugins/import-field.test.ts         |  2 +-
 packages/datasource-toolkit/src/errors.ts     | 57 +++++++++++++++----
 .../datasource-toolkit/src/utils/schema.ts    | 34 +++++++----
 .../src/validation/field.ts                   |  6 +-
 .../test/utils/collection.test.ts             |  9 +--
 .../validation/condition-tree/index.test.ts   |  5 +-
 .../test/validation/field/field.test.ts       |  5 +-
 .../test/validation/field/validate.test.ts    |  5 +-
 .../test/validation/record.test.ts            |  9 +--
 .../test/flatten-column.test.ts               | 10 +---
 .../test/flatten-relation.test.ts             |  6 +-
 22 files changed, 132 insertions(+), 93 deletions(-)

diff --git a/packages/datasource-customizer/src/decorators/operators-emulate/collection.ts b/packages/datasource-customizer/src/decorators/operators-emulate/collection.ts
index 618c64f03f..12c11cb7b3 100644
--- a/packages/datasource-customizer/src/decorators/operators-emulate/collection.ts
+++ b/packages/datasource-customizer/src/decorators/operators-emulate/collection.ts
@@ -39,11 +39,11 @@ export default class OperatorsEmulateCollectionDecorator extends CollectionDecor
     // Check that the collection can actually support our rewriting
     const pks = SchemaUtils.getPrimaryKeys(this.childCollection.schema);
     pks.forEach(pk => {
-      const schema = SchemaUtils.getColumn(
+      const schema = SchemaUtils.getField(
         this.childCollection.schema,
         pk,
         this.childCollection.name,
-      );
+      ) as ColumnSchema;
       const operators = schema.filterOperators;
 
       if (!operators?.has('Equal') || !operators?.has('In')) {
@@ -55,13 +55,8 @@ export default class OperatorsEmulateCollectionDecorator extends CollectionDecor
     });
 
     // Check that targeted field is valid
-    const field = SchemaUtils.getColumn(
-      this.childCollection.schema,
-      name,
-      this.childCollection.name,
-    );
+    SchemaUtils.throwIfMissingField(this.childCollection.schema, name, this.childCollection.name);
     FieldValidator.validate(this, name);
-    if (!field) throw new Error('Cannot replace operator for relation');
 
     // Mark the field operator as replaced.
     if (!this.fields.has(name)) this.fields.set(name, new Map());
diff --git a/packages/datasource-customizer/test/collection-customizer.test.ts b/packages/datasource-customizer/test/collection-customizer.test.ts
index ddb6d03fc6..50aa214bd0 100644
--- a/packages/datasource-customizer/test/collection-customizer.test.ts
+++ b/packages/datasource-customizer/test/collection-customizer.test.ts
@@ -1,6 +1,11 @@
 /* eslint-disable @typescript-eslint/ban-ts-comment */
 
-import { ColumnSchema, ConditionTreeLeaf, Sort } from '@forestadmin/datasource-toolkit';
+import {
+  ColumnSchema,
+  ConditionTreeLeaf,
+  MissingFieldError,
+  Sort,
+} from '@forestadmin/datasource-toolkit';
 import * as factories from '@forestadmin/datasource-toolkit/dist/test/__factories__';
 
 import {
@@ -322,9 +327,7 @@ describe('Builder > Collection', () => {
         const { dsc, customizer } = await setup();
 
         customizer.importField('translatorName', { path: 'doesNotExistPath' });
-        await expect(dsc.getDataSource(logger)).rejects.toThrow(
-          new MissingFieldError('doesNotExistPath', 'authors'),
-        );
+        await expect(dsc.getDataSource(logger)).rejects.toThrow(MissingFieldError);
       });
     });
   });
diff --git a/packages/datasource-customizer/test/decorators/computed/collection.test.ts b/packages/datasource-customizer/test/decorators/computed/collection.test.ts
index c4502e793b..5cc8b3c9b4 100644
--- a/packages/datasource-customizer/test/decorators/computed/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/computed/collection.test.ts
@@ -3,6 +3,7 @@ import {
   Collection,
   DataSource,
   DataSourceDecorator,
+  MissingFieldError,
   PaginatedFilter,
   Projection,
 } from '@forestadmin/datasource-toolkit';
@@ -102,7 +103,7 @@ describe('ComputedDecorator', () => {
         dependencies: ['__nonExisting__'],
         getValues: () => Promise.reject(),
       });
-    }).toThrow(new MissingFieldError('__nonExisting__', 'books'));
+    }).toThrow(MissingFieldError);
   });
 
   test('should throw if defining a field with invalid dependencies', () => {
diff --git a/packages/datasource-customizer/test/decorators/operators-emulate/collection.test.ts b/packages/datasource-customizer/test/decorators/operators-emulate/collection.test.ts
index 1f8093b111..92b10f4f2a 100644
--- a/packages/datasource-customizer/test/decorators/operators-emulate/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/operators-emulate/collection.test.ts
@@ -4,9 +4,11 @@ import {
   ConditionTreeLeaf,
   DataSource,
   DataSourceDecorator,
+  MissingFieldError,
   PaginatedFilter,
   Projection,
   RecordData,
+  RelationFieldAccessDeniedError,
 } from '@forestadmin/datasource-toolkit';
 import * as factories from '@forestadmin/datasource-toolkit/dist/test/__factories__';
 
@@ -115,7 +117,7 @@ describe('OperatorsEmulateCollectionDecorator', () => {
 
     test('emulateFieldOperator() should throw if the field does not exists', () => {
       expect(() => newBooks.emulateFieldOperator('__dontExist', 'Equal')).toThrow(
-        new MissingFieldError('__dontExist', 'books'),
+        MissingFieldError,
       );
     });
 
@@ -127,7 +129,7 @@ describe('OperatorsEmulateCollectionDecorator', () => {
 
     test('emulateFieldOperator() should throw if the field is in a relation', () => {
       expect(() => newBooks.emulateFieldOperator('author:firstName', 'Equal')).toThrow(
-        'Cannot replace operator for relation',
+        RelationFieldAccessDeniedError,
       );
     });
 
diff --git a/packages/datasource-customizer/test/decorators/publication/collection.test.ts b/packages/datasource-customizer/test/decorators/publication/collection.test.ts
index 46e3d82067..5bb8b11856 100644
--- a/packages/datasource-customizer/test/decorators/publication/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/publication/collection.test.ts
@@ -85,9 +85,7 @@ describe('PublicationCollectionDecorator', () => {
   });
 
   test('should throw when hiding a field which does not exists', () => {
-    expect(() => newPersons.changeFieldVisibility('unknown', false)).toThrow(
-      new MissingFieldError('unknown', 'persons'),
-    );
+    expect(() => newPersons.changeFieldVisibility('unknown', false)).toThrow(MissingFieldError);
   });
 
   test('should throw when hiding the primary key', () => {
diff --git a/packages/datasource-customizer/test/decorators/relation/collection.test.ts b/packages/datasource-customizer/test/decorators/relation/collection.test.ts
index 3a1d4c9391..e1efb5590a 100644
--- a/packages/datasource-customizer/test/decorators/relation/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/relation/collection.test.ts
@@ -7,6 +7,8 @@ import {
   DataSourceDecorator,
   Filter,
   ManyToManySchema,
+  MissingColumnError,
+  MissingFieldError,
   PaginatedFilter,
   Projection,
   Sort,
@@ -165,7 +167,7 @@ describe('RelationCollectionDecorator', () => {
             foreignCollection: 'passports',
             originKey: '__nonExisting__',
           }),
-        ).toThrow(new MissingFieldError('__nonExisting__', 'passports'));
+        ).toThrow(MissingColumnError);
       });
     });
 
@@ -282,7 +284,7 @@ describe('RelationCollectionDecorator', () => {
             foreignCollection: 'persons',
             foreignKey: '__nonExisting__',
           }),
-        ).toThrow(new MissingFieldError('__nonExisting__', 'passports'));
+        ).toThrow(MissingFieldError);
       });
     });
 
@@ -350,7 +352,7 @@ describe('RelationCollectionDecorator', () => {
             originKey: '__nonExisting__',
             throughCollection: 'passports',
           } as ManyToManySchema),
-        ).toThrow(new MissingFieldError('__nonExisting__', 'passports'));
+        ).toThrow(MissingFieldError);
       });
 
       test('should throw with a non existent fk', () => {
@@ -362,7 +364,7 @@ describe('RelationCollectionDecorator', () => {
             originKey: 'ownerId',
             throughCollection: 'passports',
           } as ManyToManySchema),
-        ).toThrow(new MissingFieldError('__nonExisting__', 'passports'));
+        ).toThrow(MissingFieldError);
       });
     });
 
diff --git a/packages/datasource-customizer/test/decorators/rename-field/collection.test.ts b/packages/datasource-customizer/test/decorators/rename-field/collection.test.ts
index f2b0f4abb0..890a297ec9 100644
--- a/packages/datasource-customizer/test/decorators/rename-field/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/rename-field/collection.test.ts
@@ -6,6 +6,7 @@ import {
   DataSource,
   DataSourceDecorator,
   Filter,
+  MissingFieldError,
   PaginatedFilter,
   Projection,
   Sort,
@@ -130,14 +131,12 @@ describe('RenameFieldCollectionDecorator', () => {
   });
 
   test('should throw when renaming a field which does not exists', () => {
-    expect(() => newPersons.renameField('unknown', 'somethingnew')).toThrow(
-      new MissingFieldError('unknown'),
-    );
+    expect(() => newPersons.renameField('unknown', 'somethingnew')).toThrow(MissingFieldError);
   });
 
   test('should throw when renaming a field using an older name', () => {
     newPersons.renameField('id', 'key');
-    expect(() => newPersons.renameField('id', 'primaryKey')).toThrow(new MissingFieldError('id'));
+    expect(() => newPersons.renameField('id', 'primaryKey')).toThrow(MissingFieldError);
   });
 
   test('should throw when renaming with a name including space', () => {
diff --git a/packages/datasource-customizer/test/decorators/segment/collection.test.ts b/packages/datasource-customizer/test/decorators/segment/collection.test.ts
index 80520ede44..e855627f23 100644
--- a/packages/datasource-customizer/test/decorators/segment/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/segment/collection.test.ts
@@ -1,4 +1,4 @@
-import { Collection, DataSource } from '@forestadmin/datasource-toolkit';
+import { Collection, DataSource, MissingFieldError } from '@forestadmin/datasource-toolkit';
 import * as factories from '@forestadmin/datasource-toolkit/dist/test/__factories__';
 
 import SegmentCollectionDecorator from '../../../src/decorators/segment/collection';
@@ -102,9 +102,7 @@ describe('SegmentCollectionDecorator', () => {
             factories.caller.build(),
             factories.filter.build({ segment: 'segmentName' }),
           ),
-        ).rejects.toThrow(
-          "The 'books.do not exists' field was not found. Available fields are: [name]. Please check if the field name is correct.",
-        );
+        ).rejects.toThrow(MissingFieldError);
 
         expect(conditionTreeGenerator).toHaveBeenCalled();
       });
diff --git a/packages/datasource-customizer/test/decorators/sort-emulate/collection.test.ts b/packages/datasource-customizer/test/decorators/sort-emulate/collection.test.ts
index 2fc03668bc..a3107eb517 100644
--- a/packages/datasource-customizer/test/decorators/sort-emulate/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/sort-emulate/collection.test.ts
@@ -3,6 +3,7 @@ import {
   ColumnSchema,
   DataSource,
   DataSourceDecorator,
+  MissingFieldError,
   Page,
   PaginatedFilter,
   Projection,
@@ -93,9 +94,7 @@ describe('SortEmulationDecoratorCollection', () => {
   });
 
   test('emulateFieldSorting() should throw if the field does not exists', () => {
-    expect(() => newBooks.emulateFieldSorting('__dontExist')).toThrow(
-      new MissingFieldError('__dontExist', 'books'),
-    );
+    expect(() => newBooks.emulateFieldSorting('__dontExist')).toThrow(MissingFieldError);
   });
 
   test('emulateFieldSorting() should throw if the field is a relation', () => {
diff --git a/packages/datasource-customizer/test/decorators/validation/collection.test.ts b/packages/datasource-customizer/test/decorators/validation/collection.test.ts
index 5fb62a5be9..0ae9cd23af 100644
--- a/packages/datasource-customizer/test/decorators/validation/collection.test.ts
+++ b/packages/datasource-customizer/test/decorators/validation/collection.test.ts
@@ -1,4 +1,10 @@
-import { Collection, DataSource, DataSourceDecorator } from '@forestadmin/datasource-toolkit';
+import {
+  Collection,
+  DataSource,
+  DataSourceDecorator,
+  MissingFieldError,
+  RelationFieldAccessDeniedError,
+} from '@forestadmin/datasource-toolkit';
 import * as factories from '@forestadmin/datasource-toolkit/dist/test/__factories__';
 
 import ValidationDecorator from '../../../src/decorators/validation/collection';
@@ -49,7 +55,7 @@ describe('SortEmulationDecoratorCollection', () => {
 
   test('addValidation() should throw if the field does not exists', () => {
     expect(() => newBooks.addValidation('__dontExist', { operator: 'Present' })).toThrow(
-      new MissingFieldError('__dontExist', 'books'),
+      MissingFieldError,
     );
   });
 
@@ -67,7 +73,7 @@ describe('SortEmulationDecoratorCollection', () => {
 
   test('addValidation() should throw if the field is in a relation', () => {
     expect(() => newBooks.addValidation('author:firstName', { operator: 'Present' })).toThrow(
-      'Cannot add validators on a relation, use the foreign key instead',
+      RelationFieldAccessDeniedError,
     );
   });
 
diff --git a/packages/datasource-customizer/test/decorators/write/write-replace/collection_basics.test.ts b/packages/datasource-customizer/test/decorators/write/write-replace/collection_basics.test.ts
index 829113cefd..16a6305864 100644
--- a/packages/datasource-customizer/test/decorators/write/write-replace/collection_basics.test.ts
+++ b/packages/datasource-customizer/test/decorators/write/write-replace/collection_basics.test.ts
@@ -24,7 +24,7 @@ describe('WriteDecorator > When their are no relations', () => {
     const decorator = new WriteDecorator(collection, dataSource);
 
     expect(() => decorator.replaceFieldWriting('inexistant', () => ({}))).toThrow(
-      new MissingFieldError('inexistant', 'books'),
+      MissingFieldError,
     );
   });
 
diff --git a/packages/datasource-customizer/test/plugins/import-field.test.ts b/packages/datasource-customizer/test/plugins/import-field.test.ts
index b3f1d52055..4a56fff759 100644
--- a/packages/datasource-customizer/test/plugins/import-field.test.ts
+++ b/packages/datasource-customizer/test/plugins/import-field.test.ts
@@ -53,6 +53,6 @@ describe('importField', () => {
         path: 'INVALID',
         name: 'NOPE',
       }),
-    ).rejects.toThrow(new MissingFieldError('INVALID', 'collection1'));
+    ).rejects.toThrow(MissingFieldError);
   });
 });
diff --git a/packages/datasource-toolkit/src/errors.ts b/packages/datasource-toolkit/src/errors.ts
index 8b962d50f6..1668e41ebd 100644
--- a/packages/datasource-toolkit/src/errors.ts
+++ b/packages/datasource-toolkit/src/errors.ts
@@ -77,26 +77,61 @@ export class MissingSchemaElementError extends ValidationError {}
 
 export class MissingCollectionError extends MissingSchemaElementError {}
 
-export class MissingFieldError extends MissingSchemaElementError {
-  constructor(options: {
-    typeOfField: 'Field' | 'Relation' | 'Column';
-    fieldName: string;
-    availableFields: string[];
-    collectionName?: string;
-  }) {
-    const { typeOfField, fieldName, availableFields, collectionName } = options;
-    const path = collectionName ? `${collectionName}.${fieldName}` : fieldName;
+function buildPath(fieldName: string, collectionName?: string): string {
+  return collectionName ? `${collectionName}.${fieldName}` : fieldName;
+}
+
+type MissingFieldErrorOptions = {
+  fieldName: string;
+  availableFields: string[];
+  collectionName?: string;
+};
+
+function buildMessageMissingElement(options: {
+  typeOfField: 'Field' | 'Column' | 'Relation';
+  fieldName: string;
+  availableFields: string[];
+  collectionName?: string;
+}): string {
+  const { typeOfField, fieldName, availableFields, collectionName } = options;
+  const path = buildPath(fieldName, collectionName);
+
+  return `The '${path}' ${typeOfField.toLowerCase()} was not found. Available ${typeOfField.toLowerCase()}s are: [${availableFields}]. Please check if the ${typeOfField.toLowerCase()} name is correct.`;
+}
+
+export class RelationFieldAccessDeniedError extends ValidationError {
+  constructor(options: Pick<MissingFieldErrorOptions, 'fieldName' | 'collectionName'>) {
+    const { fieldName, collectionName } = options;
+    const path = buildPath(fieldName, collectionName);
 
     super(
-      `The '${path}' ${typeOfField.toLowerCase()} was not found. Available ${typeOfField.toLowerCase()}s are: [${availableFields}]. Please check if the ${typeOfField.toLowerCase()} name is correct.`,
+      `Access to the '${path}' field is denied. You are trying to access a field from a related entity, but this is not allowed in the current context. Please verify the field name and context of use.`,
     );
   }
 }
 
+export class MissingFieldError extends MissingSchemaElementError {
+  constructor(options: MissingFieldErrorOptions) {
+    super(buildMessageMissingElement({ typeOfField: 'Field', ...options }));
+  }
+}
+
+export class MissingColumnError extends MissingSchemaElementError {
+  constructor(options: MissingFieldErrorOptions) {
+    super(buildMessageMissingElement({ typeOfField: 'Column', ...options }));
+  }
+}
+
+export class MissingRelationError extends MissingSchemaElementError {
+  constructor(options: MissingFieldErrorOptions) {
+    super(buildMessageMissingElement({ typeOfField: 'Relation', ...options }));
+  }
+}
+
 export class AlreadyDefinedFieldError extends ValidationError {
   constructor(options: { fieldName: string; collectionName?: string }) {
     const { fieldName, collectionName } = options;
-    const path = collectionName ? `${collectionName}.${fieldName}` : fieldName;
+    const path = buildPath(fieldName, collectionName);
 
     super(
       `The '${path}' field is already defined. Please check if the field name is correct and unique.`,
diff --git a/packages/datasource-toolkit/src/utils/schema.ts b/packages/datasource-toolkit/src/utils/schema.ts
index 517a4540fa..044fb58097 100644
--- a/packages/datasource-toolkit/src/utils/schema.ts
+++ b/packages/datasource-toolkit/src/utils/schema.ts
@@ -1,4 +1,10 @@
-import { AlreadyDefinedFieldError, MissingFieldError } from '../errors';
+import {
+  AlreadyDefinedFieldError,
+  MissingColumnError,
+  MissingFieldError,
+  MissingRelationError,
+  RelationFieldAccessDeniedError,
+} from '../errors';
 import {
   CollectionSchema,
   ColumnSchema,
@@ -24,9 +30,10 @@ export default class SchemaUtils {
     fieldName: string,
     collectionName?: string,
   ): void {
+    SchemaUtils.throwIfAccessingFieldFromRelation(fieldName, collectionName);
+
     if (!schema.fields[fieldName]) {
       throw new MissingFieldError({
-        typeOfField: 'Field',
         fieldName,
         availableFields: Object.keys(schema.fields),
         collectionName,
@@ -49,17 +56,14 @@ export default class SchemaUtils {
     fieldName: string,
     collectionName?: string,
   ): ColumnSchema {
+    SchemaUtils.throwIfAccessingFieldFromRelation(fieldName, collectionName);
+
     const columns = Object.keys(schema.fields).filter(
       name => schema.fields[name].type === 'Column',
     );
 
     if (!columns.find(name => name === fieldName)) {
-      throw new MissingFieldError({
-        typeOfField: 'Column',
-        fieldName,
-        availableFields: columns,
-        collectionName,
-      });
+      throw new MissingColumnError({ fieldName, availableFields: columns, collectionName });
     }
 
     return schema.fields[fieldName] as ColumnSchema;
@@ -70,13 +74,14 @@ export default class SchemaUtils {
     relationName: string,
     collectionName?: string,
   ): RelationSchema {
+    SchemaUtils.throwIfAccessingFieldFromRelation(relationName, collectionName);
+
     const relations = Object.keys(schema.fields).filter(
       name => schema.fields[name].type !== 'Column',
     );
 
     if (!relations.find(name => name === relationName)) {
-      throw new MissingFieldError({
-        typeOfField: 'Relation',
+      throw new MissingRelationError({
         fieldName: relationName,
         availableFields: relations,
         collectionName,
@@ -122,4 +127,13 @@ export default class SchemaUtils {
 
     return relationFieldSchema as ManyToManySchema | OneToManySchema;
   }
+
+  private static throwIfAccessingFieldFromRelation(
+    fieldName: string,
+    collectionName?: string,
+  ): void {
+    if (fieldName.includes(':')) {
+      throw new RelationFieldAccessDeniedError({ fieldName, collectionName });
+    }
+  }
 }
diff --git a/packages/datasource-toolkit/src/validation/field.ts b/packages/datasource-toolkit/src/validation/field.ts
index 5725ece2e1..fc01aa4d37 100644
--- a/packages/datasource-toolkit/src/validation/field.ts
+++ b/packages/datasource-toolkit/src/validation/field.ts
@@ -1,6 +1,6 @@
 import { MAP_ALLOWED_TYPES_FOR_COLUMN_TYPE } from './rules';
 import TypeGetter from './type-getter';
-import { MissingFieldError, ValidationError } from '../errors';
+import { MissingFieldError, MissingRelationError, ValidationError } from '../errors';
 import { Collection } from '../interfaces/collection';
 import { ColumnSchema, PrimitiveTypes } from '../interfaces/schema';
 
@@ -14,7 +14,6 @@ export default class FieldValidator {
       if (!schema) {
         throw new MissingFieldError({
           collectionName: collection.name,
-          typeOfField: 'Field',
           fieldName: field,
           availableFields: Object.keys(collection.schema.fields),
         });
@@ -35,9 +34,8 @@ export default class FieldValidator {
       const schema = collection.schema.fields[prefix];
 
       if (!schema) {
-        throw new MissingFieldError({
+        throw new MissingRelationError({
           collectionName: collection.name,
-          typeOfField: 'Relation',
           fieldName: prefix,
           availableFields: Object.keys(collection.schema.fields).filter(
             name =>
diff --git a/packages/datasource-toolkit/test/utils/collection.test.ts b/packages/datasource-toolkit/test/utils/collection.test.ts
index 5f9c05a5ed..6a1d165782 100644
--- a/packages/datasource-toolkit/test/utils/collection.test.ts
+++ b/packages/datasource-toolkit/test/utils/collection.test.ts
@@ -1,3 +1,4 @@
+import { MissingFieldError, MissingRelationError } from '../../src';
 import Aggregation from '../../src/interfaces/query/aggregation';
 import ConditionTreeFactory from '../../src/interfaces/query/condition-tree/factory';
 import ConditionTreeLeaf from '../../src/interfaces/query/condition-tree/nodes/leaf';
@@ -155,9 +156,7 @@ describe('CollectionUtils', () => {
 
         expect(() =>
           CollectionUtils.getFieldSchema(dataSource.getCollection('books'), 'unknown:id'),
-        ).toThrow(
-          "The 'books.unknown' relation was not found. Available relations are: [author]. Please check if the relation name is correct.",
-        );
+        ).toThrow(MissingRelationError);
       });
 
       test('should throw if the field is missing', () => {
@@ -165,9 +164,7 @@ describe('CollectionUtils', () => {
 
         expect(() =>
           CollectionUtils.getFieldSchema(dataSource.getCollection('books'), 'author:something'),
-        ).toThrow(
-          `The 'persons.something' field was not found. Available fields are: [id]. Please check if the field name is correct.`,
-        );
+        ).toThrow(MissingFieldError);
       });
     });
   });
diff --git a/packages/datasource-toolkit/test/validation/condition-tree/index.test.ts b/packages/datasource-toolkit/test/validation/condition-tree/index.test.ts
index bb2f3d9435..cbc2a5bc28 100644
--- a/packages/datasource-toolkit/test/validation/condition-tree/index.test.ts
+++ b/packages/datasource-toolkit/test/validation/condition-tree/index.test.ts
@@ -1,3 +1,4 @@
+import { MissingFieldError } from '../../../src';
 import ConditionTree from '../../../src/interfaces/query/condition-tree/nodes/base';
 import { Aggregator } from '../../../src/interfaces/query/condition-tree/nodes/branch';
 import ConditionTreeValidator from '../../../src/validation/condition-tree';
@@ -58,7 +59,7 @@ describe('ConditionTreeValidation', () => {
         });
 
         expect(() => ConditionTreeValidator.validate(conditionTree, collection)).toThrow(
-          "Column not found 'a collection.fieldDoesNotExistInSchema'",
+          MissingFieldError,
         );
       });
 
@@ -133,7 +134,7 @@ describe('ConditionTreeValidation', () => {
           });
 
           expect(() => ConditionTreeValidator.validate(conditionTree, collection)).toThrow(
-            "Column not found 'a collection.fieldDoesNotExistInSchema'",
+            MissingFieldError,
           );
         });
       });
diff --git a/packages/datasource-toolkit/test/validation/field/field.test.ts b/packages/datasource-toolkit/test/validation/field/field.test.ts
index cae74d719f..e7c16f467d 100644
--- a/packages/datasource-toolkit/test/validation/field/field.test.ts
+++ b/packages/datasource-toolkit/test/validation/field/field.test.ts
@@ -1,3 +1,4 @@
+import { MissingFieldError } from '../../../src';
 import FieldValidator from '../../../src/validation/field';
 import * as factories from '../../__factories__';
 
@@ -23,8 +24,6 @@ describe('FieldValidator', () => {
       },
     });
 
-    expect(() => FieldValidator.validate(collection, 'INVALID')).toThrow(
-      new MissingFieldError('INVALID', 'collection1'),
-    );
+    expect(() => FieldValidator.validate(collection, 'INVALID')).toThrow(MissingFieldError);
   });
 });
diff --git a/packages/datasource-toolkit/test/validation/field/validate.test.ts b/packages/datasource-toolkit/test/validation/field/validate.test.ts
index 4367f61f21..79b0ca868a 100644
--- a/packages/datasource-toolkit/test/validation/field/validate.test.ts
+++ b/packages/datasource-toolkit/test/validation/field/validate.test.ts
@@ -1,3 +1,4 @@
+import { MissingFieldError, MissingRelationError } from '../../../src';
 import FieldValidator from '../../../src/validation/field';
 import * as factories from '../../__factories__';
 
@@ -33,13 +34,13 @@ describe('FieldValidator', () => {
 
     test('should throw if the field does not exists', () => {
       expect(() => FieldValidator.validate(carsCollection, '__not_defined')).toThrow(
-        "The 'cars.__not_defined' field was not found. Available fields are: [id,owner,drivers]. Please check if the field name is correct.",
+        MissingFieldError,
       );
     });
 
     test('should throw if the relation does not exists', () => {
       expect(() => FieldValidator.validate(carsCollection, '__not_defined:id')).toThrow(
-        "The 'cars.__not_defined' relation was not found. Available relations are: [owner]. Please check if the relation name is correct.",
+        MissingRelationError,
       );
     });
 
diff --git a/packages/datasource-toolkit/test/validation/record.test.ts b/packages/datasource-toolkit/test/validation/record.test.ts
index 186c02a78c..df3d2c535c 100644
--- a/packages/datasource-toolkit/test/validation/record.test.ts
+++ b/packages/datasource-toolkit/test/validation/record.test.ts
@@ -1,3 +1,4 @@
+import { MissingFieldError } from '../../src';
 import RecordValidator from '../../src/validation/record';
 import * as factories from '../__factories__';
 
@@ -16,9 +17,7 @@ describe('RecordValidator', () => {
         RecordValidator.validate(collection, {
           unknownField: 'this field is not defined in the collection',
         }),
-      ).toThrow(
-        "The 'a collection.unknownField' field was not found. Available fields are: [name]. Please check if the field name is correct.",
-      );
+      ).toThrow(MissingFieldError);
     });
   });
 
@@ -112,9 +111,7 @@ describe('RecordValidator', () => {
         RecordValidator.validate(dataSourceBook.getCollection('book'), {
           relation: { fieldNotExist: 'a name' },
         }),
-      ).toThrow(
-        "The 'owner.fieldNotExist' field was not found. Available fields are: [name]. Please check if the field name is correct.",
-      );
+      ).toThrow(MissingFieldError);
     });
 
     test('should throw an error when the relation is an empty object', () => {
diff --git a/packages/plugin-flattener/test/flatten-column.test.ts b/packages/plugin-flattener/test/flatten-column.test.ts
index 13e9f28b15..0a36c8bdb8 100644
--- a/packages/plugin-flattener/test/flatten-column.test.ts
+++ b/packages/plugin-flattener/test/flatten-column.test.ts
@@ -1,5 +1,5 @@
 import { DataSourceCustomizer } from '@forestadmin/datasource-customizer';
-import { DataSource, Projection } from '@forestadmin/datasource-toolkit';
+import { DataSource, MissingColumnError, Projection } from '@forestadmin/datasource-toolkit';
 import * as factories from '@forestadmin/datasource-toolkit/dist/test/__factories__';
 
 import flattenColumn from '../src/flatten-column';
@@ -76,9 +76,7 @@ describe('flattenColumn', () => {
       customizer
         .customizeCollection('book', book => book.use(flattenColumn, options))
         .getDataSource(logger),
-    ).rejects.toThrow(
-      "The 'book.doctor who?' column was not found. Available columns are: [id,title,tags,author,meta]. Please check if the column name is correct.",
-    );
+    ).rejects.toThrow(MissingColumnError);
   });
 
   it('should throw when target is a primitive', async () => {
@@ -108,9 +106,7 @@ describe('flattenColumn', () => {
       customizer
         .customizeCollection('book', book => book.use(flattenColumn, options))
         .getDataSource(logger),
-    ).rejects.toThrow(
-      "The 'book.myself' column was not found. Available columns are: [id,title,tags,author,meta]. Please check if the column name is correct.",
-    );
+    ).rejects.toThrow(MissingColumnError);
   });
 
   it('should throw level is invalid', async () => {
diff --git a/packages/plugin-flattener/test/flatten-relation.test.ts b/packages/plugin-flattener/test/flatten-relation.test.ts
index 3239aa77ad..2de851425e 100644
--- a/packages/plugin-flattener/test/flatten-relation.test.ts
+++ b/packages/plugin-flattener/test/flatten-relation.test.ts
@@ -1,5 +1,5 @@
 import { DataSourceCustomizer } from '@forestadmin/datasource-customizer';
-import { ColumnSchema } from '@forestadmin/datasource-toolkit';
+import { ColumnSchema, MissingFieldError } from '@forestadmin/datasource-toolkit';
 import * as factories from '@forestadmin/datasource-toolkit/dist/test/__factories__';
 
 import flattenRelation from '../src/flatten-relation';
@@ -249,9 +249,7 @@ describe('flattenRelation', () => {
               book.use(flattenRelation, { relationName: 'owner', include: ['doesNotExist'] }),
             )
             .getDataSource(logger),
-        ).rejects.toThrow(
-          "The 'owner.doesNotExist' field was not found. Available fields are: [bookId,countryId,country,name]. Please check if the field name is correct.",
-        );
+        ).rejects.toThrow(MissingFieldError);
       });
     });
   });