zenstack 1.0.0-alpha.23 → 1.0.0-alpha.24
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/README.md +79 -9
- package/cli/cli-error.js +3 -5
- package/cli/cli-error.js.map +1 -1
- package/cli/cli-util.js +123 -105
- package/cli/cli-util.js.map +1 -1
- package/cli/index.js +105 -63
- package/cli/index.js.map +1 -1
- package/cli/plugin-runner.js +130 -127
- package/cli/plugin-runner.js.map +1 -1
- package/language-server/constants.js +5 -13
- package/language-server/constants.js.map +1 -1
- package/language-server/main.js +8 -15
- package/language-server/main.js.map +1 -1
- package/language-server/types.js +3 -1
- package/language-server/types.js.map +1 -1
- package/language-server/utils.js +13 -16
- package/language-server/utils.js.map +1 -1
- package/language-server/validator/attribute-validator.js +3 -7
- package/language-server/validator/attribute-validator.js.map +1 -1
- package/language-server/validator/datamodel-validator.js +293 -347
- package/language-server/validator/datamodel-validator.js.map +1 -1
- package/language-server/validator/datasource-validator.js +61 -71
- package/language-server/validator/datasource-validator.js.map +1 -1
- package/language-server/validator/enum-validator.js +6 -10
- package/language-server/validator/enum-validator.js.map +1 -1
- package/language-server/validator/expression-validator.js +25 -31
- package/language-server/validator/expression-validator.js.map +1 -1
- package/language-server/validator/schema-validator.js +18 -25
- package/language-server/validator/schema-validator.js.map +1 -1
- package/language-server/validator/utils.js +86 -85
- package/language-server/validator/utils.js.map +1 -1
- package/language-server/validator/zmodel-validator.js +55 -58
- package/language-server/validator/zmodel-validator.js.map +1 -1
- package/language-server/zmodel-formatter.js +40 -21
- package/language-server/zmodel-formatter.js.map +1 -1
- package/language-server/zmodel-linker.js +328 -331
- package/language-server/zmodel-linker.js.map +1 -1
- package/language-server/zmodel-module.js +50 -59
- package/language-server/zmodel-module.js.map +1 -1
- package/language-server/zmodel-scope.js +35 -25
- package/language-server/zmodel-scope.js.map +1 -1
- package/language-server/zmodel-workspace-manager.js +30 -18
- package/language-server/zmodel-workspace-manager.js.map +1 -1
- package/package.json +8 -12
- package/plugins/access-policy/expression-writer.js +301 -292
- package/plugins/access-policy/expression-writer.js.map +1 -1
- package/plugins/access-policy/index.js +20 -11
- package/plugins/access-policy/index.js.map +1 -1
- package/plugins/access-policy/policy-guard-generator.js +327 -321
- package/plugins/access-policy/policy-guard-generator.js.map +1 -1
- package/plugins/access-policy/typescript-expression-transformer.js +94 -95
- package/plugins/access-policy/typescript-expression-transformer.js.map +1 -1
- package/plugins/access-policy/utils.js +7 -9
- package/plugins/access-policy/utils.js.map +1 -1
- package/plugins/access-policy/zod-schema-generator.js +143 -159
- package/plugins/access-policy/zod-schema-generator.js.map +1 -1
- package/plugins/model-meta/index.js +97 -102
- package/plugins/model-meta/index.js.map +1 -1
- package/plugins/plugin-utils.js +34 -40
- package/plugins/plugin-utils.js.map +1 -1
- package/plugins/prisma/indent-string.js +4 -8
- package/plugins/prisma/indent-string.js.map +1 -1
- package/plugins/prisma/index.js +20 -11
- package/plugins/prisma/index.js.map +1 -1
- package/plugins/prisma/prisma-builder.js +235 -213
- package/plugins/prisma/prisma-builder.js.map +1 -1
- package/plugins/prisma/schema-generator.js +205 -192
- package/plugins/prisma/schema-generator.js.map +1 -1
- package/plugins/prisma/zmodel-code-generator.js +109 -114
- package/plugins/prisma/zmodel-code-generator.js.map +1 -1
- package/telemetry.js +107 -90
- package/telemetry.js.map +1 -1
- package/types.js +3 -1
- package/types.js.map +1 -1
- package/utils/ast-utils.js +67 -67
- package/utils/ast-utils.js.map +1 -1
- package/utils/exec-utils.js +6 -15
- package/utils/exec-utils.js.map +1 -1
- package/utils/pkg-utils.js +38 -35
- package/utils/pkg-utils.js.map +1 -1
- package/utils/version-utils.js +9 -10
- package/utils/version-utils.js.map +1 -1
- package/global.d.js +0 -1
- package/global.d.js.map +0 -1
|
@@ -1,375 +1,321 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var _utils = require("./utils");
|
|
12
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const ast_1 = require("@zenstackhq/language/ast");
|
|
7
|
+
const pluralize_1 = __importDefault(require("pluralize"));
|
|
8
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
9
|
+
const constants_1 = require("../constants");
|
|
10
|
+
const utils_1 = require("./utils");
|
|
13
11
|
/**
|
|
14
12
|
* Validates data model declarations.
|
|
15
13
|
*/
|
|
16
14
|
class DataModelValidator {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
validateFields(dm, accept) {
|
|
23
|
-
const idFields = dm.fields.filter(f => f.attributes.find(attr => {
|
|
24
|
-
var _attr$decl$ref;
|
|
25
|
-
return ((_attr$decl$ref = attr.decl.ref) === null || _attr$decl$ref === void 0 ? void 0 : _attr$decl$ref.name) === '@id';
|
|
26
|
-
}));
|
|
27
|
-
if (idFields.length === 0) {
|
|
28
|
-
const {
|
|
29
|
-
allows,
|
|
30
|
-
denies,
|
|
31
|
-
hasFieldValidation
|
|
32
|
-
} = (0, _astUtils.analyzePolicies)(dm);
|
|
33
|
-
if (allows.length > 0 || denies.length > 0 || hasFieldValidation) {
|
|
34
|
-
// TODO: relax this requirement to require only @unique fields
|
|
35
|
-
// when access policies or field valdaition is used, require an @id field
|
|
36
|
-
accept('error', 'Model must include a field with @id attribute', {
|
|
37
|
-
node: dm
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
} else if (idFields.length > 1) {
|
|
41
|
-
accept('error', 'Model can include at most one field with @id attribute', {
|
|
42
|
-
node: dm
|
|
43
|
-
});
|
|
44
|
-
} else {
|
|
45
|
-
if (idFields[0].type.optional) {
|
|
46
|
-
accept('error', 'Field with @id attribute must not be optional', {
|
|
47
|
-
node: idFields[0]
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
if (idFields[0].type.array || !idFields[0].type.type || !_constants.SCALAR_TYPES.includes(idFields[0].type.type)) {
|
|
51
|
-
accept('error', 'Field with @id attribute must be of scalar type', {
|
|
52
|
-
node: idFields[0]
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
dm.fields.forEach(field => this.validateField(field, accept));
|
|
57
|
-
}
|
|
58
|
-
validateField(field, accept) {
|
|
59
|
-
var _field$type$reference;
|
|
60
|
-
if (field.type.array && field.type.optional) {
|
|
61
|
-
accept('error', 'Optional lists are not supported. Use either `Type[]` or `Type?`', {
|
|
62
|
-
node: field.type
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
field.attributes.forEach(attr => this.validateAttributeApplication(attr, accept));
|
|
66
|
-
if ((0, _ast.isDataModel)((_field$type$reference = field.type.reference) === null || _field$type$reference === void 0 ? void 0 : _field$type$reference.ref)) {
|
|
67
|
-
this.validateRelationField(field, accept);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
validateAttributes(dm, accept) {
|
|
71
|
-
dm.attributes.forEach(attr => {
|
|
72
|
-
this.validateAttributeApplication(attr, accept);
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
validateAttributeApplication(attr, accept) {
|
|
76
|
-
const decl = attr.decl.ref;
|
|
77
|
-
if (!decl) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const targetDecl = attr.$container;
|
|
81
|
-
if (decl.name === '@@@targetField' && !(0, _ast.isAttribute)(targetDecl)) {
|
|
82
|
-
accept('error', `attribute "${decl.name}" can only be used on attribute declarations`, {
|
|
83
|
-
node: attr
|
|
84
|
-
});
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if ((0, _ast.isDataModelField)(targetDecl) && !this.isValidAttributeTarget(decl, targetDecl)) {
|
|
88
|
-
accept('error', `attribute "${decl.name}" cannot be used on this type of field`, {
|
|
89
|
-
node: attr
|
|
90
|
-
});
|
|
15
|
+
validate(dm, accept) {
|
|
16
|
+
(0, utils_1.validateDuplicatedDeclarations)(dm.fields, accept);
|
|
17
|
+
this.validateFields(dm, accept);
|
|
18
|
+
this.validateAttributes(dm, accept);
|
|
91
19
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
20
|
+
validateFields(dm, accept) {
|
|
21
|
+
const idFields = dm.fields.filter((f) => f.attributes.find((attr) => { var _a; return ((_a = attr.decl.ref) === null || _a === void 0 ? void 0 : _a.name) === '@id'; }));
|
|
22
|
+
if (idFields.length === 0) {
|
|
23
|
+
const { allows, denies, hasFieldValidation } = (0, ast_utils_1.analyzePolicies)(dm);
|
|
24
|
+
if (allows.length > 0 || denies.length > 0 || hasFieldValidation) {
|
|
25
|
+
// TODO: relax this requirement to require only @unique fields
|
|
26
|
+
// when access policies or field valdaition is used, require an @id field
|
|
27
|
+
accept('error', 'Model must include a field with @id attribute', {
|
|
28
|
+
node: dm,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
102
31
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
node: arg
|
|
108
|
-
});
|
|
109
|
-
return false;
|
|
32
|
+
else if (idFields.length > 1) {
|
|
33
|
+
accept('error', 'Model can include at most one field with @id attribute', {
|
|
34
|
+
node: dm,
|
|
35
|
+
});
|
|
110
36
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
node: arg
|
|
121
|
-
});
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
filledParams.add(paramDecl);
|
|
125
|
-
arg.$resolvedParam = paramDecl;
|
|
126
|
-
}
|
|
127
|
-
const missingParams = decl.params.filter(p => !p.type.optional && !filledParams.has(p));
|
|
128
|
-
if (missingParams.length > 0) {
|
|
129
|
-
accept('error', `Required ${(0, _pluralize.default)('parameter', missingParams.length)} not provided: ${missingParams.map(p => p.name).join(', ')}`, {
|
|
130
|
-
node: attr
|
|
131
|
-
});
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
isValidAttributeTarget(attrDecl, targetDecl) {
|
|
137
|
-
var _targetDecl$type$refe;
|
|
138
|
-
const targetField = attrDecl.attributes.find(attr => {
|
|
139
|
-
var _attr$decl$ref2;
|
|
140
|
-
return ((_attr$decl$ref2 = attr.decl.ref) === null || _attr$decl$ref2 === void 0 ? void 0 : _attr$decl$ref2.name) === '@@@targetField';
|
|
141
|
-
});
|
|
142
|
-
if (!targetField) {
|
|
143
|
-
// no field type constraint
|
|
144
|
-
return true;
|
|
37
|
+
else {
|
|
38
|
+
if (idFields[0].type.optional) {
|
|
39
|
+
accept('error', 'Field with @id attribute must not be optional', { node: idFields[0] });
|
|
40
|
+
}
|
|
41
|
+
if (idFields[0].type.array || !idFields[0].type.type || !constants_1.SCALAR_TYPES.includes(idFields[0].type.type)) {
|
|
42
|
+
accept('error', 'Field with @id attribute must be of scalar type', { node: idFields[0] });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
dm.fields.forEach((field) => this.validateField(field, accept));
|
|
145
46
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
break;
|
|
156
|
-
case 'IntField':
|
|
157
|
-
allowed = allowed || targetDecl.type.type === 'Int';
|
|
158
|
-
break;
|
|
159
|
-
case 'FloatField':
|
|
160
|
-
allowed = allowed || targetDecl.type.type === 'Float';
|
|
161
|
-
break;
|
|
162
|
-
case 'DecimalField':
|
|
163
|
-
allowed = allowed || targetDecl.type.type === 'Decimal';
|
|
164
|
-
break;
|
|
165
|
-
case 'BooleanField':
|
|
166
|
-
allowed = allowed || targetDecl.type.type === 'Boolean';
|
|
167
|
-
break;
|
|
168
|
-
case 'DateTimeField':
|
|
169
|
-
allowed = allowed || targetDecl.type.type === 'DateTime';
|
|
170
|
-
break;
|
|
171
|
-
case 'JsonField':
|
|
172
|
-
allowed = allowed || targetDecl.type.type === 'Json';
|
|
173
|
-
break;
|
|
174
|
-
case 'BytesField':
|
|
175
|
-
allowed = allowed || targetDecl.type.type === 'Bytes';
|
|
176
|
-
break;
|
|
177
|
-
case 'ModelField':
|
|
178
|
-
allowed = allowed || (0, _ast.isDataModel)((_targetDecl$type$refe = targetDecl.type.reference) === null || _targetDecl$type$refe === void 0 ? void 0 : _targetDecl$type$refe.ref);
|
|
179
|
-
break;
|
|
180
|
-
default:
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
if (allowed) {
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
47
|
+
validateField(field, accept) {
|
|
48
|
+
var _a;
|
|
49
|
+
if (field.type.array && field.type.optional) {
|
|
50
|
+
accept('error', 'Optional lists are not supported. Use either `Type[]` or `Type?`', { node: field.type });
|
|
51
|
+
}
|
|
52
|
+
field.attributes.forEach((attr) => this.validateAttributeApplication(attr, accept));
|
|
53
|
+
if ((0, ast_1.isDataModel)((_a = field.type.reference) === null || _a === void 0 ? void 0 : _a.ref)) {
|
|
54
|
+
this.validateRelationField(field, accept);
|
|
55
|
+
}
|
|
186
56
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
var _attr$decl$ref3;
|
|
192
|
-
return ((_attr$decl$ref3 = attr.decl.ref) === null || _attr$decl$ref3 === void 0 ? void 0 : _attr$decl$ref3.name) === '@relation';
|
|
193
|
-
});
|
|
194
|
-
let name;
|
|
195
|
-
let fields;
|
|
196
|
-
let references;
|
|
197
|
-
let valid = true;
|
|
198
|
-
if (!relAttr) {
|
|
199
|
-
return {
|
|
200
|
-
attr: relAttr,
|
|
201
|
-
name,
|
|
202
|
-
fields,
|
|
203
|
-
references,
|
|
204
|
-
valid: true
|
|
205
|
-
};
|
|
57
|
+
validateAttributes(dm, accept) {
|
|
58
|
+
dm.attributes.forEach((attr) => {
|
|
59
|
+
this.validateAttributeApplication(attr, accept);
|
|
60
|
+
});
|
|
206
61
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
|
|
62
|
+
validateAttributeApplication(attr, accept) {
|
|
63
|
+
const decl = attr.decl.ref;
|
|
64
|
+
if (!decl) {
|
|
65
|
+
return;
|
|
211
66
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
accept('error', `"fields" value cannot be emtpy`, {
|
|
217
|
-
node: arg
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
valid = false;
|
|
67
|
+
const targetDecl = attr.$container;
|
|
68
|
+
if (decl.name === '@@@targetField' && !(0, ast_1.isAttribute)(targetDecl)) {
|
|
69
|
+
accept('error', `attribute "${decl.name}" can only be used on attribute declarations`, { node: attr });
|
|
70
|
+
return;
|
|
221
71
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (references.length === 0) {
|
|
225
|
-
if (accept) {
|
|
226
|
-
accept('error', `"references" value cannot be emtpy`, {
|
|
227
|
-
node: arg
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
valid = false;
|
|
72
|
+
if ((0, ast_1.isDataModelField)(targetDecl) && !this.isValidAttributeTarget(decl, targetDecl)) {
|
|
73
|
+
accept('error', `attribute "${decl.name}" cannot be used on this type of field`, { node: attr });
|
|
231
74
|
}
|
|
232
|
-
|
|
75
|
+
const filledParams = new Set();
|
|
76
|
+
for (const arg of attr.args) {
|
|
77
|
+
let paramDecl;
|
|
78
|
+
if (!arg.name) {
|
|
79
|
+
paramDecl = decl.params.find((p) => p.default && !filledParams.has(p));
|
|
80
|
+
if (!paramDecl) {
|
|
81
|
+
accept('error', `Unexpected unnamed argument`, {
|
|
82
|
+
node: arg,
|
|
83
|
+
});
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
paramDecl = decl.params.find((p) => p.name === arg.name);
|
|
89
|
+
if (!paramDecl) {
|
|
90
|
+
accept('error', `Attribute "${decl.name}" doesn't have a parameter named "${arg.name}"`, {
|
|
91
|
+
node: arg,
|
|
92
|
+
});
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!(0, utils_1.assignableToAttributeParam)(arg, paramDecl, attr)) {
|
|
97
|
+
accept('error', `Value is not assignable to parameter`, {
|
|
98
|
+
node: arg,
|
|
99
|
+
});
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (filledParams.has(paramDecl)) {
|
|
103
|
+
accept('error', `Parameter "${paramDecl.name}" is already provided`, { node: arg });
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
filledParams.add(paramDecl);
|
|
107
|
+
arg.$resolvedParam = paramDecl;
|
|
108
|
+
}
|
|
109
|
+
const missingParams = decl.params.filter((p) => !p.type.optional && !filledParams.has(p));
|
|
110
|
+
if (missingParams.length > 0) {
|
|
111
|
+
accept('error', `Required ${(0, pluralize_1.default)('parameter', missingParams.length)} not provided: ${missingParams
|
|
112
|
+
.map((p) => p.name)
|
|
113
|
+
.join(', ')}`, { node: attr });
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
233
117
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
} else {
|
|
241
|
-
// validate "fields" and "references" typing consistency
|
|
242
|
-
if (fields.length !== references.length) {
|
|
243
|
-
if (accept) {
|
|
244
|
-
accept('error', `"references" and "fields" must have the same length`, {
|
|
245
|
-
node: relAttr
|
|
246
|
-
});
|
|
118
|
+
isValidAttributeTarget(attrDecl, targetDecl) {
|
|
119
|
+
var _a;
|
|
120
|
+
const targetField = attrDecl.attributes.find((attr) => { var _a; return ((_a = attr.decl.ref) === null || _a === void 0 ? void 0 : _a.name) === '@@@targetField'; });
|
|
121
|
+
if (!targetField) {
|
|
122
|
+
// no field type constraint
|
|
123
|
+
return true;
|
|
247
124
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
125
|
+
const fieldTypes = targetField.args[0].value.items.map((item) => { var _a; return (_a = item.target.ref) === null || _a === void 0 ? void 0 : _a.name; });
|
|
126
|
+
let allowed = false;
|
|
127
|
+
for (const allowedType of fieldTypes) {
|
|
128
|
+
switch (allowedType) {
|
|
129
|
+
case 'StringField':
|
|
130
|
+
allowed = allowed || targetDecl.type.type === 'String';
|
|
131
|
+
break;
|
|
132
|
+
case 'IntField':
|
|
133
|
+
allowed = allowed || targetDecl.type.type === 'Int';
|
|
134
|
+
break;
|
|
135
|
+
case 'FloatField':
|
|
136
|
+
allowed = allowed || targetDecl.type.type === 'Float';
|
|
137
|
+
break;
|
|
138
|
+
case 'DecimalField':
|
|
139
|
+
allowed = allowed || targetDecl.type.type === 'Decimal';
|
|
140
|
+
break;
|
|
141
|
+
case 'BooleanField':
|
|
142
|
+
allowed = allowed || targetDecl.type.type === 'Boolean';
|
|
143
|
+
break;
|
|
144
|
+
case 'DateTimeField':
|
|
145
|
+
allowed = allowed || targetDecl.type.type === 'DateTime';
|
|
146
|
+
break;
|
|
147
|
+
case 'JsonField':
|
|
148
|
+
allowed = allowed || targetDecl.type.type === 'Json';
|
|
149
|
+
break;
|
|
150
|
+
case 'BytesField':
|
|
151
|
+
allowed = allowed || targetDecl.type.type === 'Bytes';
|
|
152
|
+
break;
|
|
153
|
+
case 'ModelField':
|
|
154
|
+
allowed = allowed || (0, ast_1.isDataModel)((_a = targetDecl.type.reference) === null || _a === void 0 ? void 0 : _a.ref);
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
break;
|
|
256
158
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
159
|
+
if (allowed) {
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return allowed;
|
|
164
|
+
}
|
|
165
|
+
parseRelation(field, accept) {
|
|
166
|
+
var _a, _b, _c, _d;
|
|
167
|
+
const relAttr = field.attributes.find((attr) => { var _a; return ((_a = attr.decl.ref) === null || _a === void 0 ? void 0 : _a.name) === '@relation'; });
|
|
168
|
+
let name;
|
|
169
|
+
let fields;
|
|
170
|
+
let references;
|
|
171
|
+
let valid = true;
|
|
172
|
+
if (!relAttr) {
|
|
173
|
+
return { attr: relAttr, name, fields, references, valid: true };
|
|
174
|
+
}
|
|
175
|
+
for (const arg of relAttr.args) {
|
|
176
|
+
if (!arg.name || arg.name === 'name') {
|
|
177
|
+
if ((0, ast_1.isLiteralExpr)(arg.value)) {
|
|
178
|
+
name = arg.value.value;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else if (arg.name === 'fields') {
|
|
182
|
+
fields = arg.value.items;
|
|
183
|
+
if (fields.length === 0) {
|
|
184
|
+
if (accept) {
|
|
185
|
+
accept('error', `"fields" value cannot be emtpy`, {
|
|
186
|
+
node: arg,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
valid = false;
|
|
190
|
+
}
|
|
263
191
|
}
|
|
264
|
-
|
|
265
|
-
|
|
192
|
+
else if (arg.name === 'references') {
|
|
193
|
+
references = arg.value.items;
|
|
194
|
+
if (references.length === 0) {
|
|
195
|
+
if (accept) {
|
|
196
|
+
accept('error', `"references" value cannot be emtpy`, {
|
|
197
|
+
node: arg,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
valid = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!fields || !references) {
|
|
266
205
|
if (accept) {
|
|
267
|
-
|
|
268
|
-
node: relAttr
|
|
269
|
-
});
|
|
206
|
+
accept('error', `Both "fields" and "references" must be provided`, { node: relAttr });
|
|
270
207
|
}
|
|
271
|
-
}
|
|
272
208
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return;
|
|
305
|
-
} else if (oppositeFields.length > 1) {
|
|
306
|
-
oppositeFields.forEach(f => accept('error', `Fields ${oppositeFields.map(f => '"' + f.name + '"').join(', ')} on model "${oppositeModel.name}" refer to the same relation to model "${field.$container.name}"`, {
|
|
307
|
-
node: f
|
|
308
|
-
}));
|
|
309
|
-
return;
|
|
209
|
+
else {
|
|
210
|
+
// validate "fields" and "references" typing consistency
|
|
211
|
+
if (fields.length !== references.length) {
|
|
212
|
+
if (accept) {
|
|
213
|
+
accept('error', `"references" and "fields" must have the same length`, { node: relAttr });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
for (let i = 0; i < fields.length; i++) {
|
|
218
|
+
if (!fields[i].$resolvedType) {
|
|
219
|
+
if (accept) {
|
|
220
|
+
accept('error', `field reference is unresolved`, { node: fields[i] });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!references[i].$resolvedType) {
|
|
224
|
+
if (accept) {
|
|
225
|
+
accept('error', `field reference is unresolved`, { node: references[i] });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (((_a = fields[i].$resolvedType) === null || _a === void 0 ? void 0 : _a.decl) !== ((_b = references[i].$resolvedType) === null || _b === void 0 ? void 0 : _b.decl) ||
|
|
229
|
+
((_c = fields[i].$resolvedType) === null || _c === void 0 ? void 0 : _c.array) !== ((_d = references[i].$resolvedType) === null || _d === void 0 ? void 0 : _d.array)) {
|
|
230
|
+
if (accept) {
|
|
231
|
+
accept('error', `values of "references" and "fields" must have the same type`, {
|
|
232
|
+
node: relAttr,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { attr: relAttr, name, fields, references, valid };
|
|
310
240
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
} else if (oppositeRelation !== null && oppositeRelation !== void 0 && (_oppositeRelation$ref = oppositeRelation.references) !== null && _oppositeRelation$ref !== void 0 && _oppositeRelation$ref.length && (_oppositeRelation$fie = oppositeRelation.fields) !== null && _oppositeRelation$fie !== void 0 && _oppositeRelation$fie.length) {
|
|
324
|
-
if (thisRelation !== null && thisRelation !== void 0 && thisRelation.references || thisRelation !== null && thisRelation !== void 0 && thisRelation.fields) {
|
|
325
|
-
accept('error', '"fields" and "references" must be provided only on one side of relation field', {
|
|
326
|
-
node: field
|
|
241
|
+
validateRelationField(field, accept) {
|
|
242
|
+
var _a, _b, _c, _d, _e;
|
|
243
|
+
const thisRelation = this.parseRelation(field, accept);
|
|
244
|
+
if (!thisRelation.valid) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
248
|
+
const oppositeModel = field.type.reference.ref;
|
|
249
|
+
let oppositeFields = oppositeModel.fields.filter((f) => { var _a; return ((_a = f.type.reference) === null || _a === void 0 ? void 0 : _a.ref) === field.$container; });
|
|
250
|
+
oppositeFields = oppositeFields.filter((f) => {
|
|
251
|
+
const fieldRel = this.parseRelation(f);
|
|
252
|
+
return fieldRel.valid && fieldRel.name === thisRelation.name;
|
|
327
253
|
});
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
254
|
+
if (oppositeFields.length === 0) {
|
|
255
|
+
accept('error', `The relation field "${field.name}" on model "${field.$container.name}" is missing an opposite relation field on model "${oppositeModel.name}"`, { node: field });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
else if (oppositeFields.length > 1) {
|
|
259
|
+
oppositeFields.forEach((f) => accept('error', `Fields ${oppositeFields.map((f) => '"' + f.name + '"').join(', ')} on model "${oppositeModel.name}" refer to the same relation to model "${field.$container.name}"`, { node: f }));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const oppositeField = oppositeFields[0];
|
|
263
|
+
const oppositeRelation = this.parseRelation(oppositeField);
|
|
264
|
+
let relationOwner;
|
|
265
|
+
if (((_a = thisRelation === null || thisRelation === void 0 ? void 0 : thisRelation.references) === null || _a === void 0 ? void 0 : _a.length) && ((_b = thisRelation.fields) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
266
|
+
if ((oppositeRelation === null || oppositeRelation === void 0 ? void 0 : oppositeRelation.references) || (oppositeRelation === null || oppositeRelation === void 0 ? void 0 : oppositeRelation.fields)) {
|
|
267
|
+
accept('error', '"fields" and "references" must be provided only on one side of relation field', {
|
|
268
|
+
node: oppositeField,
|
|
269
|
+
});
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
relationOwner = oppositeField;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else if (((_c = oppositeRelation === null || oppositeRelation === void 0 ? void 0 : oppositeRelation.references) === null || _c === void 0 ? void 0 : _c.length) && ((_d = oppositeRelation.fields) === null || _d === void 0 ? void 0 : _d.length)) {
|
|
277
|
+
if ((thisRelation === null || thisRelation === void 0 ? void 0 : thisRelation.references) || (thisRelation === null || thisRelation === void 0 ? void 0 : thisRelation.fields)) {
|
|
278
|
+
accept('error', '"fields" and "references" must be provided only on one side of relation field', {
|
|
279
|
+
node: field,
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
relationOwner = field;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
[field, oppositeField].forEach((f) => accept('error', 'Field for one side of relation must carry @relation attribute with both "fields" and "references" fields', { node: f }));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (!relationOwner.type.array && !relationOwner.type.optional) {
|
|
292
|
+
accept('error', 'Relation field needs to be list or optional', {
|
|
293
|
+
node: relationOwner,
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (relationOwner !== field && !relationOwner.type.array) {
|
|
298
|
+
// one-to-one relation requires defining side's reference field to be @unique
|
|
299
|
+
// e.g.:
|
|
300
|
+
// model User {
|
|
301
|
+
// id String @id @default(cuid())
|
|
302
|
+
// data UserData?
|
|
303
|
+
// }
|
|
304
|
+
// model UserData {
|
|
305
|
+
// id String @id @default(cuid())
|
|
306
|
+
// user User @relation(fields: [userId], references: [id])
|
|
307
|
+
// userId String
|
|
308
|
+
// }
|
|
309
|
+
//
|
|
310
|
+
// UserData.userId field needs to be @unique
|
|
311
|
+
(_e = thisRelation.fields) === null || _e === void 0 ? void 0 : _e.forEach((ref) => {
|
|
312
|
+
const refField = ref.target.ref;
|
|
313
|
+
if (refField && !refField.attributes.find((a) => { var _a; return ((_a = a.decl.ref) === null || _a === void 0 ? void 0 : _a.name) === '@unique'; })) {
|
|
314
|
+
accept('error', `Field "${refField.name}" is part of a one-to-one relation and must be marked as @unique`, { node: refField });
|
|
315
|
+
}
|
|
316
|
+
});
|
|
369
317
|
}
|
|
370
|
-
});
|
|
371
318
|
}
|
|
372
|
-
}
|
|
373
319
|
}
|
|
374
320
|
exports.default = DataModelValidator;
|
|
375
321
|
//# sourceMappingURL=datamodel-validator.js.map
|