@checkdigit/eslint-plugin 7.18.0-PR.143-c535 → 7.18.0-PR.143-1b97

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.
@@ -1,103 +1,227 @@
1
1
  // src/athena/api-matcher.ts
2
2
  import debug from "debug";
3
- import { JSONPath } from "jsonpath-plus";
4
3
  var log = debug("eslint-plugin:athena:api-matcher");
5
- function getVersionMatcher(_selectAST, _tableAST) {
6
- return void 0;
4
+ var ALWAYS_TRUE = () => true;
5
+ function rec(node) {
6
+ return typeof node === "object" && node !== null ? node : void 0;
7
7
  }
8
- function getPathPartMatcher(selectAST, _tableAST) {
9
- const [pathPartCondition] = JSONPath({
10
- json: selectAST,
11
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'function' && @.left.name && @.left.name.name && @.left.name.name[0] && @.left.name.name[0].value === 'split' && @.left.args && @.left.args.value && @.left.args.value[0] && @.left.args.value[0].type === 'column_ref' && @.left.args.value[0].column === 'url' && @.left.args.value[1] && @.left.args.value[1].type === 'single_quote_string' && @.left.args.value[1].value === '/' && @.left.array_index && @.left.array_index[0] && @.left.array_index[0].brackets === true && @.left.array_index[0].index && @.left.array_index[0].index.type === 'number')]"
12
- });
13
- if (pathPartCondition !== void 0) {
14
- const [pathPartIndex] = JSONPath({
15
- json: pathPartCondition,
16
- path: "$.left.array_index[0].index.value"
17
- });
18
- const [pathPartMatch] = JSONPath({
19
- json: pathPartCondition,
20
- path: "$.right.value"
21
- });
22
- return (path, _method) => {
23
- const parts = path.split("/");
24
- const part = parts[pathPartIndex - 1];
25
- log(`checking path part`, { path, pathPartIndex, part, pathPartMatch });
26
- return part?.startsWith(":") === true ? true : parts[pathPartIndex - 1] === pathPartMatch;
27
- };
8
+ function getFunctionName(node) {
9
+ const fn = rec(node);
10
+ if (fn?.["type"] !== "function") {
11
+ return void 0;
28
12
  }
29
- return void 0;
13
+ const name = fn["name"];
14
+ const firstName = name?.name[0];
15
+ return firstName === void 0 ? void 0 : firstName.value.toLowerCase();
30
16
  }
31
- function getPathPartsCountMatcher(selectAST, _tableAST) {
32
- const [pathPartCount] = JSONPath({
33
- json: selectAST,
34
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'function' && @.left.name && @.left.name.name && @.left.name.name[0] && @.left.name.name[0].value === 'cardinality' && @.right && @.right.type === 'number' && @.left.args && @.left.args.value && @.left.args.value[0] && @.left.args.value[0].type === 'function' && @.left.args.value[0].name && @.left.args.value[0].name.name && @.left.args.value[0].name.name[0] && @.left.args.value[0].name.name[0].value === 'split' && @.left.args.value[0].args && @.left.args.value[0].args.value && @.left.args.value[0].args.value[0] && @.left.args.value[0].args.value[0].type === 'column_ref' && @.left.args.value[0].args.value[0].column === 'url' && @.left.args.value[0].args.value[1] && @.left.args.value[0].args.value[1].type === 'single_quote_string' && @.left.args.value[0].args.value[1].value === '/')].right.value"
35
- });
36
- if (pathPartCount !== void 0) {
37
- return (path, _method) => {
38
- const parts = path.split("/");
39
- return parts.length === pathPartCount;
40
- };
17
+ function getColumnName(node) {
18
+ const col = rec(node);
19
+ if (col?.["type"] !== "column_ref") {
20
+ return void 0;
41
21
  }
42
- return void 0;
22
+ const column = node.column;
23
+ return typeof column === "string" ? column.toLowerCase() : void 0;
24
+ }
25
+ function getColumnTable(node) {
26
+ const col = rec(node);
27
+ if (col?.["type"] !== "column_ref") {
28
+ return void 0;
29
+ }
30
+ return node.table;
31
+ }
32
+ function getStringValue(node) {
33
+ const val = rec(node);
34
+ if (val?.["type"] !== "single_quote_string" && val?.["type"] !== "string") {
35
+ return void 0;
36
+ }
37
+ return typeof val["value"] === "string" ? val["value"] : void 0;
38
+ }
39
+ function getNumberValue(node) {
40
+ const val = rec(node);
41
+ return val?.["type"] === "number" && typeof val["value"] === "number" ? val["value"] : void 0;
42
+ }
43
+ function isSplitUrlIndexed(node) {
44
+ const fn = rec(node);
45
+ if (fn?.["type"] !== "function") {
46
+ return false;
47
+ }
48
+ if (getFunctionName(node) !== "split") {
49
+ return false;
50
+ }
51
+ const args = fn["args"]?.value;
52
+ if (!Array.isArray(args) || args.length < 2) {
53
+ return false;
54
+ }
55
+ if (getColumnName(args[0]) !== "url") {
56
+ return false;
57
+ }
58
+ if (getStringValue(args[1]) !== "/") {
59
+ return false;
60
+ }
61
+ return Array.isArray(fn["array_index"]) && fn["array_index"].length > 0;
62
+ }
63
+ function isSplitPartUrl(node) {
64
+ const fn = rec(node);
65
+ if (fn?.["type"] !== "function") {
66
+ return false;
67
+ }
68
+ if (getFunctionName(node) !== "split_part") {
69
+ return false;
70
+ }
71
+ const args = fn["args"]?.value;
72
+ if (!Array.isArray(args) || args[2] === void 0) {
73
+ return false;
74
+ }
75
+ if (getColumnName(args[0]) !== "url") {
76
+ return false;
77
+ }
78
+ return getStringValue(args[1]) === "/" && getNumberValue(args[2]) !== void 0;
79
+ }
80
+ function isCardinalitySplitUrl(node) {
81
+ const fn = rec(node);
82
+ if (fn?.["type"] !== "function") {
83
+ return false;
84
+ }
85
+ if (getFunctionName(node) !== "cardinality") {
86
+ return false;
87
+ }
88
+ const outerArgs = fn["args"]?.value;
89
+ if (!Array.isArray(outerArgs) || outerArgs.length === 0) {
90
+ return false;
91
+ }
92
+ const innerFn = rec(outerArgs[0]);
93
+ if (innerFn?.["type"] !== "function") {
94
+ return false;
95
+ }
96
+ if (getFunctionName(outerArgs[0]) !== "split") {
97
+ return false;
98
+ }
99
+ const innerArgs = innerFn["args"]?.value;
100
+ if (!Array.isArray(innerArgs) || innerArgs.length < 2) {
101
+ return false;
102
+ }
103
+ return getColumnName(innerArgs[0]) === "url" && getStringValue(innerArgs[1]) === "/";
43
104
  }
44
- function getPathMatchers(selectAST, tableAST) {
45
- return [getPathPartMatcher(selectAST, tableAST), getPathPartsCountMatcher(selectAST, tableAST)];
105
+ function getConditionTableQualifier(left) {
106
+ const colName = getColumnName(left);
107
+ if (colName !== void 0) {
108
+ return getColumnTable(left);
109
+ }
110
+ if (isSplitUrlIndexed(left)) {
111
+ const args = rec(left)?.["args"]?.value;
112
+ return getColumnTable(args?.[0]) ?? null;
113
+ }
114
+ if (isSplitPartUrl(left)) {
115
+ const args = rec(left)?.["args"]?.value;
116
+ return getColumnTable(args?.[0]) ?? null;
117
+ }
118
+ if (isCardinalitySplitUrl(left)) {
119
+ const outerArgs = rec(left)?.["args"]?.value;
120
+ const splitFn = rec(outerArgs?.[0]);
121
+ const innerArgs = splitFn?.["args"]?.value;
122
+ return getColumnTable(innerArgs?.[0]) ?? null;
123
+ }
124
+ return void 0;
46
125
  }
47
- function getMethodMatcher(selectAST, _tableAST) {
48
- const [methodToMatch] = JSONPath({
49
- json: selectAST,
50
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'column_ref' && @.left.column === 'method' && @.right && @.right.type === 'single_quote_string')].right.value"
51
- });
52
- if (methodToMatch !== void 0) {
53
- return (_path, method) => method === methodToMatch;
126
+ function buildLeafPredicate(node, tableAlias) {
127
+ if (node.operator !== "=") {
128
+ return void 0;
129
+ }
130
+ const { left, right } = node;
131
+ const conditionTable = getConditionTableQualifier(left);
132
+ if (conditionTable !== null && conditionTable !== void 0 && tableAlias !== null && conditionTable !== tableAlias) {
133
+ return void 0;
134
+ }
135
+ if (getColumnName(left) === "method") {
136
+ const value = getStringValue(right);
137
+ if (value !== void 0) {
138
+ return (_path, method) => method === value;
139
+ }
140
+ }
141
+ if (getColumnName(left) === "responsestatus") {
142
+ const value = getStringValue(right);
143
+ if (value !== void 0) {
144
+ return (_path, _method, responseCode) => responseCode === value;
145
+ }
146
+ }
147
+ if (isSplitUrlIndexed(left)) {
148
+ const index = left.array_index[0]?.index.value;
149
+ const value = getStringValue(right);
150
+ if (typeof index === "number" && value !== void 0) {
151
+ return (path) => {
152
+ const parts = path.split("/");
153
+ const part = parts[index - 1];
154
+ log(`checking path part`, { path, index, part, value });
155
+ return part?.startsWith(":") === true ? true : part === value;
156
+ };
157
+ }
158
+ }
159
+ if (isSplitPartUrl(left)) {
160
+ const args = rec(left)?.["args"]?.value;
161
+ const index = getNumberValue(args?.[2]);
162
+ const value = getStringValue(right);
163
+ if (index !== void 0 && value !== void 0) {
164
+ return (path) => {
165
+ const parts = path.split("/");
166
+ const part = parts[index - 1];
167
+ log("checking split_part path part", { path, index, part, value });
168
+ return part?.startsWith(":") === true ? true : part === value;
169
+ };
170
+ }
171
+ }
172
+ if (isCardinalitySplitUrl(left)) {
173
+ const count = getNumberValue(right);
174
+ if (count !== void 0) {
175
+ return (path) => path.split("/").length === count;
176
+ }
54
177
  }
55
178
  return void 0;
56
179
  }
57
- function getResponseStatusToMatch(selectAST, _tableAST) {
58
- const [responseStatus] = JSONPath({
59
- json: selectAST,
60
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'column_ref' && @.left.column === 'responsestatus' && @.right && @.right.type === 'single_quote_string')].right.value"
61
- });
62
- return responseStatus;
180
+ function buildPredicate(expr, tableAlias) {
181
+ const node = rec(expr);
182
+ if (node?.["type"] !== "binary_expr") {
183
+ return ALWAYS_TRUE;
184
+ }
185
+ const binary = expr;
186
+ switch (binary.operator) {
187
+ case "AND": {
188
+ const leftPred = buildPredicate(binary.left, tableAlias);
189
+ const rightPred = buildPredicate(binary.right, tableAlias);
190
+ return (path, method, code) => leftPred(path, method, code) && rightPred(path, method, code);
191
+ }
192
+ case "OR": {
193
+ const leftPred = buildPredicate(binary.left, tableAlias);
194
+ const rightPred = buildPredicate(binary.right, tableAlias);
195
+ return (path, method, code) => leftPred(path, method, code) || rightPred(path, method, code);
196
+ }
197
+ case "NOT": {
198
+ const innerPred = buildPredicate(binary.left, tableAlias);
199
+ return (path, method, code) => !innerPred(path, method, code);
200
+ }
201
+ default: {
202
+ return buildLeafPredicate(binary, tableAlias) ?? ALWAYS_TRUE;
203
+ }
204
+ }
63
205
  }
64
206
  function matchApi(selectAST, tableAST, apiSchemas) {
65
- const schemaMatchers = [
66
- getVersionMatcher(selectAST, tableAST),
67
- getPathMatchers(selectAST, tableAST),
68
- getMethodMatcher(selectAST, tableAST)
69
- ].flat().filter((matcher) => matcher !== void 0);
70
- const allOperationSchemas = apiSchemas.flatMap((apiSchema) => Object.entries(apiSchema.apis)).flatMap(
207
+ const tableAlias = tableAST.as ?? null;
208
+ const predicate = buildPredicate(selectAST.where ?? null, tableAlias);
209
+ const allOperations = apiSchemas.flatMap((apiSchema) => Object.entries(apiSchema.apis)).flatMap(
71
210
  ([path, operations]) => Object.entries(operations).map(([method, operationSchemas]) => ({
72
211
  path,
73
212
  method: method.toUpperCase(),
74
213
  operationSchemas
75
214
  }))
76
215
  );
77
- log("total operation schemas", allOperationSchemas.length);
78
- const matchedOperationSchemas = allOperationSchemas.filter(
79
- ({ path, method }) => schemaMatchers.every((matcher) => matcher(path, method))
216
+ log("total operation schemas", allOperations.length);
217
+ const matchedApis = allOperations.flatMap(
218
+ ({ path, method, operationSchemas }) => Object.entries(operationSchemas.responses).flatMap(
219
+ ([responseCode, responseSchema]) => predicate(path, method, responseCode) ? [{ path, method, request: operationSchemas.request, response: responseSchema }] : []
220
+ )
80
221
  );
81
- log("matched operation schemas", matchedOperationSchemas.length);
82
- if (matchedOperationSchemas.length === 0) {
83
- log("no matched operation schema");
84
- throw new Error("no matched operation schema");
85
- }
86
- const matchedResponseStatus = getResponseStatusToMatch(selectAST, tableAST);
87
- log("matchedResponseStatus", matchedResponseStatus);
88
- const matchedApis = matchedOperationSchemas.flatMap(
89
- (operation) => Object.entries(operation.operationSchemas.responses).map(([responseCode, responseSchema]) => {
90
- const matchedResponseSchema = matchedResponseStatus === void 0 || responseCode === matchedResponseStatus ? responseSchema : void 0;
91
- return matchedResponseSchema === void 0 ? void 0 : {
92
- path: operation.path,
93
- method: operation.method,
94
- request: operation.operationSchemas.request,
95
- response: matchedResponseSchema
96
- };
97
- })
98
- ).filter((api) => api !== void 0);
222
+ log("matched apis", matchedApis.length);
99
223
  if (matchedApis.length === 0) {
100
- log("no api satisfy both request and response matchers");
224
+ log("no matched api");
101
225
  throw new Error("no matched api");
102
226
  }
103
227
  return matchedApis;
@@ -105,4 +229,4 @@ function matchApi(selectAST, tableAST, apiSchemas) {
105
229
  export {
106
230
  matchApi
107
231
  };
108
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2F0aGVuYS9hcGktbWF0Y2hlci50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFFQSxPQUFPLFdBQVc7QUFDbEIsU0FBUyxnQkFBZ0I7QUFLekIsSUFBTSxNQUFNLE1BQU0sa0NBQWtDO0FBa0JwRCxTQUFTLGtCQUFrQixZQUFvQixXQUF3QztBQUNyRixTQUFPO0FBQ1Q7QUFHQSxTQUFTLG1CQUFtQixXQUFtQixXQUF3QztBQUNyRixRQUFNLENBQUMsaUJBQWlCLElBQWMsU0FBUztBQUFBLElBQzdDLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxFQUNSLENBQUM7QUFHRCxNQUFJLHNCQUFzQixRQUFXO0FBQ25DLFVBQU0sQ0FBQyxhQUFhLElBQWMsU0FBUztBQUFBLE1BQ3pDLE1BQU07QUFBQSxNQUNOLE1BQU07QUFBQSxJQUNSLENBQUM7QUFDRCxVQUFNLENBQUMsYUFBYSxJQUFjLFNBQVM7QUFBQSxNQUN6QyxNQUFNO0FBQUEsTUFDTixNQUFNO0FBQUEsSUFDUixDQUFDO0FBRUQsV0FBTyxDQUFDLE1BQWMsWUFBb0I7QUFDeEMsWUFBTSxRQUFRLEtBQUssTUFBTSxHQUFHO0FBQzVCLFlBQU0sT0FBTyxNQUFNLGdCQUFnQixDQUFDO0FBQ3BDLFVBQUksc0JBQXNCLEVBQUUsTUFBTSxlQUFlLE1BQU0sY0FBYyxDQUFDO0FBQ3RFLGFBQU8sTUFBTSxXQUFXLEdBQUcsTUFBTSxPQUM3QixPQUNBLE1BQU0sZ0JBQWdCLENBQUMsTUFBTTtBQUFBLElBQ25DO0FBQUEsRUFDRjtBQUVBLFNBQU87QUFDVDtBQUdBLFNBQVMseUJBQXlCLFdBQW1CLFdBQXdDO0FBRTNGLFFBQU0sQ0FBQyxhQUFhLElBQWMsU0FBUztBQUFBLElBQ3pDLE1BQU07QUFBQSxJQUNOLE1BQU07QUFBQSxFQUNSLENBQUM7QUFFRCxNQUFJLGtCQUFrQixRQUFXO0FBRS9CLFdBQU8sQ0FBQyxNQUFjLFlBQW9CO0FBQ3hDLFlBQU0sUUFBUSxLQUFLLE1BQU0sR0FBRztB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
232
+ //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3JjL2F0aGVuYS9hcGktbWF0Y2hlci50cyJdLAogICJtYXBwaW5ncyI6ICI7QUFFQSxPQUFPLFdBQVc7QUFNbEIsSUFBTSxNQUFNLE1BQU0sa0NBQWtDO0FBa0JwRCxJQUFNLGNBQWtDLE1BQU07QUFJOUMsU0FBUyxJQUFJLE1BQW9EO0FBQy9ELFNBQU8sT0FBTyxTQUFTLFlBQVksU0FBUyxPQUFRLE9BQW1DO0FBQ3pGO0FBRUEsU0FBUyxnQkFBZ0IsTUFBbUM7QUFDMUQsUUFBTSxLQUFLLElBQUksSUFBSTtBQUNuQixNQUFJLEtBQUssTUFBTSxNQUFNLFlBQVk7QUFDL0IsV0FBTztBQUFBLEVBQ1Q7QUFDQSxRQUFNLE9BQU8sR0FBRyxNQUFNO0FBQ3RCLFFBQU0sWUFBWSxNQUFNLEtBQUssQ0FBQztBQUM5QixTQUFPLGNBQWMsU0FBWSxTQUFZLFVBQVUsTUFBTSxZQUFZO0FBQzNFO0FBRUEsU0FBUyxjQUFjLE1BQW1DO0FBQ3hELFFBQU0sTUFBTSxJQUFJLElBQUk7QUFDcEIsTUFBSSxNQUFNLE1BQU0sTUFBTSxjQUFjO0FBQ2xDLFdBQU87QUFBQSxFQUNUO0FBQ0EsUUFBTSxTQUFVLEtBQXVCO0FBQ3ZDLFNBQU8sT0FBTyxXQUFXLFdBQVcsT0FBTyxZQUFZLElBQUk7QUFDN0Q7QUFFQSxTQUFTLGVBQWUsTUFBMEM7QUFDaEUsUUFBTSxNQUFNLElBQUksSUFBSTtBQUNwQixNQUFJLE1BQU0sTUFBTSxNQUFNLGNBQWM7QUFDbEMsV0FBTztBQUFBLEVBQ1Q7QUFDQSxTQUFRLEtBQXVCO0FBQ2pDO0FBRUEsU0FBUyxlQUFlLE1BQW1DO0FBQ3pELFFBQU0sTUFBTSxJQUFJLElBQUk7QUFDcEIsTUFBSSxNQUFNLE1BQU0sTUFBTSx5QkFBeUIsTUFBTSxNQUFNLE1BQU0sVUFBVTtBQUN6RSxXQUFPO0FBQUEsRUFDVDtBQUNBLFNBQU8sT0FBTyxJQUFJLE9BQU8sTUFBTSxXQUFXLElBQUksT0FBTyxJQUFJO0FBQzNEO0FBRUEsU0FBUyxlQUFlLE1BQW1DO0FBQ3pELFFBQU0sTUFBTSxJQUFJLElBQUk7QUFDcEIsU0FBTyxNQUFNLE1BQU0sTUFBTSxZQUFZLE9BQU8sSUFBSSxPQUFPLE1BQU0sV0FBVyxJQUFJLE9BQU8sSUFBSTtBQUN6RjtBQU1BLFNBQVMsa0JBQWtCLE1BQXdDO0FBQ2pFLFFBQU0sS0FBSyxJQUFJLElBQUk7QUFDbkIsTUFBSSxLQUFLLE1BQU0sTUFBTSxZQUFZO0FBQy9CLFdBQU87QUFBQSxFQUNUO0FBQ0EsTUFBSSxnQkFBZ0IsSUFBSSxNQUFNLFNBQVM7QUFDckMsV0FBTztBQUFBLEVBQ1Q7QUFDQSxRQUFNLE9BQVEsR0FBRyxNQUFNLEdBQXlDO0FBQ2hFLE1BQUksQ0FBQyxNQUFNLFFBQVEsSUFBSSxLQUFLLEtBQUssU0FBUyxHQUFHO0FBQzNDLFdBQU87QUFBQSxFQUNUO0FBQ0EsTUFBSSxjQUFjLEtBQUssQ0FBQyxDQUFDLE1BQU0sT0FBTztBQUNwQyxXQUFPO0FBQUEsRUFDVDtBQUNBLE1BQUksZUFBZSxLQUFLLENBQUMsQ0FBQyxNQUFNLEtBQUs7QUFDbkMsV0FBTztBQUFBLEVBQ1Q7QUFDQSxTQUFPLE1BQU0sUUFBUSxHQUFHLGFBQWEsQ0FBQyxLQUFNLEdBQUcsYUFBYSxFQUFnQixTQUFTO0FBQ3ZGO0FBR0EsU0FBUyxlQUFlLE1BQW9DO0FBQzFELFFBQU0sS0FBSyxJQUFJLElBQUk7QUFDbkIsTUFBSSxLQUFLLE1BQU0sTUFBTSxZQUFZO0FBQy9CLFdBQU87QUFBQSxFQUNUO0FBQ0EsTUFBSSxnQkFBZ0IsSUFBSSxNQUFNLGNBQWM7QUFDMUMsV0FBTztBQUFBLEVBQ1Q7QUFDQSxRQUFNLE9BQVEsR0FBRyxNQUFNLEdBQXlDO0FBQ2hFLE1BQUksQ0FBQyxNQUFNLFFBQVEsSUFBSSxLQUFLLEtBQUssQ0FBQyxNQUFNLFFBQVc7QUFDakQsV0FBTztBQUFBLEVBQ1Q7QUFDQSxNQUFJLGNBQWMsS0FBSyxDQUFDLENBQUMsTUFBTSxPQUFPO0FBQ3BDLFdBQU87QUFBQSxFQUNUO0FBQ0EsU0FBTyxlQUFlLEtBQUssQ0FBQyxDQUFDLE1BQU0sT0FBTyxlQUFlLEtBQUssQ0FBQyxDQUFDLE1BQU07QUFDeEU7QUFHQSxTQUFTLHNCQUFzQixNQUFvQztBQUNqRSxRQUFNLEtBQUssSUFBSSxJQUFJO0FBQ25CLE1BQUksS0FBSyxNQUFNLE1BQU0sWUFBWTtBQUMvQixXQUFPO0FBQUEsRUFDVDtBQUNBLE1BQUksZ0JBQWdCLElBQUksTUFBTSxlQUFlO0FBQzNDLFdBQU87QUFBQSxFQUNUO0FBQ0EsUUFBTSxZQUFhLEdBQUcsTUFBTSxHQUF5QztBQUNyRSxNQUFJLENBQUMsTUFBTSxRQUFRLFNBQVMsS0FBSyxVQUFVLFdBQVcsR0FBRztBQUN2RCxXQUFPO0FBQUEsRUFDVDtBQUNBLFFBQU0sVUFBVSxJQUFJLFVBQVUsQ0FBQyxDQUFDO0FBQ2hDLE1BQUksVUFBVSxNQUFNLE1BQU0sWUFBWTtBQUNwQyxXQUFPO0FBQUEsRUFDVDtBQUNBLE1BQUksZ0JBQWdCLFVBQVUsQ0FBQyxDQUFDLE1BQU0sU0FBUztBQUM3QyxXQUFPO0FBQUEsRUFDVDtBQUNBLFFBQU0sWUFBYSxRQUFRLE1BQU0sR0FBeUM7QUFDMUUsTUFBSSxDQUFDLE1BQU0sUUFBUSxTQUFTLEtBQUssVUFBVSxTQUFTLEdBQUc7QUFDckQsV0FBTztBQUFBLEVBQ1Q7QUFDQSxTQUFPLGNBQWMsVUFBVSxDQUFDLENBQUMsTUFBTSxTQUFTLGVBQWUsVUFBVSxDQUFDLENBQUMsTUFBTTtBQUNuRjtBQUlBLFNBQVMsMkJBQTJCLE1BQTBDO0FBQzVFLFFBQU0sVUFBVSxjQUFjLElBQUk7QUFDbEMsTUFBSSxZQUFZLFFBQVc7QUFDekIsV0FBTyxlQUFlLElBQUk7QUFBQSxFQUM1QjtBQUVBLE1BQUksa0JBQWtCLElBQUksR0FBRztBQUMzQixVQUFNLE9BQVEsSUFBSSxJQUFJLElBQUksTUFBTSxHQUF5QztBQUN6RSxXQUFPLGVBQWUsT0FBTyxDQUFDLENBQUMsS0FBSztBQUFBLEVBQ3RDO0FBQ0EsTUFBSSxlQUFlLElBQUksR0FBRztBQUN4QixVQUFNLE9BQVEsSUFBSSxJQUFJLElBQUksTUFBTSxHQUF5QztBQUN6RSxXQUFPLGVBQWUsT0FBTyxDQUFDLENBQUMsS0FBSztBQUFBLEVBQ3RDO0FBQ0EsTUFBSSxzQkFBc0IsSUFBSSxHQUFHO0FBQy9CLFVBQU0sWUFBYSxJQUFJLElBQUksSUFBSSxNQUFNLEdBQXlDO0FBQzlFLFVBQU0sVUFBVSxJQUFJLFlBQVksQ0FBQyxDQUFDO0FBQ2xDLFVBQU0sWUFBYSxVQUFVLE1BQU0sR0FBeUM7QUFDNUUsV0FBTyxlQUFlLFlBQVksQ0FBQyxDQUFDLEtBQUs7QUFBQSxFQUMzQztBQUNBLFNBQU87QUFDVDtBQU1BLFNBQVMsbUJBQW1CLE1BQWMsWUFBMkQ7QUFDbkcsTUFBSSxLQUFLLGFBQWEsS0FBSztBQUN6QixXQUFPO0FBQUEsRUFDVDtBQUNBLFFBQU0sRUFBRSxNQUFNLE1BQU0sSUFBSTtBQUV4QixRQUFNLGlCQUFpQiwyQkFBMkIsSUFBSTtBQUd0RCxNQUFJLG1CQUFtQixRQUFRLG1CQUFtQixVQUFhLGVBQWUsUUFBUSxtQkFBbUIsWUFBWTtBQUNuSCxXQUFPO0FBQUEsRUFDVDtBQUdBLE1BQUksY0FBYyxJQUFJLE1BQU0sVUFBVTtBQUNwQyxVQUFNLFFBQVEsZUFBZSxLQUFLO0FBQ2xDLFFBQUksVUFBVSxRQUFXO0FBQ3ZCLGFBQU8sQ0FBQyxPQUFPLFdBQVcsV0FBVztBQUFBLElBQ3ZDO0FBQUEsRUFDRjtBQUdBLE1BQUksY0FBYyxJQUFJLE1BQU0sa0JBQWtCO0FBQzVDLFVBQU0sUUFBUSxlQUFlLEtBQUs7QUFDbEMsUUFBSSxVQUFVLFFBQVc7QUFDdkIsYUFBTyxDQUFDLE9BQU8sU0FBUyxpQkFBaUIsaUJBQWlCO0FBQUEsSUFDNUQ7QUFBQSxFQUNGO0FBR0EsTUFBSSxrQkFBa0IsSUFBSSxHQUFHO0FBQzNCLFVBQU0sUUFBUSxLQUFLLFlBQVksQ0FBQyxHQUFHLE1BQU07QUFDekMsVUFBTSxRQUFRLGVBQWUsS0FBSztBQUNsQyxRQUFJLE9BQU8sVUFBVSxZQUFZLFVBQVUsUUFBVztBQUNwRCxhQUFPLENBQUMsU0FBUztBQUNmLGNBQU0sUUFBUSxLQUFLLE1BQU0sR0FBRztBQUM1QixjQUFNLE9BQU8sTUFBTSxRQUFRLENBQUM7QUFDNUIsWUFBSSxzQkFBc0IsRUFBRSxNQUFNLE9BQU8sTUFBTSxNQUFNLENBQUM7QUFDdEQsZUFBTyxNQUFNLFdBQVcsR0FBRyxNQUFNLE9BQU8sT0FBTyxTQUFTO0FBQUEsTUFDMUQ7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUdBLE1BQUksZUFBZSxJQUFJLEdBQUc7QUFDeEIsVUFBTSxPQUFRLElBQUksSUFBSSxJQUFJLE1BQU0sR0FBeUM7QUFDekUsVUFBTSxRQUFRLGVBQWUsT0FBTyxDQUFDLENBQUM7QUFDdEMsVUFBTSxRQUFRLGVBQWUsS0FBSztBQUNsQyxRQUFJLFVBQVUsVUFBYSxVQUFVLFFBQVc7QUFDOUMsYUFBTyxDQUFDLFNBQVM7QUFDZixjQUFNLFFBQVEsS0FBSyxNQUFNLEdBQUc7QUFDNUIsY0FBTSxPQUFPLE1BQU0sUUFBUSxDQUFDO0FBQzVCLFlBQUksaUNBQWlDLEVBQUUsTUFBTSxPQUFPLE1BQU0sTUFBTSxDQUFDO0FBQ2pFLGVBQU8sTUFBTSxXQUFXLEdBQUcsTUFBTSxPQUFPLE9BQU8sU0FBUztBQUFBLE1BQzFEO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFHQSxNQUFJLHNCQUFzQixJQUFJLEdBQUc7QUFDL0IsVUFBTSxRQUFRLGVBQWUsS0FBSztBQUNsQyxRQUFJLFVBQVUsUUFBVztBQUN2QixhQUFPLENBQUMsU0FBUyxLQUFLLE1BQU0sR0FBRyxFQUFFLFdBQVc7QUFBQSxJQUM5QztBQUFBLEVBQ0Y7QUFFQSxTQUFPO0FBQ1Q7QUFFQSxTQUFTLGVBQWUsTUFBZSxZQUErQztBQUNwRixRQUFNLE9BQU8sSUFBSSxJQUFJO0FBQ3JCLE1BQUksT0FBTyxNQUFNLE1BQU0sZUFBZTtBQUNwQyxXQUFPO0FBQUEsRUFDVDtBQUVBLFFBQU0sU0FBUztBQUVmLFVBQVEsT0FBTyxVQUFVO0FBQUEsSUFDdkIsS0FBSyxPQUFPO0FBQ1YsWUFBTSxXQUFXLGVBQWUsT0FBTyxNQUFNLFVBQVU7QUFDdkQsWUFBTSxZQUFZLGVBQWUsT0FBTyxPQUFPLFVBQVU7QUFDekQsYUFBTyxDQUFDLE1BQU0sUUFBUSxTQUFTLFNBQVMsTUFBTSxRQUFRLElBQUksS0FBSyxVQUFVLE1BQU0sUUFBUSxJQUFJO0FBQUEsSUFDN0Y7QUFBQSxJQUNBLEtBQUssTUFBTTtBQUNULFlBQU0sV0FBVyxlQUFlLE9BQU8sTUFBTSxVQUFVO0FBQ3ZELFlBQU0sWUFBWSxlQUFlLE9BQU8sT0FBTyxVQUFVO0FBQ3pELGFBQU8sQ0FBQyxNQUFNLFFBQVEsU0FBUyxTQUFTLE1BQU0sUUFBUSxJQUFJLEtBQUssVUFBVSxNQUFNLFFBQVEsSUFBSTtBQUFBLElBQzdGO0FBQUEsSUFDQSxLQUFLLE9BQU87QUFDVixZQUFNLFlBQVksZUFBZSxPQUFPLE1BQU0sVUFBVTtBQUN4RCxhQUFPLENBQUMsTUFBTSxRQUFRLFNBQVMsQ0FBQyxVQUFVLE1BQU0sUUFBUSxJQUFJO0FBQUEsSUFDOUQ7QUFBQSxJQUNBLFNBQVM7QUFDUCxhQUFPLG1CQUFtQixRQUFRLFVBQVUsS0FBSztBQUFBLElBQ25EO0FBQUEsRUFDRjtBQUNGO0FBRU8sU0FBUyxTQUNkLFdBQ0EsVUFDQSxZQUNnQztBQUNoQyxRQUFNLGFBQWMsU0FBb0MsTUFBTTtBQUM5RCxRQUFNLFlBQVksZUFBZ0IsVUFBa0MsU0FBUyxNQUFNLFVBQVU7QUFFN0YsUUFBTSxnQkFBb0MsV0FDdkMsUUFBUSxDQUFDLGNBQWMsT0FBTyxRQUFRLFVBQVUsSUFBSSxDQUFDLEVBQ3JEO0FBQUEsSUFBUSxDQUFDLENBQUMsTUFBTSxVQUFVLE1BQ3pCLE9BQU8sUUFBUSxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsUUFBUSxnQkFBZ0IsT0FBTztBQUFBLE1BQzlEO0FBQUEsTUFDQSxRQUFRLE9BQU8sWUFBWTtBQUFBLE1BQzNCO0FBQUEsSUFDRixFQUFFO0FBQUEsRUFDSjtBQUNGLE1BQUksMkJBQTJCLGNBQWMsTUFBTTtBQUVuRCxRQUFNLGNBQWMsY0FBYztBQUFBLElBQVEsQ0FBQyxFQUFFLE1BQU0sUUFBUSxpQkFBaUIsTUFDMUUsT0FBTyxRQUFRLGlCQUFpQixTQUFTLEVBQUU7QUFBQSxNQUFRLENBQUMsQ0FBQyxjQUFjLGNBQWMsTUFDL0UsVUFBVSxNQUFNLFFBQVEsWUFBWSxJQUNoQyxDQUFDLEVBQUUsTUFBTSxRQUFRLFNBQVMsaUJBQWlCLFNBQVMsVUFBVSxlQUFlLENBQUMsSUFDOUUsQ0FBQztBQUFBLElBQ1A7QUFBQSxFQUNGO0FBQ0EsTUFBSSxnQkFBZ0IsWUFBWSxNQUFNO0FBRXRDLE1BQUksWUFBWSxXQUFXLEdBQUc7QUFDNUIsUUFBSSxnQkFBZ0I7QUFDcEIsVUFBTSxJQUFJLE1BQU0sZ0JBQWdCO0FBQUEsRUFDbEM7QUFDQSxTQUFPO0FBQ1Q7IiwKICAibmFtZXMiOiBbXQp9Cg==
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@checkdigit/eslint-plugin","version":"7.18.0-PR.143-c535","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-mjs","!src/**/test/**","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/test/**","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-mjs/**/test/**","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 .","lint:fix":"eslint --max-warnings 0 --fix .","peggy":"for file in ./src/peggy/*.peggy; do peggy \"$file\" --format es --output \"${file%.peggy}-peggy.ts\"; done","peggy-watch":"for file in ./src/peggy/*.peggy; do peggy \"$file\" --format=es --watch --output=\"${file%.peggy}-peggy.ts\"; done","prepare":"","prepublishOnly":"npm run build:dist-types && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@apidevtools/json-schema-ref-parser":"^15.3.5","@typescript-eslint/type-utils":"^8.60.1","@typescript-eslint/utils":"^8.60.1","ajv":"^8.20.0","debug":"^4.4.3","glob":"^13.0.6","http-status-codes":"^2.3.0","js-yaml":"^4.2.0","json-pointer":"^0.6.2","jsonpath-plus":"^10.4.0","ts-api-utils":"^2.5.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^6.1.0","@checkdigit/typescript-config":"10.0.0","@eslint/js":"^9.37.0","@types/debug":"^4.1.13","@types/eslint":"^9.6.1","@types/eslint-config-prettier":"^6.11.3","@types/js-yaml":"^4.0.9","@types/json-pointer":"^1.0.34","@typescript-eslint/parser":"^8.60.1","@typescript-eslint/rule-tester":"^8.60.1","eslint":"^9.37.0","eslint-config-prettier":"^10.1.8","eslint-import-resolver-typescript":"^4.4.5","eslint-plugin-eslint-plugin":"^6.4.0","eslint-plugin-import":"^2.32.0","eslint-plugin-no-only-tests":"^3.4.0","eslint-plugin-no-secrets":"^2.3.3","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"^1.0.4","openapi-types":"^12.1.3","peggy":"^4.2.0","rimraf":"^6.1.3","typescript-eslint":"^8.60.1"},"peerDependencies":{"eslint":">=9 <10"},"engines":{"node":">=22.18"},"service":{"api":{"root":"src","endpoints":["api/v1"]}},"wallaby":{"env":{"params":{"runner":"--experimental-vm-modules"}}}}
1
+ {"name":"@checkdigit/eslint-plugin","version":"7.18.0-PR.143-1b97","description":"Check Digit eslint plugins","keywords":["eslint","eslintplugin"],"homepage":"https://github.com/checkdigit/eslint-plugin#readme","bugs":{"url":"https://github.com/checkdigit/eslint-plugin/issues"},"repository":{"type":"git","url":"https://github.com/checkdigit/eslint-plugin"},"license":"MIT","author":"Check Digit, LLC","sideEffects":false,"type":"module","exports":{".":{"types":"./dist-types/index.d.ts","import":"./dist-mjs/index.mjs","default":"./dist-mjs/index.mjs"}},"files":["src","dist-types","dist-mjs","!src/**/test/**","!src/**/*.test.ts","!src/**/*.spec.ts","!dist-types/**/test/**","!dist-types/**/*.test.d.ts","!dist-types/**/*.spec.d.ts","!dist-mjs/**/test/**","!dist-mjs/**/*.test.mjs","!dist-mjs/**/*.spec.mjs","SECURITY.md"],"scripts":{"build:dist-mjs":"rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs && node dist-mjs/index.mjs","build:dist-types":"rimraf dist-types && npx builder --type=types --outDir=dist-types","ci:compile":"tsc --noEmit","ci:coverage":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=true","ci:lint":"npm run lint","ci:style":"npm run prettier","ci:test":"NODE_OPTIONS=\"--disable-warning ExperimentalWarning --experimental-vm-modules\" jest --coverage=false","lint":"eslint --max-warnings 0 .","lint:fix":"eslint --max-warnings 0 --fix .","peggy":"for file in ./src/peggy/*.peggy; do peggy \"$file\" --format es --output \"${file%.peggy}-peggy.ts\"; done","peggy-watch":"for file in ./src/peggy/*.peggy; do peggy \"$file\" --format=es --watch --output=\"${file%.peggy}-peggy.ts\"; done","prepare":"","prepublishOnly":"npm run build:dist-types && npm run build:dist-mjs","prettier":"prettier --ignore-path .gitignore --list-different .","prettier:fix":"prettier --ignore-path .gitignore --write .","test":"npm run ci:compile && npm run ci:test && npm run ci:lint && npm run ci:style"},"prettier":"@checkdigit/prettier-config","jest":{"preset":"@checkdigit/jest-config"},"dependencies":{"@apidevtools/json-schema-ref-parser":"^15.3.5","@typescript-eslint/type-utils":"^8.60.1","@typescript-eslint/utils":"^8.60.1","ajv":"^8.20.0","debug":"^4.4.3","glob":"^13.0.6","http-status-codes":"^2.3.0","js-yaml":"^4.2.0","json-pointer":"^0.6.2","jsonpath-plus":"^10.4.0","ts-api-utils":"^2.5.0"},"devDependencies":{"@checkdigit/jest-config":"^6.0.2","@checkdigit/prettier-config":"^6.1.0","@checkdigit/typescript-config":"10.0.0","@eslint/js":"^9.37.0","@types/debug":"^4.1.13","@types/eslint":"^9.6.1","@types/eslint-config-prettier":"^6.11.3","@types/js-yaml":"^4.0.9","@types/json-pointer":"^1.0.34","@typescript-eslint/parser":"^8.60.1","@typescript-eslint/rule-tester":"^8.60.1","eslint":"^9.37.0","eslint-config-prettier":"^10.1.8","eslint-import-resolver-typescript":"^4.4.5","eslint-plugin-eslint-plugin":"^6.4.0","eslint-plugin-import":"^2.32.0","eslint-plugin-no-only-tests":"^3.4.0","eslint-plugin-no-secrets":"^2.3.3","eslint-plugin-node":"^11.1.0","eslint-plugin-sonarjs":"^1.0.4","openapi-types":"^12.1.3","peggy":"^4.2.0","rimraf":"^6.1.3","typescript-eslint":"^8.60.1"},"peerDependencies":{"eslint":">=9 <10"},"engines":{"node":">=22.18"},"service":{"api":{"root":"src","endpoints":["api/v1"]}},"wallaby":{"env":{"params":{"runner":"--experimental-vm-modules"}}}}
@@ -1,10 +1,10 @@
1
1
  // athena/api-matcher.ts
2
2
 
3
3
  import debug from 'debug';
4
- import { JSONPath } from 'jsonpath-plus';
5
4
  import type { SchemaObject } from 'ajv/dist/2020';
6
5
 
7
6
  import type { ApiSchemas, OperationSchemas } from '../openapi/generate-schema';
7
+ import type { Binary, ColumnRefItem, Function as SqlFunction } from './types';
8
8
 
9
9
  const log = debug('eslint-plugin:athena:api-matcher');
10
10
 
@@ -21,105 +21,264 @@ export interface MatchedOperation {
21
21
  response: SchemaObject;
22
22
  }
23
23
 
24
- type Matcher = (path: string, method: string) => boolean;
24
+ // A predicate over an API operation candidate: (url path, HTTP method, HTTP response code)
25
+ type OperationPredicate = (path: string, method: string, responseCode: string) => boolean;
25
26
 
26
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
27
- function getVersionMatcher(_selectAST: object, _tableAST: object): Matcher | undefined {
28
- return undefined;
27
+ const ALWAYS_TRUE: OperationPredicate = () => true;
28
+
29
+ // --- AST accessor helpers (use Record<string,unknown> to avoid TypeScript narrowing conflicts) ---
30
+
31
+ function rec(node: unknown): Record<string, unknown> | undefined {
32
+ return typeof node === 'object' && node !== null ? (node as Record<string, unknown>) : undefined;
29
33
  }
30
34
 
31
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
32
- function getPathPartMatcher(selectAST: object, _tableAST: object): Matcher | undefined {
33
- const [pathPartCondition]: object[] = JSONPath({
34
- json: selectAST,
35
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'function' && @.left.name && @.left.name.name && @.left.name.name[0] && @.left.name.name[0].value === 'split' && @.left.args && @.left.args.value && @.left.args.value[0] && @.left.args.value[0].type === 'column_ref' && @.left.args.value[0].column === 'url' && @.left.args.value[1] && @.left.args.value[1].type === 'single_quote_string' && @.left.args.value[1].value === '/' && @.left.array_index && @.left.array_index[0] && @.left.array_index[0].brackets === true && @.left.array_index[0].index && @.left.array_index[0].index.type === 'number')]",
36
- }); /*?*/
37
- // log('pathPartCondition', pathPartCondition);
38
-
39
- if (pathPartCondition !== undefined) {
40
- const [pathPartIndex]: [number] = JSONPath({
41
- json: pathPartCondition,
42
- path: '$.left.array_index[0].index.value',
43
- }); /*?*/
44
- const [pathPartMatch]: [string] = JSONPath({
45
- json: pathPartCondition,
46
- path: '$.right.value',
47
- }); /*?*/
48
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
49
- return (path: string, _method: string) => {
50
- const parts = path.split('/'); /*?*/
51
- const part = parts[pathPartIndex - 1]; //athena index is larger than js index by one /*?*/
52
- log(`checking path part`, { path, pathPartIndex, part, pathPartMatch });
53
- return part?.startsWith(':') === true
54
- ? true // ignore path part if it presents a dynamic input parameter
55
- : parts[pathPartIndex - 1] === pathPartMatch; // try to match with static path part
56
- };
35
+ function getFunctionName(node: unknown): string | undefined {
36
+ const fn = rec(node);
37
+ if (fn?.['type'] !== 'function') {
38
+ return undefined;
57
39
  }
40
+ const name = fn['name'] as SqlFunction['name'] | undefined;
41
+ const firstName = name?.name[0];
42
+ return firstName === undefined ? undefined : firstName.value.toLowerCase();
43
+ }
58
44
 
59
- return undefined;
45
+ function getColumnName(node: unknown): string | undefined {
46
+ const col = rec(node);
47
+ if (col?.['type'] !== 'column_ref') {
48
+ return undefined;
49
+ }
50
+ const column = (node as ColumnRefItem).column;
51
+ return typeof column === 'string' ? column.toLowerCase() : undefined;
60
52
  }
61
53
 
62
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
63
- function getPathPartsCountMatcher(selectAST: object, _tableAST: object): Matcher | undefined {
64
- // log('getPathPartsCountMatcher', JSON.stringify(selectAST, undefined, 2));
65
- const [pathPartCount]: number[] = JSONPath({
66
- json: selectAST,
67
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'function' && @.left.name && @.left.name.name && @.left.name.name[0] && @.left.name.name[0].value === 'cardinality' && @.right && @.right.type === 'number' && @.left.args && @.left.args.value && @.left.args.value[0] && @.left.args.value[0].type === 'function' && @.left.args.value[0].name && @.left.args.value[0].name.name && @.left.args.value[0].name.name[0] && @.left.args.value[0].name.name[0].value === 'split' && @.left.args.value[0].args && @.left.args.value[0].args.value && @.left.args.value[0].args.value[0] && @.left.args.value[0].args.value[0].type === 'column_ref' && @.left.args.value[0].args.value[0].column === 'url' && @.left.args.value[0].args.value[1] && @.left.args.value[0].args.value[1].type === 'single_quote_string' && @.left.args.value[0].args.value[1].value === '/')].right.value",
68
- }); /*?*/
54
+ function getColumnTable(node: unknown): string | null | undefined {
55
+ const col = rec(node);
56
+ if (col?.['type'] !== 'column_ref') {
57
+ return undefined;
58
+ }
59
+ return (node as ColumnRefItem).table;
60
+ }
69
61
 
70
- if (pathPartCount !== undefined) {
71
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
72
- return (path: string, _method: string) => {
73
- const parts = path.split('/');
74
- return parts.length === pathPartCount;
75
- };
62
+ function getStringValue(node: unknown): string | undefined {
63
+ const val = rec(node);
64
+ if (val?.['type'] !== 'single_quote_string' && val?.['type'] !== 'string') {
65
+ return undefined;
76
66
  }
67
+ return typeof val['value'] === 'string' ? val['value'] : undefined;
68
+ }
77
69
 
78
- return undefined;
70
+ function getNumberValue(node: unknown): number | undefined {
71
+ const val = rec(node);
72
+ return val?.['type'] === 'number' && typeof val['value'] === 'number' ? val['value'] : undefined;
73
+ }
74
+
75
+ // Matches: split(url, '/')[N] — a Function node carrying an array_index extension
76
+ interface SplitUrlIndexed extends SqlFunction {
77
+ array_index: { brackets: true; index: { type: string; value: unknown } }[];
78
+ }
79
+ function isSplitUrlIndexed(node: unknown): node is SplitUrlIndexed {
80
+ const fn = rec(node);
81
+ if (fn?.['type'] !== 'function') {
82
+ return false;
83
+ }
84
+ if (getFunctionName(node) !== 'split') {
85
+ return false;
86
+ }
87
+ const args = (fn['args'] as { value?: unknown[] } | undefined)?.value;
88
+ if (!Array.isArray(args) || args.length < 2) {
89
+ return false;
90
+ }
91
+ if (getColumnName(args[0]) !== 'url') {
92
+ return false;
93
+ }
94
+ if (getStringValue(args[1]) !== '/') {
95
+ return false;
96
+ }
97
+ return Array.isArray(fn['array_index']) && (fn['array_index'] as unknown[]).length > 0;
98
+ }
99
+
100
+ // Matches: split_part(url, '/', N) — Presto-style, index in args[2] (1-based)
101
+ function isSplitPartUrl(node: unknown): node is SqlFunction {
102
+ const fn = rec(node);
103
+ if (fn?.['type'] !== 'function') {
104
+ return false;
105
+ }
106
+ if (getFunctionName(node) !== 'split_part') {
107
+ return false;
108
+ }
109
+ const args = (fn['args'] as { value?: unknown[] } | undefined)?.value;
110
+ if (!Array.isArray(args) || args[2] === undefined) {
111
+ return false;
112
+ }
113
+ if (getColumnName(args[0]) !== 'url') {
114
+ return false;
115
+ }
116
+ return getStringValue(args[1]) === '/' && getNumberValue(args[2]) !== undefined;
117
+ }
118
+
119
+ // Matches: cardinality(split(url, '/'))
120
+ function isCardinalitySplitUrl(node: unknown): node is SqlFunction {
121
+ const fn = rec(node);
122
+ if (fn?.['type'] !== 'function') {
123
+ return false;
124
+ }
125
+ if (getFunctionName(node) !== 'cardinality') {
126
+ return false;
127
+ }
128
+ const outerArgs = (fn['args'] as { value?: unknown[] } | undefined)?.value;
129
+ if (!Array.isArray(outerArgs) || outerArgs.length === 0) {
130
+ return false;
131
+ }
132
+ const innerFn = rec(outerArgs[0]);
133
+ if (innerFn?.['type'] !== 'function') {
134
+ return false;
135
+ }
136
+ if (getFunctionName(outerArgs[0]) !== 'split') {
137
+ return false;
138
+ }
139
+ const innerArgs = (innerFn['args'] as { value?: unknown[] } | undefined)?.value;
140
+ if (!Array.isArray(innerArgs) || innerArgs.length < 2) {
141
+ return false;
142
+ }
143
+ return getColumnName(innerArgs[0]) === 'url' && getStringValue(innerArgs[1]) === '/';
79
144
  }
80
145
 
81
- function getPathMatchers(selectAST: object, tableAST: object) {
82
- return [getPathPartMatcher(selectAST, tableAST), getPathPartsCountMatcher(selectAST, tableAST)];
146
+ // Returns the table qualifier of the left-hand side of a matchable binary condition.
147
+ // null means unqualified (applies to every table); undefined means indeterminate.
148
+ function getConditionTableQualifier(left: unknown): string | null | undefined {
149
+ const colName = getColumnName(left);
150
+ if (colName !== undefined) {
151
+ return getColumnTable(left);
152
+ }
153
+
154
+ if (isSplitUrlIndexed(left)) {
155
+ const args = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
156
+ return getColumnTable(args?.[0]) ?? null;
157
+ }
158
+ if (isSplitPartUrl(left)) {
159
+ const args = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
160
+ return getColumnTable(args?.[0]) ?? null;
161
+ }
162
+ if (isCardinalitySplitUrl(left)) {
163
+ const outerArgs = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
164
+ const splitFn = rec(outerArgs?.[0]);
165
+ const innerArgs = (splitFn?.['args'] as { value?: unknown[] } | undefined)?.value;
166
+ return getColumnTable(innerArgs?.[0]) ?? null;
167
+ }
168
+ return undefined;
83
169
  }
84
170
 
85
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
86
- function getMethodMatcher(selectAST: object, _tableAST: object): Matcher | undefined {
87
- const [methodToMatch]: string[] = JSONPath({
88
- json: selectAST,
89
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'column_ref' && @.left.column === 'method' && @.right && @.right.type === 'single_quote_string')].right.value",
90
- }); /*?*/
171
+ // --- Predicate builders ---
172
+
173
+ // tableAlias: the alias (or null if none) of the FROM-clause item we are currently matching.
174
+ // Conditions that explicitly reference a different alias are skipped (treated as ALWAYS_TRUE).
175
+ function buildLeafPredicate(node: Binary, tableAlias: string | null): OperationPredicate | undefined {
176
+ if (node.operator !== '=') {
177
+ return undefined;
178
+ }
179
+ const { left, right } = node;
180
+
181
+ const conditionTable = getConditionTableQualifier(left);
182
+ // conditionTable === null → unqualified, applies to all tables
183
+ // conditionTable === string → qualified; skip if it names a different alias
184
+ if (conditionTable !== null && conditionTable !== undefined && tableAlias !== null && conditionTable !== tableAlias) {
185
+ return undefined;
186
+ }
187
+
188
+ // method = 'GET'
189
+ if (getColumnName(left) === 'method') {
190
+ const value = getStringValue(right);
191
+ if (value !== undefined) {
192
+ return (_path, method) => method === value;
193
+ }
194
+ }
195
+
196
+ // responsestatus = '200'
197
+ if (getColumnName(left) === 'responsestatus') {
198
+ const value = getStringValue(right);
199
+ if (value !== undefined) {
200
+ return (_path, _method, responseCode) => responseCode === value;
201
+ }
202
+ }
203
+
204
+ // split(url, '/')[N] = 'value'
205
+ if (isSplitUrlIndexed(left)) {
206
+ const index = left.array_index[0]?.index.value;
207
+ const value = getStringValue(right);
208
+ if (typeof index === 'number' && value !== undefined) {
209
+ return (path) => {
210
+ const parts = path.split('/');
211
+ const part = parts[index - 1]; // athena index is 1-based
212
+ log(`checking path part`, { path, index, part, value });
213
+ return part?.startsWith(':') === true ? true : part === value;
214
+ };
215
+ }
216
+ }
217
+
218
+ // split_part(url, '/', N) = 'value'
219
+ if (isSplitPartUrl(left)) {
220
+ const args = (rec(left)?.['args'] as { value?: unknown[] } | undefined)?.value;
221
+ const index = getNumberValue(args?.[2]);
222
+ const value = getStringValue(right);
223
+ if (index !== undefined && value !== undefined) {
224
+ return (path) => {
225
+ const parts = path.split('/');
226
+ const part = parts[index - 1]; // 1-based
227
+ log('checking split_part path part', { path, index, part, value });
228
+ return part?.startsWith(':') === true ? true : part === value;
229
+ };
230
+ }
231
+ }
91
232
 
92
- if (methodToMatch !== undefined) {
93
- return (_path: string, method: string) => method === methodToMatch;
233
+ // cardinality(split(url, '/')) = N
234
+ if (isCardinalitySplitUrl(left)) {
235
+ const count = getNumberValue(right);
236
+ if (count !== undefined) {
237
+ return (path) => path.split('/').length === count;
238
+ }
94
239
  }
240
+
95
241
  return undefined;
96
242
  }
97
243
 
98
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
99
- function getResponseStatusToMatch(selectAST: object, _tableAST: object): string | undefined {
100
- const [responseStatus]: string[] = JSONPath({
101
- json: selectAST,
102
- path: "$.where..[?(@ && @.type === 'binary_expr' && @.operator === '=' && @.left && @.left.type === 'column_ref' && @.left.column === 'responsestatus' && @.right && @.right.type === 'single_quote_string')].right.value",
103
- }); /*?*/
244
+ function buildPredicate(expr: unknown, tableAlias: string | null): OperationPredicate {
245
+ const node = rec(expr);
246
+ if (node?.['type'] !== 'binary_expr') {
247
+ return ALWAYS_TRUE;
248
+ }
249
+
250
+ const binary = expr as Binary;
104
251
 
105
- return responseStatus;
252
+ switch (binary.operator) {
253
+ case 'AND': {
254
+ const leftPred = buildPredicate(binary.left, tableAlias);
255
+ const rightPred = buildPredicate(binary.right, tableAlias);
256
+ return (path, method, code) => leftPred(path, method, code) && rightPred(path, method, code);
257
+ }
258
+ case 'OR': {
259
+ const leftPred = buildPredicate(binary.left, tableAlias);
260
+ const rightPred = buildPredicate(binary.right, tableAlias);
261
+ return (path, method, code) => leftPred(path, method, code) || rightPred(path, method, code);
262
+ }
263
+ case 'NOT': {
264
+ const innerPred = buildPredicate(binary.left, tableAlias);
265
+ return (path, method, code) => !innerPred(path, method, code);
266
+ }
267
+ default: {
268
+ return buildLeafPredicate(binary, tableAlias) ?? ALWAYS_TRUE;
269
+ }
270
+ }
106
271
  }
107
272
 
108
- // [TODO:] match only relevent table in case multiple tables are joined
109
273
  export function matchApi(
110
274
  selectAST: object,
111
275
  tableAST: object,
112
276
  apiSchemas: ApiSchemas[],
113
277
  ): MatchedOperation[] | undefined {
114
- const schemaMatchers: Matcher[] = [
115
- getVersionMatcher(selectAST, tableAST),
116
- getPathMatchers(selectAST, tableAST),
117
- getMethodMatcher(selectAST, tableAST),
118
- ]
119
- .flat()
120
- .filter<Matcher>((matcher) => matcher !== undefined);
121
-
122
- const allOperationSchemas: OperationToMatch[] = apiSchemas
278
+ const tableAlias = (tableAST as { as?: string | null }).as ?? null;
279
+ const predicate = buildPredicate((selectAST as { where?: unknown }).where ?? null, tableAlias);
280
+
281
+ const allOperations: OperationToMatch[] = apiSchemas
123
282
  .flatMap((apiSchema) => Object.entries(apiSchema.apis))
124
283
  .flatMap(([path, operations]) =>
125
284
  Object.entries(operations).map(([method, operationSchemas]) => ({
@@ -128,41 +287,19 @@ export function matchApi(
128
287
  operationSchemas,
129
288
  })),
130
289
  );
131
- log('total operation schemas', allOperationSchemas.length);
290
+ log('total operation schemas', allOperations.length);
132
291
 
133
- const matchedOperationSchemas = allOperationSchemas.filter(({ path, method }) =>
134
- schemaMatchers.every((matcher) => matcher(path, method)),
292
+ const matchedApis = allOperations.flatMap(({ path, method, operationSchemas }) =>
293
+ Object.entries(operationSchemas.responses).flatMap(([responseCode, responseSchema]) =>
294
+ predicate(path, method, responseCode)
295
+ ? [{ path, method, request: operationSchemas.request, response: responseSchema }]
296
+ : [],
297
+ ),
135
298
  );
136
- log('matched operation schemas', matchedOperationSchemas.length);
137
-
138
- if (matchedOperationSchemas.length === 0) {
139
- log('no matched operation schema');
140
- throw new Error('no matched operation schema');
141
- }
142
-
143
- const matchedResponseStatus = getResponseStatusToMatch(selectAST, tableAST);
144
- // [TODO:] should we allow multiple response status?
145
- // assert.ok(matchedResponseStatus !== undefined);
146
- log('matchedResponseStatus', matchedResponseStatus);
147
-
148
- const matchedApis = matchedOperationSchemas
149
- .flatMap((operation) =>
150
- Object.entries(operation.operationSchemas.responses).map(([responseCode, responseSchema]) => {
151
- const matchedResponseSchema =
152
- matchedResponseStatus === undefined || responseCode === matchedResponseStatus ? responseSchema : undefined;
153
- return matchedResponseSchema === undefined
154
- ? undefined
155
- : {
156
- path: operation.path,
157
- method: operation.method,
158
- request: operation.operationSchemas.request,
159
- response: matchedResponseSchema,
160
- };
161
- }),
162
- )
163
- .filter((api) => api !== undefined);
299
+ log('matched apis', matchedApis.length);
300
+
164
301
  if (matchedApis.length === 0) {
165
- log('no api satisfy both request and response matchers');
302
+ log('no matched api');
166
303
  throw new Error('no matched api');
167
304
  }
168
305
  return matchedApis;