search-input-query-parser 0.1.4 → 0.2.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.
- package/dist/cjs/first-pass-parser.js +3 -0
- package/dist/cjs/lexer.js +9 -1
- package/dist/cjs/parse-in-values.js +5 -0
- package/dist/cjs/parse-primary.js +7 -0
- package/dist/cjs/parser.js +3 -1
- package/dist/cjs/validate-expression-fields.js +19 -0
- package/dist/cjs/validate-in-expression.js +4 -0
- package/dist/cjs/validate-string.js +7 -0
- package/dist/cjs/validate-wildcard.js +4 -0
- package/dist/cjs/validator.js +35 -1
- package/dist/esm/first-pass-parser.js +3 -0
- package/dist/esm/lexer.js +9 -1
- package/dist/esm/parse-in-values.js +5 -0
- package/dist/esm/parse-primary.js +7 -0
- package/dist/esm/parser.js +3 -2
- package/dist/esm/validate-expression-fields.js +19 -0
- package/dist/esm/validate-in-expression.js +5 -1
- package/dist/esm/validate-string.js +8 -1
- package/dist/esm/validate-wildcard.js +4 -0
- package/dist/esm/validator.js +34 -0
- package/dist/types/parser.d.ts +2 -2
- package/dist/types/validator.d.ts +30 -0
- package/package.json +1 -1
- package/src/first-pass-parser.ts +3 -0
- package/src/lexer.ts +10 -1
- package/src/parse-in-values.ts +5 -1
- package/src/parse-primary.ts +7 -0
- package/src/parser.test.ts +72 -0
- package/src/parser.ts +7 -1
- package/src/validate-expression-fields.ts +19 -1
- package/src/validate-in-expression.ts +5 -1
- package/src/validate-string.ts +12 -1
- package/src/validate-wildcard.ts +4 -1
- package/src/validator.test.ts +25 -1
- package/src/validator.ts +40 -0
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseExpression = void 0;
|
|
4
4
|
const lexer_1 = require("./lexer");
|
|
5
5
|
const parse_primary_1 = require("./parse-primary");
|
|
6
|
+
const validator_1 = require("./validator");
|
|
6
7
|
const getOperatorPrecedence = (type) => type === lexer_1.TokenType.AND ? 2 : type === lexer_1.TokenType.OR ? 1 : 0;
|
|
7
8
|
const parseExpression = (stream, minPrecedence = 0) => {
|
|
8
9
|
const token = (0, lexer_1.currentToken)(stream);
|
|
@@ -33,6 +34,8 @@ const parseExpression = (stream, minPrecedence = 0) => {
|
|
|
33
34
|
if (nextToken.type === lexer_1.TokenType.EOF) {
|
|
34
35
|
throw {
|
|
35
36
|
message: `Unexpected token: ${token.value}`,
|
|
37
|
+
code: validator_1.SearchQueryErrorCode.UNEXPECTED_TOKEN,
|
|
38
|
+
value: token.value,
|
|
36
39
|
position: token.position,
|
|
37
40
|
length: token.length,
|
|
38
41
|
};
|
package/dist/cjs/lexer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.tokenize = exports.advanceStream = exports.currentToken = exports.createStream = exports.TokenType = void 0;
|
|
4
|
+
const validator_1 = require("./validator");
|
|
4
5
|
// Token types and data structures
|
|
5
6
|
var TokenType;
|
|
6
7
|
(function (TokenType) {
|
|
@@ -96,7 +97,12 @@ const tokenizeQuotedString = (input, position) => {
|
|
|
96
97
|
pos++;
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
throw {
|
|
100
|
+
throw {
|
|
101
|
+
message: "Unterminated quoted string",
|
|
102
|
+
code: validator_1.SearchQueryErrorCode.UNTERMINATED_QUOTED_STRING,
|
|
103
|
+
position,
|
|
104
|
+
length,
|
|
105
|
+
};
|
|
100
106
|
};
|
|
101
107
|
const tokenizeString = (input, position) => {
|
|
102
108
|
let pos = position;
|
|
@@ -260,6 +266,7 @@ const tokenize = (input) => {
|
|
|
260
266
|
prevToken.type === TokenType.STRING)) {
|
|
261
267
|
throw {
|
|
262
268
|
message: "Invalid syntax: Missing operator or whitespace between terms",
|
|
269
|
+
code: validator_1.SearchQueryErrorCode.MISSING_OPERATOR_OR_WHITESPACE,
|
|
263
270
|
position: position,
|
|
264
271
|
length: 1,
|
|
265
272
|
};
|
|
@@ -272,6 +279,7 @@ const tokenize = (input) => {
|
|
|
272
279
|
!isSpecialChar(input[newPos])) {
|
|
273
280
|
throw {
|
|
274
281
|
message: "Invalid syntax: Missing operator or whitespace between terms",
|
|
282
|
+
code: validator_1.SearchQueryErrorCode.MISSING_OPERATOR_OR_WHITESPACE,
|
|
275
283
|
position: newPos,
|
|
276
284
|
length: 1,
|
|
277
285
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseInValues = void 0;
|
|
4
4
|
const lexer_1 = require("./lexer");
|
|
5
|
+
const validator_1 = require("./validator");
|
|
5
6
|
const parseInValues = (stream, inValuePosition) => {
|
|
6
7
|
const values = [];
|
|
7
8
|
let currentStream = stream;
|
|
@@ -9,6 +10,7 @@ const parseInValues = (stream, inValuePosition) => {
|
|
|
9
10
|
if ((0, lexer_1.currentToken)(currentStream).type !== lexer_1.TokenType.LPAREN) {
|
|
10
11
|
throw {
|
|
11
12
|
message: "Expected '(' after IN",
|
|
13
|
+
code: validator_1.SearchQueryErrorCode.EXPECTED_LPAREN_AFTER_IN,
|
|
12
14
|
position: inValuePosition, // Use the position passed from the caller
|
|
13
15
|
length: 1,
|
|
14
16
|
};
|
|
@@ -20,6 +22,7 @@ const parseInValues = (stream, inValuePosition) => {
|
|
|
20
22
|
if (values.length === 0) {
|
|
21
23
|
throw {
|
|
22
24
|
message: "IN operator requires at least one value",
|
|
25
|
+
code: validator_1.SearchQueryErrorCode.EMPTY_IN_LIST,
|
|
23
26
|
position: token.position,
|
|
24
27
|
length: 1,
|
|
25
28
|
};
|
|
@@ -36,6 +39,7 @@ const parseInValues = (stream, inValuePosition) => {
|
|
|
36
39
|
token.type !== lexer_1.TokenType.COMMA)) {
|
|
37
40
|
throw {
|
|
38
41
|
message: "Expected ',' or ')' after IN value",
|
|
42
|
+
code: validator_1.SearchQueryErrorCode.EXPECTED_IN_SEPARATOR,
|
|
39
43
|
position: token.position,
|
|
40
44
|
length: 1,
|
|
41
45
|
};
|
|
@@ -55,6 +59,7 @@ const parseInValues = (stream, inValuePosition) => {
|
|
|
55
59
|
}
|
|
56
60
|
throw {
|
|
57
61
|
message: "Expected ',' or ')' after IN value",
|
|
62
|
+
code: validator_1.SearchQueryErrorCode.EXPECTED_IN_SEPARATOR,
|
|
58
63
|
position: nextToken.position,
|
|
59
64
|
length: 1,
|
|
60
65
|
};
|
|
@@ -4,11 +4,14 @@ exports.parsePrimary = exports.extractFieldValue = exports.isFieldValuePattern =
|
|
|
4
4
|
const first_pass_parser_1 = require("./first-pass-parser");
|
|
5
5
|
const parse_in_values_1 = require("./parse-in-values");
|
|
6
6
|
const lexer_1 = require("./lexer");
|
|
7
|
+
const validator_1 = require("./validator");
|
|
7
8
|
const expectToken = (stream, type, message) => {
|
|
8
9
|
const token = (0, lexer_1.currentToken)(stream);
|
|
9
10
|
if (token.type !== type) {
|
|
10
11
|
throw {
|
|
11
12
|
message: message ? message : `Expected ${type}`,
|
|
13
|
+
code: validator_1.SearchQueryErrorCode.EXPECTED_TOKEN,
|
|
14
|
+
value: type,
|
|
12
15
|
position: token.position,
|
|
13
16
|
length: token.length,
|
|
14
17
|
};
|
|
@@ -134,18 +137,22 @@ const parsePrimary = (stream) => {
|
|
|
134
137
|
case lexer_1.TokenType.OR:
|
|
135
138
|
throw {
|
|
136
139
|
message: `${token.value} is a reserved word`,
|
|
140
|
+
code: validator_1.SearchQueryErrorCode.RESERVED_WORD,
|
|
141
|
+
value: token.value,
|
|
137
142
|
position: token.position,
|
|
138
143
|
length: token.length,
|
|
139
144
|
};
|
|
140
145
|
case lexer_1.TokenType.RPAREN:
|
|
141
146
|
throw {
|
|
142
147
|
message: 'Unexpected ")"',
|
|
148
|
+
code: validator_1.SearchQueryErrorCode.UNEXPECTED_RIGHT_PAREN,
|
|
143
149
|
position: token.position,
|
|
144
150
|
length: token.length,
|
|
145
151
|
};
|
|
146
152
|
default:
|
|
147
153
|
throw {
|
|
148
154
|
message: "Unexpected token",
|
|
155
|
+
code: validator_1.SearchQueryErrorCode.UNEXPECTED_TOKEN,
|
|
149
156
|
position: token.position,
|
|
150
157
|
length: token.length,
|
|
151
158
|
};
|
package/dist/cjs/parser.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.stringify = exports.parseSearchInputQuery = void 0;
|
|
3
|
+
exports.stringify = exports.SearchQueryErrorCode = exports.parseSearchInputQuery = void 0;
|
|
4
4
|
const lexer_1 = require("./lexer");
|
|
5
5
|
const first_pass_parser_1 = require("./first-pass-parser");
|
|
6
6
|
const validator_1 = require("./validator");
|
|
7
|
+
Object.defineProperty(exports, "SearchQueryErrorCode", { enumerable: true, get: function () { return validator_1.SearchQueryErrorCode; } });
|
|
7
8
|
const validate_expression_fields_1 = require("./validate-expression-fields");
|
|
8
9
|
const transform_to_expression_1 = require("./transform-to-expression");
|
|
9
10
|
// Helper function to stringify expressions
|
|
@@ -47,6 +48,7 @@ const parseSearchInputQuery = (input, fieldSchemas = []) => {
|
|
|
47
48
|
if (finalToken.type !== lexer_1.TokenType.EOF) {
|
|
48
49
|
throw {
|
|
49
50
|
message: 'Unexpected ")"',
|
|
51
|
+
code: validator_1.SearchQueryErrorCode.UNEXPECTED_RIGHT_PAREN,
|
|
50
52
|
position: finalToken.position,
|
|
51
53
|
length: finalToken.length,
|
|
52
54
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateExpressionFields = void 0;
|
|
4
|
+
const validator_1 = require("./validator");
|
|
4
5
|
// Helper to validate numeric values
|
|
5
6
|
const validateNumber = (value, position, errors) => {
|
|
6
7
|
if (value === "")
|
|
@@ -8,6 +9,7 @@ const validateNumber = (value, position, errors) => {
|
|
|
8
9
|
if (isNaN(Number(value))) {
|
|
9
10
|
errors.push({
|
|
10
11
|
message: "Invalid numeric value",
|
|
12
|
+
code: validator_1.SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
11
13
|
position,
|
|
12
14
|
length: value.length,
|
|
13
15
|
});
|
|
@@ -32,6 +34,7 @@ const validateNumericRange = (start, end, basePosition, errors) => {
|
|
|
32
34
|
if (startNum > endNum) {
|
|
33
35
|
errors.push({
|
|
34
36
|
message: "Range start must be less than or equal to range end",
|
|
37
|
+
code: validator_1.SearchQueryErrorCode.RANGE_START_GREATER_THAN_END,
|
|
35
38
|
position: basePosition,
|
|
36
39
|
length: start.length + 2 + end.length,
|
|
37
40
|
});
|
|
@@ -52,6 +55,8 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
52
55
|
if (!allowedFields.has(expr.field.toLowerCase())) {
|
|
53
56
|
errors.push({
|
|
54
57
|
message: `Invalid field: "${expr.field}"`,
|
|
58
|
+
code: validator_1.SearchQueryErrorCode.INVALID_FIELD_NAME,
|
|
59
|
+
value: expr.field,
|
|
55
60
|
position: expr.position,
|
|
56
61
|
length: expr.field.length,
|
|
57
62
|
});
|
|
@@ -65,6 +70,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
65
70
|
if (isNaN(Number(value))) {
|
|
66
71
|
errors.push({
|
|
67
72
|
message: "Invalid numeric value",
|
|
73
|
+
code: validator_1.SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
68
74
|
position: expr.position +
|
|
69
75
|
expr.field.length +
|
|
70
76
|
4 +
|
|
@@ -77,6 +83,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
77
83
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
78
84
|
errors.push({
|
|
79
85
|
message: "Invalid date format",
|
|
86
|
+
code: validator_1.SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
80
87
|
position: expr.position +
|
|
81
88
|
expr.field.length +
|
|
82
89
|
3 +
|
|
@@ -95,6 +102,8 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
95
102
|
const schema = schemas.get(expr.prefix.toLowerCase());
|
|
96
103
|
if ((schema === null || schema === void 0 ? void 0 : schema.type) === "number" || (schema === null || schema === void 0 ? void 0 : schema.type) === "date") {
|
|
97
104
|
errors.push({
|
|
105
|
+
code: validator_1.SearchQueryErrorCode.WILDCARD_NOT_ALLOWED,
|
|
106
|
+
value: schema.type,
|
|
98
107
|
message: `Wildcards are not allowed for ${schema.type} fields`,
|
|
99
108
|
position: expr.position,
|
|
100
109
|
length: expr.length,
|
|
@@ -111,6 +120,8 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
111
120
|
if (!allowedFields.has(fieldName.toLowerCase()) && colonIndex > 0) {
|
|
112
121
|
errors.push({
|
|
113
122
|
message: `Invalid field: "${fieldName}"`,
|
|
123
|
+
code: validator_1.SearchQueryErrorCode.INVALID_FIELD_NAME,
|
|
124
|
+
value: fieldName,
|
|
114
125
|
position: expr.position,
|
|
115
126
|
length: colonIndex,
|
|
116
127
|
});
|
|
@@ -118,6 +129,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
118
129
|
if (!value) {
|
|
119
130
|
errors.push({
|
|
120
131
|
message: "Expected field value",
|
|
132
|
+
code: validator_1.SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
121
133
|
position: expr.position,
|
|
122
134
|
length: colonIndex + 1,
|
|
123
135
|
});
|
|
@@ -126,6 +138,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
126
138
|
if (value.startsWith(":")) {
|
|
127
139
|
errors.push({
|
|
128
140
|
message: "Missing field name",
|
|
141
|
+
code: validator_1.SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
129
142
|
position: expr.position,
|
|
130
143
|
length: value.length + colonIndex + 1,
|
|
131
144
|
});
|
|
@@ -140,6 +153,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
140
153
|
if (value === ".." || value.includes("...")) {
|
|
141
154
|
errors.push({
|
|
142
155
|
message: "Invalid range format",
|
|
156
|
+
code: validator_1.SearchQueryErrorCode.INVALID_RANGE_FORMAT,
|
|
143
157
|
position: valueStartPosition,
|
|
144
158
|
length: value.length,
|
|
145
159
|
});
|
|
@@ -156,6 +170,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
156
170
|
if (invalidOp.test(value)) {
|
|
157
171
|
errors.push({
|
|
158
172
|
message: "Invalid range operator",
|
|
173
|
+
code: validator_1.SearchQueryErrorCode.INVALID_RANGE_OPERATOR,
|
|
159
174
|
position: valueStartPosition,
|
|
160
175
|
length: 3,
|
|
161
176
|
});
|
|
@@ -164,6 +179,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
164
179
|
if (!compValue) {
|
|
165
180
|
errors.push({
|
|
166
181
|
message: "Expected range value",
|
|
182
|
+
code: validator_1.SearchQueryErrorCode.EXPECTED_RANGE_VALUE,
|
|
167
183
|
position: valueStartPosition + operator.length,
|
|
168
184
|
length: 0,
|
|
169
185
|
});
|
|
@@ -191,6 +207,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
191
207
|
if (!dateValidator(start) || !dateValidator(end)) {
|
|
192
208
|
errors.push({
|
|
193
209
|
message: "Invalid date format",
|
|
210
|
+
code: validator_1.SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
194
211
|
position: valueStartPosition,
|
|
195
212
|
length: value.length,
|
|
196
213
|
});
|
|
@@ -204,6 +221,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
204
221
|
if (!dateValidator(dateStr)) {
|
|
205
222
|
errors.push({
|
|
206
223
|
message: "Invalid date format",
|
|
224
|
+
code: validator_1.SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
207
225
|
position: valueStartPosition,
|
|
208
226
|
length: value.length,
|
|
209
227
|
});
|
|
@@ -213,6 +231,7 @@ const validateFieldValue = (expr, allowedFields, errors, schemas) => {
|
|
|
213
231
|
else if (!dateValidator(value)) {
|
|
214
232
|
errors.push({
|
|
215
233
|
message: "Invalid date format",
|
|
234
|
+
code: validator_1.SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
216
235
|
position: valueStartPosition,
|
|
217
236
|
length: value.length,
|
|
218
237
|
});
|
|
@@ -7,6 +7,7 @@ const validateInExpression = (expr, errors) => {
|
|
|
7
7
|
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(expr.field)) {
|
|
8
8
|
errors.push({
|
|
9
9
|
message: "Invalid characters in field name",
|
|
10
|
+
code: validator_1.SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
10
11
|
position: expr.position,
|
|
11
12
|
length: expr.field.length,
|
|
12
13
|
});
|
|
@@ -15,6 +16,8 @@ const validateInExpression = (expr, errors) => {
|
|
|
15
16
|
if (validator_1.reservedWords.has(expr.field.toUpperCase())) {
|
|
16
17
|
errors.push({
|
|
17
18
|
message: `${expr.field} is a reserved word`,
|
|
19
|
+
code: validator_1.SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
20
|
+
value: expr.field,
|
|
18
21
|
position: expr.position,
|
|
19
22
|
length: expr.field.length,
|
|
20
23
|
});
|
|
@@ -24,6 +27,7 @@ const validateInExpression = (expr, errors) => {
|
|
|
24
27
|
if (value.includes(",")) {
|
|
25
28
|
errors.push({
|
|
26
29
|
message: "Invalid character in IN value",
|
|
30
|
+
code: validator_1.SearchQueryErrorCode.INVALID_IN_VALUE,
|
|
27
31
|
position: expr.position + expr.field.length + 3 + index * (value.length + 1),
|
|
28
32
|
length: value.length,
|
|
29
33
|
});
|
|
@@ -16,6 +16,7 @@ const validateString = (expr, errors) => {
|
|
|
16
16
|
if (expr.value.endsWith(":")) {
|
|
17
17
|
errors.push({
|
|
18
18
|
message: "Expected field value",
|
|
19
|
+
code: validator_1.SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
19
20
|
position: expr.position,
|
|
20
21
|
length: expr.length,
|
|
21
22
|
});
|
|
@@ -25,6 +26,7 @@ const validateString = (expr, errors) => {
|
|
|
25
26
|
if (expr.value.startsWith(":")) {
|
|
26
27
|
errors.push({
|
|
27
28
|
message: "Missing field name",
|
|
29
|
+
code: validator_1.SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
28
30
|
position: expr.position,
|
|
29
31
|
length: expr.length,
|
|
30
32
|
});
|
|
@@ -37,6 +39,8 @@ const validateString = (expr, errors) => {
|
|
|
37
39
|
if (validator_1.reservedWords.has(fieldName.toUpperCase())) {
|
|
38
40
|
errors.push({
|
|
39
41
|
message: `${fieldName} is a reserved word`,
|
|
42
|
+
code: validator_1.SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
43
|
+
value: fieldName,
|
|
40
44
|
position: expr.position,
|
|
41
45
|
length: fieldName.length,
|
|
42
46
|
});
|
|
@@ -46,6 +50,7 @@ const validateString = (expr, errors) => {
|
|
|
46
50
|
if (!/^[a-zA-Z0-9_-]+$/.test(fieldName)) {
|
|
47
51
|
errors.push({
|
|
48
52
|
message: "Invalid characters in field name",
|
|
53
|
+
code: validator_1.SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
49
54
|
position: expr.position,
|
|
50
55
|
length: fieldName.length,
|
|
51
56
|
});
|
|
@@ -57,6 +62,8 @@ const validateString = (expr, errors) => {
|
|
|
57
62
|
validator_1.reservedWords.has(expr.value.toUpperCase())) {
|
|
58
63
|
errors.push({
|
|
59
64
|
message: `${expr.value} is a reserved word`,
|
|
65
|
+
code: validator_1.SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
66
|
+
value: expr.value,
|
|
60
67
|
position: expr.position,
|
|
61
68
|
length: expr.length,
|
|
62
69
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateWildcard = void 0;
|
|
4
|
+
const validator_1 = require("./validator");
|
|
4
5
|
// Validates wildcard patterns
|
|
5
6
|
const validateWildcard = (expr, errors) => {
|
|
6
7
|
const value = expr.type === "STRING" ? expr.value : expr.prefix + "*";
|
|
@@ -13,6 +14,7 @@ const validateWildcard = (expr, errors) => {
|
|
|
13
14
|
const secondStar = value.indexOf("*", firstStar + 1);
|
|
14
15
|
errors.push({
|
|
15
16
|
message: "Only one trailing wildcard (*) is allowed",
|
|
17
|
+
code: validator_1.SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
16
18
|
position: expr.position + secondStar,
|
|
17
19
|
length: 1,
|
|
18
20
|
});
|
|
@@ -20,6 +22,7 @@ const validateWildcard = (expr, errors) => {
|
|
|
20
22
|
if ((firstStar !== -1 && firstStar !== value.length - 1) && !value.endsWith("**")) {
|
|
21
23
|
errors.push({
|
|
22
24
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
25
|
+
code: validator_1.SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
23
26
|
position: expr.position + firstStar,
|
|
24
27
|
length: 1,
|
|
25
28
|
});
|
|
@@ -31,6 +34,7 @@ const validateWildcard = (expr, errors) => {
|
|
|
31
34
|
if (value.endsWith("**")) {
|
|
32
35
|
errors.push({
|
|
33
36
|
message: "Only one trailing wildcard (*) is allowed",
|
|
37
|
+
code: validator_1.SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
34
38
|
position: expr.position + value.length - 1,
|
|
35
39
|
length: 1,
|
|
36
40
|
});
|
package/dist/cjs/validator.js
CHANGED
|
@@ -1,8 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateSearchQuery = exports.reservedWords = void 0;
|
|
3
|
+
exports.validateSearchQuery = exports.reservedWords = exports.SearchQueryErrorCode = void 0;
|
|
4
4
|
const validate_in_expression_1 = require("./validate-in-expression");
|
|
5
5
|
const validate_string_1 = require("./validate-string");
|
|
6
|
+
var SearchQueryErrorCode;
|
|
7
|
+
(function (SearchQueryErrorCode) {
|
|
8
|
+
// Syntax Errors (1000-1999)
|
|
9
|
+
SearchQueryErrorCode[SearchQueryErrorCode["UNTERMINATED_QUOTED_STRING"] = 1001] = "UNTERMINATED_QUOTED_STRING";
|
|
10
|
+
SearchQueryErrorCode[SearchQueryErrorCode["EXPECTED_FIELD_VALUE"] = 1002] = "EXPECTED_FIELD_VALUE";
|
|
11
|
+
SearchQueryErrorCode[SearchQueryErrorCode["MISSING_FIELD_NAME"] = 1003] = "MISSING_FIELD_NAME";
|
|
12
|
+
SearchQueryErrorCode[SearchQueryErrorCode["MISSING_FIELD_VALUE"] = 1004] = "MISSING_FIELD_VALUE";
|
|
13
|
+
SearchQueryErrorCode[SearchQueryErrorCode["UNEXPECTED_RIGHT_PAREN"] = 1005] = "UNEXPECTED_RIGHT_PAREN";
|
|
14
|
+
SearchQueryErrorCode[SearchQueryErrorCode["EXPECTED_RIGHT_PAREN"] = 1006] = "EXPECTED_RIGHT_PAREN";
|
|
15
|
+
SearchQueryErrorCode[SearchQueryErrorCode["UNEXPECTED_TOKEN"] = 1007] = "UNEXPECTED_TOKEN";
|
|
16
|
+
SearchQueryErrorCode[SearchQueryErrorCode["EXPECTED_TOKEN"] = 1008] = "EXPECTED_TOKEN";
|
|
17
|
+
SearchQueryErrorCode[SearchQueryErrorCode["MISSING_OPERATOR_OR_WHITESPACE"] = 1009] = "MISSING_OPERATOR_OR_WHITESPACE";
|
|
18
|
+
SearchQueryErrorCode[SearchQueryErrorCode["RESERVED_WORD"] = 1010] = "RESERVED_WORD";
|
|
19
|
+
// Field Validation Errors (2000-2999)
|
|
20
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_FIELD_NAME"] = 2001] = "INVALID_FIELD_NAME";
|
|
21
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_FIELD_CHARS"] = 2002] = "INVALID_FIELD_CHARS";
|
|
22
|
+
SearchQueryErrorCode[SearchQueryErrorCode["RESERVED_WORD_AS_FIELD"] = 2003] = "RESERVED_WORD_AS_FIELD";
|
|
23
|
+
// Value Validation Errors (3000-3999)
|
|
24
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_NUMERIC_VALUE"] = 3001] = "INVALID_NUMERIC_VALUE";
|
|
25
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_DATE_FORMAT"] = 3002] = "INVALID_DATE_FORMAT";
|
|
26
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_RANGE_FORMAT"] = 3003] = "INVALID_RANGE_FORMAT";
|
|
27
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_RANGE_OPERATOR"] = 3004] = "INVALID_RANGE_OPERATOR";
|
|
28
|
+
SearchQueryErrorCode[SearchQueryErrorCode["EXPECTED_RANGE_VALUE"] = 3005] = "EXPECTED_RANGE_VALUE";
|
|
29
|
+
SearchQueryErrorCode[SearchQueryErrorCode["RANGE_START_GREATER_THAN_END"] = 3006] = "RANGE_START_GREATER_THAN_END";
|
|
30
|
+
SearchQueryErrorCode[SearchQueryErrorCode["WILDCARD_NOT_ALLOWED"] = 3007] = "WILDCARD_NOT_ALLOWED";
|
|
31
|
+
// Wildcard Errors (4000-4999)
|
|
32
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_WILDCARD_POSITION"] = 4001] = "INVALID_WILDCARD_POSITION";
|
|
33
|
+
SearchQueryErrorCode[SearchQueryErrorCode["MULTIPLE_WILDCARDS"] = 4002] = "MULTIPLE_WILDCARDS";
|
|
34
|
+
// IN Expression Errors (5000-5999)
|
|
35
|
+
SearchQueryErrorCode[SearchQueryErrorCode["EMPTY_IN_LIST"] = 5001] = "EMPTY_IN_LIST";
|
|
36
|
+
SearchQueryErrorCode[SearchQueryErrorCode["INVALID_IN_VALUE"] = 5002] = "INVALID_IN_VALUE";
|
|
37
|
+
SearchQueryErrorCode[SearchQueryErrorCode["EXPECTED_IN_SEPARATOR"] = 5003] = "EXPECTED_IN_SEPARATOR";
|
|
38
|
+
SearchQueryErrorCode[SearchQueryErrorCode["EXPECTED_LPAREN_AFTER_IN"] = 5004] = "EXPECTED_LPAREN_AFTER_IN";
|
|
39
|
+
})(SearchQueryErrorCode || (exports.SearchQueryErrorCode = SearchQueryErrorCode = {}));
|
|
6
40
|
exports.reservedWords = new Set(["AND", "OR"]);
|
|
7
41
|
const walkExpression = (expr, errors) => {
|
|
8
42
|
switch (expr.type) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TokenType, currentToken, advanceStream } from "./lexer";
|
|
2
2
|
import { parsePrimary } from "./parse-primary";
|
|
3
|
+
import { SearchQueryErrorCode } from "./validator";
|
|
3
4
|
const getOperatorPrecedence = (type) => type === TokenType.AND ? 2 : type === TokenType.OR ? 1 : 0;
|
|
4
5
|
export const parseExpression = (stream, minPrecedence = 0) => {
|
|
5
6
|
const token = currentToken(stream);
|
|
@@ -30,6 +31,8 @@ export const parseExpression = (stream, minPrecedence = 0) => {
|
|
|
30
31
|
if (nextToken.type === TokenType.EOF) {
|
|
31
32
|
throw {
|
|
32
33
|
message: `Unexpected token: ${token.value}`,
|
|
34
|
+
code: SearchQueryErrorCode.UNEXPECTED_TOKEN,
|
|
35
|
+
value: token.value,
|
|
33
36
|
position: token.position,
|
|
34
37
|
length: token.length,
|
|
35
38
|
};
|
package/dist/esm/lexer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SearchQueryErrorCode } from "./validator";
|
|
1
2
|
// Token types and data structures
|
|
2
3
|
export var TokenType;
|
|
3
4
|
(function (TokenType) {
|
|
@@ -90,7 +91,12 @@ const tokenizeQuotedString = (input, position) => {
|
|
|
90
91
|
pos++;
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
|
-
throw {
|
|
94
|
+
throw {
|
|
95
|
+
message: "Unterminated quoted string",
|
|
96
|
+
code: SearchQueryErrorCode.UNTERMINATED_QUOTED_STRING,
|
|
97
|
+
position,
|
|
98
|
+
length,
|
|
99
|
+
};
|
|
94
100
|
};
|
|
95
101
|
const tokenizeString = (input, position) => {
|
|
96
102
|
let pos = position;
|
|
@@ -254,6 +260,7 @@ export const tokenize = (input) => {
|
|
|
254
260
|
prevToken.type === TokenType.STRING)) {
|
|
255
261
|
throw {
|
|
256
262
|
message: "Invalid syntax: Missing operator or whitespace between terms",
|
|
263
|
+
code: SearchQueryErrorCode.MISSING_OPERATOR_OR_WHITESPACE,
|
|
257
264
|
position: position,
|
|
258
265
|
length: 1,
|
|
259
266
|
};
|
|
@@ -266,6 +273,7 @@ export const tokenize = (input) => {
|
|
|
266
273
|
!isSpecialChar(input[newPos])) {
|
|
267
274
|
throw {
|
|
268
275
|
message: "Invalid syntax: Missing operator or whitespace between terms",
|
|
276
|
+
code: SearchQueryErrorCode.MISSING_OPERATOR_OR_WHITESPACE,
|
|
269
277
|
position: newPos,
|
|
270
278
|
length: 1,
|
|
271
279
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { currentToken, TokenType, advanceStream } from "./lexer";
|
|
2
|
+
import { SearchQueryErrorCode } from "./validator";
|
|
2
3
|
export const parseInValues = (stream, inValuePosition) => {
|
|
3
4
|
const values = [];
|
|
4
5
|
let currentStream = stream;
|
|
@@ -6,6 +7,7 @@ export const parseInValues = (stream, inValuePosition) => {
|
|
|
6
7
|
if (currentToken(currentStream).type !== TokenType.LPAREN) {
|
|
7
8
|
throw {
|
|
8
9
|
message: "Expected '(' after IN",
|
|
10
|
+
code: SearchQueryErrorCode.EXPECTED_LPAREN_AFTER_IN,
|
|
9
11
|
position: inValuePosition, // Use the position passed from the caller
|
|
10
12
|
length: 1,
|
|
11
13
|
};
|
|
@@ -17,6 +19,7 @@ export const parseInValues = (stream, inValuePosition) => {
|
|
|
17
19
|
if (values.length === 0) {
|
|
18
20
|
throw {
|
|
19
21
|
message: "IN operator requires at least one value",
|
|
22
|
+
code: SearchQueryErrorCode.EMPTY_IN_LIST,
|
|
20
23
|
position: token.position,
|
|
21
24
|
length: 1,
|
|
22
25
|
};
|
|
@@ -33,6 +36,7 @@ export const parseInValues = (stream, inValuePosition) => {
|
|
|
33
36
|
token.type !== TokenType.COMMA)) {
|
|
34
37
|
throw {
|
|
35
38
|
message: "Expected ',' or ')' after IN value",
|
|
39
|
+
code: SearchQueryErrorCode.EXPECTED_IN_SEPARATOR,
|
|
36
40
|
position: token.position,
|
|
37
41
|
length: 1,
|
|
38
42
|
};
|
|
@@ -52,6 +56,7 @@ export const parseInValues = (stream, inValuePosition) => {
|
|
|
52
56
|
}
|
|
53
57
|
throw {
|
|
54
58
|
message: "Expected ',' or ')' after IN value",
|
|
59
|
+
code: SearchQueryErrorCode.EXPECTED_IN_SEPARATOR,
|
|
55
60
|
position: nextToken.position,
|
|
56
61
|
length: 1,
|
|
57
62
|
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { parseExpression } from "./first-pass-parser";
|
|
2
2
|
import { parseInValues } from "./parse-in-values";
|
|
3
3
|
import { currentToken, TokenType, advanceStream } from "./lexer";
|
|
4
|
+
import { SearchQueryErrorCode } from "./validator";
|
|
4
5
|
export const expectToken = (stream, type, message) => {
|
|
5
6
|
const token = currentToken(stream);
|
|
6
7
|
if (token.type !== type) {
|
|
7
8
|
throw {
|
|
8
9
|
message: message ? message : `Expected ${type}`,
|
|
10
|
+
code: SearchQueryErrorCode.EXPECTED_TOKEN,
|
|
11
|
+
value: type,
|
|
9
12
|
position: token.position,
|
|
10
13
|
length: token.length,
|
|
11
14
|
};
|
|
@@ -128,18 +131,22 @@ export const parsePrimary = (stream) => {
|
|
|
128
131
|
case TokenType.OR:
|
|
129
132
|
throw {
|
|
130
133
|
message: `${token.value} is a reserved word`,
|
|
134
|
+
code: SearchQueryErrorCode.RESERVED_WORD,
|
|
135
|
+
value: token.value,
|
|
131
136
|
position: token.position,
|
|
132
137
|
length: token.length,
|
|
133
138
|
};
|
|
134
139
|
case TokenType.RPAREN:
|
|
135
140
|
throw {
|
|
136
141
|
message: 'Unexpected ")"',
|
|
142
|
+
code: SearchQueryErrorCode.UNEXPECTED_RIGHT_PAREN,
|
|
137
143
|
position: token.position,
|
|
138
144
|
length: token.length,
|
|
139
145
|
};
|
|
140
146
|
default:
|
|
141
147
|
throw {
|
|
142
148
|
message: "Unexpected token",
|
|
149
|
+
code: SearchQueryErrorCode.UNEXPECTED_TOKEN,
|
|
143
150
|
position: token.position,
|
|
144
151
|
length: token.length,
|
|
145
152
|
};
|
package/dist/esm/parser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { tokenize, createStream, currentToken, TokenType } from "./lexer";
|
|
2
2
|
import { parseExpression, } from "./first-pass-parser";
|
|
3
|
-
import { validateSearchQuery } from "./validator";
|
|
3
|
+
import { validateSearchQuery, SearchQueryErrorCode, } from "./validator";
|
|
4
4
|
import { validateExpressionFields } from "./validate-expression-fields";
|
|
5
5
|
import { transformToExpression } from "./transform-to-expression";
|
|
6
6
|
// Helper function to stringify expressions
|
|
@@ -43,6 +43,7 @@ export const parseSearchInputQuery = (input, fieldSchemas = []) => {
|
|
|
43
43
|
if (finalToken.type !== TokenType.EOF) {
|
|
44
44
|
throw {
|
|
45
45
|
message: 'Unexpected ")"',
|
|
46
|
+
code: SearchQueryErrorCode.UNEXPECTED_RIGHT_PAREN,
|
|
46
47
|
position: finalToken.position,
|
|
47
48
|
length: finalToken.length,
|
|
48
49
|
};
|
|
@@ -78,4 +79,4 @@ export const parseSearchInputQuery = (input, fieldSchemas = []) => {
|
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
};
|
|
81
|
-
export { stringify, };
|
|
82
|
+
export { SearchQueryErrorCode, stringify, };
|