@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.
- package/dist/src/acl/PredicateDefinitionProcessor.js +2 -2
- package/dist/src/acl/PredicateDefinitionProcessor.js.map +1 -1
- package/dist/src/definition-generator/TsDefinitionGenerator.d.ts +26 -0
- package/dist/src/definition-generator/TsDefinitionGenerator.d.ts.map +1 -0
- package/dist/src/definition-generator/TsDefinitionGenerator.js +246 -0
- package/dist/src/definition-generator/TsDefinitionGenerator.js.map +1 -0
- package/dist/src/definition-generator/index.d.ts +2 -0
- package/dist/src/definition-generator/index.d.ts.map +1 -0
- package/dist/src/definition-generator/index.js +18 -0
- package/dist/src/definition-generator/index.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/lax/LaxSchemaConverter.d.ts +10 -0
- package/dist/src/lax/LaxSchemaConverter.d.ts.map +1 -0
- package/dist/src/lax/LaxSchemaConverter.js +204 -0
- package/dist/src/lax/LaxSchemaConverter.js.map +1 -0
- package/dist/src/lax/index.d.ts +3 -0
- package/dist/src/lax/index.d.ts.map +1 -0
- package/dist/src/lax/index.js +6 -0
- package/dist/src/lax/index.js.map +1 -0
- package/dist/src/lax/schema.d.ts +284 -0
- package/dist/src/lax/schema.d.ts.map +1 -0
- package/dist/src/lax/schema.js +67 -0
- package/dist/src/lax/schema.js.map +1 -0
- package/dist/src/model/NamingConventions.d.ts +17 -0
- package/dist/src/model/NamingConventions.d.ts.map +1 -0
- package/dist/src/model/NamingConventions.js +29 -0
- package/dist/src/model/NamingConventions.js.map +1 -0
- package/dist/src/model/NamingHelper.d.ts +2 -0
- package/dist/src/model/NamingHelper.d.ts.map +1 -1
- package/dist/src/model/NamingHelper.js +10 -2
- package/dist/src/model/NamingHelper.js.map +1 -1
- package/dist/src/model/index.d.ts +2 -0
- package/dist/src/model/index.d.ts.map +1 -1
- package/dist/src/model/index.js +2 -0
- package/dist/src/model/index.js.map +1 -1
- package/dist/src/model/modelUtils.d.ts +1 -0
- package/dist/src/model/modelUtils.d.ts.map +1 -1
- package/dist/src/model/modelUtils.js +69 -46
- package/dist/src/model/modelUtils.js.map +1 -1
- package/dist/src/model/resolveDefaultColumnType.d.ts +3 -0
- package/dist/src/model/resolveDefaultColumnType.d.ts.map +1 -0
- package/dist/src/model/resolveDefaultColumnType.js +29 -0
- package/dist/src/model/resolveDefaultColumnType.js.map +1 -0
- package/dist/src/schemaNormalizer.d.ts.map +1 -1
- package/dist/src/schemaNormalizer.js +1 -0
- package/dist/src/schemaNormalizer.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/type-schema/acl.d.ts.map +1 -1
- package/dist/src/type-schema/acl.js +15 -4
- package/dist/src/type-schema/acl.js.map +1 -1
- package/dist/src/type-schema/index.d.ts +1 -0
- package/dist/src/type-schema/index.d.ts.map +1 -1
- package/dist/src/type-schema/index.js +1 -0
- package/dist/src/type-schema/index.js.map +1 -1
- package/dist/src/type-schema/model.js +1 -1
- package/dist/src/type-schema/model.js.map +1 -1
- package/dist/src/type-schema/settings.d.ts +10 -0
- package/dist/src/type-schema/settings.d.ts.map +1 -0
- package/dist/src/type-schema/settings.js +32 -0
- package/dist/src/type-schema/settings.js.map +1 -0
- package/dist/src/validation/AclValidator.d.ts.map +1 -1
- package/dist/src/validation/AclValidator.js +11 -11
- package/dist/src/validation/AclValidator.js.map +1 -1
- package/dist/src/validation/MembershipResolver.d.ts.map +1 -1
- package/dist/src/validation/MembershipResolver.js +12 -3
- package/dist/src/validation/MembershipResolver.js.map +1 -1
- package/dist/src/validation/ModelValidator.d.ts.map +1 -1
- package/dist/src/validation/ModelValidator.js +77 -43
- package/dist/src/validation/ModelValidator.js.map +1 -1
- package/dist/src/validation/SchemaValidator.d.ts +6 -2
- package/dist/src/validation/SchemaValidator.d.ts.map +1 -1
- package/dist/src/validation/SchemaValidator.js +7 -2
- package/dist/src/validation/SchemaValidator.js.map +1 -1
- package/dist/src/validation/ValidationValidator.js +4 -4
- package/dist/src/validation/ValidationValidator.js.map +1 -1
- package/dist/src/validation/errors.d.ts +3 -1
- package/dist/src/validation/errors.d.ts.map +1 -1
- package/dist/src/validation/errors.js +2 -2
- package/dist/src/validation/errors.js.map +1 -1
- package/dist/tests/cases/unit/laxSchemaConverter.test.d.ts +2 -0
- package/dist/tests/cases/unit/laxSchemaConverter.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/laxSchemaConverter.test.js +118 -0
- package/dist/tests/cases/unit/laxSchemaConverter.test.js.map +1 -0
- package/dist/tests/cases/unit/membershipResolver.test.js +19 -0
- package/dist/tests/cases/unit/membershipResolver.test.js.map +1 -1
- package/dist/tests/cases/unit/modelValidator.test.d.ts +2 -0
- package/dist/tests/cases/unit/modelValidator.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/modelValidator.test.js +141 -0
- package/dist/tests/cases/unit/modelValidator.test.js.map +1 -0
- package/dist/tests/cases/unit/schemas/basic.d.ts +5 -0
- package/dist/tests/cases/unit/schemas/basic.d.ts.map +1 -0
- package/dist/tests/cases/unit/schemas/basic.js +11 -0
- package/dist/tests/cases/unit/schemas/basic.js.map +1 -0
- package/dist/tests/cases/unit/schemas/enum.d.ts +7 -0
- package/dist/tests/cases/unit/schemas/enum.d.ts.map +1 -0
- package/dist/tests/cases/unit/schemas/enum.js +13 -0
- package/dist/tests/cases/unit/schemas/enum.js.map +1 -0
- package/dist/tests/cases/unit/schemas/relations.d.ts +15 -0
- package/dist/tests/cases/unit/schemas/relations.d.ts.map +1 -0
- package/dist/tests/cases/unit/schemas/relations.js +27 -0
- package/dist/tests/cases/unit/schemas/relations.js.map +1 -0
- package/dist/tests/cases/unit/schemas/unique.d.ts +5 -0
- package/dist/tests/cases/unit/schemas/unique.d.ts.map +1 -0
- package/dist/tests/cases/unit/schemas/unique.js +20 -0
- package/dist/tests/cases/unit/schemas/unique.js.map +1 -0
- package/dist/tests/cases/unit/tsDefinitionGenerator.test.d.ts +2 -0
- package/dist/tests/cases/unit/tsDefinitionGenerator.test.d.ts.map +1 -0
- package/dist/tests/cases/unit/tsDefinitionGenerator.test.js +56 -0
- package/dist/tests/cases/unit/tsDefinitionGenerator.test.js.map +1 -0
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/acl/PredicateDefinitionProcessor.ts +2 -2
- package/src/definition-generator/TsDefinitionGenerator.ts +275 -0
- package/src/definition-generator/index.ts +1 -0
- package/src/index.ts +5 -1
- package/src/lax/LaxSchemaConverter.ts +213 -0
- package/src/lax/index.ts +9 -0
- package/src/lax/schema.ts +62 -0
- package/src/model/NamingConventions.ts +53 -0
- package/src/model/NamingHelper.ts +11 -1
- package/src/model/index.ts +2 -0
- package/src/model/modelUtils.ts +76 -71
- package/src/model/resolveDefaultColumnType.ts +25 -0
- package/src/schemaNormalizer.ts +1 -0
- package/src/type-schema/acl.ts +30 -11
- package/src/type-schema/index.ts +1 -0
- package/src/type-schema/model.ts +1 -1
- package/src/type-schema/settings.ts +8 -0
- package/src/validation/AclValidator.ts +10 -11
- package/src/validation/MembershipResolver.ts +10 -3
- package/src/validation/ModelValidator.ts +77 -45
- package/src/validation/SchemaValidator.ts +17 -4
- package/src/validation/ValidationValidator.ts +4 -4
- package/src/validation/errors.ts +25 -2
- package/tests/cases/unit/__snapshots__/laxSchemaConverter.test.ts.snap +310 -0
- package/tests/cases/unit/laxSchemaConverter.test.ts +137 -0
- package/tests/cases/unit/membershipResolver.test.ts +22 -0
- package/tests/cases/unit/modelValidator.test.ts +143 -0
- package/tests/cases/unit/schemas/.eslintignore +1 -0
- package/tests/cases/unit/schemas/basic.ts +5 -0
- package/tests/cases/unit/schemas/enum.ts +8 -0
- package/tests/cases/unit/schemas/relations.ts +17 -0
- package/tests/cases/unit/schemas/unique.ts +6 -0
- 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
|
|
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 (
|
|
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(
|
|
221
|
+
.add('MODEL_NAME_COLLISION', `${description} collides with a ${relationNames[entity.tableName]}`)
|
|
217
222
|
} else {
|
|
218
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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]]
|
package/src/validation/errors.ts
CHANGED
|
@@ -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
|
}
|