search-input-query-parser 0.1.4 → 0.2.1
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 +36 -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 +35 -0
- package/dist/types/parser.d.ts +2 -2
- package/dist/types/validator.d.ts +31 -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 +42 -0
package/src/validate-string.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { StringLiteral, WildcardPattern } from "./first-pass-parser";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ValidationError,
|
|
4
|
+
reservedWords,
|
|
5
|
+
SearchQueryErrorCode,
|
|
6
|
+
} from "./validator";
|
|
3
7
|
import { validateWildcard } from "./validate-wildcard";
|
|
4
8
|
|
|
5
9
|
// Validate individual strings (field:value pairs or plain terms)
|
|
@@ -20,6 +24,7 @@ export const validateString = (
|
|
|
20
24
|
if (expr.value.endsWith(":")) {
|
|
21
25
|
errors.push({
|
|
22
26
|
message: "Expected field value",
|
|
27
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
23
28
|
position: expr.position,
|
|
24
29
|
length: expr.length,
|
|
25
30
|
});
|
|
@@ -30,6 +35,7 @@ export const validateString = (
|
|
|
30
35
|
if (expr.value.startsWith(":")) {
|
|
31
36
|
errors.push({
|
|
32
37
|
message: "Missing field name",
|
|
38
|
+
code: SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
33
39
|
position: expr.position,
|
|
34
40
|
length: expr.length,
|
|
35
41
|
});
|
|
@@ -44,6 +50,8 @@ export const validateString = (
|
|
|
44
50
|
if (reservedWords.has(fieldName.toUpperCase())) {
|
|
45
51
|
errors.push({
|
|
46
52
|
message: `${fieldName} is a reserved word`,
|
|
53
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
54
|
+
value: fieldName,
|
|
47
55
|
position: expr.position,
|
|
48
56
|
length: fieldName.length,
|
|
49
57
|
});
|
|
@@ -54,6 +62,7 @@ export const validateString = (
|
|
|
54
62
|
if (!/^[a-zA-Z0-9_-]+$/.test(fieldName)) {
|
|
55
63
|
errors.push({
|
|
56
64
|
message: "Invalid characters in field name",
|
|
65
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
57
66
|
position: expr.position,
|
|
58
67
|
length: fieldName.length,
|
|
59
68
|
});
|
|
@@ -66,6 +75,8 @@ export const validateString = (
|
|
|
66
75
|
reservedWords.has(expr.value.toUpperCase())) {
|
|
67
76
|
errors.push({
|
|
68
77
|
message: `${expr.value} is a reserved word`,
|
|
78
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
79
|
+
value: expr.value,
|
|
69
80
|
position: expr.position,
|
|
70
81
|
length: expr.length,
|
|
71
82
|
});
|
package/src/validate-wildcard.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StringLiteral, WildcardPattern } from "./first-pass-parser";
|
|
2
|
-
import { ValidationError } from "./validator";
|
|
2
|
+
import { ValidationError, SearchQueryErrorCode } from "./validator";
|
|
3
3
|
|
|
4
4
|
// Validates wildcard patterns
|
|
5
5
|
|
|
@@ -18,6 +18,7 @@ export const validateWildcard = (
|
|
|
18
18
|
const secondStar = value.indexOf("*", firstStar + 1);
|
|
19
19
|
errors.push({
|
|
20
20
|
message: "Only one trailing wildcard (*) is allowed",
|
|
21
|
+
code: SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
21
22
|
position: expr.position + secondStar,
|
|
22
23
|
length: 1,
|
|
23
24
|
});
|
|
@@ -25,6 +26,7 @@ export const validateWildcard = (
|
|
|
25
26
|
if ((firstStar !== -1 && firstStar !== value.length - 1) && !value.endsWith("**")) {
|
|
26
27
|
errors.push({
|
|
27
28
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
29
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
28
30
|
position: expr.position + firstStar,
|
|
29
31
|
length: 1,
|
|
30
32
|
});
|
|
@@ -37,6 +39,7 @@ export const validateWildcard = (
|
|
|
37
39
|
if (value.endsWith("**")) {
|
|
38
40
|
errors.push({
|
|
39
41
|
message: "Only one trailing wildcard (*) is allowed",
|
|
42
|
+
code: SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
40
43
|
position: expr.position + value.length - 1,
|
|
41
44
|
length: 1,
|
|
42
45
|
});
|
package/src/validator.test.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
validateSearchQuery,
|
|
4
|
+
ValidationError,
|
|
5
|
+
SearchQueryErrorCode
|
|
6
|
+
} from "./validator";
|
|
3
7
|
import { tokenize, createStream } from "./lexer";
|
|
4
8
|
import { parseExpression } from "./first-pass-parser";
|
|
5
9
|
|
|
@@ -23,6 +27,7 @@ describe("Search Query Validator", () => {
|
|
|
23
27
|
expect(validateQuery("special@field:value")).toEqual([
|
|
24
28
|
{
|
|
25
29
|
message: "Invalid characters in field name",
|
|
30
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
26
31
|
position: 0,
|
|
27
32
|
length: 13,
|
|
28
33
|
},
|
|
@@ -33,6 +38,7 @@ describe("Search Query Validator", () => {
|
|
|
33
38
|
expect(validateQuery("valid:value special!:value")).toEqual([
|
|
34
39
|
{
|
|
35
40
|
message: "Invalid characters in field name",
|
|
41
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
36
42
|
position: 12,
|
|
37
43
|
length: 8,
|
|
38
44
|
},
|
|
@@ -45,6 +51,7 @@ describe("Search Query Validator", () => {
|
|
|
45
51
|
expect(validateQuery("field:")).toEqual([
|
|
46
52
|
{
|
|
47
53
|
message: "Expected field value",
|
|
54
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
48
55
|
position: 0,
|
|
49
56
|
length: 6,
|
|
50
57
|
},
|
|
@@ -60,6 +67,7 @@ describe("Search Query Validator", () => {
|
|
|
60
67
|
expect(validateQuery(":value")).toEqual([
|
|
61
68
|
{
|
|
62
69
|
message: "Missing field name",
|
|
70
|
+
code: SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
63
71
|
position: 0,
|
|
64
72
|
length: 6,
|
|
65
73
|
},
|
|
@@ -72,6 +80,8 @@ describe("Search Query Validator", () => {
|
|
|
72
80
|
expect(validateQuery("AND:value")).toEqual([
|
|
73
81
|
{
|
|
74
82
|
message: "AND is a reserved word",
|
|
83
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
84
|
+
value: "AND",
|
|
75
85
|
position: 0,
|
|
76
86
|
length: 3,
|
|
77
87
|
},
|
|
@@ -80,6 +90,8 @@ describe("Search Query Validator", () => {
|
|
|
80
90
|
expect(validateQuery("OR:value")).toEqual([
|
|
81
91
|
{
|
|
82
92
|
message: "OR is a reserved word",
|
|
93
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
94
|
+
value: "OR",
|
|
83
95
|
position: 0,
|
|
84
96
|
length: 2,
|
|
85
97
|
},
|
|
@@ -104,6 +116,7 @@ describe("Search Query Validator", () => {
|
|
|
104
116
|
expect(validateQuery("(field:value AND invalid!:value)")).toEqual([
|
|
105
117
|
{
|
|
106
118
|
message: "Invalid characters in field name",
|
|
119
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
107
120
|
position: 17,
|
|
108
121
|
length: 8,
|
|
109
122
|
},
|
|
@@ -114,11 +127,14 @@ describe("Search Query Validator", () => {
|
|
|
114
127
|
expect(validateQuery("AND:test OR invalid!:value")).toEqual([
|
|
115
128
|
{
|
|
116
129
|
message: "AND is a reserved word",
|
|
130
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
131
|
+
value: "AND",
|
|
117
132
|
position: 0,
|
|
118
133
|
length: 3,
|
|
119
134
|
},
|
|
120
135
|
{
|
|
121
136
|
message: "Invalid characters in field name",
|
|
137
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
122
138
|
position: 12,
|
|
123
139
|
length: 8,
|
|
124
140
|
},
|
|
@@ -131,6 +147,8 @@ describe("Search Query Validator", () => {
|
|
|
131
147
|
).toEqual([
|
|
132
148
|
{
|
|
133
149
|
message: "OR is a reserved word",
|
|
150
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
151
|
+
value: "OR",
|
|
134
152
|
position: 18,
|
|
135
153
|
length: 2,
|
|
136
154
|
},
|
|
@@ -151,11 +169,13 @@ describe("Search Query Validator", () => {
|
|
|
151
169
|
expect(validateQuery("field::value")).toEqual([
|
|
152
170
|
{
|
|
153
171
|
message: "Expected field value",
|
|
172
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
154
173
|
position: 0,
|
|
155
174
|
length: 6,
|
|
156
175
|
},
|
|
157
176
|
{
|
|
158
177
|
message: "Missing field name",
|
|
178
|
+
code: SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
159
179
|
position: 6,
|
|
160
180
|
length: 6,
|
|
161
181
|
}
|
|
@@ -166,6 +186,7 @@ describe("Search Query Validator", () => {
|
|
|
166
186
|
expect(validateQuery("@#$:value")).toEqual([
|
|
167
187
|
{
|
|
168
188
|
message: "Invalid characters in field name",
|
|
189
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
169
190
|
position: 0,
|
|
170
191
|
length: 3,
|
|
171
192
|
},
|
|
@@ -178,11 +199,14 @@ describe("Search Query Validator", () => {
|
|
|
178
199
|
expect(validateQuery(complexQuery)).toEqual([
|
|
179
200
|
{
|
|
180
201
|
message: "Invalid characters in field name",
|
|
202
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
181
203
|
position: 32,
|
|
182
204
|
length: 8,
|
|
183
205
|
},
|
|
184
206
|
{
|
|
185
207
|
message: "OR is a reserved word",
|
|
208
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
209
|
+
value: "OR",
|
|
186
210
|
position: 51,
|
|
187
211
|
length: 2,
|
|
188
212
|
},
|
package/src/validator.ts
CHANGED
|
@@ -6,9 +6,51 @@ import { FieldSchema } from "./parser";
|
|
|
6
6
|
import { validateInExpression } from "./validate-in-expression";
|
|
7
7
|
import { validateString } from "./validate-string";
|
|
8
8
|
|
|
9
|
+
export enum SearchQueryErrorCode {
|
|
10
|
+
UNKNOWN_ERROR = 0,
|
|
11
|
+
|
|
12
|
+
// Syntax Errors (1000-1999)
|
|
13
|
+
UNTERMINATED_QUOTED_STRING = 1001,
|
|
14
|
+
EXPECTED_FIELD_VALUE = 1002,
|
|
15
|
+
MISSING_FIELD_NAME = 1003,
|
|
16
|
+
MISSING_FIELD_VALUE = 1004,
|
|
17
|
+
UNEXPECTED_RIGHT_PAREN = 1005,
|
|
18
|
+
EXPECTED_RIGHT_PAREN = 1006,
|
|
19
|
+
UNEXPECTED_TOKEN = 1007,
|
|
20
|
+
EXPECTED_TOKEN = 1008,
|
|
21
|
+
MISSING_OPERATOR_OR_WHITESPACE = 1009,
|
|
22
|
+
RESERVED_WORD = 1010,
|
|
23
|
+
|
|
24
|
+
// Field Validation Errors (2000-2999)
|
|
25
|
+
INVALID_FIELD_NAME = 2001,
|
|
26
|
+
INVALID_FIELD_CHARS = 2002,
|
|
27
|
+
RESERVED_WORD_AS_FIELD = 2003,
|
|
28
|
+
|
|
29
|
+
// Value Validation Errors (3000-3999)
|
|
30
|
+
INVALID_NUMERIC_VALUE = 3001,
|
|
31
|
+
INVALID_DATE_FORMAT = 3002,
|
|
32
|
+
INVALID_RANGE_FORMAT = 3003,
|
|
33
|
+
INVALID_RANGE_OPERATOR = 3004,
|
|
34
|
+
EXPECTED_RANGE_VALUE = 3005,
|
|
35
|
+
RANGE_START_GREATER_THAN_END = 3006,
|
|
36
|
+
WILDCARD_NOT_ALLOWED = 3007,
|
|
37
|
+
|
|
38
|
+
// Wildcard Errors (4000-4999)
|
|
39
|
+
INVALID_WILDCARD_POSITION = 4001,
|
|
40
|
+
MULTIPLE_WILDCARDS = 4002,
|
|
41
|
+
|
|
42
|
+
// IN Expression Errors (5000-5999)
|
|
43
|
+
EMPTY_IN_LIST = 5001,
|
|
44
|
+
INVALID_IN_VALUE = 5002,
|
|
45
|
+
EXPECTED_IN_SEPARATOR = 5003,
|
|
46
|
+
EXPECTED_LPAREN_AFTER_IN = 5004,
|
|
47
|
+
}
|
|
48
|
+
|
|
9
49
|
// Validation error type
|
|
10
50
|
export type ValidationError = {
|
|
11
51
|
message: string;
|
|
52
|
+
code: SearchQueryErrorCode;
|
|
53
|
+
value?: string;
|
|
12
54
|
position: number;
|
|
13
55
|
length: number;
|
|
14
56
|
};
|