Skip to content

Commit 8d5bfe4

Browse files
authored
fix: Show validation error for the field comparison not in the same model (#912)
1 parent 4ef3634 commit 8d5bfe4

File tree

4 files changed

+77
-16
lines changed

4 files changed

+77
-16
lines changed

packages/schema/src/language-server/validator/expression-validator.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import {
44
ExpressionType,
55
isDataModel,
66
isEnum,
7+
isMemberAccessExpr,
78
isNullExpr,
89
isThisExpr,
10+
isDataModelField,
11+
isLiteralExpr,
912
} from '@zenstackhq/language/ast';
10-
import { isDataModelFieldReference } from '@zenstackhq/sdk';
13+
import { isDataModelFieldReference, isEnumFieldReference } from '@zenstackhq/sdk';
1114
import { ValidationAcceptor } from 'langium';
12-
import { isAuthInvocation, isCollectionPredicate } from '../../utils/ast-utils';
15+
import { getContainingDataModel, isAuthInvocation, isCollectionPredicate } from '../../utils/ast-utils';
1316
import { AstValidator } from '../types';
1417
import { typeAssignable } from './utils';
1518

@@ -124,6 +127,24 @@ export default class ExpressionValidator implements AstValidator<Expression> {
124127
accept('error', 'incompatible operand types', { node: expr });
125128
break;
126129
}
130+
// not supported:
131+
// - foo.a == bar
132+
// - foo.user.id == userId
133+
// except:
134+
// - future().userId == userId
135+
if(isMemberAccessExpr(expr.left) && isDataModelField(expr.left.member.ref) && expr.left.member.ref.$container != getContainingDataModel(expr)
136+
|| isMemberAccessExpr(expr.right) && isDataModelField(expr.right.member.ref) && expr.right.member.ref.$container != getContainingDataModel(expr))
137+
{
138+
// foo.user.id == auth().id
139+
// foo.user.id == "123"
140+
// foo.user.id == null
141+
// foo.user.id == EnumValue
142+
if(!(this.isNotModelFieldExpr(expr.left) || this.isNotModelFieldExpr(expr.right)))
143+
{
144+
accept('error', 'comparison between fields of different models are not supported', { node: expr });
145+
break;
146+
}
147+
}
127148

128149
if (
129150
(expr.left.$resolvedType?.nullable && isNullExpr(expr.right)) ||
@@ -183,4 +204,15 @@ export default class ExpressionValidator implements AstValidator<Expression> {
183204
}
184205
}
185206
}
207+
208+
209+
private isNotModelFieldExpr(expr: Expression) {
210+
return isLiteralExpr(expr) || isEnumFieldReference(expr) || isNullExpr(expr) || this.isAuthOrAuthMemberAccess(expr)
211+
}
212+
213+
private isAuthOrAuthMemberAccess(expr: Expression) {
214+
return isAuthInvocation(expr) || (isMemberAccessExpr(expr) && isAuthInvocation(expr.operand));
215+
}
216+
186217
}
218+

packages/schema/src/language-server/zmodel-linker.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
DataModelFieldType,
1010
Enum,
1111
EnumField,
12-
Expression,
1312
ExpressionType,
1413
FunctionDecl,
1514
FunctionParam,
@@ -53,7 +52,7 @@ import {
5352
} from 'langium';
5453
import { match } from 'ts-pattern';
5554
import { CancellationToken } from 'vscode-jsonrpc';
56-
import { getAllDeclarationsFromImports, isAuthInvocation } from '../utils/ast-utils';
55+
import { getAllDeclarationsFromImports, isAuthInvocation, getContainingDataModel } from '../utils/ast-utils';
5756
import { mapBuiltinTypeToExpressionType } from './validator/utils';
5857

5958
interface DefaultReference extends Reference {
@@ -292,24 +291,13 @@ export class ZModelLinker extends DefaultLinker {
292291
}
293292
} else if (funcDecl.name === 'future' && isFromStdlib(funcDecl)) {
294293
// future() function is resolved to current model
295-
node.$resolvedType = { decl: this.getContainingDataModel(node) };
294+
node.$resolvedType = { decl: getContainingDataModel(node) };
296295
} else {
297296
this.resolveToDeclaredType(node, funcDecl.returnType);
298297
}
299298
}
300299
}
301300

302-
private getContainingDataModel(node: Expression): DataModel | undefined {
303-
let curr: AstNode | undefined = node.$container;
304-
while (curr) {
305-
if (isDataModel(curr)) {
306-
return curr;
307-
}
308-
curr = curr.$container;
309-
}
310-
return undefined;
311-
}
312-
313301
private resolveLiteral(node: LiteralExpr) {
314302
const type = match<LiteralExpr, ExpressionType>(node)
315303
.when(isStringLiteral, () => 'String')

packages/schema/src/utils/ast-utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,15 @@ export function getAllDeclarationsFromImports(documents: LangiumDocuments, model
156156
export function isCollectionPredicate(node: AstNode): node is BinaryExpr {
157157
return isBinaryExpr(node) && ['?', '!', '^'].includes(node.operator);
158158
}
159+
160+
161+
export function getContainingDataModel(node: Expression): DataModel | undefined {
162+
let curr: AstNode | undefined = node.$container;
163+
while (curr) {
164+
if (isDataModel(curr)) {
165+
return curr;
166+
}
167+
curr = curr.$container;
168+
}
169+
return undefined;
170+
}

packages/schema/tests/schema/validation/attribute-validation.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,35 @@ describe('Attribute tests', () => {
642642
`)
643643
).toContain('comparison between model-typed fields are not supported');
644644

645+
expect(
646+
await loadModelWithError(`
647+
${prelude}
648+
model User {
649+
id Int @id
650+
lists List[]
651+
todos Todo[]
652+
}
653+
654+
model List {
655+
id Int @id
656+
user User @relation(fields: [userId], references: [id])
657+
userId Int
658+
todos Todo[]
659+
}
660+
661+
model Todo {
662+
id Int @id
663+
user User @relation(fields: [userId], references: [id])
664+
userId Int
665+
list List @relation(fields: [listId], references: [id])
666+
listId Int
667+
668+
@@allow('read', list.user.id == userId)
669+
}
670+
671+
`)
672+
).toContain('comparison between fields of different models are not supported');
673+
645674
expect(
646675
await loadModelWithError(`
647676
${prelude}

0 commit comments

Comments
 (0)