@contember/schema-utils 1.1.0-alpha.5 → 1.1.0-alpha.6
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.
- package/dist/src/acl/AllowAllPermissionFactory.d.ts.map +1 -1
- package/dist/src/acl/AllowAllPermissionFactory.js.map +1 -1
- package/dist/src/acl/index.js +5 -1
- package/dist/src/acl/index.js.map +1 -1
- package/dist/src/dataUtils.d.ts +2 -2
- package/dist/src/dataUtils.d.ts.map +1 -1
- package/dist/src/dataUtils.js +1 -1
- package/dist/src/dataUtils.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +25 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/model/NamingHelper.d.ts +1 -1
- package/dist/src/model/NamingHelper.d.ts.map +1 -1
- package/dist/src/model/NamingHelper.js.map +1 -1
- package/dist/src/model/index.js +5 -1
- package/dist/src/model/index.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/type-schema/acl.d.ts +8 -0
- package/dist/src/type-schema/acl.d.ts.map +1 -0
- package/dist/src/type-schema/acl.js +88 -0
- package/dist/src/type-schema/acl.js.map +1 -0
- package/dist/src/type-schema/index.d.ts +4 -0
- package/dist/src/type-schema/index.d.ts.map +1 -0
- package/dist/src/type-schema/index.js +20 -0
- package/dist/src/type-schema/index.js.map +1 -0
- package/dist/src/type-schema/model.d.ts +30 -0
- package/dist/src/type-schema/model.d.ts.map +1 -0
- package/dist/src/type-schema/model.js +148 -0
- package/dist/src/type-schema/model.js.map +1 -0
- package/dist/src/type-schema/validation.d.ts +15 -0
- package/dist/src/type-schema/validation.d.ts.map +1 -0
- package/dist/src/type-schema/validation.js +95 -0
- package/dist/src/type-schema/validation.js.map +1 -0
- package/dist/src/utils/index.js +5 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/validation/AclValidator.d.ts +1 -2
- package/dist/src/validation/AclValidator.d.ts.map +1 -1
- package/dist/src/validation/AclValidator.js +22 -197
- package/dist/src/validation/AclValidator.js.map +1 -1
- package/dist/src/validation/ModelValidator.d.ts +1 -1
- package/dist/src/validation/ModelValidator.d.ts.map +1 -1
- package/dist/src/validation/ModelValidator.js +48 -219
- package/dist/src/validation/ModelValidator.js.map +1 -1
- package/dist/src/validation/SchemaValidator.d.ts.map +1 -1
- package/dist/src/validation/SchemaValidator.js +5 -19
- package/dist/src/validation/SchemaValidator.js.map +1 -1
- package/dist/src/validation/ValidationValidator.d.ts +1 -3
- package/dist/src/validation/ValidationValidator.d.ts.map +1 -1
- package/dist/src/validation/ValidationValidator.js +28 -201
- package/dist/src/validation/ValidationValidator.js.map +1 -1
- package/dist/src/validation/index.d.ts +2 -0
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +7 -1
- package/dist/src/validation/index.js.map +1 -1
- package/package.json +3 -2
- package/src/acl/AllowAllPermissionFactory.ts +2 -2
- package/src/dataUtils.ts +3 -3
- package/src/index.ts +8 -0
- package/src/model/NamingHelper.ts +1 -1
- package/src/tsconfig.json +3 -0
- package/src/type-schema/acl.ts +100 -0
- package/src/type-schema/index.ts +3 -0
- package/src/type-schema/model.ts +173 -0
- package/src/type-schema/validation.ts +100 -0
- package/src/validation/AclValidator.ts +42 -239
- package/src/validation/ModelValidator.ts +63 -263
- package/src/validation/SchemaValidator.ts +6 -21
- package/src/validation/ValidationValidator.ts +41 -222
- package/src/validation/index.ts +2 -0
- package/dist/src/validation/utils.d.ts +0 -18
- package/dist/src/validation/utils.d.ts.map +0 -1
- package/dist/src/validation/utils.js +0 -36
- package/dist/src/validation/utils.js.map +0 -1
- package/src/validation/utils.ts +0 -45
|
@@ -1,245 +1,88 @@
|
|
|
1
1
|
import { Model } from '@contember/schema'
|
|
2
2
|
import { ErrorBuilder, ValidationError } from './errors'
|
|
3
|
-
import { everyIs, isObject, UnknownObject } from './utils'
|
|
4
3
|
import { acceptEveryFieldVisitor, getTargetEntity, isInverseRelation, isOwningRelation } from '../model'
|
|
5
4
|
|
|
6
5
|
const IDENTIFIER_PATTERN = /^[_a-zA-Z][_a-zA-Z0-9]*$/
|
|
7
6
|
const RESERVED_WORDS = ['and', 'or', 'not']
|
|
8
7
|
|
|
8
|
+
|
|
9
9
|
export class ModelValidator {
|
|
10
10
|
constructor(private readonly model: Model.Schema) {}
|
|
11
11
|
|
|
12
|
-
public validate():
|
|
13
|
-
const model = this.model as unknown
|
|
12
|
+
public validate(): ValidationError[] {
|
|
14
13
|
const errorBuilder = new ErrorBuilder([], [])
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} else {
|
|
20
|
-
const enums = this.validateEnums(model.enums, errorBuilder.for('enums'))
|
|
21
|
-
const entities = this.validateEntities(model.entities, errorBuilder.for('entities'))
|
|
22
|
-
validModel = { enums, entities }
|
|
23
|
-
}
|
|
24
|
-
this.validateCollisions(Object.values(validModel.entities), errorBuilder.for('entities'))
|
|
25
|
-
return [validModel, errorBuilder.errors]
|
|
14
|
+
this.validateEnums(this.model.enums, errorBuilder.for('enums'))
|
|
15
|
+
this.validateEntities(this.model.entities, errorBuilder.for('entities'))
|
|
16
|
+
this.validateCollisions(Object.values(this.model.entities), errorBuilder.for('entities'))
|
|
17
|
+
return errorBuilder.errors
|
|
26
18
|
}
|
|
27
19
|
|
|
28
|
-
private validateEnums(enums:
|
|
29
|
-
|
|
30
|
-
errors.add('Enums definitions are missing')
|
|
31
|
-
return {}
|
|
32
|
-
}
|
|
33
|
-
if (!isObject(enums)) {
|
|
34
|
-
errors.add('Enums must be an object')
|
|
35
|
-
return {}
|
|
36
|
-
}
|
|
37
|
-
const validEnums: Model.Schema['enums'] = {}
|
|
38
|
-
enums: for (const [enumName, enumValues] of Object.entries(enums)) {
|
|
39
|
-
if (!Array.isArray(enumValues) || !everyIs(enumValues, (it): it is string => typeof it === 'string')) {
|
|
40
|
-
errors.for(enumName).add('Enum values must be an array of strings')
|
|
41
|
-
continue
|
|
42
|
-
}
|
|
43
|
-
if (enumValues.length === 0) {
|
|
44
|
-
errors.for(enumName).add('Enum must have at least one value')
|
|
45
|
-
}
|
|
20
|
+
private validateEnums(enums: Model.Schema['enums'], errors: ErrorBuilder): void {
|
|
21
|
+
for (const [enumName, enumValues] of Object.entries(enums)) {
|
|
46
22
|
for (const value of enumValues) {
|
|
47
|
-
const valueErrors = errors.for(value)
|
|
48
|
-
|
|
49
|
-
continue enums
|
|
50
|
-
}
|
|
23
|
+
const valueErrors = errors.for(enumName, value)
|
|
24
|
+
this.validateIdentifier(value, valueErrors)
|
|
51
25
|
}
|
|
52
|
-
validEnums[enumName] = enumValues
|
|
53
26
|
}
|
|
54
|
-
return validEnums
|
|
55
27
|
}
|
|
56
28
|
|
|
57
|
-
private validateEntities(entities:
|
|
58
|
-
if (!entities) {
|
|
59
|
-
errors.add('Entities definitions are missing')
|
|
60
|
-
return {}
|
|
61
|
-
}
|
|
62
|
-
if (!isObject(entities)) {
|
|
63
|
-
errors.add('Entities must be an object')
|
|
64
|
-
return {}
|
|
65
|
-
}
|
|
66
|
-
const validEntities: Model.Schema['entities'] = {}
|
|
29
|
+
private validateEntities(entities: Model.Schema['entities'], errors: ErrorBuilder): void {
|
|
67
30
|
for (const [entityName, entity] of Object.entries(entities)) {
|
|
68
31
|
const entityErrors = errors.for(entityName)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
continue
|
|
32
|
+
if (entity.name !== entityName) {
|
|
33
|
+
entityErrors.add(`Entity name "${entity.name}" does not match the name in a map "${entityName}"`)
|
|
72
34
|
}
|
|
73
|
-
|
|
74
|
-
entityErrors.add(`Entity name "${validEntity.name}" does not match the name in a map "${entityName}"`)
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
validEntities[entityName] = validEntity
|
|
35
|
+
this.validateEntity(entity, entityErrors)
|
|
79
36
|
}
|
|
80
|
-
return validEntities
|
|
81
37
|
}
|
|
82
38
|
|
|
83
|
-
private validateEntity(entity:
|
|
84
|
-
|
|
85
|
-
errors.add('Entity must be an object')
|
|
86
|
-
return undefined
|
|
87
|
-
}
|
|
88
|
-
if (typeof entity.name !== 'string') {
|
|
89
|
-
errors.for('name').add('Entity name must be a string')
|
|
90
|
-
return undefined
|
|
91
|
-
}
|
|
92
|
-
if (!this.validateIdentifier(entity.name, errors)) {
|
|
93
|
-
return undefined
|
|
94
|
-
}
|
|
95
|
-
if (typeof entity.primary !== 'string') {
|
|
96
|
-
errors.for('primary').add('Primary must be a string')
|
|
97
|
-
return undefined
|
|
98
|
-
}
|
|
99
|
-
if (typeof entity.primaryColumn !== 'string') {
|
|
100
|
-
errors.for('primaryColumn').add('Primary column must be a string')
|
|
101
|
-
return undefined
|
|
102
|
-
}
|
|
103
|
-
if (typeof entity.tableName !== 'string') {
|
|
104
|
-
errors.for('tableName').add('Table name must be a string')
|
|
105
|
-
return undefined
|
|
106
|
-
}
|
|
107
|
-
const fields = entity.fields
|
|
108
|
-
if (!isObject(fields)) {
|
|
109
|
-
errors.add('Fields must be an object')
|
|
110
|
-
return undefined
|
|
111
|
-
}
|
|
112
|
-
const partialEntity: Model.Entity = {
|
|
113
|
-
name: entity.name,
|
|
114
|
-
primary: entity.primary,
|
|
115
|
-
primaryColumn: entity.primaryColumn,
|
|
116
|
-
tableName: entity.tableName,
|
|
117
|
-
fields: {},
|
|
118
|
-
unique: {},
|
|
119
|
-
}
|
|
39
|
+
private validateEntity(entity: Model.Entity, errors: ErrorBuilder): void {
|
|
40
|
+
this.validateIdentifier(entity.name, errors)
|
|
120
41
|
|
|
121
|
-
|
|
122
|
-
const viewTmp = entity.view
|
|
123
|
-
if (!isObject(viewTmp)) {
|
|
124
|
-
errors.for('view').add('View must be an object')
|
|
125
|
-
return undefined
|
|
126
|
-
}
|
|
127
|
-
if (typeof viewTmp.sql !== 'string') {
|
|
128
|
-
errors.for('view', 'sql').add('View SQL must be a string')
|
|
129
|
-
return undefined
|
|
130
|
-
}
|
|
131
|
-
partialEntity.view = entity.view as Model.View
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const validFields: Model.Entity['fields'] = {}
|
|
135
|
-
for (const [fieldName, field] of Object.entries(fields)) {
|
|
42
|
+
for (const [fieldName, field] of Object.entries(entity.fields)) {
|
|
136
43
|
const fieldErrors = errors.for(fieldName)
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
44
|
+
this.validateField(entity, field, fieldErrors)
|
|
45
|
+
if (field.name !== fieldName) {
|
|
46
|
+
fieldErrors.add(`Field name "${field.name}" does not match the name in a map "${fieldName}"`)
|
|
140
47
|
}
|
|
141
|
-
if (validField.name !== fieldName) {
|
|
142
|
-
fieldErrors.add(`Field name "${validField.name}" does not match the name in a map "${fieldName}"`)
|
|
143
|
-
continue
|
|
144
|
-
}
|
|
145
|
-
validFields[fieldName] = validField
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const uniqueConstraints = entity.unique
|
|
149
|
-
if (!isObject(uniqueConstraints)) {
|
|
150
|
-
errors.add('Unique constraints must be an object')
|
|
151
|
-
return undefined
|
|
152
48
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
new Set(Object.keys(
|
|
49
|
+
this.validateUniqueConstraints(
|
|
50
|
+
entity.unique,
|
|
51
|
+
new Set(Object.keys(entity.fields)),
|
|
156
52
|
errors.for('unique'),
|
|
157
53
|
)
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
...partialEntity,
|
|
161
|
-
fields: validFields,
|
|
162
|
-
unique: validUniqueConstraints,
|
|
163
|
-
}
|
|
164
54
|
}
|
|
165
55
|
|
|
166
|
-
private validateUniqueConstraints(
|
|
167
|
-
uniqueConstraints
|
|
168
|
-
fields: Set<string>,
|
|
169
|
-
errors: ErrorBuilder,
|
|
170
|
-
): Model.Entity['unique'] {
|
|
171
|
-
if (!isObject(uniqueConstraints)) {
|
|
172
|
-
errors.add('Unique constraints must be an object')
|
|
173
|
-
return {}
|
|
174
|
-
}
|
|
175
|
-
const validUniqueConstraints: Model.Entity['unique'] = {}
|
|
176
|
-
constraint: for (const [constraintName, constraint] of Object.entries(uniqueConstraints)) {
|
|
56
|
+
private validateUniqueConstraints(uniqueConstraints: Model.Entity['unique'], fields: Set<string>, errors: ErrorBuilder): void {
|
|
57
|
+
for (const [constraintName, constraint] of Object.entries(uniqueConstraints)) {
|
|
177
58
|
const uniqueErrors = errors.for(constraintName)
|
|
178
|
-
if (!isObject(constraint)) {
|
|
179
|
-
uniqueErrors.add('Unique constraint must be an object')
|
|
180
|
-
continue
|
|
181
|
-
}
|
|
182
|
-
if (typeof constraint.name !== 'string') {
|
|
183
|
-
uniqueErrors.add('Constraint name is not defined')
|
|
184
|
-
continue
|
|
185
|
-
}
|
|
186
59
|
if (constraint.name !== constraintName) {
|
|
187
60
|
uniqueErrors.add(`Constraint name ${constraint.name} does not match the name in a map "${constraintName}"`)
|
|
188
61
|
continue
|
|
189
62
|
}
|
|
190
|
-
if (
|
|
191
|
-
!Array.isArray(constraint.fields) ||
|
|
192
|
-
!everyIs(constraint.fields, (it): it is string => typeof it === 'string')
|
|
193
|
-
) {
|
|
194
|
-
uniqueErrors.add('Every field must be a string')
|
|
195
|
-
continue
|
|
196
|
-
}
|
|
197
63
|
for (const field of constraint.fields) {
|
|
198
64
|
if (!fields.has(field)) {
|
|
199
65
|
uniqueErrors.add(`Referenced field ${field} in a constraint does not exists`)
|
|
200
|
-
continue constraint
|
|
201
66
|
}
|
|
202
67
|
}
|
|
203
|
-
validUniqueConstraints[constraintName] = { name: constraint.name, fields: constraint.fields }
|
|
204
68
|
}
|
|
205
|
-
return validUniqueConstraints
|
|
206
69
|
}
|
|
207
70
|
|
|
208
|
-
private validateField(partialEntity: Model.Entity, field:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
71
|
+
private validateField(partialEntity: Model.Entity, field: Model.AnyField, errors: ErrorBuilder): void {
|
|
72
|
+
this.validateIdentifier(field.name, errors)
|
|
73
|
+
if (isRelation(field)) {
|
|
74
|
+
this.validateRelation(partialEntity, field, errors)
|
|
212
75
|
}
|
|
213
|
-
if (typeof field.type !== 'string') {
|
|
214
|
-
errors.add('Field type must be a string')
|
|
215
|
-
return undefined
|
|
216
|
-
}
|
|
217
|
-
if (typeof field.name !== 'string') {
|
|
218
|
-
errors.for('name').add('Field name must be a string')
|
|
219
|
-
return undefined
|
|
220
|
-
}
|
|
221
|
-
if (!this.validateIdentifier(field.name, errors)) {
|
|
222
|
-
return undefined
|
|
223
|
-
}
|
|
224
|
-
if (isRelation(field as any)) {
|
|
225
|
-
return this.validateRelation(partialEntity, field, errors)
|
|
226
|
-
}
|
|
227
|
-
return field as unknown as Model.AnyColumn
|
|
228
76
|
}
|
|
229
77
|
|
|
230
|
-
private validateRelation(
|
|
231
|
-
partialEntity: Model.Entity,
|
|
232
|
-
field: UnknownObject,
|
|
233
|
-
errors: ErrorBuilder,
|
|
234
|
-
): Model.AnyRelation | undefined {
|
|
78
|
+
private validateRelation(partialEntity: Model.Entity, field: Model.AnyRelation, errors: ErrorBuilder): void {
|
|
235
79
|
const entityName = partialEntity.name
|
|
236
|
-
const targetEntityName = field.target
|
|
80
|
+
const targetEntityName = field.target
|
|
237
81
|
const targetEntity = this.model.entities[targetEntityName] || undefined
|
|
238
82
|
if (!targetEntity) {
|
|
239
|
-
errors.add(`Target entity ${targetEntityName} not found`)
|
|
240
|
-
return undefined
|
|
83
|
+
return errors.add(`Target entity ${targetEntityName} not found`)
|
|
241
84
|
}
|
|
242
|
-
if (((it: Model.AnyRelation): it is Model.AnyRelation & Model.OrderableRelation => 'orderBy' in it)(field
|
|
85
|
+
if (((it: Model.AnyRelation): it is Model.AnyRelation & Model.OrderableRelation => 'orderBy' in it)(field)) {
|
|
243
86
|
(field as Model.OrderableRelation).orderBy?.forEach(it => {
|
|
244
87
|
let entity = targetEntity
|
|
245
88
|
|
|
@@ -262,133 +105,95 @@ export class ModelValidator {
|
|
|
262
105
|
})
|
|
263
106
|
}
|
|
264
107
|
if (partialEntity.view) {
|
|
265
|
-
const type = (field as any as Model.Relation).type
|
|
266
108
|
if (
|
|
267
|
-
type === Model.RelationType.ManyHasMany ||
|
|
268
|
-
type === Model.RelationType.OneHasMany ||
|
|
269
|
-
(type === Model.RelationType.OneHasOne && !('joiningColumn' in field))
|
|
109
|
+
field.type === Model.RelationType.ManyHasMany ||
|
|
110
|
+
field.type === Model.RelationType.OneHasMany ||
|
|
111
|
+
(field.type === Model.RelationType.OneHasOne && !('joiningColumn' in field))
|
|
270
112
|
) {
|
|
271
|
-
errors.add(
|
|
272
|
-
'This relation type is not allowed on a view entity. Only one-has-one owning and many-has one are allowed.',
|
|
273
|
-
)
|
|
113
|
+
return errors.add('This relation type is not allowed on a view entity. Only one-has-one owning and many-has one are allowed.')
|
|
274
114
|
}
|
|
275
115
|
}
|
|
276
|
-
if (isInverseRelation(field
|
|
277
|
-
// todo
|
|
116
|
+
if (isInverseRelation(field)) {
|
|
278
117
|
const ownedBy = field.ownedBy
|
|
279
|
-
if (typeof ownedBy !== 'string') {
|
|
280
|
-
errors.add('Owned by is not defined')
|
|
281
|
-
return undefined
|
|
282
|
-
}
|
|
283
118
|
const targetField = targetEntity.fields[ownedBy]
|
|
284
119
|
const relationDescription = `Target relation ${targetEntityName}::${ownedBy}:`
|
|
285
120
|
if (!targetField) {
|
|
286
|
-
errors.add(`${relationDescription} not exists`)
|
|
287
|
-
return undefined
|
|
121
|
+
return errors.add(`${relationDescription} not exists`)
|
|
288
122
|
}
|
|
123
|
+
|
|
289
124
|
if (!isRelation(targetField)) {
|
|
290
|
-
errors.add(`${relationDescription} not a relation`)
|
|
291
|
-
return undefined
|
|
125
|
+
return errors.add(`${relationDescription} not a relation`)
|
|
292
126
|
}
|
|
293
127
|
if (targetField.target !== entityName) {
|
|
294
|
-
errors.add(
|
|
295
|
-
`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`,
|
|
296
|
-
)
|
|
297
|
-
return undefined
|
|
128
|
+
return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
|
|
298
129
|
}
|
|
299
130
|
if (!isOwningRelation(targetField)) {
|
|
300
131
|
errors.add(`${relationDescription} not an owning relation`)
|
|
301
|
-
return
|
|
132
|
+
return
|
|
302
133
|
}
|
|
303
134
|
if (!targetField.inversedBy) {
|
|
304
|
-
errors.add(`${relationDescription} inverse relation is not set`)
|
|
305
|
-
return undefined
|
|
135
|
+
return errors.add(`${relationDescription} inverse relation is not set`)
|
|
306
136
|
}
|
|
307
137
|
if (targetField.inversedBy !== field.name) {
|
|
308
|
-
errors.add(
|
|
309
|
-
`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`,
|
|
310
|
-
)
|
|
311
|
-
return undefined
|
|
138
|
+
return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`)
|
|
312
139
|
}
|
|
313
140
|
if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
|
|
314
|
-
errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
|
|
315
|
-
return undefined
|
|
141
|
+
return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
|
|
316
142
|
}
|
|
317
143
|
if (field.type === Model.RelationType.OneHasMany && targetField.type !== Model.RelationType.ManyHasOne) {
|
|
318
|
-
errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
|
|
319
|
-
return undefined
|
|
144
|
+
return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
|
|
320
145
|
}
|
|
321
146
|
if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
|
|
322
|
-
errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
|
|
323
|
-
return undefined
|
|
147
|
+
return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
|
|
324
148
|
}
|
|
325
149
|
} else {
|
|
326
|
-
const inversedBy = field.inversedBy
|
|
150
|
+
const inversedBy = field.inversedBy
|
|
327
151
|
if (targetEntity.view) {
|
|
328
152
|
if ('joiningColumn' in field) {
|
|
329
|
-
errors.add(
|
|
330
|
-
`View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`,
|
|
331
|
-
)
|
|
153
|
+
return errors.add(`View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`)
|
|
332
154
|
}
|
|
333
155
|
}
|
|
334
156
|
if (inversedBy) {
|
|
335
157
|
const targetField = targetEntity.fields[inversedBy]
|
|
336
158
|
const relationDescription = `Target relation ${targetEntityName}::${inversedBy}:`
|
|
337
159
|
if (!targetField) {
|
|
338
|
-
errors.add(`${relationDescription} not exists`)
|
|
339
|
-
return undefined
|
|
160
|
+
return errors.add(`${relationDescription} not exists`)
|
|
340
161
|
}
|
|
341
162
|
if (!isRelation(targetField)) {
|
|
342
|
-
errors.add(`${relationDescription} not a relation`)
|
|
343
|
-
return undefined
|
|
163
|
+
return errors.add(`${relationDescription} not a relation`)
|
|
344
164
|
}
|
|
345
165
|
if (targetField.target !== entityName) {
|
|
346
|
-
errors.add(
|
|
347
|
-
`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`,
|
|
348
|
-
)
|
|
349
|
-
return undefined
|
|
166
|
+
return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
|
|
350
167
|
}
|
|
351
168
|
if (!isInverseRelation(targetField)) {
|
|
352
|
-
errors.add(`${relationDescription} not an inverse relation`)
|
|
353
|
-
return undefined
|
|
169
|
+
return errors.add(`${relationDescription} not an inverse relation`)
|
|
354
170
|
}
|
|
355
171
|
if (!targetField.ownedBy) {
|
|
356
|
-
errors.add(`${relationDescription} owning relation is not set`)
|
|
357
|
-
return undefined
|
|
172
|
+
return errors.add(`${relationDescription} owning relation is not set`)
|
|
358
173
|
}
|
|
359
174
|
if (targetField.ownedBy !== field.name) {
|
|
360
|
-
errors.add(
|
|
361
|
-
`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`,
|
|
362
|
-
)
|
|
363
|
-
return undefined
|
|
175
|
+
return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`)
|
|
364
176
|
}
|
|
365
177
|
if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
|
|
366
|
-
errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
|
|
367
|
-
return undefined
|
|
178
|
+
return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
|
|
368
179
|
}
|
|
369
180
|
if (field.type === Model.RelationType.ManyHasOne && targetField.type !== Model.RelationType.OneHasMany) {
|
|
370
|
-
errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
|
|
371
|
-
return undefined
|
|
181
|
+
return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
|
|
372
182
|
}
|
|
373
183
|
if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
|
|
374
|
-
errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
|
|
375
|
-
return undefined
|
|
184
|
+
return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
|
|
376
185
|
}
|
|
377
186
|
}
|
|
378
187
|
}
|
|
379
|
-
return field as any as Model.AnyRelation // todo
|
|
380
188
|
}
|
|
381
189
|
|
|
382
190
|
private validateIdentifier(value: string, errorBuilder: ErrorBuilder) {
|
|
383
191
|
if (!value.match(IDENTIFIER_PATTERN)) {
|
|
384
|
-
errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
|
|
385
|
-
return false
|
|
192
|
+
return errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
|
|
386
193
|
}
|
|
387
194
|
if (RESERVED_WORDS.includes(value)) {
|
|
388
195
|
errorBuilder.add(`${value} is reserved word`)
|
|
389
|
-
return false
|
|
390
196
|
}
|
|
391
|
-
return true
|
|
392
197
|
}
|
|
393
198
|
|
|
394
199
|
private validateCollisions(entities: Model.Entity[], errorBuilder: ErrorBuilder) {
|
|
@@ -399,11 +204,7 @@ export class ModelValidator {
|
|
|
399
204
|
if (tableNames[entity.tableName]) {
|
|
400
205
|
errorBuilder
|
|
401
206
|
.for(entity.name)
|
|
402
|
-
.add(
|
|
403
|
-
`Table name ${entity.tableName} of ${description} collides with a table name of ${
|
|
404
|
-
tableNames[entity.tableName]
|
|
405
|
-
}`,
|
|
406
|
-
)
|
|
207
|
+
.add(`Table name ${entity.tableName} of ${description} collides with a table name of ${tableNames[entity.tableName]}`)
|
|
407
208
|
} else {
|
|
408
209
|
tableNames[entity.tableName] = description
|
|
409
210
|
}
|
|
@@ -418,9 +219,8 @@ export class ModelValidator {
|
|
|
418
219
|
entityErrorBuilder
|
|
419
220
|
.for(relation.name)
|
|
420
221
|
.add(
|
|
421
|
-
`Joining table name ${joiningTable.tableName} of ${description} collides with a table name of ${
|
|
422
|
-
|
|
423
|
-
}. Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.`,
|
|
222
|
+
`Joining table name ${joiningTable.tableName} of ${description} collides with a table name of ${tableNames[joiningTable.tableName]}.` +
|
|
223
|
+
'Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.',
|
|
424
224
|
)
|
|
425
225
|
} else {
|
|
426
226
|
tableNames[joiningTable.tableName] = description
|
|
@@ -2,35 +2,20 @@ import { Schema } from '@contember/schema'
|
|
|
2
2
|
import { ValidationError } from './errors'
|
|
3
3
|
import { AclValidator } from './AclValidator'
|
|
4
4
|
import { ModelValidator } from './ModelValidator'
|
|
5
|
-
import { isDeepStrictEqual } from 'util'
|
|
6
5
|
import { ValidationValidator } from './ValidationValidator'
|
|
7
|
-
import { deepCompare } from '../utils'
|
|
8
6
|
|
|
9
7
|
export class SchemaValidator {
|
|
10
8
|
public static validate(schema: Schema): ValidationError[] {
|
|
9
|
+
const modelValidator = new ModelValidator(schema.model)
|
|
10
|
+
const modelErrors = modelValidator.validate()
|
|
11
|
+
|
|
11
12
|
const aclValidator = new AclValidator(schema.model)
|
|
12
|
-
const
|
|
13
|
+
const aclErrors = aclValidator.validate(schema.acl)
|
|
13
14
|
|
|
14
|
-
const modelValidator = new ModelValidator(schema.model)
|
|
15
|
-
const [model, modelErrors] = modelValidator.validate()
|
|
16
15
|
|
|
17
16
|
const validationValidator = new ValidationValidator(schema.model)
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const validSchema = { ...schema, acl, model, validation }
|
|
17
|
+
const validationErrors = validationValidator.validate(schema.validation)
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
if (errors.length === 0) {
|
|
24
|
-
const errors = deepCompare(validSchema, schema, [])
|
|
25
|
-
if (errors.length) {
|
|
26
|
-
let message = 'There is something wrong with a schema validator:'
|
|
27
|
-
for (const err of errors) {
|
|
28
|
-
message += '\n\t' + err.path.join('.') + ': ' + err.message
|
|
29
|
-
}
|
|
30
|
-
message += '\n\nPlease fill a bug report'
|
|
31
|
-
throw new Error(message)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return errors
|
|
19
|
+
return [...aclErrors, ...modelErrors, ...validationErrors]
|
|
35
20
|
}
|
|
36
21
|
}
|