@swaggerexpert/arazzo-criterion 1.0.0

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.
Files changed (37) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +20 -0
  3. package/README.md +406 -0
  4. package/SECURITY.md +15 -0
  5. package/cjs/apg-lite.cjs +1223 -0
  6. package/cjs/errors/ArazzoCriterionError.cjs +46 -0
  7. package/cjs/errors/ArazzoCriterionEvaluateError.cjs +10 -0
  8. package/cjs/errors/ArazzoCriterionParseError.cjs +10 -0
  9. package/cjs/evaluate/index.cjs +139 -0
  10. package/cjs/grammar.cjs +1111 -0
  11. package/cjs/index.cjs +83 -0
  12. package/cjs/parse/callbacks/cst.cjs +30 -0
  13. package/cjs/parse/index.cjs +42 -0
  14. package/cjs/parse/trace/Expectations.cjs +12 -0
  15. package/cjs/parse/trace/Trace.cjs +37 -0
  16. package/cjs/parse/translators/ASTTranslator/index.cjs +17 -0
  17. package/cjs/parse/translators/ASTTranslator/transformers.cjs +203 -0
  18. package/cjs/parse/translators/CSTTranslator.cjs +49 -0
  19. package/cjs/parse/translators/XMLTranslator.cjs +14 -0
  20. package/cjs/test/index.cjs +20 -0
  21. package/es/errors/ArazzoCriterionError.mjs +40 -0
  22. package/es/errors/ArazzoCriterionEvaluateError.mjs +3 -0
  23. package/es/errors/ArazzoCriterionParseError.mjs +3 -0
  24. package/es/evaluate/index.mjs +132 -0
  25. package/es/grammar.mjs +1105 -0
  26. package/es/index.mjs +11 -0
  27. package/es/parse/callbacks/cst.mjs +24 -0
  28. package/es/parse/index.mjs +35 -0
  29. package/es/parse/trace/Expectations.mjs +6 -0
  30. package/es/parse/trace/Trace.mjs +30 -0
  31. package/es/parse/translators/ASTTranslator/index.mjs +9 -0
  32. package/es/parse/translators/ASTTranslator/transformers.mjs +195 -0
  33. package/es/parse/translators/CSTTranslator.mjs +42 -0
  34. package/es/parse/translators/XMLTranslator.mjs +7 -0
  35. package/es/test/index.mjs +13 -0
  36. package/package.json +95 -0
  37. package/types/index.d.ts +283 -0
@@ -0,0 +1,132 @@
1
+ import parse from "../parse/index.mjs";
2
+ import ArazzoCriterionEvaluateError from "../errors/ArazzoCriterionEvaluateError.mjs";
3
+ /**
4
+ * Evaluate a "simple" Arazzo criterion condition.
5
+ *
6
+ * Runtime expression values are supplied by the caller through `resolve`, which
7
+ * receives the runtime expression string and its parsed sub-AST, and returns the
8
+ * concrete value.
9
+ *
10
+ * @example
11
+ * evaluate('$statusCode == 200', {
12
+ * resolve: (expression, ast) => (expression === '$statusCode' ? 200 : undefined),
13
+ * }); // => true
14
+ */
15
+ const evaluate = (condition, {
16
+ resolve
17
+ } = {}) => {
18
+ if (typeof condition !== 'string') {
19
+ throw new TypeError('Criterion condition must be a string');
20
+ }
21
+ if (typeof resolve !== 'function') {
22
+ throw new TypeError('evaluate requires a "resolve" function in options');
23
+ }
24
+ const {
25
+ result,
26
+ tree
27
+ } = parse(condition);
28
+ if (!result.success) {
29
+ throw new ArazzoCriterionEvaluateError('Invalid criterion condition', {
30
+ condition
31
+ });
32
+ }
33
+ return toBoolean(evaluateNode(tree, resolve));
34
+ };
35
+ const evaluateNode = (node, resolve) => {
36
+ switch (node.type) {
37
+ case 'LogicalExpression':
38
+ return evaluateLogical(node, resolve);
39
+ case 'UnaryExpression':
40
+ return !toBoolean(evaluateNode(node.argument, resolve));
41
+ case 'BinaryExpression':
42
+ return evaluateComparison(node, resolve);
43
+ case 'Literal':
44
+ return node.value;
45
+ case 'RuntimeExpression':
46
+ return resolve(node.text, node.expression);
47
+ case 'RuntimeExpressionNavigation':
48
+ return evaluateNavigation(node, resolve);
49
+ default:
50
+ throw new ArazzoCriterionEvaluateError(`Unknown AST node type: ${node.type}`);
51
+ }
52
+ };
53
+ const evaluateLogical = (node, resolve) => {
54
+ if (node.operator === '&&') {
55
+ return toBoolean(evaluateNode(node.left, resolve)) && toBoolean(evaluateNode(node.right, resolve));
56
+ }
57
+ // '||'
58
+ return toBoolean(evaluateNode(node.left, resolve)) || toBoolean(evaluateNode(node.right, resolve));
59
+ };
60
+ const evaluateNavigation = (node, resolve) => {
61
+ let value = resolve(node.expression.text, node.expression.expression);
62
+ for (const step of node.navigation) {
63
+ if (value == null) return undefined;
64
+ value = step.type === 'MemberAccess' ? value[step.name] : value[step.value];
65
+ }
66
+ return value;
67
+ };
68
+ const evaluateComparison = (node, resolve) => {
69
+ const left = evaluateNode(node.left, resolve);
70
+ const right = evaluateNode(node.right, resolve);
71
+ switch (node.operator) {
72
+ case '==':
73
+ return looseEquals(left, right);
74
+ case '!=':
75
+ return !looseEquals(left, right);
76
+ case '<':
77
+ case '<=':
78
+ case '>':
79
+ case '>=':
80
+ return relational(node.operator, left, right);
81
+ default:
82
+ throw new ArazzoCriterionEvaluateError(`Unknown comparison operator: ${node.operator}`);
83
+ }
84
+ };
85
+
86
+ // null equals only itself; comparing null to any other value is false.
87
+ const looseEquals = (a, b) => {
88
+ const aNull = a === null || a === undefined;
89
+ const bNull = b === null || b === undefined;
90
+ if (aNull || bNull) return aNull && bNull;
91
+
92
+ // string comparisons MUST be case insensitive
93
+ if (typeof a === 'string' && typeof b === 'string') {
94
+ return a.toLowerCase() === b.toLowerCase();
95
+ }
96
+
97
+ // numeric strings SHOULD be coerced to numbers when compared with a number
98
+ if (typeof a === 'number' && typeof b === 'string' && isNumeric(b)) return a === Number(b);
99
+ if (typeof b === 'number' && typeof a === 'string' && isNumeric(a)) return Number(a) === b;
100
+ return a === b;
101
+ };
102
+ const relational = (operator, a, b) => {
103
+ // comparing null/undefined with a relational operator is always false
104
+ if (a === null || a === undefined || b === null || b === undefined) return false;
105
+ let left = a;
106
+ let right = b;
107
+
108
+ // numeric coercion of numeric strings under numeric operators
109
+ if (typeof left === 'number' && typeof right === 'string' && isNumeric(right)) right = Number(right);else if (typeof right === 'number' && typeof left === 'string' && isNumeric(left)) left = Number(left);else if (typeof left === 'string' && typeof right === 'string') {
110
+ // case-insensitive string ordering
111
+ left = left.toLowerCase();
112
+ right = right.toLowerCase();
113
+ }
114
+ switch (operator) {
115
+ case '<':
116
+ return left < right;
117
+ case '<=':
118
+ return left <= right;
119
+ case '>':
120
+ return left > right;
121
+ case '>=':
122
+ return left >= right;
123
+ default:
124
+ return false;
125
+ }
126
+ };
127
+ const isNumeric = str => str.trim() !== '' && Number.isFinite(Number(str));
128
+
129
+ // A condition passes (truthy) when it evaluates to a truthy value; it fails (falsy)
130
+ // on false, null, or a missing (undefined) value.
131
+ const toBoolean = value => Boolean(value);
132
+ export default evaluate;