@halleyassist/rule-templater 0.0.15 → 0.0.17
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/dist/rule-templater.browser.js +100 -6
- package/index.d.ts +26 -1
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/HalleyFunctionBlob.js +126 -0
- package/src/RuleTemplate.js +44 -4
- package/src/RuleTemplate.production.js +44 -4
- package/src/VariableValidate.js +55 -1
|
@@ -3918,17 +3918,20 @@ class RuleTemplate {
|
|
|
3918
3918
|
/**
|
|
3919
3919
|
* Validate variable types against the AST
|
|
3920
3920
|
* @param {Object} variables - Object mapping variable names to {type} objects
|
|
3921
|
-
* @
|
|
3921
|
+
* @param {Object} [functionBlob] - Optional HalleyFunctionBlob used for non-fatal function warnings
|
|
3922
|
+
* @returns {Object} Object with validation results: {valid: boolean, errors: [], warnings: []}
|
|
3922
3923
|
*/
|
|
3923
|
-
validate(variables) {
|
|
3924
|
+
validate(variables, functionBlob) {
|
|
3924
3925
|
if (!variables || typeof variables !== 'object') {
|
|
3925
3926
|
return {
|
|
3926
3927
|
valid: false,
|
|
3927
|
-
errors: ['Variables must be provided as an object']
|
|
3928
|
+
errors: ['Variables must be provided as an object'],
|
|
3929
|
+
warnings: []
|
|
3928
3930
|
};
|
|
3929
3931
|
}
|
|
3930
3932
|
|
|
3931
3933
|
const errors = [];
|
|
3934
|
+
const warnings = [];
|
|
3932
3935
|
const extractedVars = this.extractVariables();
|
|
3933
3936
|
|
|
3934
3937
|
for (const varInfo of extractedVars) {
|
|
@@ -3961,13 +3964,50 @@ class RuleTemplate {
|
|
|
3961
3964
|
}
|
|
3962
3965
|
}
|
|
3963
3966
|
}
|
|
3967
|
+
|
|
3968
|
+
if (functionBlob && typeof functionBlob.validate === 'function') {
|
|
3969
|
+
for (const functionCall of this._extractFunctionCalls()) {
|
|
3970
|
+
warnings.push(...functionBlob.validate(functionCall.name, functionCall.arguments));
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3964
3973
|
|
|
3965
3974
|
return {
|
|
3966
3975
|
valid: errors.length === 0,
|
|
3967
|
-
errors
|
|
3976
|
+
errors,
|
|
3977
|
+
warnings
|
|
3968
3978
|
};
|
|
3969
3979
|
}
|
|
3970
3980
|
|
|
3981
|
+
_extractFunctionCalls() {
|
|
3982
|
+
const functionCalls = [];
|
|
3983
|
+
|
|
3984
|
+
const traverse = (node) => {
|
|
3985
|
+
if (!node) return;
|
|
3986
|
+
|
|
3987
|
+
if (node.type === 'fcall') {
|
|
3988
|
+
const functionName = node.children?.find(c => c.type === 'fname')?.text?.trim();
|
|
3989
|
+
const argumentsNode = node.children?.find(c => c.type === 'arguments');
|
|
3990
|
+
if (functionName) {
|
|
3991
|
+
functionCalls.push({
|
|
3992
|
+
name: functionName,
|
|
3993
|
+
arguments: argumentsNode?.children
|
|
3994
|
+
?.filter(c => c.type === 'argument')
|
|
3995
|
+
.map(c => c.text) || []
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
if (node.children) {
|
|
4001
|
+
for (const child of node.children) {
|
|
4002
|
+
traverse(child);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
};
|
|
4006
|
+
|
|
4007
|
+
traverse(this.ast);
|
|
4008
|
+
return functionCalls;
|
|
4009
|
+
}
|
|
4010
|
+
|
|
3971
4011
|
/**
|
|
3972
4012
|
* Prepare the template by replacing variables with their values
|
|
3973
4013
|
* Rebuilds from AST by iterating through children
|
|
@@ -4311,6 +4351,7 @@ module.exports = TemplateFilters;
|
|
|
4311
4351
|
},{}],19:[function(require,module,exports){
|
|
4312
4352
|
const { Parser } = require('ebnf');
|
|
4313
4353
|
const TemplateGrammar = require('./RuleTemplate.ebnf');
|
|
4354
|
+
const TemplateFilters = require('./TemplateFilters');
|
|
4314
4355
|
const RuleParser = require('@halleyassist/rule-parser');
|
|
4315
4356
|
const RuleParserRules = RuleParser.ParserRules;
|
|
4316
4357
|
|
|
@@ -4357,7 +4398,17 @@ class VariableValidate {
|
|
|
4357
4398
|
};
|
|
4358
4399
|
}
|
|
4359
4400
|
|
|
4360
|
-
|
|
4401
|
+
let normalizedVarData;
|
|
4402
|
+
try {
|
|
4403
|
+
normalizedVarData = VariableValidate._normalizeVarData(variableData);
|
|
4404
|
+
} catch (error) {
|
|
4405
|
+
return {
|
|
4406
|
+
valid: false,
|
|
4407
|
+
error: error.message
|
|
4408
|
+
};
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
return VariableValidate.validateValue(normalizedVarData.type, normalizedVarData.value);
|
|
4361
4412
|
}
|
|
4362
4413
|
|
|
4363
4414
|
static validateValue(type, value) {
|
|
@@ -4424,6 +4475,49 @@ class VariableValidate {
|
|
|
4424
4475
|
return ParserCache;
|
|
4425
4476
|
}
|
|
4426
4477
|
|
|
4478
|
+
static _normalizeVarData(variableData) {
|
|
4479
|
+
const normalizedVarData = VariableValidate._cloneVarData(variableData);
|
|
4480
|
+
|
|
4481
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedVarData, 'value')) {
|
|
4482
|
+
throw new Error('Variable data must include a value property');
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedVarData, 'filters')) {
|
|
4486
|
+
return normalizedVarData;
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
if (!Array.isArray(normalizedVarData.filters)) {
|
|
4490
|
+
throw new Error('Variable data filters must be an array');
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
for (const filterName of normalizedVarData.filters) {
|
|
4494
|
+
if (!TemplateFilters[filterName]) {
|
|
4495
|
+
throw new Error(`Unknown filter '${filterName}'`);
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
TemplateFilters[filterName](normalizedVarData);
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
return normalizedVarData;
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
static _cloneVarData(variableData) {
|
|
4505
|
+
const cloned = Object.assign({}, variableData);
|
|
4506
|
+
if (Array.isArray(cloned.filters)) {
|
|
4507
|
+
cloned.filters = cloned.filters.slice();
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
if (cloned.value && typeof cloned.value === 'object') {
|
|
4511
|
+
if (Array.isArray(cloned.value)) {
|
|
4512
|
+
cloned.value = cloned.value.slice();
|
|
4513
|
+
} else {
|
|
4514
|
+
cloned.value = Object.assign({}, cloned.value);
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
return cloned;
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4427
4521
|
static _serializeString(value) {
|
|
4428
4522
|
if (typeof value !== 'string') {
|
|
4429
4523
|
return null;
|
|
@@ -4654,5 +4748,5 @@ VariableValidate.validators = Object.freeze({
|
|
|
4654
4748
|
});
|
|
4655
4749
|
|
|
4656
4750
|
module.exports = VariableValidate;
|
|
4657
|
-
},{"./RuleTemplate.ebnf":15,"@halleyassist/rule-parser":2,"ebnf":13}]},{},[14])(14)
|
|
4751
|
+
},{"./RuleTemplate.ebnf":15,"./TemplateFilters":18,"@halleyassist/rule-parser":2,"ebnf":13}]},{},[14])(14)
|
|
4658
4752
|
});
|
package/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface VariableValue {
|
|
|
15
15
|
to: string;
|
|
16
16
|
ago?: [number, string];
|
|
17
17
|
} | Record<string, any> | string[] | number[] | boolean[] | Record<string, any>[];
|
|
18
|
+
filters?: string[];
|
|
18
19
|
type?: 'string' | 'number' | 'boolean' | 'object' | 'time period' | 'time period ago' | 'time value' | 'number time' | 'string array' | 'number array' | 'boolean array' | 'object array';
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -25,6 +26,7 @@ export interface Variables {
|
|
|
25
26
|
export interface ValidationResult {
|
|
26
27
|
valid: boolean;
|
|
27
28
|
errors: string[];
|
|
29
|
+
warnings: string[];
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface VariableValidationResult {
|
|
@@ -59,6 +61,17 @@ export interface TemplateFiltersType {
|
|
|
59
61
|
[key: string]: FilterFunction;
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
export interface HalleyFunctionDefinition {
|
|
65
|
+
name: string;
|
|
66
|
+
arguments: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface HalleyFunctionBlobData {
|
|
70
|
+
_schema?: number;
|
|
71
|
+
version?: string;
|
|
72
|
+
functions?: HalleyFunctionDefinition[];
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
export class RuleTemplate {
|
|
63
76
|
ruleTemplateText: string;
|
|
64
77
|
ast: ASTNode;
|
|
@@ -89,7 +102,7 @@ export class RuleTemplate {
|
|
|
89
102
|
* @param variables Object mapping variable names to {value, type} objects
|
|
90
103
|
* @returns Object with validation results: {valid, errors}
|
|
91
104
|
*/
|
|
92
|
-
validate(variables: Variables): ValidationResult;
|
|
105
|
+
validate(variables: Variables, functionBlob?: HalleyFunctionBlob): ValidationResult;
|
|
93
106
|
|
|
94
107
|
/**
|
|
95
108
|
* Prepare the template by replacing variables with their values
|
|
@@ -124,6 +137,18 @@ export class GeneralTemplate {
|
|
|
124
137
|
prepare(variables: Variables): string;
|
|
125
138
|
}
|
|
126
139
|
|
|
140
|
+
export class HalleyFunctionBlob {
|
|
141
|
+
_schema?: number;
|
|
142
|
+
version?: string;
|
|
143
|
+
functions: HalleyFunctionDefinition[];
|
|
144
|
+
|
|
145
|
+
constructor(jsonData: HalleyFunctionBlobData);
|
|
146
|
+
|
|
147
|
+
static fromURL(url: string): Promise<HalleyFunctionBlob>;
|
|
148
|
+
|
|
149
|
+
validate(functionName: string, variables?: any[]): string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
127
152
|
export class VariableTemplate {
|
|
128
153
|
templateText: string;
|
|
129
154
|
ast: ASTNode;
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const RuleTemplate = require('./src/RuleTemplate');
|
|
2
2
|
const GeneralTemplate = require('./src/GeneralTemplate');
|
|
3
|
+
const HalleyFunctionBlob = require('./src/HalleyFunctionBlob');
|
|
3
4
|
const VariableTemplate = require('./src/VariableTemplate');
|
|
4
5
|
const VariableValidate = require('./src/VariableValidate');
|
|
5
6
|
|
|
@@ -9,4 +10,5 @@ module.exports.VariableTypes = RuleTemplate.VariableTypes;
|
|
|
9
10
|
module.exports.TemplateFilters = RuleTemplate.TemplateFilters;
|
|
10
11
|
module.exports.VariableValidate = VariableValidate;
|
|
11
12
|
module.exports.GeneralTemplate = GeneralTemplate;
|
|
13
|
+
module.exports.HalleyFunctionBlob = HalleyFunctionBlob;
|
|
12
14
|
module.exports.VariableTemplate = VariableTemplate;
|
package/package.json
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
class HalleyFunctionBlob {
|
|
2
|
+
constructor(jsonData) {
|
|
3
|
+
const blobData = jsonData && typeof jsonData === 'object' ? jsonData : {};
|
|
4
|
+
|
|
5
|
+
this._schema = blobData._schema;
|
|
6
|
+
this.version = blobData.version;
|
|
7
|
+
this.functions = [];
|
|
8
|
+
this.functionMap = new Map();
|
|
9
|
+
|
|
10
|
+
const functions = Array.isArray(blobData.functions) ? blobData.functions : [];
|
|
11
|
+
for (const definition of functions) {
|
|
12
|
+
if (!definition || typeof definition.name !== 'string') {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const normalizedDefinition = {
|
|
17
|
+
name: definition.name,
|
|
18
|
+
arguments: Array.isArray(definition.arguments) ? definition.arguments.slice() : []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
this.functions.push(normalizedDefinition);
|
|
22
|
+
this.functionMap.set(normalizedDefinition.name, normalizedDefinition);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async fromURL(url) {
|
|
27
|
+
if (typeof url !== 'string' || !url.trim()) {
|
|
28
|
+
throw new Error('A function blob URL must be provided');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
32
|
+
throw new Error('Fetch API is not available');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const response = await globalThis.fetch(url);
|
|
36
|
+
if (!response || !response.ok) {
|
|
37
|
+
throw new Error(`Failed to fetch function blob from '${url}'`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new HalleyFunctionBlob(await response.json());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validate(functionName, variables = []) {
|
|
44
|
+
const warnings = [];
|
|
45
|
+
const functionDefinition = this.functionMap.get(functionName);
|
|
46
|
+
|
|
47
|
+
if (!functionDefinition) {
|
|
48
|
+
return [`function '${functionName}' does not exist`];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const providedVariables = Array.isArray(variables) ? variables : [];
|
|
52
|
+
const providedCount = providedVariables.length;
|
|
53
|
+
const parameterRange = this._getParameterRange(functionDefinition.arguments);
|
|
54
|
+
|
|
55
|
+
for (let idx = 0; idx < functionDefinition.arguments.length; idx++) {
|
|
56
|
+
const argumentName = functionDefinition.arguments[idx];
|
|
57
|
+
if (argumentName === '...') {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (providedCount > idx || argumentName.endsWith('?')) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
warnings.push(
|
|
66
|
+
`parameter ${idx + 1} of ${functionName} '${argumentName}' is missing, function expects ${parameterRange}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this._hasTooManyArguments(functionDefinition.arguments, providedCount)) {
|
|
71
|
+
warnings.push(
|
|
72
|
+
`${functionName} received ${providedCount} parameters, function expects ${parameterRange}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return warnings;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_hasTooManyArguments(argumentList, providedCount) {
|
|
80
|
+
const { max } = this._getArgumentBounds(argumentList);
|
|
81
|
+
return max !== Infinity && providedCount > max;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_getParameterRange(argumentList) {
|
|
85
|
+
const { min, max } = this._getArgumentBounds(argumentList);
|
|
86
|
+
|
|
87
|
+
if (max === Infinity) {
|
|
88
|
+
if (min === 0) {
|
|
89
|
+
return 'any number of parameters';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `at least ${min} ${min === 1 ? 'parameter' : 'parameters'}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (min === max) {
|
|
96
|
+
return `${min} ${min === 1 ? 'parameter' : 'parameters'}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return `${min} to ${max} parameters`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_getArgumentBounds(argumentList) {
|
|
103
|
+
let min = 0;
|
|
104
|
+
let max = 0;
|
|
105
|
+
let variadic = false;
|
|
106
|
+
|
|
107
|
+
for (const argumentName of argumentList) {
|
|
108
|
+
if (argumentName === '...') {
|
|
109
|
+
variadic = true;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
max++;
|
|
114
|
+
if (!argumentName.endsWith('?')) {
|
|
115
|
+
min++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
min,
|
|
121
|
+
max: variadic ? Infinity : max
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = HalleyFunctionBlob;
|
package/src/RuleTemplate.js
CHANGED
|
@@ -215,17 +215,20 @@ class RuleTemplate {
|
|
|
215
215
|
/**
|
|
216
216
|
* Validate variable types against the AST
|
|
217
217
|
* @param {Object} variables - Object mapping variable names to {type} objects
|
|
218
|
-
* @
|
|
218
|
+
* @param {Object} [functionBlob] - Optional HalleyFunctionBlob used for non-fatal function warnings
|
|
219
|
+
* @returns {Object} Object with validation results: {valid: boolean, errors: [], warnings: []}
|
|
219
220
|
*/
|
|
220
|
-
validate(variables) {
|
|
221
|
+
validate(variables, functionBlob) {
|
|
221
222
|
if (!variables || typeof variables !== 'object') {
|
|
222
223
|
return {
|
|
223
224
|
valid: false,
|
|
224
|
-
errors: ['Variables must be provided as an object']
|
|
225
|
+
errors: ['Variables must be provided as an object'],
|
|
226
|
+
warnings: []
|
|
225
227
|
};
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
const errors = [];
|
|
231
|
+
const warnings = [];
|
|
229
232
|
const extractedVars = this.extractVariables();
|
|
230
233
|
|
|
231
234
|
for (const varInfo of extractedVars) {
|
|
@@ -258,13 +261,50 @@ class RuleTemplate {
|
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
263
|
}
|
|
264
|
+
|
|
265
|
+
if (functionBlob && typeof functionBlob.validate === 'function') {
|
|
266
|
+
for (const functionCall of this._extractFunctionCalls()) {
|
|
267
|
+
warnings.push(...functionBlob.validate(functionCall.name, functionCall.arguments));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
261
270
|
|
|
262
271
|
return {
|
|
263
272
|
valid: errors.length === 0,
|
|
264
|
-
errors
|
|
273
|
+
errors,
|
|
274
|
+
warnings
|
|
265
275
|
};
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
_extractFunctionCalls() {
|
|
279
|
+
const functionCalls = [];
|
|
280
|
+
|
|
281
|
+
const traverse = (node) => {
|
|
282
|
+
if (!node) return;
|
|
283
|
+
|
|
284
|
+
if (node.type === 'fcall') {
|
|
285
|
+
const functionName = node.children?.find(c => c.type === 'fname')?.text?.trim();
|
|
286
|
+
const argumentsNode = node.children?.find(c => c.type === 'arguments');
|
|
287
|
+
if (functionName) {
|
|
288
|
+
functionCalls.push({
|
|
289
|
+
name: functionName,
|
|
290
|
+
arguments: argumentsNode?.children
|
|
291
|
+
?.filter(c => c.type === 'argument')
|
|
292
|
+
.map(c => c.text) || []
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (node.children) {
|
|
298
|
+
for (const child of node.children) {
|
|
299
|
+
traverse(child);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
traverse(this.ast);
|
|
305
|
+
return functionCalls;
|
|
306
|
+
}
|
|
307
|
+
|
|
268
308
|
/**
|
|
269
309
|
* Prepare the template by replacing variables with their values
|
|
270
310
|
* Rebuilds from AST by iterating through children
|
|
@@ -215,17 +215,20 @@ class RuleTemplate {
|
|
|
215
215
|
/**
|
|
216
216
|
* Validate variable types against the AST
|
|
217
217
|
* @param {Object} variables - Object mapping variable names to {type} objects
|
|
218
|
-
* @
|
|
218
|
+
* @param {Object} [functionBlob] - Optional HalleyFunctionBlob used for non-fatal function warnings
|
|
219
|
+
* @returns {Object} Object with validation results: {valid: boolean, errors: [], warnings: []}
|
|
219
220
|
*/
|
|
220
|
-
validate(variables) {
|
|
221
|
+
validate(variables, functionBlob) {
|
|
221
222
|
if (!variables || typeof variables !== 'object') {
|
|
222
223
|
return {
|
|
223
224
|
valid: false,
|
|
224
|
-
errors: ['Variables must be provided as an object']
|
|
225
|
+
errors: ['Variables must be provided as an object'],
|
|
226
|
+
warnings: []
|
|
225
227
|
};
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
const errors = [];
|
|
231
|
+
const warnings = [];
|
|
229
232
|
const extractedVars = this.extractVariables();
|
|
230
233
|
|
|
231
234
|
for (const varInfo of extractedVars) {
|
|
@@ -258,13 +261,50 @@ class RuleTemplate {
|
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
263
|
}
|
|
264
|
+
|
|
265
|
+
if (functionBlob && typeof functionBlob.validate === 'function') {
|
|
266
|
+
for (const functionCall of this._extractFunctionCalls()) {
|
|
267
|
+
warnings.push(...functionBlob.validate(functionCall.name, functionCall.arguments));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
261
270
|
|
|
262
271
|
return {
|
|
263
272
|
valid: errors.length === 0,
|
|
264
|
-
errors
|
|
273
|
+
errors,
|
|
274
|
+
warnings
|
|
265
275
|
};
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
_extractFunctionCalls() {
|
|
279
|
+
const functionCalls = [];
|
|
280
|
+
|
|
281
|
+
const traverse = (node) => {
|
|
282
|
+
if (!node) return;
|
|
283
|
+
|
|
284
|
+
if (node.type === 'fcall') {
|
|
285
|
+
const functionName = node.children?.find(c => c.type === 'fname')?.text?.trim();
|
|
286
|
+
const argumentsNode = node.children?.find(c => c.type === 'arguments');
|
|
287
|
+
if (functionName) {
|
|
288
|
+
functionCalls.push({
|
|
289
|
+
name: functionName,
|
|
290
|
+
arguments: argumentsNode?.children
|
|
291
|
+
?.filter(c => c.type === 'argument')
|
|
292
|
+
.map(c => c.text) || []
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (node.children) {
|
|
298
|
+
for (const child of node.children) {
|
|
299
|
+
traverse(child);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
traverse(this.ast);
|
|
305
|
+
return functionCalls;
|
|
306
|
+
}
|
|
307
|
+
|
|
268
308
|
/**
|
|
269
309
|
* Prepare the template by replacing variables with their values
|
|
270
310
|
* Rebuilds from AST by iterating through children
|
package/src/VariableValidate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { Parser } = require('ebnf');
|
|
2
2
|
const TemplateGrammar = require('./RuleTemplate.ebnf');
|
|
3
|
+
const TemplateFilters = require('./TemplateFilters');
|
|
3
4
|
const RuleParser = require('@halleyassist/rule-parser');
|
|
4
5
|
const RuleParserRules = RuleParser.ParserRules;
|
|
5
6
|
|
|
@@ -46,7 +47,17 @@ class VariableValidate {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
let normalizedVarData;
|
|
51
|
+
try {
|
|
52
|
+
normalizedVarData = VariableValidate._normalizeVarData(variableData);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: error.message
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return VariableValidate.validateValue(normalizedVarData.type, normalizedVarData.value);
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
static validateValue(type, value) {
|
|
@@ -113,6 +124,49 @@ class VariableValidate {
|
|
|
113
124
|
return ParserCache;
|
|
114
125
|
}
|
|
115
126
|
|
|
127
|
+
static _normalizeVarData(variableData) {
|
|
128
|
+
const normalizedVarData = VariableValidate._cloneVarData(variableData);
|
|
129
|
+
|
|
130
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedVarData, 'value')) {
|
|
131
|
+
throw new Error('Variable data must include a value property');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedVarData, 'filters')) {
|
|
135
|
+
return normalizedVarData;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!Array.isArray(normalizedVarData.filters)) {
|
|
139
|
+
throw new Error('Variable data filters must be an array');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const filterName of normalizedVarData.filters) {
|
|
143
|
+
if (!TemplateFilters[filterName]) {
|
|
144
|
+
throw new Error(`Unknown filter '${filterName}'`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
TemplateFilters[filterName](normalizedVarData);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return normalizedVarData;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static _cloneVarData(variableData) {
|
|
154
|
+
const cloned = Object.assign({}, variableData);
|
|
155
|
+
if (Array.isArray(cloned.filters)) {
|
|
156
|
+
cloned.filters = cloned.filters.slice();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (cloned.value && typeof cloned.value === 'object') {
|
|
160
|
+
if (Array.isArray(cloned.value)) {
|
|
161
|
+
cloned.value = cloned.value.slice();
|
|
162
|
+
} else {
|
|
163
|
+
cloned.value = Object.assign({}, cloned.value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return cloned;
|
|
168
|
+
}
|
|
169
|
+
|
|
116
170
|
static _serializeString(value) {
|
|
117
171
|
if (typeof value !== 'string') {
|
|
118
172
|
return null;
|