@xano/xanoscript-language-server 11.8.3 → 11.8.5
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/.claude/settings.local.json +2 -1
- package/cache/documentCache.js +58 -10
- package/lexer/db.js +9 -1
- package/lexer/security.js +16 -0
- package/onCompletion/onCompletion.js +61 -1
- package/onDefinition/onDefinition.js +150 -0
- package/onDefinition/onDefinition.spec.js +313 -0
- package/onDidChangeContent/onDidChangeContent.js +52 -5
- package/onHover/functions.md +28 -0
- package/package.json +1 -1
- package/parser/base_parser.js +61 -3
- package/parser/clauses/middlewareClause.js +16 -0
- package/parser/definitions/columnDefinition.js +5 -0
- package/parser/functions/api/apiCallFn.js +5 -3
- package/parser/functions/controls/functionCallFn.js +5 -3
- package/parser/functions/controls/functionRunFn.js +61 -5
- package/parser/functions/controls/taskCallFn.js +5 -3
- package/parser/functions/db/captureFieldName.js +63 -0
- package/parser/functions/db/dbAddFn.js +5 -3
- package/parser/functions/db/dbAddOrEditFn.js +13 -3
- package/parser/functions/db/dbBulkAddFn.js +5 -3
- package/parser/functions/db/dbBulkDeleteFn.js +5 -3
- package/parser/functions/db/dbBulkPatchFn.js +5 -3
- package/parser/functions/db/dbBulkUpdateFn.js +5 -3
- package/parser/functions/db/dbDelFn.js +10 -3
- package/parser/functions/db/dbEditFn.js +13 -3
- package/parser/functions/db/dbGetFn.js +10 -3
- package/parser/functions/db/dbHasFn.js +9 -3
- package/parser/functions/db/dbPatchFn.js +10 -3
- package/parser/functions/db/dbQueryFn.js +29 -3
- package/parser/functions/db/dbSchemaFn.js +5 -3
- package/parser/functions/db/dbTruncateFn.js +5 -3
- package/parser/functions/middlewareCallFn.js +3 -1
- package/parser/functions/security/register.js +19 -9
- package/parser/functions/security/securityCreateAuthTokenFn.js +22 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweDecodeLegacyFn.spec.js +26 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.js +24 -0
- package/parser/functions/security/securityJweEncodeLegacyFn.spec.js +25 -0
- package/parser/functions/securityFn.js +2 -0
- package/parser/functions/varFn.js +1 -1
- package/parser/generic/asVariable.js +2 -0
- package/parser/generic/assignableVariableAs.js +1 -0
- package/parser/generic/assignableVariableProperty.js +5 -2
- package/parser/table_trigger_parser.js +21 -0
- package/parser/table_trigger_parser.spec.js +29 -0
- package/parser/tests/variable_test/coverage_check.xs +293 -0
- package/parser/variableScanner.js +64 -0
- package/parser/variableValidator.js +44 -0
- package/parser/variableValidator.spec.js +179 -0
- package/server.js +164 -10
- package/utils.js +32 -0
- package/utils.spec.js +93 -1
- package/workspace/crossFileValidator.js +166 -0
- package/workspace/crossFileValidator.spec.js +654 -0
- package/workspace/referenceTracking.spec.js +420 -0
- package/workspace/workspaceIndex.js +149 -0
- package/workspace/workspaceIndex.spec.js +189 -0
|
@@ -2,6 +2,7 @@ import { CallToken } from "../../lexer/action.js";
|
|
|
2
2
|
import { StringLiteral } from "../../lexer/literal.js";
|
|
3
3
|
import { MiddlewareToken } from "../../lexer/middleware.js";
|
|
4
4
|
import { DotToken, Identifier } from "../../lexer/tokens.js";
|
|
5
|
+
import { getVarName } from "../generic/utils.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
*
|
|
@@ -15,10 +16,11 @@ export function middlewareCallFn($) {
|
|
|
15
16
|
$.CONSUME(DotToken); // "."
|
|
16
17
|
const fnToken = $.CONSUME(CallToken); // "call"
|
|
17
18
|
|
|
18
|
-
$.OR([
|
|
19
|
+
const nameToken = $.OR([
|
|
19
20
|
{ ALT: () => $.CONSUME(Identifier) }, // user
|
|
20
21
|
{ ALT: () => $.CONSUME1(StringLiteral) }, // "user auth"
|
|
21
22
|
]);
|
|
23
|
+
$.addReference("middleware", getVarName(nameToken), nameToken);
|
|
22
24
|
|
|
23
25
|
$.OPTION(() => {
|
|
24
26
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
@@ -8,7 +8,9 @@ import { securityCreateUuidFn } from "./securityCreateUuidFn.js";
|
|
|
8
8
|
import { securityDecryptFn } from "./securityDecryptFn.js";
|
|
9
9
|
import { securityEncryptFn } from "./securityEncryptFn.js";
|
|
10
10
|
import { securityJweDecodeFn } from "./securityJweDecodeFn.js";
|
|
11
|
+
import { securityJweDecodeLegacyFn } from "./securityJweDecodeLegacyFn.js";
|
|
11
12
|
import { securityJweEncodeFn } from "./securityJweEncodeFn.js";
|
|
13
|
+
import { securityJweEncodeLegacyFn } from "./securityJweEncodeLegacyFn.js";
|
|
12
14
|
import { securityJwsDecodeFn } from "./securityJwsDecodeFn.js";
|
|
13
15
|
import { securityJwsEncodeFn } from "./securityJwsEncodeFn.js";
|
|
14
16
|
import { securityRandomBytesFn } from "./securityRandomBytesFn.js";
|
|
@@ -21,44 +23,52 @@ import { securityRandomNumberFn } from "./securityRandomNumberFn.js";
|
|
|
21
23
|
export const register = ($) => {
|
|
22
24
|
$.securityCheckPasswordFn = $.RULE(
|
|
23
25
|
"securityCheckPasswordFn",
|
|
24
|
-
securityCheckPasswordFn($)
|
|
26
|
+
securityCheckPasswordFn($),
|
|
25
27
|
);
|
|
26
28
|
$.securityCreateAuthTokenFn = $.RULE(
|
|
27
29
|
"securityCreateAuthTokenFn",
|
|
28
|
-
securityCreateAuthTokenFn($)
|
|
30
|
+
securityCreateAuthTokenFn($),
|
|
29
31
|
);
|
|
30
32
|
$.securityCreateCurveKeyFn = $.RULE(
|
|
31
33
|
"securityCreateCurveKeyFn",
|
|
32
|
-
securityCreateCurveKeyFn($)
|
|
34
|
+
securityCreateCurveKeyFn($),
|
|
33
35
|
);
|
|
34
36
|
$.securityCreatePasswordFn = $.RULE(
|
|
35
37
|
"securityCreatePasswordFn",
|
|
36
|
-
securityCreatePasswordFn($)
|
|
38
|
+
securityCreatePasswordFn($),
|
|
37
39
|
);
|
|
38
40
|
$.securityCreateRsaKeyFn = $.RULE(
|
|
39
41
|
"securityCreateRsaKeyFn",
|
|
40
|
-
securityCreateRsaKeyFn($)
|
|
42
|
+
securityCreateRsaKeyFn($),
|
|
41
43
|
);
|
|
42
44
|
$.securityCreateSecretKeyFn = $.RULE(
|
|
43
45
|
"securityCreateSecretKeyFn",
|
|
44
|
-
securityCreateSecretKeyFn($)
|
|
46
|
+
securityCreateSecretKeyFn($),
|
|
45
47
|
);
|
|
46
48
|
$.securityCreateUuidFn = $.RULE(
|
|
47
49
|
"securityCreateUuidFn",
|
|
48
|
-
securityCreateUuidFn($)
|
|
50
|
+
securityCreateUuidFn($),
|
|
49
51
|
);
|
|
50
52
|
$.securityDecryptFn = $.RULE("securityDecryptFn", securityDecryptFn($));
|
|
51
53
|
$.securityEncryptFn = $.RULE("securityEncryptFn", securityEncryptFn($));
|
|
52
54
|
$.securityJweDecodeFn = $.RULE("securityJweDecodeFn", securityJweDecodeFn($));
|
|
53
55
|
$.securityJweEncodeFn = $.RULE("securityJweEncodeFn", securityJweEncodeFn($));
|
|
56
|
+
$.securityJweDecodeLegacyFn = $.RULE(
|
|
57
|
+
"securityJweDecodeLegacyFn",
|
|
58
|
+
securityJweDecodeLegacyFn($),
|
|
59
|
+
);
|
|
60
|
+
$.securityJweEncodeLegacyFn = $.RULE(
|
|
61
|
+
"securityJweEncodeLegacyFn",
|
|
62
|
+
securityJweEncodeLegacyFn($),
|
|
63
|
+
);
|
|
54
64
|
$.securityJwsDecodeFn = $.RULE("securityJwsDecodeFn", securityJwsDecodeFn($));
|
|
55
65
|
$.securityJwsEncodeFn = $.RULE("securityJwsEncodeFn", securityJwsEncodeFn($));
|
|
56
66
|
$.securityRandomBytesFn = $.RULE(
|
|
57
67
|
"securityRandomBytesFn",
|
|
58
|
-
securityRandomBytesFn($)
|
|
68
|
+
securityRandomBytesFn($),
|
|
59
69
|
);
|
|
60
70
|
$.securityRandomNumberFn = $.RULE(
|
|
61
71
|
"securityRandomNumberFn",
|
|
62
|
-
securityRandomNumberFn($)
|
|
72
|
+
securityRandomNumberFn($),
|
|
63
73
|
);
|
|
64
74
|
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { SingleQuotedStringLiteral,StringLiteral } from "../../../lexer/literal.js";
|
|
1
2
|
import { CreateAuthTokenToken } from "../../../lexer/security.js";
|
|
3
|
+
import { getVarName } from "../../generic/utils.js";
|
|
4
|
+
|
|
5
|
+
const stringTokens = new Set([StringLiteral.name, SingleQuotedStringLiteral.name]);
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
@@ -8,6 +12,7 @@ export function securityCreateAuthTokenFn($) {
|
|
|
8
12
|
$.sectionStack.push("securityCreateAuthTokenFn");
|
|
9
13
|
const fnToken = $.CONSUME(CreateAuthTokenToken); // "create_auth_token"
|
|
10
14
|
|
|
15
|
+
const captured = {};
|
|
11
16
|
$.SUBRULE($.schemaParseAttributeFn, {
|
|
12
17
|
ARGS: [
|
|
13
18
|
fnToken,
|
|
@@ -19,9 +24,26 @@ export function securityCreateAuthTokenFn($) {
|
|
|
19
24
|
"description?": "[string]",
|
|
20
25
|
"disabled?": "[boolean]",
|
|
21
26
|
},
|
|
27
|
+
captured,
|
|
22
28
|
],
|
|
23
29
|
});
|
|
24
30
|
|
|
31
|
+
$.ACTION(() => {
|
|
32
|
+
if (!captured.table) return;
|
|
33
|
+
const keyOffset = captured.table.key?.startOffset;
|
|
34
|
+
if (keyOffset == null) return;
|
|
35
|
+
const tokens = $.input;
|
|
36
|
+
for (let i = 0; i < tokens.length - 2; i++) {
|
|
37
|
+
if (tokens[i].startOffset === keyOffset) {
|
|
38
|
+
const valueToken = tokens[i + 2];
|
|
39
|
+
if (valueToken && stringTokens.has(valueToken.tokenType.name)) {
|
|
40
|
+
$.addReference("table", getVarName(valueToken), valueToken);
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
25
47
|
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
26
48
|
$.sectionStack.pop();
|
|
27
49
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EqualToken, LCurly, RCurly } from "../../../lexer/control.js";
|
|
2
|
+
import { JweDecodeLegacyToken } from "../../../lexer/security.js";
|
|
3
|
+
import { Identifier, NewlineToken } from "../../../lexer/tokens.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
7
|
+
*/
|
|
8
|
+
export function securityJweDecodeLegacyFn($) {
|
|
9
|
+
return () => {
|
|
10
|
+
$.sectionStack.push("securityJweDecodeLegacyFn");
|
|
11
|
+
const fnToken = $.CONSUME(JweDecodeLegacyToken); // "jwe_decode_legacy"
|
|
12
|
+
$.CONSUME(LCurly); // "{"
|
|
13
|
+
$.MANY(() => {
|
|
14
|
+
$.AT_LEAST_ONE(() => $.CONSUME(NewlineToken));
|
|
15
|
+
$.CONSUME1(Identifier); // "field_name"
|
|
16
|
+
$.CONSUME(EqualToken); // "="
|
|
17
|
+
$.SUBRULE($.expressionFn);
|
|
18
|
+
});
|
|
19
|
+
$.MANY1(() => $.CONSUME1(NewlineToken));
|
|
20
|
+
$.CONSUME(RCurly); // "}"
|
|
21
|
+
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
22
|
+
$.sectionStack.pop();
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { lexDocument } from "../../../lexer/lexer.js";
|
|
4
|
+
import { parser } from "../../test_parser.js";
|
|
5
|
+
|
|
6
|
+
function parse(inputText) {
|
|
7
|
+
parser.reset();
|
|
8
|
+
const lexResult = lexDocument(inputText);
|
|
9
|
+
parser.input = lexResult.tokens;
|
|
10
|
+
parser.securityJweDecodeLegacyFn();
|
|
11
|
+
return parser;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("securityJweDecodeLegacyFn", () => {
|
|
15
|
+
it("securityJweDecodeLegacyFn accepts attributes and store value in a variable", () => {
|
|
16
|
+
const parser = parse(`jwe_decode_legacy {
|
|
17
|
+
token = $input.magic_token
|
|
18
|
+
key = $env.magic_jwt_secret
|
|
19
|
+
audience = "Xano"
|
|
20
|
+
key_algorithm = "A256KW"
|
|
21
|
+
content_algorithm = "A256CBC-HS512"
|
|
22
|
+
} as $decoded_magic_token`);
|
|
23
|
+
console.log(parser.errors);
|
|
24
|
+
expect(parser.errors).to.be.empty;
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { EqualToken, LCurly, RCurly } from "../../../lexer/control.js";
|
|
2
|
+
import { JweEncodeLegacyToken } from "../../../lexer/security.js";
|
|
3
|
+
import { Identifier, NewlineToken } from "../../../lexer/tokens.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
7
|
+
*/
|
|
8
|
+
export function securityJweEncodeLegacyFn($) {
|
|
9
|
+
return () => {
|
|
10
|
+
$.sectionStack.push("securityJweEncodeLegacyFn");
|
|
11
|
+
const fnToken = $.CONSUME(JweEncodeLegacyToken); // "jwe_encode_legacy"
|
|
12
|
+
$.CONSUME(LCurly); // "{"
|
|
13
|
+
$.MANY(() => {
|
|
14
|
+
$.AT_LEAST_ONE(() => $.CONSUME(NewlineToken));
|
|
15
|
+
$.CONSUME1(Identifier); // "field_name"
|
|
16
|
+
$.CONSUME(EqualToken); // "="
|
|
17
|
+
$.SUBRULE($.expressionFn);
|
|
18
|
+
});
|
|
19
|
+
$.MANY1(() => $.CONSUME1(NewlineToken));
|
|
20
|
+
$.CONSUME(RCurly); // "}"
|
|
21
|
+
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
22
|
+
$.sectionStack.pop();
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { lexDocument } from "../../../lexer/lexer.js";
|
|
4
|
+
import { parser } from "../../test_parser.js";
|
|
5
|
+
|
|
6
|
+
function parse(inputText) {
|
|
7
|
+
parser.reset();
|
|
8
|
+
const lexResult = lexDocument(inputText);
|
|
9
|
+
parser.input = lexResult.tokens;
|
|
10
|
+
parser.securityJweEncodeLegacyFn();
|
|
11
|
+
return parser;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("securityJweEncodeLegacyFn", () => {
|
|
15
|
+
it("securityJweEncodeLegacyFn accepts attributes and store value in a variable", () => {
|
|
16
|
+
const parser = parse(`jwe_encode_legacy {
|
|
17
|
+
payload = $jwt_payload
|
|
18
|
+
audience = "Xano"
|
|
19
|
+
key = $env.magic_jwt_secret
|
|
20
|
+
key_algorithm = "A256KW"
|
|
21
|
+
content_algorithm = "A256CBC-HS512"
|
|
22
|
+
} as $jwt`);
|
|
23
|
+
expect(parser.errors).to.be.empty;
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -20,6 +20,8 @@ export function securityFn($) {
|
|
|
20
20
|
{ ALT: () => $.SUBRULE($.securityEncryptFn) }, // security.encrypt
|
|
21
21
|
{ ALT: () => $.SUBRULE($.securityJweDecodeFn) }, // security.jwe_decode
|
|
22
22
|
{ ALT: () => $.SUBRULE($.securityJweEncodeFn) }, // security.jwe_encode
|
|
23
|
+
{ ALT: () => $.SUBRULE($.securityJweDecodeLegacyFn) }, // security.jwe_decode_legacy
|
|
24
|
+
{ ALT: () => $.SUBRULE($.securityJweEncodeLegacyFn) }, // security.jwe_encode_legacy
|
|
23
25
|
{ ALT: () => $.SUBRULE($.securityJwsDecodeFn) }, // security.jws_decode
|
|
24
26
|
{ ALT: () => $.SUBRULE($.securityJwsEncodeFn) }, // security.jws_encode
|
|
25
27
|
{ ALT: () => $.SUBRULE($.securityRandomBytesFn) }, // security.random_bytes
|
|
@@ -68,7 +68,7 @@ export function varFn($) {
|
|
|
68
68
|
// we can directly add the variable here but
|
|
69
69
|
// not necessary for the `assignableVariableProperty` below
|
|
70
70
|
// which will add it on its own
|
|
71
|
-
$.addVariable(variable.image, "unknown");
|
|
71
|
+
$.addVariable(variable.image, "unknown", variable);
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
},
|
|
@@ -13,7 +13,9 @@ export function asVariable($) {
|
|
|
13
13
|
$.OPTION(() => {
|
|
14
14
|
hasAs = true;
|
|
15
15
|
$.CONSUME(AsToken); // "as"
|
|
16
|
+
$.__assignableIsDeclaration = true;
|
|
16
17
|
$.SUBRULE($.assignableVariableProperty);
|
|
18
|
+
$.__assignableIsDeclaration = false;
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
if (!hasAs && parent) {
|
|
@@ -128,8 +128,11 @@ export function assignableVariableProperty($) {
|
|
|
128
128
|
},
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
// Only register as a declaration when used in an "as $var" context.
|
|
132
|
+
// Other callers (var.update, math.*, text.*) are usages, not declarations,
|
|
133
|
+
// and the token scanner will pick them up automatically.
|
|
134
|
+
if (variableName && $.__assignableIsDeclaration) {
|
|
135
|
+
$.addVariable(variableName, "unknown", variableToken);
|
|
133
136
|
}
|
|
134
137
|
};
|
|
135
138
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EqualToken, LCurly, RCurly } from "../lexer/control.js";
|
|
2
|
+
import { WhereToken } from "../lexer/db.js";
|
|
2
3
|
import { StringLiteral } from "../lexer/literal.js";
|
|
3
4
|
import {
|
|
4
5
|
ActionsToken,
|
|
@@ -20,6 +21,7 @@ export function tableTriggerDeclaration($) {
|
|
|
20
21
|
let hasInput = false;
|
|
21
22
|
let hasStack = false;
|
|
22
23
|
let hasTags = false;
|
|
24
|
+
let hasWhere = false;
|
|
23
25
|
|
|
24
26
|
// @TODO: search
|
|
25
27
|
|
|
@@ -127,6 +129,25 @@ export function tableTriggerDeclaration($) {
|
|
|
127
129
|
$.SUBRULE($.tagsAttribute);
|
|
128
130
|
},
|
|
129
131
|
},
|
|
132
|
+
{
|
|
133
|
+
GATE: () => !hasWhere,
|
|
134
|
+
ALT: () => {
|
|
135
|
+
hasWhere = true;
|
|
136
|
+
const token = $.CONSUME(WhereToken, { LABEL: "where" });
|
|
137
|
+
$.CONSUME1(EqualToken);
|
|
138
|
+
$.SUBRULE($.expressionFn, {
|
|
139
|
+
ARGS: [
|
|
140
|
+
token,
|
|
141
|
+
{
|
|
142
|
+
allowQueryExpression: true,
|
|
143
|
+
allowVariable: true,
|
|
144
|
+
allowExpression: false,
|
|
145
|
+
allowComparison: true,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
},
|
|
130
151
|
]);
|
|
131
152
|
});
|
|
132
153
|
|
|
@@ -121,6 +121,35 @@ describe("table_trigger", () => {
|
|
|
121
121
|
expect(parser.errors).to.be.empty;
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
it("should accept a where clause in actions", () => {
|
|
125
|
+
const parser =
|
|
126
|
+
xanoscriptParser(`// Sends a Slack notification when a conversation's markdown is updated
|
|
127
|
+
table_trigger conversation_slack_notification {
|
|
128
|
+
table = "conversations"
|
|
129
|
+
|
|
130
|
+
input {
|
|
131
|
+
json new
|
|
132
|
+
json old
|
|
133
|
+
enum action {
|
|
134
|
+
values = ["insert", "update", "delete", "truncate"]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
text datasource
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
stack {
|
|
141
|
+
function.run "notifications/send_conversation_slack_notification" {
|
|
142
|
+
input = {id: $input.new.id}
|
|
143
|
+
} as $slack_result
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
where = $db.NEW.markdown != null
|
|
147
|
+
actions = {insert: true, update: true}
|
|
148
|
+
history = 100
|
|
149
|
+
}`);
|
|
150
|
+
expect(parser.errors).to.be.empty;
|
|
151
|
+
});
|
|
152
|
+
|
|
124
153
|
it("should parse actions with fields in any order", () => {
|
|
125
154
|
const parser = xanoscriptParser(`table_trigger foo {
|
|
126
155
|
table = "blah"
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// Valide a couverture pour un assuré
|
|
2
|
+
query "coverage/check" verb=GET {
|
|
3
|
+
api_group = "coverage"
|
|
4
|
+
|
|
5
|
+
input {
|
|
6
|
+
// VIN (17 caractères)
|
|
7
|
+
text vin filters=trim|upper
|
|
8
|
+
|
|
9
|
+
// Code garage (optionnel)
|
|
10
|
+
text shop_code? filters=trim|upper
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
stack {
|
|
14
|
+
// Recherche et validation du véhicule
|
|
15
|
+
group {
|
|
16
|
+
stack {
|
|
17
|
+
// Trouve le vehicle a partir du VIN
|
|
18
|
+
db.query vehicle {
|
|
19
|
+
where = $db.vehicle.vin == $input.vin
|
|
20
|
+
return = {type: "single"}
|
|
21
|
+
mock = {
|
|
22
|
+
"devrait retourner véhicule sans police" : {id: 2, vin: "2HGCM82633A004353", make: "Toyota", model: "Camry", year: 2021, owner_name: "Marie Martin"}
|
|
23
|
+
"devrait échouer si VIN introuvable" : null
|
|
24
|
+
"devrait avoir coverages vide si pas de police": {id: 2, vin: "2HGCM82633A004353", make: "Toyota", model: "Camry", year: 2021, owner_name: "Marie Martin"}
|
|
25
|
+
}
|
|
26
|
+
} as $vehicle
|
|
27
|
+
|
|
28
|
+
// Véhicule requis
|
|
29
|
+
precondition ($vehicle != null) {
|
|
30
|
+
error_type = "notfound"
|
|
31
|
+
error = "Vehicle not found with VIN: " ~ $input.vin
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
var $unused_variable {
|
|
36
|
+
value = "This variable is not used anywhere in the code"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Réponse véhicule
|
|
40
|
+
var $vehicle_response {
|
|
41
|
+
value = {
|
|
42
|
+
vin : $vehicle.vin
|
|
43
|
+
make : $vehicle.make
|
|
44
|
+
model : $vehicle.model
|
|
45
|
+
year : $vehicle.year
|
|
46
|
+
owner_name: $vehicle.owner_name
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Recherche de la police d'assurance active
|
|
53
|
+
group {
|
|
54
|
+
stack {
|
|
55
|
+
// Police active du véhicule
|
|
56
|
+
db.query insurance_policy {
|
|
57
|
+
where = $db.insurance_policy.vehicle_id == $vehicle.id && $db.insurance_policy.status == "active"
|
|
58
|
+
return = {type: "single"}
|
|
59
|
+
mock = {
|
|
60
|
+
"devrait retourner véhicule sans police" : null
|
|
61
|
+
"devrait avoir coverages vide si pas de police": null
|
|
62
|
+
}
|
|
63
|
+
} as $policy
|
|
64
|
+
|
|
65
|
+
// Init réponses
|
|
66
|
+
var $policy_response {
|
|
67
|
+
value = null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var $coverages_response {
|
|
71
|
+
value = []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
var $claims_summary {
|
|
75
|
+
value = {total_claims: 0, recent_claims: []}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Traitement si police active
|
|
81
|
+
conditional {
|
|
82
|
+
if ($policy != null) {
|
|
83
|
+
// Construction de la réponse police
|
|
84
|
+
group {
|
|
85
|
+
stack {
|
|
86
|
+
// Réponse police
|
|
87
|
+
var.update $policy_response {
|
|
88
|
+
value = {
|
|
89
|
+
policy_number : $policy.policy_number
|
|
90
|
+
status : $policy.status
|
|
91
|
+
effective_date : $policy.effective_date
|
|
92
|
+
expiration_date: $policy.expiration_date
|
|
93
|
+
insurer_name : $policy.insurer_name
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Récupération des couvertures
|
|
100
|
+
group {
|
|
101
|
+
stack {
|
|
102
|
+
db.query policy_coverage {
|
|
103
|
+
where = $db.policy_coverage.policy_id == $policy.id
|
|
104
|
+
return = {type: "list"}
|
|
105
|
+
} as $policy_coverages
|
|
106
|
+
|
|
107
|
+
// Requête par couverture
|
|
108
|
+
foreach ($policy_coverages) {
|
|
109
|
+
each as $pc {
|
|
110
|
+
db.get coverage_type {
|
|
111
|
+
field_name = "id"
|
|
112
|
+
field_value = $pc.coverage_type_id
|
|
113
|
+
} as $ctype
|
|
114
|
+
|
|
115
|
+
// Objet couverture
|
|
116
|
+
var $coverage_item {
|
|
117
|
+
value = {
|
|
118
|
+
code : $ctype.code
|
|
119
|
+
name : $ctype.name
|
|
120
|
+
coverage_limit: $pc.coverage_limit
|
|
121
|
+
deductible : $pc.deductible
|
|
122
|
+
is_active : $pc.is_active
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Ajout au tableau
|
|
127
|
+
array.push $coverages_response {
|
|
128
|
+
value = $coverage_item
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Récupération et filtrage des sinistres
|
|
136
|
+
group {
|
|
137
|
+
stack {
|
|
138
|
+
// Récupère TOUS les sinistres puis filtre en code
|
|
139
|
+
db.query claim {
|
|
140
|
+
where = $db.claim.policy_id == $policy.id
|
|
141
|
+
return = {type: "list"}
|
|
142
|
+
} as $all_claims
|
|
143
|
+
|
|
144
|
+
// Total sinistres
|
|
145
|
+
var $total_claims_count {
|
|
146
|
+
value = $all_claims|count
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Date il y a 2 ans
|
|
150
|
+
var $two_years_ago {
|
|
151
|
+
value = now|transform_timestamp:"-2 years"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Filtrage en code (au lieu de SQL)
|
|
155
|
+
var $recent_claims_list {
|
|
156
|
+
value = []
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
foreach ($all_claims) {
|
|
160
|
+
each as $claim {
|
|
161
|
+
// Date en timestamp
|
|
162
|
+
var $claim_date_ts {
|
|
163
|
+
value = $claim.incident_date|to_timestamp
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ⚠️ Filtre en code - devrait être en SQL
|
|
167
|
+
conditional {
|
|
168
|
+
if ($claim_date_ts >= $two_years_ago) {
|
|
169
|
+
var $recent_claim_item {
|
|
170
|
+
value = {
|
|
171
|
+
claim_number : $claim.claim_number
|
|
172
|
+
incident_date : $claim.incident_date
|
|
173
|
+
status : $claim.status
|
|
174
|
+
amount_claimed: $claim.amount_claimed
|
|
175
|
+
amount_paid : $claim.amount_paid
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
array.push $recent_claims_list {
|
|
180
|
+
value = $recent_claim_item
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Résumé sinistres
|
|
188
|
+
var.update $claims_summary {
|
|
189
|
+
value = {
|
|
190
|
+
total_claims : $total_claims_count
|
|
191
|
+
recent_claims: $recent_claims_list
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Recherche du garage si code fourni
|
|
200
|
+
group {
|
|
201
|
+
stack {
|
|
202
|
+
var $queried_by {
|
|
203
|
+
value = null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
conditional {
|
|
207
|
+
if ($input.shop_code != null && ($input.shop_code|strlen) > 0) {
|
|
208
|
+
// Récupère garage
|
|
209
|
+
db.query repair_shop {
|
|
210
|
+
where = $db.repair_shop.shop_code == $input.shop_code
|
|
211
|
+
return = {type: "single"}
|
|
212
|
+
} as $shop
|
|
213
|
+
|
|
214
|
+
// Si garage trouvé
|
|
215
|
+
conditional {
|
|
216
|
+
if ($shop != null) {
|
|
217
|
+
var.update $queried_by {
|
|
218
|
+
value = {shop_code: $shop.shop_code, shop_name: $shop.name}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Construction de la réponse finale
|
|
228
|
+
group {
|
|
229
|
+
stack {
|
|
230
|
+
var $result {
|
|
231
|
+
value = {
|
|
232
|
+
vehicle : $vehicle_response
|
|
233
|
+
policy : $policy_response
|
|
234
|
+
coverages : $coverages_response
|
|
235
|
+
claims_summary: $claims_summary
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Ajoute queried_by si présent
|
|
240
|
+
conditional {
|
|
241
|
+
if ($queried_by != null) {
|
|
242
|
+
var.update $result {
|
|
243
|
+
value = $result|set:"queried_by":$queried_by
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
response = $result
|
|
252
|
+
history = 100
|
|
253
|
+
|
|
254
|
+
test "devrait retourner véhicule sans police" {
|
|
255
|
+
input = {vin: "2HGCM82633A004353"}
|
|
256
|
+
|
|
257
|
+
expect.to_equal ($response.vehicle.vin) {
|
|
258
|
+
value = "2HGCM82633A004353"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
expect.to_equal ($response.vehicle.make) {
|
|
262
|
+
value = "Toyota"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
expect.to_be_null ($response.policy)
|
|
266
|
+
expect.to_be_empty ($response.coverages)
|
|
267
|
+
expect.to_equal ($response.claims_summary.total_claims) {
|
|
268
|
+
value = 0
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
test "devrait échouer si VIN introuvable" {
|
|
273
|
+
input = {vin: "INVALID12345678901"}
|
|
274
|
+
|
|
275
|
+
expect.to_throw {
|
|
276
|
+
exception = "ERROR_CODE_NOT_FOUND"
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
test "devrait avoir coverages vide si pas de police" {
|
|
281
|
+
input = {vin: "2HGCM82633A004353"}
|
|
282
|
+
|
|
283
|
+
expect.to_be_empty ($response.coverages)
|
|
284
|
+
expect.to_be_null ($response.policy)
|
|
285
|
+
expect.to_equal ($response.claims_summary.total_claims) {
|
|
286
|
+
value = 0
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
expect.to_be_empty ($response.claims_summary.recent_claims)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
guid = "n20O4mlUR72ZWVYiGAE-QFk_ZO4"
|
|
293
|
+
}
|