@contember/schema-utils 1.2.0-alpha.8 → 1.2.0-beta.1
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.d.ts.map +1 -1
- package/dist/src/acl/PredicateDefinitionProcessor.js +3 -2
- package/dist/src/acl/PredicateDefinitionProcessor.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- 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/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/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/condition.d.ts.map +1 -1
- package/dist/src/type-schema/condition.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/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/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/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/acl/PredicateDefinitionProcessor.ts +3 -2
- package/src/index.ts +3 -1
- package/src/model/NamingHelper.ts +11 -1
- package/src/model/modelUtils.ts +76 -71
- package/src/schemaNormalizer.ts +1 -0
- package/src/type-schema/acl.ts +30 -11
- package/src/type-schema/condition.ts +3 -3
- package/src/type-schema/index.ts +1 -0
- 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/membershipResolver.test.ts +22 -0
- package/tests/cases/unit/modelValidator.test.ts +143 -0
package/src/model/modelUtils.ts
CHANGED
|
@@ -28,10 +28,18 @@ export const getEntity = (schema: Model.Schema, entityName: string): Model.Entit
|
|
|
28
28
|
return entity
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export const getField = (entity: Model.Entity, fieldName: string): Model.AnyField => {
|
|
32
|
+
const field = entity.fields[fieldName]
|
|
33
|
+
if (!field) {
|
|
34
|
+
throw createFieldNotFoundError(entity.name, fieldName)
|
|
35
|
+
}
|
|
36
|
+
return field
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
export const getColumnName = (schema: Model.Schema, entity: Model.Entity, fieldName: string) => {
|
|
32
40
|
return acceptFieldVisitor(schema, entity, fieldName, {
|
|
33
|
-
visitColumn: (
|
|
34
|
-
visitRelation: (entity, relation) => {
|
|
41
|
+
visitColumn: ({ column }) => column.columnName,
|
|
42
|
+
visitRelation: ({ entity, relation }) => {
|
|
35
43
|
if (isIt<Model.JoiningColumnRelation>(relation, 'joiningColumn')) {
|
|
36
44
|
return relation.joiningColumn.columnName
|
|
37
45
|
}
|
|
@@ -57,8 +65,8 @@ export const tryGetColumnName = (schema: Model.Schema, entity: Model.Entity, fie
|
|
|
57
65
|
export const getColumnType = (schema: Model.Schema, entity: Model.Entity, fieldName: string): string => {
|
|
58
66
|
return acceptFieldVisitor(schema, entity, fieldName, {
|
|
59
67
|
// TODO solve enum handling properly maybe we should distinguish between domain and column type
|
|
60
|
-
visitColumn: (
|
|
61
|
-
visitRelation: (entity, relation, targetEntity) => {
|
|
68
|
+
visitColumn: ({ column }) => (column.type === Model.ColumnType.Enum ? 'text' : column.columnType),
|
|
69
|
+
visitRelation: ({ entity, relation, targetEntity }) => {
|
|
62
70
|
if (isIt<Model.JoiningColumnRelation>(relation, 'joiningColumn')) {
|
|
63
71
|
return getColumnType(schema, targetEntity, targetEntity.primary)
|
|
64
72
|
}
|
|
@@ -77,7 +85,7 @@ export const getTargetEntity = (
|
|
|
77
85
|
): Model.Entity | null => {
|
|
78
86
|
return acceptFieldVisitor(schema, entity, relationName, {
|
|
79
87
|
visitColumn: () => null,
|
|
80
|
-
visitRelation: (
|
|
88
|
+
visitRelation: ({ targetEntity }) => targetEntity,
|
|
81
89
|
})
|
|
82
90
|
}
|
|
83
91
|
|
|
@@ -87,9 +95,7 @@ export const acceptEveryFieldVisitor = <T>(
|
|
|
87
95
|
visitor: Model.FieldVisitor<T>,
|
|
88
96
|
): { [fieldName: string]: T } => {
|
|
89
97
|
const entityObj: Model.Entity = typeof entity === 'string' ? getEntity(schema, entity) : entity
|
|
90
|
-
|
|
91
|
-
throw createEntityNotFoundError(typeof entity === 'string' ? entity : 'unknown')
|
|
92
|
-
}
|
|
98
|
+
|
|
93
99
|
const result: { [fieldName: string]: T } = {}
|
|
94
100
|
for (const field in entityObj.fields) {
|
|
95
101
|
result[field] = acceptFieldVisitor(schema, entityObj, field, visitor)
|
|
@@ -104,34 +110,28 @@ export const acceptFieldVisitor = <T>(
|
|
|
104
110
|
visitor: Model.FieldVisitor<T>,
|
|
105
111
|
): T => {
|
|
106
112
|
const entityObj: Model.Entity = typeof entity === 'string' ? getEntity(schema, entity) : entity
|
|
107
|
-
|
|
108
|
-
throw createEntityNotFoundError(typeof entity === 'string' ? entity : 'unknown')
|
|
109
|
-
}
|
|
113
|
+
|
|
110
114
|
const fieldObj: Model.AnyField = typeof field === 'string' ? entityObj.fields[field] : field
|
|
111
115
|
if (!fieldObj) {
|
|
112
116
|
throw createFieldNotFoundError(entityObj.name, typeof field === 'string' ? field : 'unknown')
|
|
113
117
|
}
|
|
118
|
+
|
|
114
119
|
if (isIt<Model.AnyColumn>(fieldObj, 'columnType')) {
|
|
115
|
-
return visitor.visitColumn(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
return visitor.visitColumn({
|
|
121
|
+
entity: entityObj,
|
|
122
|
+
column: fieldObj,
|
|
123
|
+
})
|
|
119
124
|
}
|
|
120
|
-
const targetEntity = getEntity(schema, fieldObj.target)
|
|
121
125
|
|
|
122
126
|
if (isIt<Model.ColumnVisitor<T> & Model.RelationVisitor<T>>(visitor, 'visitRelation')) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
if (targetRelation && !isIt<Model.Relation>(targetRelation, 'target')) {
|
|
132
|
-
throw new Error('Invalid target')
|
|
133
|
-
}
|
|
134
|
-
return visitor.visitRelation(entityObj, fieldObj, targetEntity, targetRelation)
|
|
127
|
+
return acceptRelationTypeVisitor(schema, entityObj, fieldObj, {
|
|
128
|
+
visitManyHasManyInverse: visitor.visitRelation.bind(visitor),
|
|
129
|
+
visitManyHasManyOwning: visitor.visitRelation.bind(visitor),
|
|
130
|
+
visitOneHasMany: visitor.visitRelation.bind(visitor),
|
|
131
|
+
visitOneHasOneInverse: visitor.visitRelation.bind(visitor),
|
|
132
|
+
visitOneHasOneOwning: visitor.visitRelation.bind(visitor),
|
|
133
|
+
visitManyHasOne: visitor.visitRelation.bind(visitor),
|
|
134
|
+
})
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
if (isIt<Model.ColumnVisitor<T> & Model.RelationByGenericTypeVisitor<T>>(visitor, 'visitHasMany')) {
|
|
@@ -148,7 +148,8 @@ export const acceptFieldVisitor = <T>(
|
|
|
148
148
|
if (isIt<Model.ColumnVisitor<T> & Model.RelationByTypeVisitor<T>>(visitor, 'visitManyHasManyInverse')) {
|
|
149
149
|
return acceptRelationTypeVisitor(schema, entityObj, fieldObj, visitor)
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
|
|
152
|
+
throw new Error()
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
export const acceptRelationTypeVisitor = <T>(
|
|
@@ -157,49 +158,50 @@ export const acceptRelationTypeVisitor = <T>(
|
|
|
157
158
|
relation: string | Model.AnyRelation,
|
|
158
159
|
visitor: Model.RelationByTypeVisitor<T>,
|
|
159
160
|
): T => {
|
|
160
|
-
const entityObj
|
|
161
|
-
|
|
162
|
-
throw createEntityNotFoundError(typeof entity === 'string' ? entity : 'unknown')
|
|
163
|
-
}
|
|
161
|
+
const entityObj = typeof entity === 'string' ? getEntity(schema, entity) : entity
|
|
162
|
+
|
|
164
163
|
const relationObj: Model.AnyField = typeof relation === 'string' ? entityObj.fields[relation] : relation
|
|
165
164
|
if (!relationObj) {
|
|
166
165
|
throw createFieldNotFoundError(entityObj.name, typeof relation === 'string' ? relation : 'unknown')
|
|
167
166
|
}
|
|
167
|
+
|
|
168
168
|
if (!isRelation(relationObj)) {
|
|
169
169
|
throw new ModelError(
|
|
170
170
|
ModelErrorCode.NOT_RELATION,
|
|
171
171
|
`Field ${relationObj.name} of entity ${entityObj.name} is not a relation`,
|
|
172
172
|
)
|
|
173
173
|
}
|
|
174
|
+
|
|
174
175
|
const targetEntity = getEntity(schema, relationObj.target)
|
|
175
176
|
|
|
176
177
|
if (isInverseRelation(relationObj)) {
|
|
177
178
|
const targetRelation = targetEntity.fields[relationObj.ownedBy]
|
|
178
|
-
if (!isRelation(targetRelation)) {
|
|
179
|
-
throw new Error('Invalid target relation')
|
|
180
|
-
}
|
|
181
179
|
switch (relationObj.type) {
|
|
182
180
|
case Model.RelationType.ManyHasMany:
|
|
183
|
-
return visitor.visitManyHasManyInverse(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
181
|
+
return visitor.visitManyHasManyInverse({
|
|
182
|
+
type: 'manyHasManyInverse',
|
|
183
|
+
entity: entityObj,
|
|
184
|
+
relation: relationObj,
|
|
185
|
+
targetEntity: targetEntity,
|
|
186
|
+
targetRelation: targetRelation as Model.ManyHasManyOwningRelation,
|
|
187
|
+
},
|
|
188
188
|
)
|
|
189
189
|
case Model.RelationType.OneHasOne:
|
|
190
|
-
return visitor.visitOneHasOneInverse(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
return visitor.visitOneHasOneInverse({
|
|
191
|
+
type: 'oneHasOneInverse',
|
|
192
|
+
entity: entityObj,
|
|
193
|
+
relation: relationObj,
|
|
194
|
+
targetEntity: targetEntity,
|
|
195
|
+
targetRelation: targetRelation as Model.OneHasOneOwningRelation,
|
|
196
|
+
})
|
|
196
197
|
case Model.RelationType.OneHasMany:
|
|
197
|
-
return visitor.visitOneHasMany(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
198
|
+
return visitor.visitOneHasMany({
|
|
199
|
+
type: 'oneHasMany',
|
|
200
|
+
entity: entityObj,
|
|
201
|
+
relation: relationObj,
|
|
202
|
+
targetEntity: targetEntity,
|
|
203
|
+
targetRelation: targetRelation as Model.ManyHasOneRelation,
|
|
204
|
+
})
|
|
203
205
|
default:
|
|
204
206
|
return assertNever(relationObj)
|
|
205
207
|
}
|
|
@@ -208,26 +210,29 @@ export const acceptRelationTypeVisitor = <T>(
|
|
|
208
210
|
|
|
209
211
|
switch (relationObj.type) {
|
|
210
212
|
case Model.RelationType.ManyHasMany:
|
|
211
|
-
return visitor.visitManyHasManyOwning(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
return visitor.visitManyHasManyOwning({
|
|
214
|
+
type: 'manyHasManyOwning',
|
|
215
|
+
entity: entityObj,
|
|
216
|
+
relation: relationObj,
|
|
217
|
+
targetEntity: targetEntity,
|
|
218
|
+
targetRelation: targetRelation as Model.ManyHasManyInverseRelation,
|
|
219
|
+
})
|
|
217
220
|
case Model.RelationType.OneHasOne:
|
|
218
|
-
return visitor.visitOneHasOneOwning(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
return visitor.visitOneHasOneOwning({
|
|
222
|
+
type: 'oneHasOneOwning',
|
|
223
|
+
entity: entityObj,
|
|
224
|
+
relation: relationObj,
|
|
225
|
+
targetEntity: targetEntity,
|
|
226
|
+
targetRelation: targetRelation as Model.OneHasOneInverseRelation,
|
|
227
|
+
})
|
|
224
228
|
case Model.RelationType.ManyHasOne:
|
|
225
|
-
return visitor.visitManyHasOne(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
return visitor.visitManyHasOne({
|
|
230
|
+
type: 'manyHasOne',
|
|
231
|
+
entity: entityObj, //
|
|
232
|
+
relation: relationObj,
|
|
233
|
+
targetEntity: targetEntity,
|
|
234
|
+
targetRelation: targetRelation as Model.OneHasManyRelation,
|
|
235
|
+
})
|
|
231
236
|
default:
|
|
232
237
|
return assertNever(relationObj)
|
|
233
238
|
}
|
package/src/schemaNormalizer.ts
CHANGED
package/src/type-schema/acl.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Acl, Model } from '@contember/schema'
|
|
2
2
|
import * as Typesafe from '@contember/typesafe'
|
|
3
|
+
import { conditionSchema } from './condition'
|
|
3
4
|
|
|
4
5
|
const membershipMatchRuleSchema = Typesafe.record(
|
|
5
6
|
Typesafe.string,
|
|
@@ -40,20 +41,37 @@ const contentPermissionsSchema = Typesafe.partial({
|
|
|
40
41
|
})
|
|
41
42
|
const contentSchemaCheck: Typesafe.Equals<Acl.ContentPermissions, ReturnType<typeof contentPermissionsSchema>> = true
|
|
42
43
|
|
|
44
|
+
const entityVariableSchema = Typesafe.intersection(
|
|
45
|
+
Typesafe.object({
|
|
46
|
+
type: Typesafe.literal(Acl.VariableType.entity),
|
|
47
|
+
entityName: Typesafe.string,
|
|
48
|
+
}),
|
|
49
|
+
Typesafe.partial({
|
|
50
|
+
fallback: conditionSchema(),
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
const entityVariableSchemaCheck: Typesafe.Equals<Acl.EntityVariable, ReturnType<typeof entityVariableSchema>> = true
|
|
54
|
+
|
|
55
|
+
const predefinedVariableSchema = Typesafe.object({
|
|
56
|
+
type: Typesafe.literal(Acl.VariableType.predefined),
|
|
57
|
+
value: Typesafe.enumeration('identityID', 'personID'),
|
|
58
|
+
})
|
|
59
|
+
const conditionVariableSchema = Typesafe.intersection(
|
|
60
|
+
Typesafe.object({
|
|
61
|
+
type: Typesafe.literal(Acl.VariableType.condition),
|
|
62
|
+
}),
|
|
63
|
+
Typesafe.partial({
|
|
64
|
+
fallback: conditionSchema(),
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
67
|
+
const conditionVariableSchemaCheck: Typesafe.Equals<Acl.ConditionVariable, ReturnType<typeof conditionVariableSchema>> = true
|
|
68
|
+
|
|
43
69
|
const variablesSchema = Typesafe.record(
|
|
44
70
|
Typesafe.string,
|
|
45
71
|
Typesafe.union(
|
|
46
|
-
Typesafe.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}),
|
|
50
|
-
Typesafe.object({
|
|
51
|
-
type: Typesafe.literal(Acl.VariableType.predefined),
|
|
52
|
-
value: Typesafe.enumeration('identityID', 'personID'),
|
|
53
|
-
}),
|
|
54
|
-
Typesafe.object({
|
|
55
|
-
type: Typesafe.literal(Acl.VariableType.condition),
|
|
56
|
-
}),
|
|
72
|
+
entityVariableSchema as Typesafe.Type<Acl.EntityVariable>,
|
|
73
|
+
predefinedVariableSchema,
|
|
74
|
+
conditionVariableSchema as Typesafe.Type<Acl.ConditionVariable>,
|
|
57
75
|
),
|
|
58
76
|
)
|
|
59
77
|
|
|
@@ -102,6 +120,7 @@ const baseRolePermissionsSchema = Typesafe.intersection(
|
|
|
102
120
|
tenant: tenantPermissionsSchema,
|
|
103
121
|
system: systemPermissionsSchema,
|
|
104
122
|
content: contentPermissionsSchema,
|
|
123
|
+
debug: Typesafe.boolean,
|
|
105
124
|
}),
|
|
106
125
|
)
|
|
107
126
|
const baseRolePermissionsCheck: Typesafe.Equals<Acl.BaseRolePermissions, ReturnType<typeof baseRolePermissionsSchema>> = true
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Typesafe from '@contember/typesafe'
|
|
2
|
-
import { Type } from '@contember/typesafe'
|
|
2
|
+
import { Json, Type } from '@contember/typesafe'
|
|
3
3
|
import { Input, Model, Value } from '@contember/schema'
|
|
4
4
|
|
|
5
5
|
export const conditionSchema = <T extends Value.FieldValue>(type?: Model.ColumnType): Type<Input.Condition<T>> => {
|
|
@@ -7,13 +7,13 @@ export const conditionSchema = <T extends Value.FieldValue>(type?: Model.ColumnT
|
|
|
7
7
|
return conditionSchemaInner(metadata)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
interface ResolvedColumnMetadata<T> {
|
|
10
|
+
interface ResolvedColumnMetadata<T extends Json> {
|
|
11
11
|
type: Typesafe.Type<T>
|
|
12
12
|
isJson?: boolean
|
|
13
13
|
isString?: boolean
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const conditionSchemaInner = <T>(metadata: ResolvedColumnMetadata<T>): Type<Input.Condition<T>> => {
|
|
16
|
+
const conditionSchemaInner = <T extends Json>(metadata: ResolvedColumnMetadata<T>): Type<Input.Condition<T>> => {
|
|
17
17
|
return (input: unknown, path: PropertyKey[] = []): Input.Condition<T> => {
|
|
18
18
|
const self = conditionSchemaInner(metadata)
|
|
19
19
|
const inner = metadata.type
|
package/src/type-schema/index.ts
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as Typesafe from '@contember/typesafe'
|
|
2
|
+
import { Settings } from '@contember/schema'
|
|
3
|
+
|
|
4
|
+
export const settingsSchema = Typesafe.partial({
|
|
5
|
+
useExistsInHasManyFilter: Typesafe.boolean,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const settingSchemaCheck: Typesafe.Equals<Settings.Schema, ReturnType<typeof settingsSchema>> = true
|
|
@@ -2,8 +2,7 @@ import { Acl, Model } from '@contember/schema'
|
|
|
2
2
|
import { PredicateDefinitionProcessor } from '../acl'
|
|
3
3
|
import { getEntity } from '../model'
|
|
4
4
|
import { ErrorBuilder, ValidationError } from './errors'
|
|
5
|
-
import { conditionSchema } from '../type-schema
|
|
6
|
-
import { anyJson } from '@contember/typesafe'
|
|
5
|
+
import { conditionSchema } from '../type-schema'
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
export class AclValidator {
|
|
@@ -42,7 +41,7 @@ export class AclValidator {
|
|
|
42
41
|
}
|
|
43
42
|
for (const inheritsFrom of inherits) {
|
|
44
43
|
if (!roles.includes(inheritsFrom)) {
|
|
45
|
-
errorBuilder.for(inheritsFrom).add('Referenced role not exists.')
|
|
44
|
+
errorBuilder.for(inheritsFrom).add('ACL_UNDEFINED_ROLE', 'Referenced role not exists.')
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
}
|
|
@@ -60,7 +59,7 @@ export class AclValidator {
|
|
|
60
59
|
switch (variable.type) {
|
|
61
60
|
case 'entity':
|
|
62
61
|
if (!this.model.entities[variable.entityName]) {
|
|
63
|
-
errorBuilder.add(`Entity "${variable.entityName}" not found`)
|
|
62
|
+
errorBuilder.add('ACL_UNDEFINED_ENTITY', `Entity "${variable.entityName}" not found`)
|
|
64
63
|
return
|
|
65
64
|
}
|
|
66
65
|
}
|
|
@@ -73,7 +72,7 @@ export class AclValidator {
|
|
|
73
72
|
continue
|
|
74
73
|
}
|
|
75
74
|
if (!this.model.entities[entityName]) {
|
|
76
|
-
errorBuilder.add(`Entity ${entityName} not found`)
|
|
75
|
+
errorBuilder.add('ACL_UNDEFINED_ENTITY', `Entity ${entityName} not found`)
|
|
77
76
|
continue
|
|
78
77
|
}
|
|
79
78
|
const entity = getEntity(this.model, entityName)
|
|
@@ -132,25 +131,25 @@ export class AclValidator {
|
|
|
132
131
|
handleColumn: ctx => {
|
|
133
132
|
if (typeof ctx.value === 'string') {
|
|
134
133
|
if (!variables[ctx.value]) {
|
|
135
|
-
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`)
|
|
134
|
+
errorBuilder.for(...ctx.path).add('ACL_UNDEFINED_VARIABLE', `Undefined variable ${ctx.value}`)
|
|
136
135
|
}
|
|
137
136
|
} else {
|
|
138
137
|
try {
|
|
139
138
|
conditionSchema(ctx.column.type)(ctx.value)
|
|
140
139
|
} catch (e: any) {
|
|
141
|
-
errorBuilder.for(...ctx.path).add(`Invalid condition (${e.message}): ${JSON.stringify(ctx.value)}`)
|
|
140
|
+
errorBuilder.for(...ctx.path).add('ACL_INVALID_CONDITION', `Invalid condition (${e.message}): ${JSON.stringify(ctx.value)}`)
|
|
142
141
|
}
|
|
143
142
|
}
|
|
144
143
|
return ctx.value
|
|
145
144
|
},
|
|
146
145
|
handleRelation: ctx => {
|
|
147
146
|
if (typeof ctx.value === 'string' && !variables[ctx.value]) {
|
|
148
|
-
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`)
|
|
147
|
+
errorBuilder.for(...ctx.path).add('ACL_UNDEFINED_VARIABLE', `Undefined variable ${ctx.value}`)
|
|
149
148
|
}
|
|
150
149
|
return ctx.value
|
|
151
150
|
},
|
|
152
151
|
handleUndefinedField: ctx => {
|
|
153
|
-
errorBuilder.for(...ctx.path).add(`Undefined field ${ctx.name} on entity ${ctx.entity.name}`)
|
|
152
|
+
errorBuilder.for(...ctx.path).add('ACL_UNDEFINED_FIELD', `Undefined field ${ctx.name} on entity ${ctx.entity.name}`)
|
|
154
153
|
return ctx.value
|
|
155
154
|
},
|
|
156
155
|
})
|
|
@@ -184,7 +183,7 @@ export class AclValidator {
|
|
|
184
183
|
continue
|
|
185
184
|
}
|
|
186
185
|
if (!entity.fields[field]) {
|
|
187
|
-
errorBuilder.add(`Field ${field} not found on entity ${entity.name}`)
|
|
186
|
+
errorBuilder.add('ACL_UNDEFINED_FIELD', `Field ${field} not found on entity ${entity.name}`)
|
|
188
187
|
}
|
|
189
188
|
this.validatePredicate(permissions[field], predicates, errorBuilder.for(field))
|
|
190
189
|
}
|
|
@@ -199,7 +198,7 @@ export class AclValidator {
|
|
|
199
198
|
return
|
|
200
199
|
}
|
|
201
200
|
if (!predicates[predicate]) {
|
|
202
|
-
errorBuilder.add(`Predicate ${predicate} not found`)
|
|
201
|
+
errorBuilder.add('ACL_UNDEFINED_PREDICATE', `Predicate ${predicate} not found`)
|
|
203
202
|
}
|
|
204
203
|
}
|
|
205
204
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Acl, Input } from '@contember/schema'
|
|
2
2
|
import { getRoleVariables } from '../acl'
|
|
3
3
|
import { conditionSchema } from '../type-schema'
|
|
4
|
+
import { assertNever } from '../utils'
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
export class MembershipResolver {
|
|
@@ -51,7 +52,6 @@ export class MembershipResolver {
|
|
|
51
52
|
const errors: MembershipValidationError[] = []
|
|
52
53
|
const parsedVariables: ParsedMembershipVariable[] = []
|
|
53
54
|
for (const [name, variable] of Object.entries(roleVariables)) {
|
|
54
|
-
const inputVariable = membership.variables.find(it => it.name === name)
|
|
55
55
|
if (variable.type === Acl.VariableType.predefined) {
|
|
56
56
|
switch (variable.value) {
|
|
57
57
|
case 'identityID':
|
|
@@ -71,9 +71,14 @@ export class MembershipResolver {
|
|
|
71
71
|
continue
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
const inputVariable = membership.variables.find(it => it.name === name)
|
|
74
75
|
if (!inputVariable) {
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
if (variable.fallback) {
|
|
77
|
+
parsedVariables.push({ name, condition: variable.fallback })
|
|
78
|
+
} else {
|
|
79
|
+
errors.push(new MembershipValidationError(membership.role, MembershipValidationErrorType.VARIABLE_EMPTY, name))
|
|
80
|
+
parsedVariables.push({ name, condition: { never: true } })
|
|
81
|
+
}
|
|
77
82
|
continue
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -91,6 +96,8 @@ export class MembershipResolver {
|
|
|
91
96
|
errors.push(new MembershipValidationError(membership.role, MembershipValidationErrorType.VARIABLE_INVALID, name))
|
|
92
97
|
parsedVariables.push({ name, condition: { never: true } })
|
|
93
98
|
}
|
|
99
|
+
} else {
|
|
100
|
+
assertNever(variable)
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
return [errors, parsedVariables]
|