@contember/schema-utils 1.3.0-rc.1 → 1.3.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ export class ModelValidator {
13
13
  const errorBuilder = new ErrorBuilder([], [])
14
14
  this.validateEnums(this.model.enums, errorBuilder.for('enums'))
15
15
  this.validateEntities(this.model.entities, errorBuilder.for('entities'))
16
- this.validateCollisions(Object.values(this.model.entities), errorBuilder.for('entities'))
16
+
17
17
  return errorBuilder.errors
18
18
  }
19
19
 
@@ -34,10 +34,15 @@ export class ModelValidator {
34
34
  }
35
35
  this.validateEntity(entity, entityErrors)
36
36
  }
37
+ const entitiesArr = Object.values(this.model.entities)
38
+ this.validateMetaSuffixCollisions(entitiesArr, errors)
39
+ this.validateTableNameCollisions(entitiesArr, errors)
40
+ this.validateAliasedTypesCollision(entitiesArr, errors)
37
41
  }
38
42
 
39
43
  private validateEntity(entity: Model.Entity, errors: ErrorBuilder): void {
40
44
  this.validateIdentifier(entity.name, errors)
45
+ this.validateEntityName(entity.name, errors)
41
46
 
42
47
  for (const [fieldName, field] of Object.entries(entity.fields)) {
43
48
  const fieldErrors = errors.for(fieldName)
@@ -51,6 +56,7 @@ export class ModelValidator {
51
56
  new Set(Object.keys(entity.fields)),
52
57
  errors.for('unique'),
53
58
  )
59
+ this.validateColumnNamesCollision(entity, errors)
54
60
  }
55
61
 
56
62
  private validateUniqueConstraints(uniqueConstraints: Model.Entity['unique'], fields: Set<string>, errors: ErrorBuilder): void {
@@ -63,6 +69,34 @@ export class ModelValidator {
63
69
  }
64
70
  }
65
71
 
72
+ private validateColumnNamesCollision(entity: Model.Entity, errors: ErrorBuilder): void {
73
+ const existingColumnNames = new Map<string, string>()
74
+ const addColumnName = (fieldName: string, columnName: string) => {
75
+ if (existingColumnNames.has(columnName)) {
76
+ const exitingName = existingColumnNames.get(columnName)
77
+ errors
78
+ .for(fieldName)
79
+ .add('MODEL_NAME_COLLISION', `Column name "${columnName}" on field "${fieldName}" collides with a column name on field "${exitingName}".`)
80
+ }
81
+ existingColumnNames.set(columnName, fieldName)
82
+ }
83
+ acceptEveryFieldVisitor(this.model, entity, {
84
+ visitColumn: ({ column }) => {
85
+ addColumnName(column.name, column.columnName)
86
+ },
87
+ visitOneHasOneOwning: ({ relation }) => {
88
+ addColumnName(relation.name, relation.joiningColumn.columnName)
89
+ },
90
+ visitManyHasOne: ({ relation }) => {
91
+ addColumnName(relation.name, relation.joiningColumn.columnName)
92
+ },
93
+ visitOneHasOneInverse: () => {},
94
+ visitOneHasMany: () => {},
95
+ visitManyHasManyInverse: () => {},
96
+ visitManyHasManyOwning: () => {},
97
+ })
98
+ }
99
+
66
100
  private validateField(partialEntity: Model.Entity, field: Model.AnyField, errors: ErrorBuilder): void {
67
101
  this.validateIdentifier(field.name, errors)
68
102
  if (isRelation(field)) {
@@ -204,10 +238,29 @@ export class ModelValidator {
204
238
  }
205
239
  }
206
240
 
207
- private validateCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) {
208
- const relationNames: Record<string, string> = {}
209
- const aliasedTypes = new Map<string, Model.ColumnType>()
241
+ private validateEntityName(value: string, errorBuilder: ErrorBuilder) {
242
+ if (['Query', 'Mutation'].includes(value)) {
243
+ errorBuilder.add('MODEL_INVALID_ENTITY_NAME', `${value} is reserved word`)
244
+ }
245
+ }
246
+
247
+
248
+ private validateMetaSuffixCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) {
210
249
  const entityNames = new Set(entities.map(it => it.name))
250
+ for (const entity of entities) {
251
+ if (entity.name.endsWith('Meta')) {
252
+ const baseName = entity.name.substring(0, entity.name.length - 4)
253
+ if (entityNames.has(baseName)) {
254
+ errorBuilder.for(entity.name)
255
+ .add('MODEL_NAME_COLLISION', `entity ${entity.name} collides with entity ${baseName}, because a GraphQL type with "Meta" suffix is created for every entity`)
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+
262
+ private validateTableNameCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) {
263
+ const relationNames: Record<string, string> = {}
211
264
  for (const entity of entities) {
212
265
  const description = `table name ${entity.tableName} of entity ${entity.name}`
213
266
  if (relationNames[entity.tableName]) {
@@ -217,14 +270,6 @@ export class ModelValidator {
217
270
  } else {
218
271
  relationNames[entity.tableName] = description
219
272
  }
220
-
221
- if (entity.name.endsWith('Meta')) {
222
- const baseName = entity.name.substring(0, entity.name.length - 4)
223
- if (entityNames.has(baseName)) {
224
- errorBuilder.for(entity.name)
225
- .add('MODEL_NAME_COLLISION', `entity ${entity.name} collides with entity ${baseName}, because a GraphQL type with "Meta" suffix is created for every entity`)
226
- }
227
- }
228
273
  }
229
274
  for (const entity of entities) {
230
275
  const entityErrorBuilder = errorBuilder.for(entity.name)
@@ -243,7 +288,22 @@ export class ModelValidator {
243
288
  relationNames[joiningTable.tableName] = description
244
289
  }
245
290
  },
246
- visitColumn: ({ entity, column }) => {
291
+ visitColumn: () => {},
292
+ visitManyHasManyInverse: () => { },
293
+ visitOneHasMany: () => { },
294
+ visitOneHasOneInverse: () => { },
295
+ visitOneHasOneOwning: () => { },
296
+ visitManyHasOne: () => { },
297
+ })
298
+ }
299
+ }
300
+
301
+
302
+ private validateAliasedTypesCollision(entities: Model.Entity[], errorBuilder: ErrorBuilder) {
303
+ const aliasedTypes = new Map<string, Model.ColumnType>()
304
+ for (const entity of entities) {
305
+ acceptEveryFieldVisitor(this.model, entity, {
306
+ visitColumn: ({ column }) => {
247
307
  if (!column.typeAlias) {
248
308
  return
249
309
  }
@@ -254,6 +314,7 @@ export class ModelValidator {
254
314
  }
255
315
  aliasedTypes.set(column.typeAlias, column.type)
256
316
  },
317
+ visitManyHasManyOwning: () => { },
257
318
  visitManyHasManyInverse: () => { },
258
319
  visitOneHasMany: () => { },
259
320
  visitOneHasOneInverse: () => { },
@@ -7,6 +7,7 @@ export type ValidationErrorCode =
7
7
  | 'MODEL_INVALID_COLUMN_DEFINITION'
8
8
  | 'MODEL_INVALID_VIEW_USAGE'
9
9
  | 'MODEL_INVALID_IDENTIFIER'
10
+ | 'MODEL_INVALID_ENTITY_NAME'
10
11
  | 'MODEL_NAME_COLLISION'
11
12
 
12
13
  | 'ACL_INVALID_CONDITION'
@@ -1,9 +1,7 @@
1
1
  import { assert, test } from 'vitest'
2
2
  import { Model } from '@contember/schema'
3
3
  import { ModelValidator } from '../../../src'
4
-
5
-
6
-
4
+ import { c, createSchema } from '@contember/schema-definition'
7
5
 
8
6
  test('"meta" collision', () => {
9
7
  const model: Model.Schema = {
@@ -74,3 +72,22 @@ test('"meta" collision', () => {
74
72
  },
75
73
  ])
76
74
  })
75
+
76
+ namespace ColumnNameCollision {
77
+ export class Bar {
78
+ rel = c.oneHasOne(Bar)
79
+ relId = c.intColumn()
80
+ }
81
+ }
82
+
83
+ test('column name collision', () => {
84
+ const schema = createSchema(ColumnNameCollision)
85
+ const validator = new ModelValidator(schema.model)
86
+ assert.deepStrictEqual(validator.validate(), [
87
+ {
88
+ code: 'MODEL_NAME_COLLISION',
89
+ message: 'Column name "rel_id" on field "relId" collides with a column name on field "rel".',
90
+ path: ['entities', 'Bar', 'relId'],
91
+ },
92
+ ])
93
+ })