@contember/schema-utils 1.2.0-alpha.17 → 1.2.0-alpha.19

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.
Files changed (44) hide show
  1. package/dist/src/index.d.ts.map +1 -1
  2. package/dist/src/index.js +2 -0
  3. package/dist/src/index.js.map +1 -1
  4. package/dist/src/model/NamingHelper.js +2 -2
  5. package/dist/src/model/NamingHelper.js.map +1 -1
  6. package/dist/src/tsconfig.tsbuildinfo +1 -1
  7. package/dist/src/type-schema/index.d.ts +1 -0
  8. package/dist/src/type-schema/index.d.ts.map +1 -1
  9. package/dist/src/type-schema/index.js +1 -0
  10. package/dist/src/type-schema/index.js.map +1 -1
  11. package/dist/src/type-schema/settings.d.ts +10 -0
  12. package/dist/src/type-schema/settings.d.ts.map +1 -0
  13. package/dist/src/type-schema/settings.js +32 -0
  14. package/dist/src/type-schema/settings.js.map +1 -0
  15. package/dist/src/validation/AclValidator.d.ts.map +1 -1
  16. package/dist/src/validation/AclValidator.js +11 -11
  17. package/dist/src/validation/AclValidator.js.map +1 -1
  18. package/dist/src/validation/ModelValidator.d.ts.map +1 -1
  19. package/dist/src/validation/ModelValidator.js +45 -37
  20. package/dist/src/validation/ModelValidator.js.map +1 -1
  21. package/dist/src/validation/SchemaValidator.d.ts +6 -2
  22. package/dist/src/validation/SchemaValidator.d.ts.map +1 -1
  23. package/dist/src/validation/SchemaValidator.js +7 -2
  24. package/dist/src/validation/SchemaValidator.js.map +1 -1
  25. package/dist/src/validation/ValidationValidator.js +4 -4
  26. package/dist/src/validation/ValidationValidator.js.map +1 -1
  27. package/dist/src/validation/errors.d.ts +3 -1
  28. package/dist/src/validation/errors.d.ts.map +1 -1
  29. package/dist/src/validation/errors.js +2 -2
  30. package/dist/src/validation/errors.js.map +1 -1
  31. package/dist/tests/cases/unit/modelValidator.test.js +118 -47
  32. package/dist/tests/cases/unit/modelValidator.test.js.map +1 -1
  33. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +3 -3
  35. package/src/index.ts +3 -1
  36. package/src/model/NamingHelper.ts +1 -1
  37. package/src/type-schema/index.ts +1 -0
  38. package/src/type-schema/settings.ts +8 -0
  39. package/src/validation/AclValidator.ts +10 -11
  40. package/src/validation/ModelValidator.ts +46 -37
  41. package/src/validation/SchemaValidator.ts +17 -4
  42. package/src/validation/ValidationValidator.ts +4 -4
  43. package/src/validation/errors.ts +25 -2
  44. package/tests/cases/unit/modelValidator.test.ts +121 -47
@@ -30,7 +30,7 @@ export class ModelValidator {
30
30
  for (const [entityName, entity] of Object.entries(entities)) {
31
31
  const entityErrors = errors.for(entityName)
32
32
  if (entity.name !== entityName) {
33
- entityErrors.add(`Entity name "${entity.name}" does not match the name in a map "${entityName}"`)
33
+ entityErrors.add('MODEL_NAME_MISMATCH', `Entity name "${entity.name}" does not match the name in a map "${entityName}"`)
34
34
  }
35
35
  this.validateEntity(entity, entityErrors)
36
36
  }
@@ -43,7 +43,7 @@ export class ModelValidator {
43
43
  const fieldErrors = errors.for(fieldName)
44
44
  this.validateField(entity, field, fieldErrors)
45
45
  if (field.name !== fieldName) {
46
- fieldErrors.add(`Field name "${field.name}" does not match the name in a map "${fieldName}"`)
46
+ fieldErrors.add('MODEL_NAME_MISMATCH', `Field name "${field.name}" does not match the name in a map "${fieldName}"`)
47
47
  }
48
48
  }
49
49
  this.validateUniqueConstraints(
@@ -57,12 +57,12 @@ export class ModelValidator {
57
57
  for (const [constraintName, constraint] of Object.entries(uniqueConstraints)) {
58
58
  const uniqueErrors = errors.for(constraintName)
59
59
  if (constraint.name !== constraintName) {
60
- uniqueErrors.add(`Constraint name ${constraint.name} does not match the name in a map "${constraintName}"`)
60
+ uniqueErrors.add('MODEL_NAME_MISMATCH', `Constraint name ${constraint.name} does not match the name in a map "${constraintName}"`)
61
61
  continue
62
62
  }
63
63
  for (const field of constraint.fields) {
64
64
  if (!fields.has(field)) {
65
- uniqueErrors.add(`Referenced field ${field} in a constraint does not exists`)
65
+ uniqueErrors.add('MODEL_UNDEFINED_FIELD', `Referenced field ${field} in a constraint does not exists`)
66
66
  }
67
67
  }
68
68
  }
@@ -74,7 +74,7 @@ export class ModelValidator {
74
74
  this.validateRelation(partialEntity, field, errors)
75
75
  } else {
76
76
  if (field.sequence && field.nullable) {
77
- errors.add('Column with sequence cannot be nullable.')
77
+ errors.add('MODEL_INVALID_COLUMN_DEFINITION', 'Column with sequence cannot be nullable.')
78
78
  }
79
79
  }
80
80
  }
@@ -84,7 +84,7 @@ export class ModelValidator {
84
84
  const targetEntityName = field.target
85
85
  const targetEntity = this.model.entities[targetEntityName] || undefined
86
86
  if (!targetEntity) {
87
- return errors.add(`Target entity ${targetEntityName} not found`)
87
+ return errors.add('MODEL_UNDEFINED_ENTITY', `Target entity ${targetEntityName} not found`)
88
88
  }
89
89
  if (((it: Model.AnyRelation): it is Model.AnyRelation & Model.OrderableRelation => 'orderBy' in it)(field)) {
90
90
  (field as Model.OrderableRelation).orderBy?.forEach(it => {
@@ -94,13 +94,13 @@ export class ModelValidator {
94
94
  const orderByField = entity.fields[it.path[i]]
95
95
  const pathStr = it.path.slice(0, i + 1).join('.')
96
96
  if (!orderByField) {
97
- errors.add(`Invalid orderBy of ${entityName}::${field.name}: field ${pathStr} is not defined`)
97
+ errors.add('ACL_UNDEFINED_FIELD', `Invalid orderBy of ${entityName}::${field.name}: field ${pathStr} is not defined`)
98
98
  return
99
99
  }
100
100
  if (i + 1 < it.path.length) {
101
101
  const targetEntity = getTargetEntity(this.model, entity, orderByField.name)
102
102
  if (!targetEntity) {
103
- errors.add(`Invalid orderBy of ${entityName}::${field.name}: field ${pathStr} is not a relation`)
103
+ errors.add('MODEL_RELATION_REQUIRED', `Invalid orderBy of ${entityName}::${field.name}: field ${pathStr} is not a relation`)
104
104
  return
105
105
  }
106
106
  entity = targetEntity
@@ -110,20 +110,20 @@ export class ModelValidator {
110
110
  }
111
111
  if (partialEntity.view) {
112
112
  if (field.type === Model.RelationType.ManyHasMany) {
113
- return errors.add('Many-has-many relation is not allowed on a view entity.')
113
+ return errors.add('MODEL_INVALID_VIEW_USAGE', 'Many-has-many relation is not allowed on a view entity.')
114
114
  }
115
115
  if (
116
116
  !targetEntity.view &&
117
117
  field.type === Model.RelationType.OneHasMany
118
118
  ) {
119
- return errors.add('One-has-many relation fields on views must point to a view entity.')
119
+ return errors.add('MODEL_INVALID_VIEW_USAGE', 'One-has-many relation fields on views must point to a view entity.')
120
120
  }
121
121
  if (
122
122
  !targetEntity.view &&
123
123
  field.type === Model.RelationType.OneHasOne &&
124
124
  !('joiningColumn' in field)
125
125
  ) {
126
- return errors.add('One-has-one relation fields on views must be owning or point to a view entity.')
126
+ return errors.add('MODEL_INVALID_VIEW_USAGE', 'One-has-one relation fields on views must be owning or point to a view entity.')
127
127
  }
128
128
  }
129
129
  if (isInverseRelation(field)) {
@@ -131,70 +131,70 @@ export class ModelValidator {
131
131
  const targetField = targetEntity.fields[ownedBy]
132
132
  const relationDescription = `Target relation ${targetEntityName}::${ownedBy}:`
133
133
  if (!targetField) {
134
- return errors.add(`${relationDescription} not exists`)
134
+ return errors.add('MODEL_UNDEFINED_FIELD', `${relationDescription} not exists`)
135
135
  }
136
136
 
137
137
  if (!isRelation(targetField)) {
138
- return errors.add(`${relationDescription} not a relation`)
138
+ return errors.add('MODEL_RELATION_REQUIRED', `${relationDescription} not a relation`)
139
139
  }
140
140
  if (targetField.target !== entityName) {
141
- return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
141
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
142
142
  }
143
143
  if (!isOwningRelation(targetField)) {
144
- errors.add(`${relationDescription} not an owning relation`)
144
+ errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} not an owning relation`)
145
145
  return
146
146
  }
147
147
  if (!targetField.inversedBy) {
148
- return errors.add(`${relationDescription} inverse relation is not set`)
148
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} inverse relation is not set`)
149
149
  }
150
150
  if (targetField.inversedBy !== field.name) {
151
- return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`)
151
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`)
152
152
  }
153
153
  if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
154
- return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
154
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
155
155
  }
156
156
  if (field.type === Model.RelationType.OneHasMany && targetField.type !== Model.RelationType.ManyHasOne) {
157
- return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
157
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
158
158
  }
159
159
  if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
160
- return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
160
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
161
161
  }
162
162
  } else {
163
163
  const inversedBy = field.inversedBy
164
164
  if (targetEntity.view) {
165
165
  if ('joiningColumn' in field) {
166
- return errors.add(`View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`)
166
+ return errors.add('MODEL_INVALID_VIEW_USAGE', `View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`)
167
167
  }
168
168
  }
169
169
  if (inversedBy) {
170
170
  const targetField = targetEntity.fields[inversedBy]
171
171
  const relationDescription = `Target relation ${targetEntityName}::${inversedBy}:`
172
172
  if (!targetField) {
173
- return errors.add(`${relationDescription} not exists`)
173
+ return errors.add('MODEL_UNDEFINED_FIELD', `${relationDescription} not exists`)
174
174
  }
175
175
  if (!isRelation(targetField)) {
176
- return errors.add(`${relationDescription} not a relation`)
176
+ return errors.add('MODEL_RELATION_REQUIRED', `${relationDescription} not a relation`)
177
177
  }
178
178
  if (targetField.target !== entityName) {
179
- return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
179
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
180
180
  }
181
181
  if (!isInverseRelation(targetField)) {
182
- return errors.add(`${relationDescription} not an inverse relation`)
182
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} not an inverse relation`)
183
183
  }
184
184
  if (!targetField.ownedBy) {
185
- return errors.add(`${relationDescription} owning relation is not set`)
185
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} owning relation is not set`)
186
186
  }
187
187
  if (targetField.ownedBy !== field.name) {
188
- return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`)
188
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`)
189
189
  }
190
190
  if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
191
- return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
191
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
192
192
  }
193
193
  if (field.type === Model.RelationType.ManyHasOne && targetField.type !== Model.RelationType.OneHasMany) {
194
- return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
194
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
195
195
  }
196
196
  if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
197
- return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
197
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
198
198
  }
199
199
  }
200
200
  }
@@ -202,25 +202,34 @@ export class ModelValidator {
202
202
 
203
203
  private validateIdentifier(value: string, errorBuilder: ErrorBuilder) {
204
204
  if (!value.match(IDENTIFIER_PATTERN)) {
205
- return errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
205
+ return errorBuilder.add('MODEL_INVALID_IDENTIFIER', `${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
206
206
  }
207
207
  if (RESERVED_WORDS.includes(value)) {
208
- errorBuilder.add(`${value} is reserved word`)
208
+ errorBuilder.add('MODEL_INVALID_IDENTIFIER', `${value} is reserved word`)
209
209
  }
210
210
  }
211
211
 
212
212
  private validateCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) {
213
213
  const relationNames: Record<string, string> = {}
214
214
  const aliasedTypes = new Map<string, Model.ColumnType>()
215
+ const entityNames = new Set(entities.map(it => it.name))
215
216
  for (const entity of entities) {
216
217
  const description = `table name ${entity.tableName} of entity ${entity.name}`
217
218
  if (relationNames[entity.tableName]) {
218
219
  errorBuilder
219
220
  .for(entity.name)
220
- .add(`${description} collides with a ${relationNames[entity.tableName]}`)
221
+ .add('MODEL_NAME_COLLISION', `${description} collides with a ${relationNames[entity.tableName]}`)
221
222
  } else {
222
223
  relationNames[entity.tableName] = description
223
224
  }
225
+
226
+ if (entity.name.endsWith('Meta')) {
227
+ const baseName = entity.name.substring(0, entity.name.length - 4)
228
+ if (entityNames.has(baseName)) {
229
+ errorBuilder.for(entity.name)
230
+ .add('MODEL_NAME_COLLISION', `entity ${entity.name} collides with entity ${baseName}, because a GraphQL type with "Meta" suffix is created for every entity`)
231
+ }
232
+ }
224
233
  }
225
234
  for (const entity of entities) {
226
235
  const entityErrorBuilder = errorBuilder.for(entity.name)
@@ -231,7 +240,7 @@ export class ModelValidator {
231
240
  if (relationNames[joiningTable.tableName]) {
232
241
  entityErrorBuilder
233
242
  .for(relation.name)
234
- .add(
243
+ .add('MODEL_NAME_COLLISION',
235
244
  `${description} collides with a ${relationNames[joiningTable.tableName]}.` +
236
245
  'Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.',
237
246
  )
@@ -246,7 +255,7 @@ export class ModelValidator {
246
255
  if (aliasedTypes.has(column.typeAlias) && aliasedTypes.get(column.typeAlias) !== column.type) {
247
256
  errorBuilder
248
257
  .for(column.name)
249
- .add(`Type alias ${column.typeAlias} already exists for base type ${column.type}`)
258
+ .add('MODEL_NAME_COLLISION', `Type alias ${column.typeAlias} already exists for base type ${column.type}`)
250
259
  }
251
260
  aliasedTypes.set(column.typeAlias, column.type)
252
261
  },
@@ -262,7 +271,7 @@ export class ModelValidator {
262
271
  for (const index of Object.values(entity.indexes)) {
263
272
  const description = `index name ${index.name} of entity ${entity.name}`
264
273
  if (relationNames[index.name]) {
265
- entityErrorBuilder.add(`${description} collides with ${relationNames[index.name]}`)
274
+ entityErrorBuilder.add('MODEL_NAME_COLLISION', `${description} collides with ${relationNames[index.name]}`)
266
275
  } else {
267
276
  relationNames[index.name] = description
268
277
  }
@@ -270,7 +279,7 @@ export class ModelValidator {
270
279
  for (const unique of Object.values(entity.unique)) {
271
280
  const description = `unique index name ${unique.name} of entity ${entity.name}`
272
281
  if (relationNames[unique.name]) {
273
- entityErrorBuilder.add(`${description} collides with ${relationNames[unique.name]}`)
282
+ entityErrorBuilder.add('MODEL_NAME_COLLISION', `${description} collides with ${relationNames[unique.name]}`)
274
283
  } else {
275
284
  relationNames[unique.name] = description
276
285
  }
@@ -1,11 +1,16 @@
1
1
  import { Schema } from '@contember/schema'
2
- import { ValidationError } from './errors'
2
+ import { ValidationError, ValidationErrorCode } from './errors'
3
3
  import { AclValidator } from './AclValidator'
4
4
  import { ModelValidator } from './ModelValidator'
5
5
  import { ValidationValidator } from './ValidationValidator'
6
6
 
7
+ export interface SchemaValidatorSkippedErrors {
8
+ code: ValidationErrorCode
9
+ path?: string
10
+ }
11
+
7
12
  export class SchemaValidator {
8
- public static validate(schema: Schema): ValidationError[] {
13
+ public static validate(schema: Schema, skippedErrors: SchemaValidatorSkippedErrors[] = []): ValidationError[] {
9
14
  const modelValidator = new ModelValidator(schema.model)
10
15
  const modelErrors = modelValidator.validate()
11
16
 
@@ -15,7 +20,15 @@ export class SchemaValidator {
15
20
 
16
21
  const validationValidator = new ValidationValidator(schema.model)
17
22
  const validationErrors = validationValidator.validate(schema.validation)
18
-
19
- return [...aclErrors, ...modelErrors, ...validationErrors]
23
+ const allErrors = [...aclErrors, ...modelErrors, ...validationErrors]
24
+ if (skippedErrors.length === 0) {
25
+ return allErrors
26
+ }
27
+ return allErrors.filter(
28
+ err => !skippedErrors.some(
29
+ rule => err.code === rule.code
30
+ && (!rule.path || err.path.join('.') === rule.path),
31
+ ),
32
+ )
20
33
  }
21
34
  }
@@ -11,7 +11,7 @@ export class ValidationValidator {
11
11
  const entity = this.model.entities[entityName]
12
12
  const entityErrorBuilder = errorBuilder.for(entityName)
13
13
  if (!entity) {
14
- entityErrorBuilder.add('Entity not found')
14
+ entityErrorBuilder.add('VALIDATION_UNDEFINED_ENTITY', 'Entity not found')
15
15
  } else {
16
16
  this.validateEntityRules(entityErrorBuilder, schema[entityName], entity)
17
17
  }
@@ -28,7 +28,7 @@ export class ValidationValidator {
28
28
  const field = entity.fields[fieldName]
29
29
  const fieldErrorBuilder = errorBuilder.for(fieldName)
30
30
  if (!field) {
31
- fieldErrorBuilder.add('Field not found')
31
+ fieldErrorBuilder.add('VALIDATION_UNDEFINED_FIELD', 'Field not found')
32
32
  } else {
33
33
  this.validateFieldRules(fieldErrorBuilder, entitySchema[fieldName], entity, field)
34
34
  }
@@ -60,7 +60,7 @@ export class ValidationValidator {
60
60
  },
61
61
  })
62
62
  if (errorMessage) {
63
- return errorBuilder.add(errorMessage)
63
+ return errorBuilder.add('VALIDATION_NOT_IMPLEMENTED', errorMessage)
64
64
  }
65
65
  this.validateValidator(errorBuilder.for('validator'), rule.validator, entity, field)
66
66
  }
@@ -146,7 +146,7 @@ export class ValidationValidator {
146
146
  visitRelation: () => true,
147
147
  })
148
148
  if (isRelation) {
149
- errorBuilder.add('Rules depending on relations are currently not supported.')
149
+ errorBuilder.add('VALIDATION_NOT_IMPLEMENTED', 'Rules depending on relations are currently not supported.')
150
150
  return undefined
151
151
  }
152
152
  return entity.fields[argument.path[0]]
@@ -1,6 +1,29 @@
1
+ export type ValidationErrorCode =
2
+ | 'MODEL_NAME_MISMATCH'
3
+ | 'MODEL_UNDEFINED_FIELD'
4
+ | 'MODEL_UNDEFINED_ENTITY'
5
+ | 'MODEL_RELATION_REQUIRED'
6
+ | 'MODEL_INVALID_RELATION_DEFINITION'
7
+ | 'MODEL_INVALID_COLUMN_DEFINITION'
8
+ | 'MODEL_INVALID_VIEW_USAGE'
9
+ | 'MODEL_INVALID_IDENTIFIER'
10
+ | 'MODEL_NAME_COLLISION'
11
+
12
+ | 'ACL_INVALID_CONDITION'
13
+ | 'ACL_UNDEFINED_VARIABLE'
14
+ | 'ACL_UNDEFINED_FIELD'
15
+ | 'ACL_UNDEFINED_PREDICATE'
16
+ | 'ACL_UNDEFINED_ROLE'
17
+ | 'ACL_UNDEFINED_ENTITY'
18
+
19
+ | 'VALIDATION_UNDEFINED_ENTITY'
20
+ | 'VALIDATION_UNDEFINED_FIELD'
21
+ | 'VALIDATION_NOT_IMPLEMENTED'
22
+
1
23
  export interface ValidationError {
2
24
  path: (string | number)[]
3
25
  message: string
26
+ code: ValidationErrorCode
4
27
  }
5
28
 
6
29
  export class ErrorBuilder {
@@ -10,7 +33,7 @@ export class ErrorBuilder {
10
33
  return new ErrorBuilder(this.errors, [...this.path, ...path])
11
34
  }
12
35
 
13
- add(message: string): void {
14
- this.errors.push({ path: this.path, message })
36
+ add(code: ValidationErrorCode, message: string): void {
37
+ this.errors.push({ path: this.path, message, code })
15
38
  }
16
39
  }
@@ -3,67 +3,141 @@ import { Model } from '@contember/schema'
3
3
  import { ModelValidator } from '../../../src'
4
4
 
5
5
 
6
- const model: Model.Schema = {
7
- enums: {},
8
- entities: {
9
- Foo: {
10
- fields: {
11
- id: {
12
- columnName: 'id',
13
- name: 'id',
14
- nullable: false,
15
- type: Model.ColumnType.Uuid,
16
- columnType: 'uuid',
6
+
7
+ test('index name collision', () => {
8
+ const model: Model.Schema = {
9
+ enums: {},
10
+ entities: {
11
+ Foo: {
12
+ fields: {
13
+ id: {
14
+ columnName: 'id',
15
+ name: 'id',
16
+ nullable: false,
17
+ type: Model.ColumnType.Uuid,
18
+ columnType: 'uuid',
19
+ },
17
20
  },
18
- },
19
- name: 'Foo',
20
- primary: 'id',
21
- primaryColumn: 'id',
22
- tableName: 'foo',
23
- unique: {},
24
- eventLog: {
25
- enabled: true,
26
- },
27
- indexes: {
28
- test: { fields: ['id'], name: 'test' },
29
- },
30
- },
31
- Bar: {
32
- fields: {
33
- id: {
34
- columnName: 'id',
35
- name: 'id',
36
- nullable: false,
37
- type: Model.ColumnType.Uuid,
38
- columnType: 'uuid',
21
+ name: 'Foo',
22
+ primary: 'id',
23
+ primaryColumn: 'id',
24
+ tableName: 'foo',
25
+ unique: {},
26
+ eventLog: {
27
+ enabled: true,
28
+ },
29
+ indexes: {
30
+ test: { fields: ['id'], name: 'test' },
39
31
  },
40
32
  },
41
- name: 'Bar',
42
- primary: 'id',
43
- primaryColumn: 'id',
44
- tableName: 'bar',
45
- unique: {
46
- test: { fields: ['id'], name: 'test' },
47
- },
48
- eventLog: {
49
- enabled: true,
50
- },
51
- indexes: {
52
- foo: { fields: ['id'], name: 'foo' },
33
+ Bar: {
34
+ fields: {
35
+ id: {
36
+ columnName: 'id',
37
+ name: 'id',
38
+ nullable: false,
39
+ type: Model.ColumnType.Uuid,
40
+ columnType: 'uuid',
41
+ },
42
+ },
43
+ name: 'Bar',
44
+ primary: 'id',
45
+ primaryColumn: 'id',
46
+ tableName: 'bar',
47
+ unique: {
48
+ test: { fields: ['id'], name: 'test' },
49
+ },
50
+ eventLog: {
51
+ enabled: true,
52
+ },
53
+ indexes: {
54
+ foo: { fields: ['id'], name: 'foo' },
55
+ },
53
56
  },
54
57
  },
55
- },
56
- }
57
- test('index name collision', () => {
58
+ }
58
59
  const validator = new ModelValidator(model)
59
60
  assert.deepStrictEqual(validator.validate(), [
60
61
  {
62
+ code: 'MODEL_NAME_COLLISION',
61
63
  message: 'index name foo of entity Bar collides with table name foo of entity Foo',
62
64
  path: ['entities', 'Bar'],
63
65
  },
64
66
  {
67
+ code: 'MODEL_NAME_COLLISION',
65
68
  message: 'unique index name test of entity Bar collides with index name test of entity Foo',
66
69
  path: ['entities', 'Bar'],
67
70
  },
68
71
  ])
69
72
  })
73
+
74
+
75
+ test('"meta" collision', () => {
76
+ const model: Model.Schema = {
77
+ enums: {},
78
+ entities: {
79
+ Foo: {
80
+ fields: {
81
+ id: {
82
+ columnName: 'id',
83
+ name: 'id',
84
+ nullable: false,
85
+ type: Model.ColumnType.Uuid,
86
+ columnType: 'uuid',
87
+ },
88
+ },
89
+ name: 'Foo',
90
+ primary: 'id',
91
+ primaryColumn: 'id',
92
+ tableName: 'foo',
93
+ unique: {},
94
+ eventLog: { enabled: true },
95
+ indexes: {},
96
+ },
97
+ FooMeta: {
98
+ fields: {
99
+ id: {
100
+ columnName: 'id',
101
+ name: 'id',
102
+ nullable: false,
103
+ type: Model.ColumnType.Uuid,
104
+ columnType: 'uuid',
105
+ },
106
+ },
107
+ name: 'FooMeta',
108
+ primary: 'id',
109
+ primaryColumn: 'id',
110
+ tableName: 'foo_meta',
111
+ unique: {},
112
+ eventLog: { enabled: true },
113
+ indexes: {},
114
+ },
115
+ BarMeta: {
116
+ fields: {
117
+ id: {
118
+ columnName: 'id',
119
+ name: 'id',
120
+ nullable: false,
121
+ type: Model.ColumnType.Uuid,
122
+ columnType: 'uuid',
123
+ },
124
+ },
125
+ name: 'BarMeta',
126
+ primary: 'id',
127
+ primaryColumn: 'id',
128
+ tableName: 'bar',
129
+ unique: {},
130
+ eventLog: { enabled: true },
131
+ indexes: {},
132
+ },
133
+ },
134
+ }
135
+ const validator = new ModelValidator(model)
136
+ assert.deepStrictEqual(validator.validate(), [
137
+ {
138
+ code: 'MODEL_NAME_COLLISION',
139
+ message: 'entity FooMeta collides with entity Foo, because a GraphQL type with "Meta" suffix is created for every entity',
140
+ path: ['entities', 'FooMeta'],
141
+ },
142
+ ])
143
+ })