UNPKG

zenstack

Version:

FullStack enhancement for Prisma ORM: seamless integration from database to UI

229 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ast_1 = require("@zenstackhq/language/ast"); const sdk_1 = require("@zenstackhq/sdk"); const langium_1 = require("langium"); const ast_utils_1 = require("../../utils/ast-utils"); const utils_1 = require("./utils"); /** * Validates expressions. */ class ExpressionValidator { validate(expr, accept) { // deal with a few cases where reference resolution fail silently if (!expr.$resolvedType) { if ((0, sdk_1.isAuthInvocation)(expr)) { // check was done at link time accept('error', 'auth() cannot be resolved because no model marked with "@@auth()" or named "User" is found', { node: expr }); } else { const hasReferenceResolutionError = (0, langium_1.streamAst)(expr).some((node) => { if ((0, ast_1.isMemberAccessExpr)(node)) { return !!node.member.error; } if ((0, ast_1.isReferenceExpr)(node)) { return !!node.target.error; } return false; }); if (!hasReferenceResolutionError) { // report silent errors not involving linker errors accept('error', 'Expression cannot be resolved', { node: expr, }); } } } // extra validations by expression type switch (expr.$type) { case 'BinaryExpr': this.validateBinaryExpr(expr, accept); break; } } validateBinaryExpr(expr, accept) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q; switch (expr.operator) { case 'in': { if (typeof ((_a = expr.left.$resolvedType) === null || _a === void 0 ? void 0 : _a.decl) !== 'string' && !(0, ast_1.isEnum)((_b = expr.left.$resolvedType) === null || _b === void 0 ? void 0 : _b.decl)) { accept('error', 'left operand of "in" must be of scalar type', { node: expr.left }); } if (!((_c = expr.right.$resolvedType) === null || _c === void 0 ? void 0 : _c.array)) { accept('error', 'right operand of "in" must be an array', { node: expr.right, }); } this.validateCrossModelFieldComparison(expr, accept); break; } case '>': case '>=': case '<': case '<=': case '&&': case '||': { if ((_d = expr.left.$resolvedType) === null || _d === void 0 ? void 0 : _d.array) { accept('error', 'operand cannot be an array', { node: expr.left }); break; } if ((_e = expr.right.$resolvedType) === null || _e === void 0 ? void 0 : _e.array) { accept('error', 'operand cannot be an array', { node: expr.right }); break; } let supportedShapes; if (['>', '>=', '<', '<='].includes(expr.operator)) { supportedShapes = ['Int', 'Float', 'DateTime', 'Any']; } else { supportedShapes = ['Boolean', 'Any']; } if (typeof ((_f = expr.left.$resolvedType) === null || _f === void 0 ? void 0 : _f.decl) !== 'string' || !supportedShapes.includes(expr.left.$resolvedType.decl)) { accept('error', `invalid operand type for "${expr.operator}" operator`, { node: expr.left, }); return; } if (typeof ((_g = expr.right.$resolvedType) === null || _g === void 0 ? void 0 : _g.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 }); } if (expr.operator !== '&&' && expr.operator !== '||') { this.validateCrossModelFieldComparison(expr, accept); } break; } case '==': case '!=': { if (this.isInValidationContext(expr)) { // in validation context, all fields are optional, so we should allow // comparing any field against null if (((0, sdk_1.isDataModelFieldReference)(expr.left) && (0, ast_1.isNullExpr)(expr.right)) || ((0, sdk_1.isDataModelFieldReference)(expr.right) && (0, ast_1.isNullExpr)(expr.left))) { return; } } if (!!((_h = expr.left.$resolvedType) === null || _h === void 0 ? void 0 : _h.array) !== !!((_j = expr.right.$resolvedType) === null || _j === void 0 ? void 0 : _j.array)) { accept('error', 'incompatible operand types', { node: expr }); break; } if (!this.validateCrossModelFieldComparison(expr, accept)) { break; } if ((((_k = expr.left.$resolvedType) === null || _k === void 0 ? void 0 : _k.nullable) && (0, ast_1.isNullExpr)(expr.right)) || (((_l = expr.right.$resolvedType) === null || _l === void 0 ? void 0 : _l.nullable) && (0, ast_1.isNullExpr)(expr.left))) { // comparing nullable field with null return; } if (typeof ((_m = expr.left.$resolvedType) === null || _m === void 0 ? void 0 : _m.decl) === 'string' && typeof ((_o = expr.right.$resolvedType) === null || _o === void 0 ? void 0 : _o.decl) === 'string') { // scalar types assignability if (!(0, utils_1.typeAssignable)(expr.left.$resolvedType.decl, expr.right.$resolvedType.decl) && !(0, utils_1.typeAssignable)(expr.right.$resolvedType.decl, expr.left.$resolvedType.decl)) { accept('error', 'incompatible operand types', { node: expr }); } return; } // disallow comparing model type with scalar type or comparison between // incompatible model types const leftType = (_p = expr.left.$resolvedType) === null || _p === void 0 ? void 0 : _p.decl; const rightType = (_q = expr.right.$resolvedType) === null || _q === void 0 ? void 0 : _q.decl; if ((0, ast_1.isDataModel)(leftType) && (0, ast_1.isDataModel)(rightType)) { if (leftType != rightType) { // incompatible model types // TODO: inheritance case? accept('error', 'incompatible operand types', { node: expr }); } // not supported: // - foo == bar // - foo == this if ((0, sdk_1.isDataModelFieldReference)(expr.left) && ((0, ast_1.isThisExpr)(expr.right) || (0, sdk_1.isDataModelFieldReference)(expr.right))) { accept('error', 'comparison between model-typed fields are not supported', { node: expr }); } else if ((0, sdk_1.isDataModelFieldReference)(expr.right) && ((0, ast_1.isThisExpr)(expr.left) || (0, sdk_1.isDataModelFieldReference)(expr.left))) { accept('error', 'comparison between model-typed fields are not supported', { node: expr }); } } else if (((0, ast_1.isDataModel)(leftType) && !(0, ast_1.isNullExpr)(expr.right)) || ((0, ast_1.isDataModel)(rightType) && !(0, ast_1.isNullExpr)(expr.left))) { // comparing model against scalar (except null) accept('error', 'incompatible operand types', { node: expr }); } break; } case '?': case '!': case '^': this.validateCollectionPredicate(expr, accept); break; } } validateCrossModelFieldComparison(expr, accept) { // not supported in "read" rules: // - foo.a == bar // - foo.user.id == userId // except: // - future().userId == userId if (((0, ast_1.isMemberAccessExpr)(expr.left) && (0, ast_1.isDataModelField)(expr.left.member.ref) && expr.left.member.ref.$container != (0, ast_utils_1.getContainingDataModel)(expr)) || ((0, ast_1.isMemberAccessExpr)(expr.right) && (0, ast_1.isDataModelField)(expr.right.member.ref) && expr.right.member.ref.$container != (0, ast_utils_1.getContainingDataModel)(expr))) { // foo.user.id == auth().id // foo.user.id == "123" // foo.user.id == null // foo.user.id == EnumValue if (!(this.isNotModelFieldExpr(expr.left) || this.isNotModelFieldExpr(expr.right))) { const containingPolicyAttr = (0, ast_utils_1.findUpAst)(expr, (node) => (0, ast_1.isDataModelAttribute)(node) && ['@@allow', '@@deny'].includes(node.decl.$refText)); if (containingPolicyAttr) { const operation = (0, sdk_1.getAttributeArgLiteral)(containingPolicyAttr, 'operation'); if ((operation === null || operation === void 0 ? void 0 : operation.split(',').includes('all')) || (operation === null || operation === void 0 ? void 0 : operation.split(',').includes('read'))) { accept('error', 'comparison between fields of different models is not supported in model-level "read" rules', { node: expr, }); return false; } } } } return true; } validateCollectionPredicate(expr, accept) { if (!expr.$resolvedType) { accept('error', 'collection predicate can only be used on an array of model type', { node: expr }); return; } } isInValidationContext(node) { return (0, ast_utils_1.findUpAst)(node, (n) => (0, ast_1.isDataModelAttribute)(n) && n.decl.$refText === '@@validate'); } isNotModelFieldExpr(expr) { return ( // literal (0, ast_1.isLiteralExpr)(expr) || // enum field (0, sdk_1.isEnumFieldReference)(expr) || // null (0, ast_1.isNullExpr)(expr) || // `auth()` access (0, utils_1.isAuthOrAuthMemberAccess)(expr) || // array ((0, ast_1.isArrayExpr)(expr) && expr.items.every((item) => this.isNotModelFieldExpr(item)))); } } exports.default = ExpressionValidator; //# sourceMappingURL=expression-validator.js.map