From 1decb41f7d7a3c7355497877a15c1cca366a22d9 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 24 Mar 2023 00:14:55 +0800 Subject: [PATCH 1/4] feat: add support for filter operator functions --- packages/language/src/ast.ts | 7 +- packages/language/src/generated/ast.ts | 11 +- packages/language/src/generated/grammar.ts | 278 +++++++++++------- packages/language/src/zmodel.langium | 13 +- .../language/syntaxes/zmodel.tmLanguage.json | 2 +- .../schema/src/language-server/constants.ts | 11 + .../validator/expression-validator.ts | 37 ++- .../function-invocation-validator.ts | 125 ++++++++ .../src/language-server/validator/utils.ts | 9 +- .../validator/zmodel-validator.ts | 17 +- .../src/language-server/zmodel-linker.ts | 1 + .../access-policy/expression-writer.ts | 218 +++++++++----- packages/schema/src/res/stdlib.zmodel | 24 ++ packages/schema/src/utils/ast-utils.ts | 18 ++ .../tests/generator/expression-writer.test.ts | 246 +++++++++++++--- ...rator.test.ts => zmodel-generator.test.ts} | 2 +- .../validation/attribute-validation.test.ts | 133 +++++++++ 17 files changed, 917 insertions(+), 235 deletions(-) create mode 100644 packages/schema/src/language-server/validator/function-invocation-validator.ts rename packages/schema/tests/generator/{code-generator.test.ts => zmodel-generator.test.ts} (98%) diff --git a/packages/language/src/ast.ts b/packages/language/src/ast.ts index b58e2c737..e5abfb58c 100644 --- a/packages/language/src/ast.ts +++ b/packages/language/src/ast.ts @@ -28,10 +28,11 @@ export const BinaryExprOperatorPriority: Record '<': 3, '>=': 3, '<=': 3, + in: 4, //CollectionPredicateExpr - '^': 4, - '?': 4, - '!': 4, + '^': 5, + '?': 5, + '!': 5, }; declare module './generated/ast' { diff --git a/packages/language/src/generated/ast.ts b/packages/language/src/generated/ast.ts index 46d8e0e0d..711ecadae 100644 --- a/packages/language/src/generated/ast.ts +++ b/packages/language/src/generated/ast.ts @@ -150,7 +150,7 @@ export interface BinaryExpr extends AstNode { readonly $container: Argument | ArrayExpr | AttributeArg | BinaryExpr | DataSourceField | FieldInitializer | FunctionDecl | GeneratorField | MemberAccessExpr | PluginField | UnaryExpr; readonly $type: 'BinaryExpr'; left: Expression - operator: '!' | '!=' | '&&' | '<' | '<=' | '==' | '>' | '>=' | '?' | '^' | '||' + operator: '!' | '!=' | '&&' | '<' | '<=' | '==' | '>' | '>=' | '?' | '^' | 'in' | '||' right: Expression } @@ -318,6 +318,7 @@ export interface FunctionParam extends AstNode { readonly $container: DataModel | Enum | FunctionDecl; readonly $type: 'FunctionParam'; name: string + optional: boolean type: FunctionParamType } @@ -752,6 +753,14 @@ export class ZModelAstReflection extends AbstractAstReflection { ] }; } + case 'FunctionParam': { + return { + name: 'FunctionParam', + mandatory: [ + { name: 'optional', type: 'boolean' } + ] + }; + } case 'FunctionParamType': { return { name: 'FunctionParamType', diff --git a/packages/language/src/generated/grammar.ts b/packages/language/src/generated/grammar.ts index 635c514e8..0292c7c88 100644 --- a/packages/language/src/generated/grammar.ts +++ b/packages/language/src/generated/grammar.ts @@ -64,28 +64,28 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@28" + "$ref": "#/rules@29" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@31" + "$ref": "#/rules@32" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@33" + "$ref": "#/rules@34" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@41" + "$ref": "#/rules@42" }, "arguments": [] } @@ -107,7 +107,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -123,7 +123,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -167,7 +167,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -179,7 +179,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -237,7 +237,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -253,7 +253,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -297,7 +297,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -309,7 +309,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -360,7 +360,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -376,7 +376,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -420,7 +420,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -432,7 +432,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -480,7 +480,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "definition": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@22" + "$ref": "#/rules@23" }, "arguments": [] }, @@ -504,21 +504,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@52" + "$ref": "#/rules@53" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@57" + "$ref": "#/rules@58" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@56" + "$ref": "#/rules@57" }, "arguments": [] } @@ -605,7 +605,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@54" + "$ref": "#/rules@55" }, "arguments": [] } @@ -627,7 +627,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@53" + "$ref": "#/rules@54" }, "arguments": [] } @@ -657,7 +657,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] }, @@ -802,7 +802,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@33" + "$ref": "#/rules@34" }, "deprecatedSyntax": false } @@ -814,7 +814,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@26" + "$ref": "#/rules@27" }, "arguments": [], "cardinality": "?" @@ -881,7 +881,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@23" + "$ref": "#/rules@24" }, "arguments": [] }, @@ -911,7 +911,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@29" + "$ref": "#/rules@30" }, "deprecatedSyntax": false } @@ -1015,7 +1015,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel }, { "$type": "ParserRule", - "name": "ComparisonExpr", + "name": "InExpr", "inferredType": { "$type": "InferredType", "name": "Expression" @@ -1030,6 +1030,68 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel }, "arguments": [] }, + { + "$type": "Group", + "elements": [ + { + "$type": "Action", + "inferredType": { + "$type": "InferredType", + "name": "BinaryExpr" + }, + "feature": "left", + "operator": "=" + }, + { + "$type": "Assignment", + "feature": "operator", + "operator": "=", + "terminal": { + "$type": "Keyword", + "value": "in" + } + }, + { + "$type": "Assignment", + "feature": "right", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@19" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "ComparisonExpr", + "inferredType": { + "$type": "InferredType", + "name": "Expression" + }, + "definition": { + "$type": "Group", + "elements": [ + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@20" + }, + "arguments": [] + }, { "$type": "Group", "elements": [ @@ -1075,7 +1137,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@19" + "$ref": "#/rules@20" }, "arguments": [] } @@ -1105,7 +1167,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@20" + "$ref": "#/rules@21" }, "arguments": [] }, @@ -1146,7 +1208,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@20" + "$ref": "#/rules@21" }, "arguments": [] } @@ -1176,7 +1238,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@21" + "$ref": "#/rules@22" }, "arguments": [] }, @@ -1217,7 +1279,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@21" + "$ref": "#/rules@22" }, "arguments": [] } @@ -1316,7 +1378,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@24" + "$ref": "#/rules@25" }, "arguments": [] } @@ -1349,7 +1411,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@25" + "$ref": "#/rules@26" }, "arguments": [] } @@ -1368,7 +1430,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@25" + "$ref": "#/rules@26" }, "arguments": [] } @@ -1410,7 +1472,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -1454,7 +1516,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@27" + "$ref": "#/rules@28" }, "arguments": [] } @@ -1473,7 +1535,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@27" + "$ref": "#/rules@28" }, "arguments": [] } @@ -1505,7 +1567,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -1551,7 +1613,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [] }, @@ -1568,7 +1630,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -1587,7 +1649,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@29" + "$ref": "#/rules@30" }, "arguments": [] } @@ -1599,7 +1661,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@45" + "$ref": "#/rules@46" }, "arguments": [] } @@ -1633,7 +1695,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [] }, @@ -1646,7 +1708,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -1658,7 +1720,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@30" + "$ref": "#/rules@31" }, "arguments": [] } @@ -1670,7 +1732,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@44" + "$ref": "#/rules@45" }, "arguments": [] }, @@ -1701,7 +1763,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@50" + "$ref": "#/rules@51" }, "arguments": [] } @@ -1718,7 +1780,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] }, @@ -1778,7 +1840,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [] }, @@ -1795,7 +1857,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -1814,7 +1876,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@32" + "$ref": "#/rules@33" }, "arguments": [] } @@ -1826,7 +1888,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@45" + "$ref": "#/rules@46" }, "arguments": [] } @@ -1860,7 +1922,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [] }, @@ -1873,7 +1935,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -1885,7 +1947,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@44" + "$ref": "#/rules@45" }, "arguments": [] }, @@ -1909,7 +1971,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -1925,7 +1987,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -1944,7 +2006,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@34" + "$ref": "#/rules@35" }, "arguments": [] } @@ -1963,7 +2025,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@34" + "$ref": "#/rules@35" }, "arguments": [] } @@ -1989,7 +2051,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@35" + "$ref": "#/rules@36" }, "arguments": [] } @@ -2033,7 +2095,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -2045,7 +2107,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -2061,10 +2123,20 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@35" + "$ref": "#/rules@36" }, "arguments": [] } + }, + { + "$type": "Assignment", + "feature": "optional", + "operator": "?=", + "terminal": { + "$type": "Keyword", + "value": "?" + }, + "cardinality": "?" } ] }, @@ -2091,7 +2163,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@49" + "$ref": "#/rules@50" }, "arguments": [] } @@ -2148,7 +2220,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] }, @@ -2165,14 +2237,14 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@50" + "$ref": "#/rules@51" }, "arguments": [] } @@ -2204,7 +2276,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@36" + "$ref": "#/rules@37" }, "arguments": [] } @@ -2231,7 +2303,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@36" + "$ref": "#/rules@37" }, "arguments": [] } @@ -2258,7 +2330,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@36" + "$ref": "#/rules@37" }, "arguments": [] } @@ -2281,21 +2353,21 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@38" + "$ref": "#/rules@39" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@39" + "$ref": "#/rules@40" }, "arguments": [] }, { "$type": "RuleCall", "rule": { - "$ref": "#/rules@37" + "$ref": "#/rules@38" }, "arguments": [] } @@ -2317,7 +2389,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -2333,7 +2405,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@40" + "$ref": "#/rules@41" }, "arguments": [] } @@ -2352,7 +2424,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@42" + "$ref": "#/rules@43" }, "arguments": [] } @@ -2371,7 +2443,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@42" + "$ref": "#/rules@43" }, "arguments": [] } @@ -2393,7 +2465,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@46" + "$ref": "#/rules@47" }, "arguments": [] }, @@ -2417,7 +2489,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -2439,7 +2511,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -2455,7 +2527,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@43" + "$ref": "#/rules@44" }, "arguments": [] } @@ -2488,7 +2560,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@49" + "$ref": "#/rules@50" }, "arguments": [] }, @@ -2519,7 +2591,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] }, @@ -2579,12 +2651,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@41" + "$ref": "#/rules@42" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@39" + "$ref": "#/rules@40" }, "arguments": [] }, @@ -2601,7 +2673,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@47" + "$ref": "#/rules@48" }, "arguments": [], "cardinality": "?" @@ -2631,7 +2703,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@58" + "$ref": "#/rules@59" }, "arguments": [], "cardinality": "*" @@ -2643,12 +2715,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@41" + "$ref": "#/rules@42" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@38" + "$ref": "#/rules@39" }, "arguments": [] }, @@ -2665,7 +2737,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@47" + "$ref": "#/rules@48" }, "arguments": [], "cardinality": "?" @@ -2699,12 +2771,12 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@41" + "$ref": "#/rules@42" }, "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@37" + "$ref": "#/rules@38" }, "arguments": [] }, @@ -2721,7 +2793,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "RuleCall", "rule": { - "$ref": "#/rules@47" + "$ref": "#/rules@48" }, "arguments": [], "cardinality": "?" @@ -2756,7 +2828,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@48" + "$ref": "#/rules@49" }, "arguments": [] } @@ -2775,7 +2847,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@48" + "$ref": "#/rules@49" }, "arguments": [] } @@ -2807,7 +2879,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@55" + "$ref": "#/rules@56" }, "arguments": [] } @@ -3062,7 +3134,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@34" + "$ref": "#/rules@35" }, "isArray": false, "isRef": false @@ -3070,7 +3142,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@29" + "$ref": "#/rules@30" }, "isArray": false, "isRef": false @@ -3078,7 +3150,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@32" + "$ref": "#/rules@33" }, "isArray": false, "isRef": false @@ -3092,7 +3164,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@28" + "$ref": "#/rules@29" }, "isArray": false, "isRef": false @@ -3100,7 +3172,7 @@ export const ZModelGrammar = (): Grammar => loadedZModelGrammar ?? (loadedZModel { "$type": "AtomType", "refType": { - "$ref": "#/rules@31" + "$ref": "#/rules@32" }, "isArray": false, "isRef": false diff --git a/packages/language/src/zmodel.langium b/packages/language/src/zmodel.langium index 95da83dbd..40566e697 100644 --- a/packages/language/src/zmodel.langium +++ b/packages/language/src/zmodel.langium @@ -94,13 +94,20 @@ CollectionPredicateExpr infers Expression: // right=MultDivExpr // )*; -ComparisonExpr infers Expression: +InExpr infers Expression: CollectionPredicateExpr ( {infer BinaryExpr.left=current} - operator=('>'|'<'|'>='|'<=') + operator=('in') right=CollectionPredicateExpr )*; +ComparisonExpr infers Expression: + InExpr ( + {infer BinaryExpr.left=current} + operator=('>'|'<'|'>='|'<=') + right=InExpr + )*; + EqualityExpr infers Expression: ComparisonExpr ( {infer BinaryExpr.left=current} @@ -174,7 +181,7 @@ FunctionDecl: TRIPLE_SLASH_COMMENT* 'function' name=ID '(' (params+=FunctionParam (',' params+=FunctionParam)*)? ')' ':' returnType=FunctionParamType '{' (expression=Expression)? '}'; FunctionParam: - TRIPLE_SLASH_COMMENT* name=ID ':' type=FunctionParamType; + TRIPLE_SLASH_COMMENT* name=ID ':' type=FunctionParamType (optional?='?')?; FunctionParamType: (type=ExpressionType | reference=[TypeDeclaration]) (array?='[' ']')?; diff --git a/packages/language/syntaxes/zmodel.tmLanguage.json b/packages/language/syntaxes/zmodel.tmLanguage.json index 0230e8ab2..f33d54cf5 100644 --- a/packages/language/syntaxes/zmodel.tmLanguage.json +++ b/packages/language/syntaxes/zmodel.tmLanguage.json @@ -10,7 +10,7 @@ }, { "name": "keyword.control.zmodel", - "match": "\\b(Any|Asc|attribute|BigInt|Boolean|Bytes|ContextType|datasource|DateTime|Decimal|Desc|enum|FieldReference|Float|function|generator|Int|Json|model|Null|Object|plugin|sort|String|TransitiveFieldReference)\\b" + "match": "\\b(Any|Asc|attribute|BigInt|Boolean|Bytes|ContextType|datasource|DateTime|Decimal|Desc|enum|FieldReference|Float|function|generator|in|Int|Json|model|Null|Object|plugin|sort|String|TransitiveFieldReference)\\b" }, { "name": "string.quoted.double.zmodel", diff --git a/packages/schema/src/language-server/constants.ts b/packages/schema/src/language-server/constants.ts index 448a2fae1..7ddfda51e 100644 --- a/packages/schema/src/language-server/constants.ts +++ b/packages/schema/src/language-server/constants.ts @@ -21,3 +21,14 @@ export const PLUGIN_MODULE_NAME = 'plugin.zmodel'; export enum IssueCodes { MissingOppositeRelation = 'miss-opposite-relation', } + +export const FILTER_OPERATOR_FUNCTIONS = [ + 'contains', + 'search', + 'startsWith', + 'endsWith', + 'has', + 'hasEvery', + 'hasSome', + 'isEmpty', +]; diff --git a/packages/schema/src/language-server/validator/expression-validator.ts b/packages/schema/src/language-server/validator/expression-validator.ts index 2f9ca1cf2..f19e9bf99 100644 --- a/packages/schema/src/language-server/validator/expression-validator.ts +++ b/packages/schema/src/language-server/validator/expression-validator.ts @@ -1,6 +1,6 @@ -import { Expression, isBinaryExpr } from '@zenstackhq/language/ast'; +import { BinaryExpr, Expression, isArrayExpr, isBinaryExpr, isEnum, isLiteralExpr } from '@zenstackhq/language/ast'; import { ValidationAcceptor } from 'langium'; -import { isAuthInvocation } from '../../utils/ast-utils'; +import { isAuthInvocation, isDataModelFieldReference, isEnumFieldReference } from '../../utils/ast-utils'; import { AstValidator } from '../types'; /** @@ -8,6 +8,7 @@ import { AstValidator } from '../types'; */ export default class ExpressionValidator implements AstValidator { validate(expr: Expression, accept: ValidationAcceptor): void { + // deal with a few cases where reference resolution fail silently if (!expr.$resolvedType) { if (isAuthInvocation(expr)) { // check was done at link time @@ -20,6 +21,38 @@ export default class ExpressionValidator implements AstValidator { }); } } + + switch (expr.$type) { + case 'BinaryExpr': + this.validateBinaryExpr(expr, accept); + break; + } + } + + private validateBinaryExpr(expr: BinaryExpr, accept: ValidationAcceptor) { + switch (expr.operator) { + case 'in': { + if (!isDataModelFieldReference(expr.left)) { + accept('error', 'left operand of "in" must be a field reference', { node: expr.left }); + } + + if (typeof expr.left.$resolvedType?.decl !== 'string' && !isEnum(expr.left.$resolvedType?.decl)) { + accept('error', 'left operand of "in" must be of scalar type', { node: expr.left }); + } + + if ( + !( + isArrayExpr(expr.right) && + expr.right.items.every((item) => isLiteralExpr(item) || isEnumFieldReference(item)) + ) + ) { + accept('error', 'right operand of "in" must be an array of literals or enum values', { + node: expr.right, + }); + } + break; + } + } } private isCollectionPredicate(expr: Expression) { diff --git a/packages/schema/src/language-server/validator/function-invocation-validator.ts b/packages/schema/src/language-server/validator/function-invocation-validator.ts new file mode 100644 index 000000000..5165c85b1 --- /dev/null +++ b/packages/schema/src/language-server/validator/function-invocation-validator.ts @@ -0,0 +1,125 @@ +import { + Argument, + Expression, + FunctionDecl, + FunctionParam, + InvocationExpr, + isArrayExpr, + isLiteralExpr, +} from '@zenstackhq/language/ast'; +import { ValidationAcceptor } from 'langium'; +import { isDataModelFieldReference, isEnumFieldReference } from '../../utils/ast-utils'; +import { FILTER_OPERATOR_FUNCTIONS } from '../constants'; +import { AstValidator } from '../types'; +import { isFromStdlib } from '../utils'; +import { typeAssignable } from './utils'; + +/** + * Validates expressions. + */ +export default class FunctionInvocationValidator implements AstValidator { + validate(expr: InvocationExpr, accept: ValidationAcceptor): void { + const funcDecl = expr.function.ref; + if (!funcDecl) { + accept('error', 'function cannot be resolved', { node: expr }); + return; + } + + if (!this.validateArgs(funcDecl, expr.args, accept)) { + return; + } + + if (isFromStdlib(funcDecl)) { + if (FILTER_OPERATOR_FUNCTIONS.includes(funcDecl.name)) { + // validate filter operators + + // first argument must be a field reference + const firstArg = expr.args?.[0]?.value; + if (firstArg) { + if (!isDataModelFieldReference(firstArg)) { + accept('error', 'first argument must be a field reference', { node: firstArg }); + } + } + + // second argument must be a literal or array of literal + const secondArg = expr.args?.[1]?.value; + if ( + secondArg && + // literal + !isLiteralExpr(secondArg) && + // enum field + !isEnumFieldReference(secondArg) && + // array of literal/enum + !( + isArrayExpr(secondArg) && + secondArg.items.every((item) => isLiteralExpr(item) || isEnumFieldReference(item)) + ) + ) { + accept('error', 'second argument must be a literal, an enum, or an array of them', { + node: secondArg, + }); + } + } + } + } + + private validateArgs(funcDecl: FunctionDecl, args: Argument[], accept: ValidationAcceptor) { + let success = true; + for (let i = 0; i < funcDecl.params.length; i++) { + const param = funcDecl.params[i]; + const arg = args[i]; + if (!arg) { + if (!param.optional) { + accept('error', `missing argument for parameter "${param.name}"`, { node: funcDecl }); + success = false; + } + } else { + if (!this.validateInvocationArg(arg, param, accept)) { + success = false; + } + } + } + return success; + } + + private validateInvocationArg(arg: Argument, param: FunctionParam, accept: ValidationAcceptor) { + const argResolvedType = arg?.value?.$resolvedType; + if (!argResolvedType) { + accept('error', 'argument cannot be resolved', { node: arg }); + return false; + } + + const dstType = param.type.type; + if (!dstType) { + accept('error', 'parameter type cannot be resolved', { node: param }); + return false; + } + + const dstIsArray = param.type.array; + const dstRef = param.type.reference; + + if (dstType === 'Any' && !dstIsArray) { + return true; + } + + if (typeof argResolvedType.decl === 'string') { + // scalar type + if (!typeAssignable(dstType, argResolvedType.decl, arg.value) || dstIsArray !== argResolvedType.array) { + accept('error', `argument is not assignable to parameter`, { + node: arg, + }); + return false; + } + } else { + // enum or model type + if ((dstRef?.ref !== argResolvedType.decl && dstType !== 'Any') || dstIsArray !== argResolvedType.array) { + accept('error', `argument is not assignable to parameter`, { + node: arg, + }); + return false; + } + } + + return true; + } +} diff --git a/packages/schema/src/language-server/validator/utils.ts b/packages/schema/src/language-server/validator/utils.ts index 163bef002..c4ec11115 100644 --- a/packages/schema/src/language-server/validator/utils.ts +++ b/packages/schema/src/language-server/validator/utils.ts @@ -122,6 +122,10 @@ export function assignableToAttributeParam( let dstIsArray = param.type.array; const dstRef = param.type.reference; + if (dstType === 'Any' && !dstIsArray) { + return true; + } + // destination is field reference or transitive field reference, check if // argument is reference or array or reference if (dstType === 'FieldReference' || dstType === 'TransitiveFieldReference') { @@ -168,10 +172,7 @@ export function assignableToAttributeParam( } } - return ( - typeAssignable(dstType, argResolvedType.decl, arg.value) && - (dstType === 'Any' || dstIsArray === argResolvedType.array) - ); + return typeAssignable(dstType, argResolvedType.decl, arg.value) && dstIsArray === argResolvedType.array; } else { // reference type return dstRef?.ref === argResolvedType.decl && dstIsArray === argResolvedType.array; diff --git a/packages/schema/src/language-server/validator/zmodel-validator.ts b/packages/schema/src/language-server/validator/zmodel-validator.ts index 077add682..4ea4c59dd 100644 --- a/packages/schema/src/language-server/validator/zmodel-validator.ts +++ b/packages/schema/src/language-server/validator/zmodel-validator.ts @@ -1,5 +1,14 @@ import { AstNode, LangiumDocument, ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; -import { Attribute, DataModel, DataSource, Enum, Expression, Model, ZModelAstType } from '@zenstackhq/language/ast'; +import { + Attribute, + DataModel, + DataSource, + Enum, + Expression, + InvocationExpr, + Model, + ZModelAstType, +} from '@zenstackhq/language/ast'; import type { ZModelServices } from '../zmodel-module'; import SchemaValidator from './schema-validator'; import DataSourceValidator from './datasource-validator'; @@ -7,6 +16,7 @@ import DataModelValidator from './datamodel-validator'; import AttributeValidator from './attribute-validator'; import EnumValidator from './enum-validator'; import ExpressionValidator from './expression-validator'; +import FunctionInvocationValidator from './function-invocation-validator'; /** * Registry for validation checks. @@ -22,6 +32,7 @@ export class ZModelValidationRegistry extends ValidationRegistry { Enum: validator.checkEnum, Attribute: validator.checkAttribute, Expression: validator.checkExpression, + InvocationExpr: validator.checkFunctionInvocation, }; this.register(checks, validator); } @@ -68,4 +79,8 @@ export class ZModelValidator { checkExpression(node: Expression, accept: ValidationAcceptor): void { this.shouldCheck(node) && new ExpressionValidator().validate(node, accept); } + + checkFunctionInvocation(node: InvocationExpr, accept: ValidationAcceptor): void { + this.shouldCheck(node) && new FunctionInvocationValidator().validate(node, accept); + } } diff --git a/packages/schema/src/language-server/zmodel-linker.ts b/packages/schema/src/language-server/zmodel-linker.ts index cbeffab2b..63ca3448a 100644 --- a/packages/schema/src/language-server/zmodel-linker.ts +++ b/packages/schema/src/language-server/zmodel-linker.ts @@ -184,6 +184,7 @@ export class ZModelLinker extends DefaultLinker { case '!=': case '&&': case '||': + case 'in': this.resolve(node.left, document, extraScopes); this.resolve(node.right, document, extraScopes); this.resolveToBuiltinTypeOrDecl(node, 'Boolean'); diff --git a/packages/schema/src/plugins/access-policy/expression-writer.ts b/packages/schema/src/plugins/access-policy/expression-writer.ts index e2449326f..5c0bb0f31 100644 --- a/packages/schema/src/plugins/access-policy/expression-writer.ts +++ b/packages/schema/src/plugins/access-policy/expression-writer.ts @@ -2,6 +2,7 @@ import { BinaryExpr, DataModel, Expression, + InvocationExpr, isDataModel, isDataModelField, isEnumField, @@ -13,13 +14,28 @@ import { ReferenceExpr, UnaryExpr, } from '@zenstackhq/language/ast'; -import { GUARD_FIELD_NAME, PluginError } from '@zenstackhq/sdk'; +import { getLiteral, GUARD_FIELD_NAME, PluginError } from '@zenstackhq/sdk'; import { CodeBlockWriter } from 'ts-morph'; +import { FILTER_OPERATOR_FUNCTIONS } from '../../language-server/constants'; import { getIdField, isAuthInvocation } from '../../utils/ast-utils'; import TypeScriptExpressionTransformer from './typescript-expression-transformer'; import { isFutureExpr } from './utils'; type ComparisonOperator = '==' | '!=' | '>' | '>=' | '<' | '<='; +type FilterOperators = + | 'is' + | 'some' + | 'every' + | 'none' + | 'in' + | 'contains' + | 'search' + | 'startsWith' + | 'endsWith' + | 'has' + | 'hasEvery' + | 'hasSome' + | 'isEmpty'; /** * Utility for writing ZModel expression as Prisma query argument objects into a ts-morph writer @@ -61,6 +77,10 @@ export class ExpressionWriter { this.writeMemberAccess(expr as MemberAccessExpr); break; + case InvocationExpr: + this.writeInvocation(expr as InvocationExpr); + break; + default: throw new Error(`Not implemented: ${expr.$type}`); } @@ -79,15 +99,11 @@ export class ExpressionWriter { private writeMemberAccess(expr: MemberAccessExpr) { this.block(() => { // must be a boolean member - this.writeFieldCondition( - expr.operand, - () => { - this.block(() => { - this.writer.write(`${expr.member.ref?.name}: true`); - }); - }, - 'is' - ); + this.writeFieldCondition(expr.operand, () => { + this.block(() => { + this.writer.write(`${expr.member.ref?.name}: true`); + }); + }); }); } @@ -118,6 +134,10 @@ export class ExpressionWriter { this.writeComparison(expr, expr.operator); break; + case 'in': + this.writeIn(expr); + break; + case '?': case '!': case '^': @@ -126,6 +146,18 @@ export class ExpressionWriter { } } + private writeIn(expr: BinaryExpr) { + this.block(() => { + this.writeFieldCondition( + expr.left, + () => { + this.plain(expr.right); + }, + 'in' + ); + }); + } + private writeCollectionPredicate(expr: BinaryExpr, operator: string) { this.block(() => { this.writeFieldCondition( @@ -217,44 +249,40 @@ export class ExpressionWriter { } this.block(() => { - this.writeFieldCondition( - fieldAccess, - () => { - this.block( - () => { - const dataModel = this.isModelTyped(fieldAccess); - if (dataModel) { - const idField = getIdField(dataModel); - if (!idField) { - throw new PluginError(`Data model ${dataModel.name} does not have an id field`); - } - // comparing with an object, convert to "id" comparison instead - this.writer.write(`${idField.name}: `); - this.block(() => { - this.writeOperator(operator, () => { - // operand ? operand.field : null - this.writer.write('('); - this.plain(operand); - this.writer.write(' ? '); - this.plain(operand); - this.writer.write(`.${idField.name}`); - this.writer.write(' : null'); - this.writer.write(')'); - }); - }); - } else { + this.writeFieldCondition(fieldAccess, () => { + this.block( + () => { + const dataModel = this.isModelTyped(fieldAccess); + if (dataModel) { + const idField = getIdField(dataModel); + if (!idField) { + throw new PluginError(`Data model ${dataModel.name} does not have an id field`); + } + // comparing with an object, convert to "id" comparison instead + this.writer.write(`${idField.name}: `); + this.block(() => { this.writeOperator(operator, () => { + // operand ? operand.field : null + this.writer.write('('); + this.plain(operand); + this.writer.write(' ? '); this.plain(operand); + this.writer.write(`.${idField.name}`); + this.writer.write(' : null'); + this.writer.write(')'); }); - } - }, - // "this" expression is compiled away (to .id access), so we should - // avoid generating a new layer - !isThisExpr(fieldAccess) - ); - }, - 'is' - ); + }); + } else { + this.writeOperator(operator, () => { + this.plain(operand); + }); + } + }, + // "this" expression is compiled away (to .id access), so we should + // avoid generating a new layer + !isThisExpr(fieldAccess) + ); + }); }); } @@ -278,7 +306,8 @@ export class ExpressionWriter { private writeFieldCondition( fieldAccess: Expression, writeCondition: () => void, - relationOp: 'is' | 'some' | 'every' | 'none' + filterOp?: FilterOperators, + extraArgs?: Record ) { let selector: string | undefined; let operand: Expression | undefined; @@ -305,43 +334,37 @@ export class ExpressionWriter { throw new PluginError(`Failed to write FieldAccess expression`); } - if (operand) { - // member access expression - this.writeFieldCondition( - operand, - () => { - this.block( - () => { - this.writer.write(selector + ': '); - if (this.isModelTyped(fieldAccess)) { - // expression is resolved to a model, generate relation query - this.block(() => { - this.writer.write(`${relationOp}: `); - writeCondition(); - }); - } else { - // generate plain query - writeCondition(); - } - }, - // if operand is "this", it doesn't really generate a new layer of query, - // so we should avoid generating a new block - !isThisExpr(operand) - ); - }, - 'is' - ); - } else if (this.isModelTyped(fieldAccess)) { - // reference resolved to a model, generate relation query + const writerFilterOutput = () => { this.writer.write(selector + ': '); - this.block(() => { - this.writer.write(`${relationOp}: `); + if (filterOp) { + this.block(() => { + this.writer.write(`${filterOp}: `); + writeCondition(); + + if (extraArgs) { + for (const [k, v] of Object.entries(extraArgs)) { + this.writer.write(`,\n${k}: `); + this.plain(v); + } + } + }); + } else { writeCondition(); + } + }; + + if (operand) { + // member access expression + this.writeFieldCondition(operand, () => { + this.block( + writerFilterOutput, + // if operand is "this", it doesn't really generate a new layer of query, + // so we should avoid generating a new block + !isThisExpr(operand) + ); }); } else { - // generate a plain query - this.writer.write(selector + ': '); - writeCondition(); + writerFilterOutput(); } } @@ -414,4 +437,41 @@ export class ExpressionWriter { }); }); } + + private writeInvocation(expr: InvocationExpr) { + const funcDecl = expr.function.ref; + if (!funcDecl) { + throw new PluginError(`Failed to resolve function declaration`); + } + + if (FILTER_OPERATOR_FUNCTIONS.includes(funcDecl.name)) { + let valueArg = expr.args[1]?.value; + + // isEmpty function is zero arity, it's mapped to a boolean literal + if (funcDecl.name === 'isEmpty') { + valueArg = { $type: LiteralExpr, value: true } as LiteralExpr; + } + + // contains function has a 3rd argument that indicates whether the comparison should be case-insensitive + let extraArgs: Record | undefined = undefined; + if (funcDecl.name === 'contains') { + if (getLiteral(expr.args[2]?.value) === true) { + extraArgs = { mode: { $type: LiteralExpr, value: 'insensitive' } as LiteralExpr }; + } + } + + this.block(() => { + this.writeFieldCondition( + expr.args[0].value, + () => { + this.plain(valueArg); + }, + funcDecl.name as FilterOperators, + extraArgs + ); + }); + } else { + throw new PluginError(`Unsupported function ${funcDecl.name}`); + } + } } diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index 984f0c1d7..2e0324112 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -98,6 +98,30 @@ function dbgenerated(expr: String): Any { function future(): Any { } +function contains(field: String, search: String, caseSensitive: Boolean?): Boolean { +} + +function search(field: String, search: String): Boolean { +} + +function startsWith(field: String, search: String): Boolean { +} + +function endsWith(field: String, search: String): Boolean { +} + +function has(field: Any[], search: Any): Boolean { +} + +function hasEvery(field: Any[], search: Any[]): Boolean { +} + +function hasSome(field: Any[], search: Any[]): Boolean { +} + +function isEmpty(field: Any[]): Boolean { +} + /** * Marks an attribute to be only applicable to certain field types. */ diff --git a/packages/schema/src/utils/ast-utils.ts b/packages/schema/src/utils/ast-utils.ts index ebde2e6c8..7452d50fa 100644 --- a/packages/schema/src/utils/ast-utils.ts +++ b/packages/schema/src/utils/ast-utils.ts @@ -3,7 +3,11 @@ import { DataModelAttribute, Expression, isDataModel, + isDataModelField, + isEnumField, isInvocationExpr, + isMemberAccessExpr, + isReferenceExpr, Model, } from '@zenstackhq/language/ast'; import { PolicyOperationKind } from '@zenstackhq/runtime'; @@ -103,3 +107,17 @@ export function getIdField(dataModel: DataModel) { export function isAuthInvocation(expr: Expression) { return isInvocationExpr(expr) && expr.function.ref?.name === 'auth' && isFromStdlib(expr.function.ref); } + +export function isEnumFieldReference(expr: Expression) { + return isReferenceExpr(expr) && isEnumField(expr.target.ref); +} + +export function isDataModelFieldReference(expr: Expression): boolean { + if (isReferenceExpr(expr)) { + return isDataModelField(expr.target.ref); + } else if (isMemberAccessExpr(expr)) { + return true; + } else { + return false; + } +} diff --git a/packages/schema/tests/generator/expression-writer.test.ts b/packages/schema/tests/generator/expression-writer.test.ts index 79e22174f..8476418b0 100644 --- a/packages/schema/tests/generator/expression-writer.test.ts +++ b/packages/schema/tests/generator/expression-writer.test.ts @@ -322,10 +322,8 @@ describe('Expression Writer Tests', () => { (model) => model.attributes[0].args[1].value, `{ foo: { - is: { - x : { - lte: 0 - } + x : { + lte: 0 } } }` @@ -351,10 +349,8 @@ describe('Expression Writer Tests', () => { NOT: { foo: { - is: { - x : { - gt: 0 - } + x : { + gt: 0 } } } @@ -380,9 +376,7 @@ describe('Expression Writer Tests', () => { `{ NOT: { foo: { - is: { - x: true - } + x: true } } }` @@ -413,13 +407,9 @@ describe('Expression Writer Tests', () => { (model) => model.attributes[0].args[1].value, `{ foo: { - is: { - bar: { - is: { - x : { - lte: 0 - } - } + bar: { + x : { + lte: 0 } } } @@ -534,12 +524,10 @@ describe('Expression Writer Tests', () => { (model) => model.attributes[0].args[1].value, `{ foo: { - is: { - bars: { - some: { - x: { - lte: 0 - } + bars: { + some: { + x: { + lte: 0 } } } @@ -594,10 +582,8 @@ describe('Expression Writer Tests', () => { { zenstack_guard : false } : { owner: { - is: { - id: { - equals: (user ? user.id : null) - } + id: { + equals: (user ? user.id : null) } } } @@ -623,11 +609,9 @@ describe('Expression Writer Tests', () => { { zenstack_guard : false } : { owner: { - is: { - id: { - not: { - equals: (user ? user.id : null) - } + id: { + not: { + equals: (user ? user.id : null) } } } @@ -653,15 +637,203 @@ describe('Expression Writer Tests', () => { { zenstack_guard : false } : { owner: { - is: { - id: { - equals: (user ? user.id : null) - } + id: { + equals: (user ? user.id : null) } } }` ); }); + + it('filter operators', async () => { + await check( + ` + enum Role { + USER + ADMIN + } + model Test { + id String @id + role Role + @@allow('all', role in [USER, ADMIN]) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + role: { in: [Role.USER, Role.ADMIN] } + } + ` + ); + + await check( + ` + model Test { + id String @id + value String + @@allow('all', contains(value, 'foo')) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + value: { contains: 'foo' } + } + ` + ); + + await check( + ` + model Test { + id String @id + value String + @@allow('all', contains(value, 'foo', true)) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + value: { contains: 'foo', mode: 'insensitive' } + } + ` + ); + + await check( + ` + model Test { + id String @id + value String + @@allow('all', contains(value, 'foo', false)) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + value: { contains: 'foo' } + } + ` + ); + + await check( + ` + model Foo { + id String @id + value String + test Test @relation(fields: [testId], references: [id]) + testId String @unique + } + model Test { + id String @id + foo Foo? + @@allow('all', search(foo.value, 'foo')) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + foo: { + value: { search: 'foo' } + } + } + ` + ); + + await check( + ` + model Test { + id String @id + value String + @@allow('all', startsWith(value, 'foo') && endsWith(value, 'bar')) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + AND: [ { value: { startsWith: 'foo' } }, { value: { endsWith: 'bar' } } ] + } + ` + ); + + await check( + ` + model Test { + id String @id + value String + @@allow('all', !startsWith(value, 'foo')) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + NOT: { value: { startsWith: 'foo' } } + } + ` + ); + + await check( + ` + model Test { + id String @id + values Int[] + @@allow('all', has(values, 1)) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + values: { has: 1 } + } + ` + ); + + await check( + ` + model Test { + id String @id + values Int[] + @@allow('all', hasSome(values, [1, 2])) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + values: { hasSome: [1, 2] } + } + ` + ); + + await check( + ` + model Test { + id String @id + values Int[] + @@allow('all', hasEvery(values, [1, 2])) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + values: { hasEvery: [1, 2] } + } + ` + ); + + await check( + ` + model Test { + id String @id + values Int[] + @@allow('all', isEmpty(values)) + } + `, + (model) => model.attributes[0].args[1].value, + ` + { + values: { isEmpty: true } + } + ` + ); + }); }); async function check(schema: string, getExpr: (model: DataModel) => Expression, expected: string) { diff --git a/packages/schema/tests/generator/code-generator.test.ts b/packages/schema/tests/generator/zmodel-generator.test.ts similarity index 98% rename from packages/schema/tests/generator/code-generator.test.ts rename to packages/schema/tests/generator/zmodel-generator.test.ts index 6df2144bb..91ddacca2 100644 --- a/packages/schema/tests/generator/code-generator.test.ts +++ b/packages/schema/tests/generator/zmodel-generator.test.ts @@ -2,7 +2,7 @@ import { loadModel } from '../utils'; import ZModelCodeGenerator from '../../src/plugins/prisma/zmodel-code-generator'; import { DataModel, DataModelAttribute, DataModelFieldAttribute } from '@zenstackhq/language/ast'; -describe('Code Generator Tests', () => { +describe('ZModel Generator Tests', () => { const generator = new ZModelCodeGenerator(); function checkAttribute(ast: DataModelAttribute | DataModelFieldAttribute, expected: string) { diff --git a/packages/schema/tests/schema/validation/attribute-validation.test.ts b/packages/schema/tests/schema/validation/attribute-validation.test.ts index f93cccec1..46801c100 100644 --- a/packages/schema/tests/schema/validation/attribute-validation.test.ts +++ b/packages/schema/tests/schema/validation/attribute-validation.test.ts @@ -366,6 +366,139 @@ describe('Attribute tests', () => { ).toContain(`Value is not assignable to parameter`); }); + it('filter function check', async () => { + await loadModel(` + ${prelude} + enum E { + E1 + E2 + } + + model M { + id String @id + s String + e E + es E[] + + @@allow('all', e in [E1, E2]) + @@allow('all', contains(s, 'a')) + @@allow('all', contains(s, 'a', true)) + @@allow('all', search(s, 'a')) + @@allow('all', startsWith(s, 'a')) + @@allow('all', endsWith(s, 'a')) + @@allow('all', has(es, E1)) + @@allow('all', hasSome(es, [E1])) + @@allow('all', hasEvery(es, [E1])) + @@allow('all', isEmpty(es)) + } + `); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + s String + @@allow('all', contains(s)) + } + `) + ).toContain('missing argument for parameter "search"'); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + s String + @@allow('all', contains('a', s)) + } + `) + ).toContain('first argument must be a field reference'); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + s String + s1 String + @@allow('all', contains(s, s1)) + } + `) + ).toContain('second argument must be a literal, an enum, or an array of them'); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + i Int + @@allow('all', contains(i, 1)) + } + `) + ).toContain('argument is not assignable to parameter'); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + i Int[] + @@allow('all', 1 in i) + } + `) + ).toContain('left operand of "in" must be a field reference'); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + i Int + @@allow('all', i in 1) + } + `) + ).toContain('right operand of "in" must be an array of literals or enum values'); + + expect( + await loadModelWithError(` + ${prelude} + model N { + id String @id + m M @relation(fields: [mId], references: [id]) + mId String + } + model M { + id String @id + n N? + @@allow('all', n in [1]) + } + `) + ).toContain('left operand of "in" must be of scalar type'); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + x Int + @@allow('all', has(x, 1)) + } + `) + ).toContain('argument is not assignable to parameter'); + + expect( + await loadModelWithError(` + ${prelude} + model M { + id String @id + x Int[] + @@allow('all', hasSome(x, 1)) + } + `) + ).toContain('argument is not assignable to parameter'); + }); + it('auth function check', async () => { expect( await loadModelWithError(` From 3ec4c7c51a068dfd55157f67d674068433544a20 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 24 Mar 2023 09:07:03 +0800 Subject: [PATCH 2/4] updates --- .../schema/src/language-server/constants.ts | 6 +++++ .../validator/expression-validator.ts | 1 + .../function-invocation-validator.ts | 12 ++++++---- .../src/language-server/validator/utils.ts | 2 +- .../access-policy/expression-writer.ts | 1 + packages/schema/src/res/stdlib.zmodel | 24 +++++++++++++++++++ .../validation/attribute-validation.test.ts | 23 ++++++++++++++++++ 7 files changed, 64 insertions(+), 5 deletions(-) diff --git a/packages/schema/src/language-server/constants.ts b/packages/schema/src/language-server/constants.ts index 7ddfda51e..9c62f2882 100644 --- a/packages/schema/src/language-server/constants.ts +++ b/packages/schema/src/language-server/constants.ts @@ -18,10 +18,16 @@ export const STD_LIB_MODULE_NAME = 'stdlib.zmodel'; */ export const PLUGIN_MODULE_NAME = 'plugin.zmodel'; +/** + * Validation issues + */ export enum IssueCodes { MissingOppositeRelation = 'miss-opposite-relation', } +/** + * Filter operation function names (mapped to Prisma filter operators) + */ export const FILTER_OPERATOR_FUNCTIONS = [ 'contains', 'search', diff --git a/packages/schema/src/language-server/validator/expression-validator.ts b/packages/schema/src/language-server/validator/expression-validator.ts index f19e9bf99..ea15766db 100644 --- a/packages/schema/src/language-server/validator/expression-validator.ts +++ b/packages/schema/src/language-server/validator/expression-validator.ts @@ -22,6 +22,7 @@ export default class ExpressionValidator implements AstValidator { } } + // extra validations by expression type switch (expr.$type) { case 'BinaryExpr': this.validateBinaryExpr(expr, accept); diff --git a/packages/schema/src/language-server/validator/function-invocation-validator.ts b/packages/schema/src/language-server/validator/function-invocation-validator.ts index 5165c85b1..e5a5c76f0 100644 --- a/packages/schema/src/language-server/validator/function-invocation-validator.ts +++ b/packages/schema/src/language-server/validator/function-invocation-validator.ts @@ -15,7 +15,7 @@ import { isFromStdlib } from '../utils'; import { typeAssignable } from './utils'; /** - * Validates expressions. + * InvocationExpr validation */ export default class FunctionInvocationValidator implements AstValidator { validate(expr: InvocationExpr, accept: ValidationAcceptor): void { @@ -30,10 +30,12 @@ export default class FunctionInvocationValidator implements AstValidator' | '>=' | '<' | '<='; + type FilterOperators = | 'is' | 'some' diff --git a/packages/schema/src/res/stdlib.zmodel b/packages/schema/src/res/stdlib.zmodel index 2e0324112..f76ef7251 100644 --- a/packages/schema/src/res/stdlib.zmodel +++ b/packages/schema/src/res/stdlib.zmodel @@ -98,27 +98,51 @@ function dbgenerated(expr: String): Any { function future(): Any { } +/* + * If the field value contains the search string + */ function contains(field: String, search: String, caseSensitive: Boolean?): Boolean { } +/* + * If the field value matches the search condition with [full-text-search](https://www.prisma.io/docs/concepts/components/prisma-client/full-text-search). Need to enable "fullTextSearch" preview feature to use. + */ function search(field: String, search: String): Boolean { } +/* + * If the field value starts with the search string + */ function startsWith(field: String, search: String): Boolean { } +/* + * If the field value ends with the search string + */ function endsWith(field: String, search: String): Boolean { } +/* + * If the field value (a list) has the given search value + */ function has(field: Any[], search: Any): Boolean { } +/* + * If the field value (a list) has every element of the search list + */ function hasEvery(field: Any[], search: Any[]): Boolean { } +/* + * If the field value (a list) has at least one element of the search list + */ function hasSome(field: Any[], search: Any[]): Boolean { } +/* + * If the field value (a list) is empty + */ function isEmpty(field: Any[]): Boolean { } diff --git a/packages/schema/tests/schema/validation/attribute-validation.test.ts b/packages/schema/tests/schema/validation/attribute-validation.test.ts index 46801c100..d541d43a9 100644 --- a/packages/schema/tests/schema/validation/attribute-validation.test.ts +++ b/packages/schema/tests/schema/validation/attribute-validation.test.ts @@ -374,11 +374,22 @@ describe('Attribute tests', () => { E2 } + model N { + id String @id + e E + es E[] + s String + i Int + m M @relation(fields: [mId], references: [id]) + mId String @unique + } + model M { id String @id s String e E es E[] + n N? @@allow('all', e in [E1, E2]) @@allow('all', contains(s, 'a')) @@ -390,6 +401,18 @@ describe('Attribute tests', () => { @@allow('all', hasSome(es, [E1])) @@allow('all', hasEvery(es, [E1])) @@allow('all', isEmpty(es)) + + @@allow('all', n.e in [E1, E2]) + @@allow('all', n.i in [1, 2]) + @@allow('all', contains(n.s, 'a')) + @@allow('all', contains(n.s, 'a', true)) + @@allow('all', search(n.s, 'a')) + @@allow('all', startsWith(n.s, 'a')) + @@allow('all', endsWith(n.s, 'a')) + @@allow('all', has(n.es, E1)) + @@allow('all', hasSome(n.es, [E1])) + @@allow('all', hasEvery(n.es, [E1])) + @@allow('all', isEmpty(n.es)) } `); From 4137712e089d0cf6974c210f3de3183fdc1bc537 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 24 Mar 2023 10:14:47 +0800 Subject: [PATCH 3/4] add tests and try out buildjet CI runner --- .github/workflows/build-test.yml | 2 +- .../e2e/filter-function-coverage.test.ts | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 tests/integration/tests/e2e/filter-function-coverage.test.ts diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 511bd9326..de39262eb 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -15,7 +15,7 @@ on: jobs: build-test: - runs-on: ubuntu-latest + runs-on: buildjet-4vcpu-ubuntu-2204 strategy: matrix: diff --git a/tests/integration/tests/e2e/filter-function-coverage.test.ts b/tests/integration/tests/e2e/filter-function-coverage.test.ts new file mode 100644 index 000000000..4f8619832 --- /dev/null +++ b/tests/integration/tests/e2e/filter-function-coverage.test.ts @@ -0,0 +1,66 @@ +import { AuthUser } from '@zenstackhq/runtime'; +import { loadSchema, run, type WeakDbClientContract } from '@zenstackhq/testtools'; +import Decimal from 'decimal.js'; +import superjson from 'superjson'; + +describe('Filter Function Coverage Tests', () => { + it('contains case-sensitive', async () => { + const { withPresets } = await loadSchema( + ` + model Foo { + id String @id @default(cuid()) + string String + @@allow('all', contains(string, 'a')) + } + ` + ); + + await expect(withPresets().foo.create({ data: { string: 'bcd' } })).toBeRejectedByPolicy(); + await expect(withPresets().foo.create({ data: { string: 'bac' } })).toResolveTruthy(); + }); + + it('startsWith', async () => { + const { withPresets } = await loadSchema( + ` + model Foo { + id String @id @default(cuid()) + string String + @@allow('all', startsWith(string, 'a')) + } + ` + ); + + await expect(withPresets().foo.create({ data: { string: 'bac' } })).toBeRejectedByPolicy(); + await expect(withPresets().foo.create({ data: { string: 'abc' } })).toResolveTruthy(); + }); + + it('endsWith', async () => { + const { withPresets } = await loadSchema( + ` + model Foo { + id String @id @default(cuid()) + string String + @@allow('all', endsWith(string, 'a')) + } + ` + ); + + await expect(withPresets().foo.create({ data: { string: 'bac' } })).toBeRejectedByPolicy(); + await expect(withPresets().foo.create({ data: { string: 'bca' } })).toResolveTruthy(); + }); + + it('in', async () => { + const { withPresets } = await loadSchema( + ` + model Foo { + id String @id @default(cuid()) + string String + @@allow('all', string in ['a', 'b']) + } + ` + ); + + await expect(withPresets().foo.create({ data: { string: 'c' } })).toBeRejectedByPolicy(); + await expect(withPresets().foo.create({ data: { string: 'b' } })).toResolveTruthy(); + }); +}); From a7d12133216bc6d337de6c736991d40636eea112 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 24 Mar 2023 10:44:00 +0800 Subject: [PATCH 4/4] update CI and bump version --- .github/workflows/build-test.yml | 2 -- package.json | 2 +- packages/language/package.json | 2 +- packages/next/package.json | 2 +- packages/plugins/openapi/package.json | 2 +- packages/plugins/react/package.json | 2 +- packages/plugins/trpc/package.json | 2 +- packages/runtime/package.json | 2 +- packages/schema/package.json | 2 +- packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- tests/integration/test-run/package-lock.json | 4 ++-- tests/integration/tests/e2e/filter-function-coverage.test.ts | 3 --- 14 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index de39262eb..28481aecf 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -8,8 +8,6 @@ env: DO_NOT_TRACK: '1' on: - push: - branches: ['dev', 'main', 'canary'] pull_request: branches: ['dev', 'main', 'canary'] diff --git a/package.json b/package.json index edcfe5c82..b89c20425 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-monorepo", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "description": "", "scripts": { "build": "pnpm -r build", diff --git a/packages/language/package.json b/packages/language/package.json index 75de13f1f..958842c96 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/language", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "displayName": "ZenStack modeling language compiler", "description": "ZenStack modeling language compiler", "homepage": "https://zenstack.dev", diff --git a/packages/next/package.json b/packages/next/package.json index 6a189d0fb..92acef8fb 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/next", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "displayName": "ZenStack Next.js integration", "description": "ZenStack Next.js integration", "homepage": "https://zenstack.dev", diff --git a/packages/plugins/openapi/package.json b/packages/plugins/openapi/package.json index 62fc4a242..6e97fa57c 100644 --- a/packages/plugins/openapi/package.json +++ b/packages/plugins/openapi/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/openapi", "displayName": "ZenStack Plugin and Runtime for OpenAPI", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "description": "ZenStack plugin and runtime supporting OpenAPI", "main": "index.js", "repository": { diff --git a/packages/plugins/react/package.json b/packages/plugins/react/package.json index 1ab246060..36bc89aaf 100644 --- a/packages/plugins/react/package.json +++ b/packages/plugins/react/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/react", "displayName": "ZenStack plugin and runtime for ReactJS", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "description": "ZenStack plugin and runtime for ReactJS", "main": "index.js", "repository": { diff --git a/packages/plugins/trpc/package.json b/packages/plugins/trpc/package.json index edee7573c..362681501 100644 --- a/packages/plugins/trpc/package.json +++ b/packages/plugins/trpc/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/trpc", "displayName": "ZenStack plugin for tRPC", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "description": "ZenStack plugin for tRPC", "main": "index.js", "repository": { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index e45798a46..37586726a 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/runtime", "displayName": "ZenStack Runtime Library", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "description": "Runtime of ZenStack for both client-side and server-side environments.", "repository": { "type": "git", diff --git a/packages/schema/package.json b/packages/schema/package.json index 4e8378105..0d57272d3 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack Language Tools", "description": "A toolkit for building secure CRUD apps with Next.js + Typescript", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "author": { "name": "ZenStack Team" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 3d718bd59..0881a9408 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "description": "ZenStack plugin development SDK", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 5053e2c9c..066821294 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "displayName": "ZenStack Server-side Adapters", "description": "ZenStack server-side adapters", "homepage": "https://zenstack.dev", diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 7e2b6e85a..6202899b1 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "description": "ZenStack Test Tools", "main": "index.js", "publishConfig": { diff --git a/tests/integration/test-run/package-lock.json b/tests/integration/test-run/package-lock.json index 65febe9d1..3f644e59b 100644 --- a/tests/integration/test-run/package-lock.json +++ b/tests/integration/test-run/package-lock.json @@ -126,7 +126,7 @@ }, "../../../packages/runtime/dist": { "name": "@zenstackhq/runtime", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "license": "MIT", "dependencies": { "@types/bcryptjs": "^2.4.2", @@ -158,7 +158,7 @@ }, "../../../packages/schema/dist": { "name": "zenstack", - "version": "1.0.0-alpha.81", + "version": "1.0.0-alpha.82", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/tests/integration/tests/e2e/filter-function-coverage.test.ts b/tests/integration/tests/e2e/filter-function-coverage.test.ts index 4f8619832..049c45e3b 100644 --- a/tests/integration/tests/e2e/filter-function-coverage.test.ts +++ b/tests/integration/tests/e2e/filter-function-coverage.test.ts @@ -1,7 +1,4 @@ -import { AuthUser } from '@zenstackhq/runtime'; import { loadSchema, run, type WeakDbClientContract } from '@zenstackhq/testtools'; -import Decimal from 'decimal.js'; -import superjson from 'superjson'; describe('Filter Function Coverage Tests', () => { it('contains case-sensitive', async () => {