@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.
Files changed (75) hide show
  1. package/dist/src/acl/PredicateDefinitionProcessor.d.ts.map +1 -1
  2. package/dist/src/acl/PredicateDefinitionProcessor.js +3 -2
  3. package/dist/src/acl/PredicateDefinitionProcessor.js.map +1 -1
  4. package/dist/src/index.d.ts.map +1 -1
  5. package/dist/src/index.js +2 -0
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/model/NamingHelper.d.ts +2 -0
  8. package/dist/src/model/NamingHelper.d.ts.map +1 -1
  9. package/dist/src/model/NamingHelper.js +10 -2
  10. package/dist/src/model/NamingHelper.js.map +1 -1
  11. package/dist/src/model/modelUtils.d.ts +1 -0
  12. package/dist/src/model/modelUtils.d.ts.map +1 -1
  13. package/dist/src/model/modelUtils.js +69 -46
  14. package/dist/src/model/modelUtils.js.map +1 -1
  15. package/dist/src/schemaNormalizer.d.ts.map +1 -1
  16. package/dist/src/schemaNormalizer.js +1 -0
  17. package/dist/src/schemaNormalizer.js.map +1 -1
  18. package/dist/src/tsconfig.tsbuildinfo +1 -1
  19. package/dist/src/type-schema/acl.d.ts.map +1 -1
  20. package/dist/src/type-schema/acl.js +15 -4
  21. package/dist/src/type-schema/acl.js.map +1 -1
  22. package/dist/src/type-schema/condition.d.ts.map +1 -1
  23. package/dist/src/type-schema/condition.js.map +1 -1
  24. package/dist/src/type-schema/index.d.ts +1 -0
  25. package/dist/src/type-schema/index.d.ts.map +1 -1
  26. package/dist/src/type-schema/index.js +1 -0
  27. package/dist/src/type-schema/index.js.map +1 -1
  28. package/dist/src/type-schema/settings.d.ts +10 -0
  29. package/dist/src/type-schema/settings.d.ts.map +1 -0
  30. package/dist/src/type-schema/settings.js +32 -0
  31. package/dist/src/type-schema/settings.js.map +1 -0
  32. package/dist/src/validation/AclValidator.d.ts.map +1 -1
  33. package/dist/src/validation/AclValidator.js +11 -11
  34. package/dist/src/validation/AclValidator.js.map +1 -1
  35. package/dist/src/validation/MembershipResolver.d.ts.map +1 -1
  36. package/dist/src/validation/MembershipResolver.js +12 -3
  37. package/dist/src/validation/MembershipResolver.js.map +1 -1
  38. package/dist/src/validation/ModelValidator.d.ts.map +1 -1
  39. package/dist/src/validation/ModelValidator.js +77 -43
  40. package/dist/src/validation/ModelValidator.js.map +1 -1
  41. package/dist/src/validation/SchemaValidator.d.ts +6 -2
  42. package/dist/src/validation/SchemaValidator.d.ts.map +1 -1
  43. package/dist/src/validation/SchemaValidator.js +7 -2
  44. package/dist/src/validation/SchemaValidator.js.map +1 -1
  45. package/dist/src/validation/ValidationValidator.js +4 -4
  46. package/dist/src/validation/ValidationValidator.js.map +1 -1
  47. package/dist/src/validation/errors.d.ts +3 -1
  48. package/dist/src/validation/errors.d.ts.map +1 -1
  49. package/dist/src/validation/errors.js +2 -2
  50. package/dist/src/validation/errors.js.map +1 -1
  51. package/dist/tests/cases/unit/membershipResolver.test.js +19 -0
  52. package/dist/tests/cases/unit/membershipResolver.test.js.map +1 -1
  53. package/dist/tests/cases/unit/modelValidator.test.d.ts +2 -0
  54. package/dist/tests/cases/unit/modelValidator.test.d.ts.map +1 -0
  55. package/dist/tests/cases/unit/modelValidator.test.js +141 -0
  56. package/dist/tests/cases/unit/modelValidator.test.js.map +1 -0
  57. package/dist/tests/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +4 -4
  59. package/src/acl/PredicateDefinitionProcessor.ts +3 -2
  60. package/src/index.ts +3 -1
  61. package/src/model/NamingHelper.ts +11 -1
  62. package/src/model/modelUtils.ts +76 -71
  63. package/src/schemaNormalizer.ts +1 -0
  64. package/src/type-schema/acl.ts +30 -11
  65. package/src/type-schema/condition.ts +3 -3
  66. package/src/type-schema/index.ts +1 -0
  67. package/src/type-schema/settings.ts +8 -0
  68. package/src/validation/AclValidator.ts +10 -11
  69. package/src/validation/MembershipResolver.ts +10 -3
  70. package/src/validation/ModelValidator.ts +77 -45
  71. package/src/validation/SchemaValidator.ts +17 -4
  72. package/src/validation/ValidationValidator.ts +4 -4
  73. package/src/validation/errors.ts +25 -2
  74. package/tests/cases/unit/membershipResolver.test.ts +22 -0
  75. package/tests/cases/unit/modelValidator.test.ts +143 -0
@@ -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: (entity, column) => column.columnName,
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: (entity, column) => (column.type === Model.ColumnType.Enum ? 'text' : column.columnType),
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: (entity, relation, targetEntity) => targetEntity,
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
- if (!entityObj) {
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
- if (!entityObj) {
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(entityObj, fieldObj)
116
- }
117
- if (!isIt<Model.AnyRelation>(fieldObj, 'target')) {
118
- throw new Error(`Invalid field type`)
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
- let targetRelation = null
124
- if (isOwningRelation(fieldObj)) {
125
- targetRelation = fieldObj.inversedBy ? targetEntity.fields[fieldObj.inversedBy] || null : null
126
- } else if (isInverseRelation(fieldObj)) {
127
- targetRelation = targetEntity.fields[fieldObj.ownedBy]
128
- } else {
129
- throw new Error('Invalid relaton type')
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
- throw new Error('Invalid field type')
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: Model.Entity = typeof entity === 'string' ? getEntity(schema, entity) : entity
161
- if (!entityObj) {
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
- entityObj,
185
- relationObj,
186
- targetEntity,
187
- targetRelation as Model.ManyHasManyOwningRelation,
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
- entityObj,
192
- relationObj,
193
- targetEntity,
194
- targetRelation as Model.OneHasOneOwningRelation,
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
- entityObj, //
199
- relationObj,
200
- targetEntity,
201
- targetRelation as Model.ManyHasOneRelation,
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
- entityObj,
213
- relationObj,
214
- targetEntity,
215
- targetRelation as Model.ManyHasManyInverseRelation,
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
- entityObj,
220
- relationObj,
221
- targetEntity,
222
- targetRelation as Model.OneHasOneInverseRelation,
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
- entityObj, //
227
- relationObj,
228
- targetEntity,
229
- targetRelation as Model.OneHasManyRelation,
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
  }
@@ -26,6 +26,7 @@ export const normalizeSchema = <S extends Schema>(schema: S): S => {
26
26
  export: true,
27
27
  import: true,
28
28
  },
29
+ debug: true,
29
30
  ...((schema.acl.roles?.[ProjectRole.ADMIN] as Acl.RolePermissions | undefined) || {}),
30
31
  },
31
32
  [ProjectRole.CONTENT_ADMIN]: {
@@ -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.object({
47
- type: Typesafe.literal(Acl.VariableType.entity),
48
- entityName: Typesafe.string,
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
@@ -1,4 +1,5 @@
1
1
  export * from './acl'
2
2
  export * from './condition'
3
3
  export * from './model'
4
+ export * from './settings'
4
5
  export * from './validation'
@@ -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/condition'
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
- errors.push(new MembershipValidationError(membership.role, MembershipValidationErrorType.VARIABLE_EMPTY, name))
76
- parsedVariables.push({ name, condition: { never: true } })
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]