@contember/schema-utils 1.2.0-alpha.9 → 1.2.0-beta.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.
Files changed (147) hide show
  1. package/dist/src/acl/PredicateDefinitionProcessor.js +2 -2
  2. package/dist/src/acl/PredicateDefinitionProcessor.js.map +1 -1
  3. package/dist/src/definition-generator/TsDefinitionGenerator.d.ts +26 -0
  4. package/dist/src/definition-generator/TsDefinitionGenerator.d.ts.map +1 -0
  5. package/dist/src/definition-generator/TsDefinitionGenerator.js +246 -0
  6. package/dist/src/definition-generator/TsDefinitionGenerator.js.map +1 -0
  7. package/dist/src/definition-generator/index.d.ts +2 -0
  8. package/dist/src/definition-generator/index.d.ts.map +1 -0
  9. package/dist/src/definition-generator/index.js +18 -0
  10. package/dist/src/definition-generator/index.js.map +1 -0
  11. package/dist/src/index.d.ts +2 -0
  12. package/dist/src/index.d.ts.map +1 -1
  13. package/dist/src/index.js +4 -0
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/lax/LaxSchemaConverter.d.ts +10 -0
  16. package/dist/src/lax/LaxSchemaConverter.d.ts.map +1 -0
  17. package/dist/src/lax/LaxSchemaConverter.js +204 -0
  18. package/dist/src/lax/LaxSchemaConverter.js.map +1 -0
  19. package/dist/src/lax/index.d.ts +3 -0
  20. package/dist/src/lax/index.d.ts.map +1 -0
  21. package/dist/src/lax/index.js +6 -0
  22. package/dist/src/lax/index.js.map +1 -0
  23. package/dist/src/lax/schema.d.ts +284 -0
  24. package/dist/src/lax/schema.d.ts.map +1 -0
  25. package/dist/src/lax/schema.js +67 -0
  26. package/dist/src/lax/schema.js.map +1 -0
  27. package/dist/src/model/NamingConventions.d.ts +17 -0
  28. package/dist/src/model/NamingConventions.d.ts.map +1 -0
  29. package/dist/src/model/NamingConventions.js +29 -0
  30. package/dist/src/model/NamingConventions.js.map +1 -0
  31. package/dist/src/model/NamingHelper.d.ts +2 -0
  32. package/dist/src/model/NamingHelper.d.ts.map +1 -1
  33. package/dist/src/model/NamingHelper.js +10 -2
  34. package/dist/src/model/NamingHelper.js.map +1 -1
  35. package/dist/src/model/index.d.ts +2 -0
  36. package/dist/src/model/index.d.ts.map +1 -1
  37. package/dist/src/model/index.js +2 -0
  38. package/dist/src/model/index.js.map +1 -1
  39. package/dist/src/model/modelUtils.d.ts +1 -0
  40. package/dist/src/model/modelUtils.d.ts.map +1 -1
  41. package/dist/src/model/modelUtils.js +69 -46
  42. package/dist/src/model/modelUtils.js.map +1 -1
  43. package/dist/src/model/resolveDefaultColumnType.d.ts +3 -0
  44. package/dist/src/model/resolveDefaultColumnType.d.ts.map +1 -0
  45. package/dist/src/model/resolveDefaultColumnType.js +29 -0
  46. package/dist/src/model/resolveDefaultColumnType.js.map +1 -0
  47. package/dist/src/schemaNormalizer.d.ts.map +1 -1
  48. package/dist/src/schemaNormalizer.js +1 -0
  49. package/dist/src/schemaNormalizer.js.map +1 -1
  50. package/dist/src/tsconfig.tsbuildinfo +1 -1
  51. package/dist/src/type-schema/acl.d.ts.map +1 -1
  52. package/dist/src/type-schema/acl.js +15 -4
  53. package/dist/src/type-schema/acl.js.map +1 -1
  54. package/dist/src/type-schema/index.d.ts +1 -0
  55. package/dist/src/type-schema/index.d.ts.map +1 -1
  56. package/dist/src/type-schema/index.js +1 -0
  57. package/dist/src/type-schema/index.js.map +1 -1
  58. package/dist/src/type-schema/model.js +1 -1
  59. package/dist/src/type-schema/model.js.map +1 -1
  60. package/dist/src/type-schema/settings.d.ts +10 -0
  61. package/dist/src/type-schema/settings.d.ts.map +1 -0
  62. package/dist/src/type-schema/settings.js +32 -0
  63. package/dist/src/type-schema/settings.js.map +1 -0
  64. package/dist/src/validation/AclValidator.d.ts.map +1 -1
  65. package/dist/src/validation/AclValidator.js +11 -11
  66. package/dist/src/validation/AclValidator.js.map +1 -1
  67. package/dist/src/validation/MembershipResolver.d.ts.map +1 -1
  68. package/dist/src/validation/MembershipResolver.js +12 -3
  69. package/dist/src/validation/MembershipResolver.js.map +1 -1
  70. package/dist/src/validation/ModelValidator.d.ts.map +1 -1
  71. package/dist/src/validation/ModelValidator.js +77 -43
  72. package/dist/src/validation/ModelValidator.js.map +1 -1
  73. package/dist/src/validation/SchemaValidator.d.ts +6 -2
  74. package/dist/src/validation/SchemaValidator.d.ts.map +1 -1
  75. package/dist/src/validation/SchemaValidator.js +7 -2
  76. package/dist/src/validation/SchemaValidator.js.map +1 -1
  77. package/dist/src/validation/ValidationValidator.js +4 -4
  78. package/dist/src/validation/ValidationValidator.js.map +1 -1
  79. package/dist/src/validation/errors.d.ts +3 -1
  80. package/dist/src/validation/errors.d.ts.map +1 -1
  81. package/dist/src/validation/errors.js +2 -2
  82. package/dist/src/validation/errors.js.map +1 -1
  83. package/dist/tests/cases/unit/laxSchemaConverter.test.d.ts +2 -0
  84. package/dist/tests/cases/unit/laxSchemaConverter.test.d.ts.map +1 -0
  85. package/dist/tests/cases/unit/laxSchemaConverter.test.js +118 -0
  86. package/dist/tests/cases/unit/laxSchemaConverter.test.js.map +1 -0
  87. package/dist/tests/cases/unit/membershipResolver.test.js +19 -0
  88. package/dist/tests/cases/unit/membershipResolver.test.js.map +1 -1
  89. package/dist/tests/cases/unit/modelValidator.test.d.ts +2 -0
  90. package/dist/tests/cases/unit/modelValidator.test.d.ts.map +1 -0
  91. package/dist/tests/cases/unit/modelValidator.test.js +141 -0
  92. package/dist/tests/cases/unit/modelValidator.test.js.map +1 -0
  93. package/dist/tests/cases/unit/schemas/basic.d.ts +5 -0
  94. package/dist/tests/cases/unit/schemas/basic.d.ts.map +1 -0
  95. package/dist/tests/cases/unit/schemas/basic.js +11 -0
  96. package/dist/tests/cases/unit/schemas/basic.js.map +1 -0
  97. package/dist/tests/cases/unit/schemas/enum.d.ts +7 -0
  98. package/dist/tests/cases/unit/schemas/enum.d.ts.map +1 -0
  99. package/dist/tests/cases/unit/schemas/enum.js +13 -0
  100. package/dist/tests/cases/unit/schemas/enum.js.map +1 -0
  101. package/dist/tests/cases/unit/schemas/relations.d.ts +15 -0
  102. package/dist/tests/cases/unit/schemas/relations.d.ts.map +1 -0
  103. package/dist/tests/cases/unit/schemas/relations.js +27 -0
  104. package/dist/tests/cases/unit/schemas/relations.js.map +1 -0
  105. package/dist/tests/cases/unit/schemas/unique.d.ts +5 -0
  106. package/dist/tests/cases/unit/schemas/unique.d.ts.map +1 -0
  107. package/dist/tests/cases/unit/schemas/unique.js +20 -0
  108. package/dist/tests/cases/unit/schemas/unique.js.map +1 -0
  109. package/dist/tests/cases/unit/tsDefinitionGenerator.test.d.ts +2 -0
  110. package/dist/tests/cases/unit/tsDefinitionGenerator.test.d.ts.map +1 -0
  111. package/dist/tests/cases/unit/tsDefinitionGenerator.test.js +56 -0
  112. package/dist/tests/cases/unit/tsDefinitionGenerator.test.js.map +1 -0
  113. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  114. package/package.json +3 -3
  115. package/src/acl/PredicateDefinitionProcessor.ts +2 -2
  116. package/src/definition-generator/TsDefinitionGenerator.ts +275 -0
  117. package/src/definition-generator/index.ts +1 -0
  118. package/src/index.ts +5 -1
  119. package/src/lax/LaxSchemaConverter.ts +213 -0
  120. package/src/lax/index.ts +9 -0
  121. package/src/lax/schema.ts +62 -0
  122. package/src/model/NamingConventions.ts +53 -0
  123. package/src/model/NamingHelper.ts +11 -1
  124. package/src/model/index.ts +2 -0
  125. package/src/model/modelUtils.ts +76 -71
  126. package/src/model/resolveDefaultColumnType.ts +25 -0
  127. package/src/schemaNormalizer.ts +1 -0
  128. package/src/type-schema/acl.ts +30 -11
  129. package/src/type-schema/index.ts +1 -0
  130. package/src/type-schema/model.ts +1 -1
  131. package/src/type-schema/settings.ts +8 -0
  132. package/src/validation/AclValidator.ts +10 -11
  133. package/src/validation/MembershipResolver.ts +10 -3
  134. package/src/validation/ModelValidator.ts +77 -45
  135. package/src/validation/SchemaValidator.ts +17 -4
  136. package/src/validation/ValidationValidator.ts +4 -4
  137. package/src/validation/errors.ts +25 -2
  138. package/tests/cases/unit/__snapshots__/laxSchemaConverter.test.ts.snap +310 -0
  139. package/tests/cases/unit/laxSchemaConverter.test.ts +137 -0
  140. package/tests/cases/unit/membershipResolver.test.ts +22 -0
  141. package/tests/cases/unit/modelValidator.test.ts +143 -0
  142. package/tests/cases/unit/schemas/.eslintignore +1 -0
  143. package/tests/cases/unit/schemas/basic.ts +5 -0
  144. package/tests/cases/unit/schemas/enum.ts +8 -0
  145. package/tests/cases/unit/schemas/relations.ts +17 -0
  146. package/tests/cases/unit/schemas/unique.ts +6 -0
  147. package/tests/cases/unit/tsDefinitionGenerator.test.ts +31 -0
@@ -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
  }
@@ -72,6 +72,10 @@ export class ModelValidator {
72
72
  this.validateIdentifier(field.name, errors)
73
73
  if (isRelation(field)) {
74
74
  this.validateRelation(partialEntity, field, errors)
75
+ } else {
76
+ if (field.sequence && field.nullable) {
77
+ errors.add('MODEL_INVALID_COLUMN_DEFINITION', 'Column with sequence cannot be nullable.')
78
+ }
75
79
  }
76
80
  }
77
81
 
@@ -80,7 +84,7 @@ export class ModelValidator {
80
84
  const targetEntityName = field.target
81
85
  const targetEntity = this.model.entities[targetEntityName] || undefined
82
86
  if (!targetEntity) {
83
- return errors.add(`Target entity ${targetEntityName} not found`)
87
+ return errors.add('MODEL_UNDEFINED_ENTITY', `Target entity ${targetEntityName} not found`)
84
88
  }
85
89
  if (((it: Model.AnyRelation): it is Model.AnyRelation & Model.OrderableRelation => 'orderBy' in it)(field)) {
86
90
  (field as Model.OrderableRelation).orderBy?.forEach(it => {
@@ -90,13 +94,13 @@ export class ModelValidator {
90
94
  const orderByField = entity.fields[it.path[i]]
91
95
  const pathStr = it.path.slice(0, i + 1).join('.')
92
96
  if (!orderByField) {
93
- 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`)
94
98
  return
95
99
  }
96
100
  if (i + 1 < it.path.length) {
97
101
  const targetEntity = getTargetEntity(this.model, entity, orderByField.name)
98
102
  if (!targetEntity) {
99
- 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`)
100
104
  return
101
105
  }
102
106
  entity = targetEntity
@@ -106,20 +110,20 @@ export class ModelValidator {
106
110
  }
107
111
  if (partialEntity.view) {
108
112
  if (field.type === Model.RelationType.ManyHasMany) {
109
- 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.')
110
114
  }
111
115
  if (
112
116
  !targetEntity.view &&
113
117
  field.type === Model.RelationType.OneHasMany
114
118
  ) {
115
- 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.')
116
120
  }
117
121
  if (
118
122
  !targetEntity.view &&
119
- field.type === Model.RelationType.OneHasOne &&
123
+ field.type === Model.RelationType.OneHasOne &&
120
124
  !('joiningColumn' in field)
121
125
  ) {
122
- 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.')
123
127
  }
124
128
  }
125
129
  if (isInverseRelation(field)) {
@@ -127,70 +131,70 @@ export class ModelValidator {
127
131
  const targetField = targetEntity.fields[ownedBy]
128
132
  const relationDescription = `Target relation ${targetEntityName}::${ownedBy}:`
129
133
  if (!targetField) {
130
- return errors.add(`${relationDescription} not exists`)
134
+ return errors.add('MODEL_UNDEFINED_FIELD', `${relationDescription} not exists`)
131
135
  }
132
136
 
133
137
  if (!isRelation(targetField)) {
134
- return errors.add(`${relationDescription} not a relation`)
138
+ return errors.add('MODEL_RELATION_REQUIRED', `${relationDescription} not a relation`)
135
139
  }
136
140
  if (targetField.target !== entityName) {
137
- 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`)
138
142
  }
139
143
  if (!isOwningRelation(targetField)) {
140
- errors.add(`${relationDescription} not an owning relation`)
144
+ errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} not an owning relation`)
141
145
  return
142
146
  }
143
147
  if (!targetField.inversedBy) {
144
- return errors.add(`${relationDescription} inverse relation is not set`)
148
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} inverse relation is not set`)
145
149
  }
146
150
  if (targetField.inversedBy !== field.name) {
147
- 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`)
148
152
  }
149
153
  if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
150
- 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`)
151
155
  }
152
156
  if (field.type === Model.RelationType.OneHasMany && targetField.type !== Model.RelationType.ManyHasOne) {
153
- 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`)
154
158
  }
155
159
  if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
156
- 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`)
157
161
  }
158
162
  } else {
159
163
  const inversedBy = field.inversedBy
160
164
  if (targetEntity.view) {
161
165
  if ('joiningColumn' in field) {
162
- 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.`)
163
167
  }
164
168
  }
165
169
  if (inversedBy) {
166
170
  const targetField = targetEntity.fields[inversedBy]
167
171
  const relationDescription = `Target relation ${targetEntityName}::${inversedBy}:`
168
172
  if (!targetField) {
169
- return errors.add(`${relationDescription} not exists`)
173
+ return errors.add('MODEL_UNDEFINED_FIELD', `${relationDescription} not exists`)
170
174
  }
171
175
  if (!isRelation(targetField)) {
172
- return errors.add(`${relationDescription} not a relation`)
176
+ return errors.add('MODEL_RELATION_REQUIRED', `${relationDescription} not a relation`)
173
177
  }
174
178
  if (targetField.target !== entityName) {
175
- 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`)
176
180
  }
177
181
  if (!isInverseRelation(targetField)) {
178
- return errors.add(`${relationDescription} not an inverse relation`)
182
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} not an inverse relation`)
179
183
  }
180
184
  if (!targetField.ownedBy) {
181
- return errors.add(`${relationDescription} owning relation is not set`)
185
+ return errors.add('MODEL_INVALID_RELATION_DEFINITION', `${relationDescription} owning relation is not set`)
182
186
  }
183
187
  if (targetField.ownedBy !== field.name) {
184
- 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`)
185
189
  }
186
190
  if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
187
- 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`)
188
192
  }
189
193
  if (field.type === Model.RelationType.ManyHasOne && targetField.type !== Model.RelationType.OneHasMany) {
190
- 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`)
191
195
  }
192
196
  if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
193
- 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`)
194
198
  }
195
199
  }
196
200
  }
@@ -198,51 +202,60 @@ export class ModelValidator {
198
202
 
199
203
  private validateIdentifier(value: string, errorBuilder: ErrorBuilder) {
200
204
  if (!value.match(IDENTIFIER_PATTERN)) {
201
- return errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
205
+ return errorBuilder.add('MODEL_INVALID_IDENTIFIER', `${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
202
206
  }
203
207
  if (RESERVED_WORDS.includes(value)) {
204
- errorBuilder.add(`${value} is reserved word`)
208
+ errorBuilder.add('MODEL_INVALID_IDENTIFIER', `${value} is reserved word`)
205
209
  }
206
210
  }
207
211
 
208
212
  private validateCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) {
209
- const tableNames: Record<string, string> = {}
213
+ const relationNames: Record<string, string> = {}
210
214
  const aliasedTypes = new Map<string, Model.ColumnType>()
215
+ const entityNames = new Set(entities.map(it => it.name))
211
216
  for (const entity of entities) {
212
- const description = `entity ${entity.name}`
213
- if (tableNames[entity.tableName]) {
217
+ const description = `table name ${entity.tableName} of entity ${entity.name}`
218
+ if (relationNames[entity.tableName]) {
214
219
  errorBuilder
215
220
  .for(entity.name)
216
- .add(`Table name ${entity.tableName} of ${description} collides with a table name of ${tableNames[entity.tableName]}`)
221
+ .add('MODEL_NAME_COLLISION', `${description} collides with a ${relationNames[entity.tableName]}`)
217
222
  } else {
218
- tableNames[entity.tableName] = description
223
+ relationNames[entity.tableName] = description
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
+ }
219
232
  }
220
233
  }
221
234
  for (const entity of entities) {
222
235
  const entityErrorBuilder = errorBuilder.for(entity.name)
223
236
  acceptEveryFieldVisitor(this.model, entity, {
224
- visitManyHasManyOwning: (entity, relation) => {
237
+ visitManyHasManyOwning: ({ entity, relation }) => {
225
238
  const joiningTable = relation.joiningTable
226
- const description = `relation ${entity.name}::${relation.name}`
227
- if (tableNames[joiningTable.tableName]) {
239
+ const description = `joining table name ${joiningTable.tableName} of relation ${entity.name}::${relation.name}`
240
+ if (relationNames[joiningTable.tableName]) {
228
241
  entityErrorBuilder
229
242
  .for(relation.name)
230
- .add(
231
- `Joining table name ${joiningTable.tableName} of ${description} collides with a table name of ${tableNames[joiningTable.tableName]}.` +
243
+ .add('MODEL_NAME_COLLISION',
244
+ `${description} collides with a ${relationNames[joiningTable.tableName]}.` +
232
245
  'Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.',
233
246
  )
234
247
  } else {
235
- tableNames[joiningTable.tableName] = description
248
+ relationNames[joiningTable.tableName] = description
236
249
  }
237
250
  },
238
- visitColumn: (entity, column) => {
251
+ visitColumn: ({ entity, column }) => {
239
252
  if (!column.typeAlias) {
240
253
  return
241
254
  }
242
255
  if (aliasedTypes.has(column.typeAlias) && aliasedTypes.get(column.typeAlias) !== column.type) {
243
256
  errorBuilder
244
257
  .for(column.name)
245
- .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}`)
246
259
  }
247
260
  aliasedTypes.set(column.typeAlias, column.type)
248
261
  },
@@ -253,6 +266,25 @@ export class ModelValidator {
253
266
  visitManyHasOne: () => {},
254
267
  })
255
268
  }
269
+ for (const entity of entities) {
270
+ const entityErrorBuilder = errorBuilder.for(entity.name)
271
+ for (const index of Object.values(entity.indexes)) {
272
+ const description = `index name ${index.name} of entity ${entity.name}`
273
+ if (relationNames[index.name]) {
274
+ entityErrorBuilder.add('MODEL_NAME_COLLISION', `${description} collides with ${relationNames[index.name]}`)
275
+ } else {
276
+ relationNames[index.name] = description
277
+ }
278
+ }
279
+ for (const unique of Object.values(entity.unique)) {
280
+ const description = `unique index name ${unique.name} of entity ${entity.name}`
281
+ if (relationNames[unique.name]) {
282
+ entityErrorBuilder.add('MODEL_NAME_COLLISION', `${description} collides with ${relationNames[unique.name]}`)
283
+ } else {
284
+ relationNames[unique.name] = description
285
+ }
286
+ }
287
+ }
256
288
  }
257
289
  }
258
290
 
@@ -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
  }