Skip to content

Commit

Permalink
fix: improve binary & unary expression applicability check (zenstackh…
Browse files Browse the repository at this point in the history
  • Loading branch information
ymc9 authored Jul 19, 2023
1 parent 87be443 commit eb2d896
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BinaryExpr, Expression, isBinaryExpr, isEnum } from '@zenstackhq/language/ast';
import { BinaryExpr, Expression, ExpressionType, isBinaryExpr, isEnum } from '@zenstackhq/language/ast';
import { ValidationAcceptor } from 'langium';
import { isAuthInvocation } from '../../utils/ast-utils';
import { AstValidator } from '../types';
Expand Down Expand Up @@ -48,6 +48,51 @@ export default class ExpressionValidator implements AstValidator<Expression> {
}
break;
}

case '>':
case '>=':
case '<':
case '<=':
case '&&':
case '||': {
let supportedShapes: ExpressionType[];
if (['>', '>=', '<', '<='].includes(expr.operator)) {
supportedShapes = ['Int', 'Float', 'DateTime', 'Any'];
} else {
supportedShapes = ['Boolean', 'Any'];
}

if (
typeof expr.left.$resolvedType?.decl !== 'string' ||
!supportedShapes.includes(expr.left.$resolvedType.decl)
) {
accept('error', `invalid operand type for "${expr.operator}" operator`, {
node: expr.left,
});
return;
}
if (
typeof expr.right.$resolvedType?.decl !== 'string' ||
!supportedShapes.includes(expr.right.$resolvedType.decl)
) {
accept('error', `invalid operand type for "${expr.operator}" operator`, {
node: expr.right,
});
return;
}

// DateTime comparison is only allowed between two DateTime values
if (expr.left.$resolvedType.decl === 'DateTime' && expr.right.$resolvedType.decl !== 'DateTime') {
accept('error', 'incompatible operand types', { node: expr });
} else if (
expr.right.$resolvedType.decl === 'DateTime' &&
expr.left.$resolvedType.decl !== 'DateTime'
) {
accept('error', 'incompatible operand types', { node: expr });
}

break;
}
}
}

Expand Down
8 changes: 7 additions & 1 deletion packages/schema/src/language-server/zmodel-linker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,13 @@ export class ZModelLinker extends DefaultLinker {

private resolveUnary(node: UnaryExpr, document: LangiumDocument<AstNode>, extraScopes: ScopeProvider[]) {
this.resolve(node.operand, document, extraScopes);
node.$resolvedType = node.operand.$resolvedType;
switch (node.operator) {
case '!':
this.resolveToBuiltinTypeOrDecl(node, 'Boolean');
break;
default:
throw Error(`Unsupported unary operator: ${node.operator}`);
}
}

private resolveObject(node: ObjectExpr, document: LangiumDocument<AstNode>, extraScopes: ScopeProvider[]) {
Expand Down
110 changes: 110 additions & 0 deletions packages/schema/tests/schema/validation/attribute-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,116 @@ describe('Attribute tests', () => {
).toContain(`Value is not assignable to parameter`);
});

it('policy expressions', async () => {
await loadModel(`
${prelude}
model A {
id String @id
x Int
x1 Int
y DateTime
y1 DateTime
z Float
z1 Decimal
foo Boolean
bar Boolean
@@allow('all', x > 0)
@@allow('all', x > x1)
@@allow('all', y >= y1)
@@allow('all', z < z1)
@@allow('all', z1 < z)
@@allow('all', x < z)
@@allow('all', x < z1)
@@allow('all', foo && bar)
@@allow('all', foo || bar)
@@allow('all', !foo)
}
`);

expect(
await loadModelWithError(`
${prelude}
model A {
id String @id
x String
@@allow('all', x > 0)
}
`)
).toContain('invalid operand type for ">" operator');

expect(
await loadModelWithError(`
${prelude}
model A {
id String @id
x String
@@allow('all', x < 0)
}
`)
).toContain('invalid operand type for "<" operator');

expect(
await loadModelWithError(`
${prelude}
model A {
id String @id
x String
y String
@@allow('all', x < y)
}
`)
).toContain('invalid operand type for "<" operator');

expect(
await loadModelWithError(`
${prelude}
model A {
id String @id
x String
y String
@@allow('all', x <= y)
}
`)
).toContain('invalid operand type for "<=" operator');

expect(
await loadModelWithError(`
${prelude}
model A {
id String @id
x Int
y DateTime
@@allow('all', x <= y)
}
`)
).toContain('incompatible operand types');

expect(
await loadModelWithError(`
${prelude}
model A {
id String @id
x String
y String
@@allow('all', x && y)
}
`)
).toContain('invalid operand type for "&&" operator');

expect(
await loadModelWithError(`
${prelude}
model A {
id String @id
x String
y String
@@allow('all', x || y)
}
`)
).toContain('invalid operand type for "||" operator');
});

it('policy filter function check', async () => {
await loadModel(`
${prelude}
Expand Down

0 comments on commit eb2d896

Please sign in to comment.