search-input-query-parser 0.1.3 → 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 +5 -4
- 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
package/src/parser.test.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
stringify,
|
|
8
8
|
ValidationError,
|
|
9
9
|
FieldSchema,
|
|
10
|
+
SearchQueryErrorCode,
|
|
10
11
|
} from "./parser";
|
|
11
12
|
|
|
12
13
|
describe("Search Query Parser", () => {
|
|
@@ -103,6 +104,7 @@ describe("Search Query Parser", () => {
|
|
|
103
104
|
{
|
|
104
105
|
length: 6,
|
|
105
106
|
message: "Expected field value",
|
|
107
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
106
108
|
position: 0,
|
|
107
109
|
},
|
|
108
110
|
]);
|
|
@@ -111,6 +113,7 @@ describe("Search Query Parser", () => {
|
|
|
111
113
|
{
|
|
112
114
|
length: 6,
|
|
113
115
|
message: "Expected field value",
|
|
116
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
114
117
|
position: 0,
|
|
115
118
|
},
|
|
116
119
|
]);
|
|
@@ -119,6 +122,7 @@ describe("Search Query Parser", () => {
|
|
|
119
122
|
{
|
|
120
123
|
length: 1,
|
|
121
124
|
message: "Expected field value",
|
|
125
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
122
126
|
position: 6,
|
|
123
127
|
},
|
|
124
128
|
]);
|
|
@@ -128,6 +132,7 @@ describe("Search Query Parser", () => {
|
|
|
128
132
|
{
|
|
129
133
|
length: 6,
|
|
130
134
|
message: "Expected field value",
|
|
135
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
131
136
|
position: 0,
|
|
132
137
|
},
|
|
133
138
|
]);
|
|
@@ -136,6 +141,7 @@ describe("Search Query Parser", () => {
|
|
|
136
141
|
{
|
|
137
142
|
length: 15,
|
|
138
143
|
message: "Missing field name",
|
|
144
|
+
code: SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
139
145
|
position: 6,
|
|
140
146
|
},
|
|
141
147
|
]);
|
|
@@ -437,6 +443,7 @@ describe("Search Query Parser", () => {
|
|
|
437
443
|
{
|
|
438
444
|
length: 6,
|
|
439
445
|
message: "Expected field value",
|
|
446
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
440
447
|
position: 0,
|
|
441
448
|
},
|
|
442
449
|
]);
|
|
@@ -444,6 +451,7 @@ describe("Search Query Parser", () => {
|
|
|
444
451
|
{
|
|
445
452
|
length: 6,
|
|
446
453
|
message: "Missing field name",
|
|
454
|
+
code: SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
447
455
|
position: 0,
|
|
448
456
|
},
|
|
449
457
|
]);
|
|
@@ -451,6 +459,7 @@ describe("Search Query Parser", () => {
|
|
|
451
459
|
{
|
|
452
460
|
length: 1,
|
|
453
461
|
message: "Expected field value",
|
|
462
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
454
463
|
position: 0,
|
|
455
464
|
},
|
|
456
465
|
]);
|
|
@@ -458,11 +467,13 @@ describe("Search Query Parser", () => {
|
|
|
458
467
|
{
|
|
459
468
|
length: 6,
|
|
460
469
|
message: "Expected field value",
|
|
470
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
461
471
|
position: 0,
|
|
462
472
|
},
|
|
463
473
|
{
|
|
464
474
|
length: 1,
|
|
465
475
|
message: "Expected field value",
|
|
476
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
466
477
|
position: 6,
|
|
467
478
|
},
|
|
468
479
|
]);
|
|
@@ -473,6 +484,8 @@ describe("Search Query Parser", () => {
|
|
|
473
484
|
{
|
|
474
485
|
length: 3,
|
|
475
486
|
message: "AND is a reserved word",
|
|
487
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
488
|
+
value: "AND",
|
|
476
489
|
position: 0,
|
|
477
490
|
},
|
|
478
491
|
]);
|
|
@@ -480,6 +493,8 @@ describe("Search Query Parser", () => {
|
|
|
480
493
|
{
|
|
481
494
|
length: 2,
|
|
482
495
|
message: "OR is a reserved word",
|
|
496
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
497
|
+
value: "OR",
|
|
483
498
|
position: 0,
|
|
484
499
|
},
|
|
485
500
|
]);
|
|
@@ -490,6 +505,7 @@ describe("Search Query Parser", () => {
|
|
|
490
505
|
{
|
|
491
506
|
length: 1,
|
|
492
507
|
message: 'Unexpected ")"',
|
|
508
|
+
code: SearchQueryErrorCode.UNEXPECTED_RIGHT_PAREN,
|
|
493
509
|
position: 1,
|
|
494
510
|
},
|
|
495
511
|
]);
|
|
@@ -497,6 +513,7 @@ describe("Search Query Parser", () => {
|
|
|
497
513
|
{
|
|
498
514
|
length: 1,
|
|
499
515
|
message: 'Unexpected ")"',
|
|
516
|
+
code: SearchQueryErrorCode.UNEXPECTED_RIGHT_PAREN,
|
|
500
517
|
position: 6,
|
|
501
518
|
},
|
|
502
519
|
]);
|
|
@@ -507,6 +524,7 @@ describe("Search Query Parser", () => {
|
|
|
507
524
|
{
|
|
508
525
|
length: 6,
|
|
509
526
|
message: "Unterminated quoted string",
|
|
527
|
+
code: SearchQueryErrorCode.UNTERMINATED_QUOTED_STRING,
|
|
510
528
|
position: 6,
|
|
511
529
|
},
|
|
512
530
|
]);
|
|
@@ -514,6 +532,7 @@ describe("Search Query Parser", () => {
|
|
|
514
532
|
{
|
|
515
533
|
length: 7,
|
|
516
534
|
message: "Unterminated quoted string",
|
|
535
|
+
code: SearchQueryErrorCode.UNTERMINATED_QUOTED_STRING,
|
|
517
536
|
position: 6,
|
|
518
537
|
},
|
|
519
538
|
]);
|
|
@@ -521,6 +540,7 @@ describe("Search Query Parser", () => {
|
|
|
521
540
|
{
|
|
522
541
|
length: 8,
|
|
523
542
|
message: "Unterminated quoted string",
|
|
543
|
+
code: SearchQueryErrorCode.UNTERMINATED_QUOTED_STRING,
|
|
524
544
|
position: 6,
|
|
525
545
|
},
|
|
526
546
|
]);
|
|
@@ -528,6 +548,7 @@ describe("Search Query Parser", () => {
|
|
|
528
548
|
{
|
|
529
549
|
length: 16,
|
|
530
550
|
message: "Unterminated quoted string",
|
|
551
|
+
code: SearchQueryErrorCode.UNTERMINATED_QUOTED_STRING,
|
|
531
552
|
position: 0,
|
|
532
553
|
},
|
|
533
554
|
]);
|
|
@@ -538,6 +559,8 @@ describe("Search Query Parser", () => {
|
|
|
538
559
|
{
|
|
539
560
|
length: 3,
|
|
540
561
|
message: "AND is a reserved word",
|
|
562
|
+
code: SearchQueryErrorCode.RESERVED_WORD,
|
|
563
|
+
value: "AND",
|
|
541
564
|
position: 0,
|
|
542
565
|
},
|
|
543
566
|
]);
|
|
@@ -545,6 +568,8 @@ describe("Search Query Parser", () => {
|
|
|
545
568
|
{
|
|
546
569
|
length: 2,
|
|
547
570
|
message: "OR is a reserved word",
|
|
571
|
+
code: SearchQueryErrorCode.RESERVED_WORD,
|
|
572
|
+
value: "OR",
|
|
548
573
|
position: 0,
|
|
549
574
|
},
|
|
550
575
|
]);
|
|
@@ -552,6 +577,8 @@ describe("Search Query Parser", () => {
|
|
|
552
577
|
{
|
|
553
578
|
length: 3,
|
|
554
579
|
message: "Unexpected token: AND",
|
|
580
|
+
code: SearchQueryErrorCode.UNEXPECTED_TOKEN,
|
|
581
|
+
value: "AND",
|
|
555
582
|
position: 5,
|
|
556
583
|
},
|
|
557
584
|
]);
|
|
@@ -559,6 +586,8 @@ describe("Search Query Parser", () => {
|
|
|
559
586
|
{
|
|
560
587
|
length: 2,
|
|
561
588
|
message: "Unexpected token: OR",
|
|
589
|
+
code: SearchQueryErrorCode.UNEXPECTED_TOKEN,
|
|
590
|
+
value: "OR",
|
|
562
591
|
position: 5,
|
|
563
592
|
},
|
|
564
593
|
]);
|
|
@@ -566,6 +595,8 @@ describe("Search Query Parser", () => {
|
|
|
566
595
|
{
|
|
567
596
|
length: 3,
|
|
568
597
|
message: "AND is a reserved word",
|
|
598
|
+
code: SearchQueryErrorCode.RESERVED_WORD,
|
|
599
|
+
value: "AND",
|
|
569
600
|
position: 0,
|
|
570
601
|
},
|
|
571
602
|
]);
|
|
@@ -573,6 +604,8 @@ describe("Search Query Parser", () => {
|
|
|
573
604
|
{
|
|
574
605
|
length: 2,
|
|
575
606
|
message: "OR is a reserved word",
|
|
607
|
+
code: SearchQueryErrorCode.RESERVED_WORD,
|
|
608
|
+
value: "OR",
|
|
576
609
|
position: 0,
|
|
577
610
|
},
|
|
578
611
|
]);
|
|
@@ -585,16 +618,19 @@ describe("Search Query Parser", () => {
|
|
|
585
618
|
{
|
|
586
619
|
length: 6,
|
|
587
620
|
message: "Expected field value",
|
|
621
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
588
622
|
position: 29,
|
|
589
623
|
},
|
|
590
624
|
{
|
|
591
625
|
length: 6,
|
|
592
626
|
message: "Expected field value",
|
|
627
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
593
628
|
position: 39,
|
|
594
629
|
},
|
|
595
630
|
{
|
|
596
631
|
length: 5,
|
|
597
632
|
message: "Expected field value",
|
|
633
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
598
634
|
position: 51,
|
|
599
635
|
},
|
|
600
636
|
]
|
|
@@ -605,6 +641,8 @@ describe("Search Query Parser", () => {
|
|
|
605
641
|
{
|
|
606
642
|
length: 3,
|
|
607
643
|
message: "AND is a reserved word",
|
|
644
|
+
code: SearchQueryErrorCode.RESERVED_WORD,
|
|
645
|
+
value: "AND",
|
|
608
646
|
position: 61,
|
|
609
647
|
},
|
|
610
648
|
]
|
|
@@ -615,6 +653,7 @@ describe("Search Query Parser", () => {
|
|
|
615
653
|
testSchemaErrorQuery("amount:>", [
|
|
616
654
|
{
|
|
617
655
|
message: "Expected range value",
|
|
656
|
+
code: SearchQueryErrorCode.EXPECTED_RANGE_VALUE,
|
|
618
657
|
position: 8,
|
|
619
658
|
length: 0,
|
|
620
659
|
},
|
|
@@ -623,6 +662,7 @@ describe("Search Query Parser", () => {
|
|
|
623
662
|
testSchemaErrorQuery("price:>=>100", [
|
|
624
663
|
{
|
|
625
664
|
message: "Invalid range operator",
|
|
665
|
+
code: SearchQueryErrorCode.INVALID_RANGE_OPERATOR,
|
|
626
666
|
position: 6,
|
|
627
667
|
length: 3,
|
|
628
668
|
},
|
|
@@ -631,6 +671,7 @@ describe("Search Query Parser", () => {
|
|
|
631
671
|
testSchemaErrorQuery("price:>..", [
|
|
632
672
|
{
|
|
633
673
|
message: "Invalid numeric value",
|
|
674
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
634
675
|
position: 6,
|
|
635
676
|
length: 1,
|
|
636
677
|
},
|
|
@@ -639,6 +680,7 @@ describe("Search Query Parser", () => {
|
|
|
639
680
|
testSchemaErrorQuery("price:...", [
|
|
640
681
|
{
|
|
641
682
|
message: "Invalid range format",
|
|
683
|
+
code: SearchQueryErrorCode.INVALID_RANGE_FORMAT,
|
|
642
684
|
position: 6,
|
|
643
685
|
length: 3,
|
|
644
686
|
},
|
|
@@ -647,6 +689,7 @@ describe("Search Query Parser", () => {
|
|
|
647
689
|
testSchemaErrorQuery("price:100...", [
|
|
648
690
|
{
|
|
649
691
|
message: "Invalid range format",
|
|
692
|
+
code: SearchQueryErrorCode.INVALID_RANGE_FORMAT,
|
|
650
693
|
position: 6,
|
|
651
694
|
length: 6,
|
|
652
695
|
},
|
|
@@ -655,6 +698,7 @@ describe("Search Query Parser", () => {
|
|
|
655
698
|
testSchemaErrorQuery("price:...200", [
|
|
656
699
|
{
|
|
657
700
|
message: "Invalid range format",
|
|
701
|
+
code: SearchQueryErrorCode.INVALID_RANGE_FORMAT,
|
|
658
702
|
position: 6,
|
|
659
703
|
length: 6,
|
|
660
704
|
},
|
|
@@ -665,11 +709,13 @@ describe("Search Query Parser", () => {
|
|
|
665
709
|
testSchemaErrorQuery("price:abc..def", [
|
|
666
710
|
{
|
|
667
711
|
message: "Invalid numeric value",
|
|
712
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
668
713
|
position: 6,
|
|
669
714
|
length: 3,
|
|
670
715
|
},
|
|
671
716
|
{
|
|
672
717
|
message: "Invalid numeric value",
|
|
718
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
673
719
|
position: 11,
|
|
674
720
|
length: 3,
|
|
675
721
|
},
|
|
@@ -678,6 +724,7 @@ describe("Search Query Parser", () => {
|
|
|
678
724
|
testSchemaErrorQuery("amount:>abc", [
|
|
679
725
|
{
|
|
680
726
|
message: "Invalid numeric value",
|
|
727
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
681
728
|
position: 8,
|
|
682
729
|
length: 3,
|
|
683
730
|
},
|
|
@@ -688,6 +735,7 @@ describe("Search Query Parser", () => {
|
|
|
688
735
|
testSchemaErrorQuery("date:>not-a-date", [
|
|
689
736
|
{
|
|
690
737
|
message: "Invalid date format",
|
|
738
|
+
code: SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
691
739
|
position: 5,
|
|
692
740
|
length: 11,
|
|
693
741
|
},
|
|
@@ -696,6 +744,7 @@ describe("Search Query Parser", () => {
|
|
|
696
744
|
testSchemaErrorQuery("date:2024-13-01..2024-12-31", [
|
|
697
745
|
{
|
|
698
746
|
message: "Invalid date format",
|
|
747
|
+
code: SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
699
748
|
position: 5,
|
|
700
749
|
length: 22,
|
|
701
750
|
},
|
|
@@ -708,6 +757,7 @@ describe("Search Query Parser", () => {
|
|
|
708
757
|
testErrorQuery("test*test", [
|
|
709
758
|
{
|
|
710
759
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
760
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
711
761
|
position: 4,
|
|
712
762
|
length: 1,
|
|
713
763
|
},
|
|
@@ -716,11 +766,13 @@ describe("Search Query Parser", () => {
|
|
|
716
766
|
testErrorQuery("te*st*", [
|
|
717
767
|
{
|
|
718
768
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
769
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
719
770
|
position: 2,
|
|
720
771
|
length: 1,
|
|
721
772
|
},
|
|
722
773
|
{
|
|
723
774
|
message: "Only one trailing wildcard (*) is allowed",
|
|
775
|
+
code: SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
724
776
|
position: 5,
|
|
725
777
|
length: 1,
|
|
726
778
|
},
|
|
@@ -731,6 +783,7 @@ describe("Search Query Parser", () => {
|
|
|
731
783
|
testErrorQuery("test**", [
|
|
732
784
|
{
|
|
733
785
|
message: "Only one trailing wildcard (*) is allowed",
|
|
786
|
+
code: SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
734
787
|
position: 5,
|
|
735
788
|
length: 1,
|
|
736
789
|
},
|
|
@@ -739,6 +792,7 @@ describe("Search Query Parser", () => {
|
|
|
739
792
|
testErrorQuery('"test"**', [
|
|
740
793
|
{
|
|
741
794
|
message: "Only one trailing wildcard (*) is allowed",
|
|
795
|
+
code: SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
742
796
|
position: 7,
|
|
743
797
|
length: 1,
|
|
744
798
|
},
|
|
@@ -747,6 +801,7 @@ describe("Search Query Parser", () => {
|
|
|
747
801
|
testErrorQuery('field:"test"**', [
|
|
748
802
|
{
|
|
749
803
|
message: "Only one trailing wildcard (*) is allowed",
|
|
804
|
+
code: SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
750
805
|
position: 13,
|
|
751
806
|
length: 1,
|
|
752
807
|
},
|
|
@@ -757,11 +812,13 @@ describe("Search Query Parser", () => {
|
|
|
757
812
|
testErrorQuery("fie*ld:value", [
|
|
758
813
|
{
|
|
759
814
|
message: "Invalid characters in field name",
|
|
815
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
760
816
|
position: 0,
|
|
761
817
|
length: 6,
|
|
762
818
|
},
|
|
763
819
|
{
|
|
764
820
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
821
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
765
822
|
position: 3,
|
|
766
823
|
length: 1,
|
|
767
824
|
},
|
|
@@ -770,11 +827,13 @@ describe("Search Query Parser", () => {
|
|
|
770
827
|
testErrorQuery("field*:value", [
|
|
771
828
|
{
|
|
772
829
|
message: "Invalid characters in field name",
|
|
830
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
773
831
|
position: 0,
|
|
774
832
|
length: 6,
|
|
775
833
|
},
|
|
776
834
|
{
|
|
777
835
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
836
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
778
837
|
position: 5,
|
|
779
838
|
length: 1,
|
|
780
839
|
},
|
|
@@ -783,11 +842,13 @@ describe("Search Query Parser", () => {
|
|
|
783
842
|
testErrorQuery("f*:value", [
|
|
784
843
|
{
|
|
785
844
|
message: "Invalid characters in field name",
|
|
845
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
786
846
|
position: 0,
|
|
787
847
|
length: 2,
|
|
788
848
|
},
|
|
789
849
|
{
|
|
790
850
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
851
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
791
852
|
position: 1,
|
|
792
853
|
length: 1,
|
|
793
854
|
},
|
|
@@ -798,11 +859,13 @@ describe("Search Query Parser", () => {
|
|
|
798
859
|
testErrorQuery('field*:"test"', [
|
|
799
860
|
{
|
|
800
861
|
message: "Invalid characters in field name",
|
|
862
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
801
863
|
position: 0,
|
|
802
864
|
length: 6,
|
|
803
865
|
},
|
|
804
866
|
{
|
|
805
867
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
868
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
806
869
|
position: 5,
|
|
807
870
|
length: 1,
|
|
808
871
|
},
|
|
@@ -811,6 +874,7 @@ describe("Search Query Parser", () => {
|
|
|
811
874
|
testErrorQuery('field:"test * test"**', [
|
|
812
875
|
{
|
|
813
876
|
message: "Only one trailing wildcard (*) is allowed",
|
|
877
|
+
code: SearchQueryErrorCode.MULTIPLE_WILDCARDS,
|
|
814
878
|
position: 20,
|
|
815
879
|
length: 1,
|
|
816
880
|
},
|
|
@@ -819,11 +883,13 @@ describe("Search Query Parser", () => {
|
|
|
819
883
|
testErrorQuery("test* field*:value", [
|
|
820
884
|
{
|
|
821
885
|
message: "Invalid characters in field name",
|
|
886
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
822
887
|
position: 6,
|
|
823
888
|
length: 6,
|
|
824
889
|
},
|
|
825
890
|
{
|
|
826
891
|
message: "Wildcard (*) can only appear at the end of a term",
|
|
892
|
+
code: SearchQueryErrorCode.INVALID_WILDCARD_POSITION,
|
|
827
893
|
position: 11,
|
|
828
894
|
length: 1,
|
|
829
895
|
},
|
|
@@ -892,11 +958,13 @@ describe("Search Query Parser", () => {
|
|
|
892
958
|
expect((invalidNumResult as SearchQueryError).errors).toStrictEqual([
|
|
893
959
|
{
|
|
894
960
|
message: "Invalid numeric value",
|
|
961
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
895
962
|
position: 9,
|
|
896
963
|
length: 3,
|
|
897
964
|
},
|
|
898
965
|
{
|
|
899
966
|
message: "Invalid numeric value",
|
|
967
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
900
968
|
position: 13,
|
|
901
969
|
length: 3,
|
|
902
970
|
},
|
|
@@ -931,6 +999,7 @@ describe("Search Query Parser", () => {
|
|
|
931
999
|
testErrorQuery("status:IN()", [
|
|
932
1000
|
{
|
|
933
1001
|
message: "IN operator requires at least one value",
|
|
1002
|
+
code: SearchQueryErrorCode.EMPTY_IN_LIST,
|
|
934
1003
|
position: 10,
|
|
935
1004
|
length: 1,
|
|
936
1005
|
},
|
|
@@ -941,6 +1010,7 @@ describe("Search Query Parser", () => {
|
|
|
941
1010
|
testErrorQuery("status:IN(active,pending", [
|
|
942
1011
|
{
|
|
943
1012
|
message: "Expected ',' or ')' after IN value",
|
|
1013
|
+
code: SearchQueryErrorCode.EXPECTED_IN_SEPARATOR,
|
|
944
1014
|
position: 5,
|
|
945
1015
|
length: 1,
|
|
946
1016
|
},
|
|
@@ -962,6 +1032,7 @@ describe("Search Query Parser", () => {
|
|
|
962
1032
|
testErrorQuery("status:IN(active pending)", [
|
|
963
1033
|
{
|
|
964
1034
|
message: "Expected ',' or ')' after IN value",
|
|
1035
|
+
code: SearchQueryErrorCode.EXPECTED_IN_SEPARATOR,
|
|
965
1036
|
position: 17,
|
|
966
1037
|
length: 1,
|
|
967
1038
|
},
|
|
@@ -972,6 +1043,7 @@ describe("Search Query Parser", () => {
|
|
|
972
1043
|
testErrorQuery('status:IN("unclosed', [
|
|
973
1044
|
{
|
|
974
1045
|
message: "Unterminated quoted string",
|
|
1046
|
+
code: SearchQueryErrorCode.UNTERMINATED_QUOTED_STRING,
|
|
975
1047
|
position: 10,
|
|
976
1048
|
length: 10,
|
|
977
1049
|
},
|
package/src/parser.ts
CHANGED
|
@@ -4,7 +4,11 @@ import {
|
|
|
4
4
|
PositionLength,
|
|
5
5
|
WildcardPattern as FirstPassWildcard,
|
|
6
6
|
} from "./first-pass-parser";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
validateSearchQuery,
|
|
9
|
+
ValidationError,
|
|
10
|
+
SearchQueryErrorCode,
|
|
11
|
+
} from "./validator";
|
|
8
12
|
import { validateExpressionFields } from "./validate-expression-fields";
|
|
9
13
|
import { transformToExpression } from "./transform-to-expression";
|
|
10
14
|
|
|
@@ -142,6 +146,7 @@ export const parseSearchInputQuery = (
|
|
|
142
146
|
if (finalToken.type !== TokenType.EOF) {
|
|
143
147
|
throw {
|
|
144
148
|
message: 'Unexpected ")"',
|
|
149
|
+
code: SearchQueryErrorCode.UNEXPECTED_RIGHT_PAREN,
|
|
145
150
|
position: finalToken.position,
|
|
146
151
|
length: finalToken.length,
|
|
147
152
|
};
|
|
@@ -210,6 +215,7 @@ export {
|
|
|
210
215
|
type SearchQueryError,
|
|
211
216
|
type Expression,
|
|
212
217
|
type ValidationError,
|
|
218
|
+
SearchQueryErrorCode,
|
|
213
219
|
type FieldSchema,
|
|
214
220
|
type RangeOperator,
|
|
215
221
|
type RangeExpression,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FieldSchema } from "./parser";
|
|
2
2
|
import { FirstPassExpression } from "./first-pass-parser";
|
|
3
|
-
import { ValidationError } from "./validator";
|
|
3
|
+
import { ValidationError, SearchQueryErrorCode } from "./validator";
|
|
4
4
|
|
|
5
5
|
// Helper to validate numeric values
|
|
6
6
|
const validateNumber = (
|
|
@@ -12,6 +12,7 @@ const validateNumber = (
|
|
|
12
12
|
if (isNaN(Number(value))) {
|
|
13
13
|
errors.push({
|
|
14
14
|
message: "Invalid numeric value",
|
|
15
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
15
16
|
position,
|
|
16
17
|
length: value.length,
|
|
17
18
|
});
|
|
@@ -44,6 +45,7 @@ const validateNumericRange = (
|
|
|
44
45
|
if (startNum > endNum) {
|
|
45
46
|
errors.push({
|
|
46
47
|
message: "Range start must be less than or equal to range end",
|
|
48
|
+
code: SearchQueryErrorCode.RANGE_START_GREATER_THAN_END,
|
|
47
49
|
position: basePosition,
|
|
48
50
|
length: start.length + 2 + end.length,
|
|
49
51
|
});
|
|
@@ -77,6 +79,8 @@ const validateFieldValue = (
|
|
|
77
79
|
if (!allowedFields.has(expr.field.toLowerCase())) {
|
|
78
80
|
errors.push({
|
|
79
81
|
message: `Invalid field: "${expr.field}"`,
|
|
82
|
+
code: SearchQueryErrorCode.INVALID_FIELD_NAME,
|
|
83
|
+
value: expr.field,
|
|
80
84
|
position: expr.position,
|
|
81
85
|
length: expr.field.length,
|
|
82
86
|
});
|
|
@@ -91,6 +95,7 @@ const validateFieldValue = (
|
|
|
91
95
|
if (isNaN(Number(value))) {
|
|
92
96
|
errors.push({
|
|
93
97
|
message: "Invalid numeric value",
|
|
98
|
+
code: SearchQueryErrorCode.INVALID_NUMERIC_VALUE,
|
|
94
99
|
position:
|
|
95
100
|
expr.position +
|
|
96
101
|
expr.field.length +
|
|
@@ -104,6 +109,7 @@ const validateFieldValue = (
|
|
|
104
109
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
105
110
|
errors.push({
|
|
106
111
|
message: "Invalid date format",
|
|
112
|
+
code: SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
107
113
|
position:
|
|
108
114
|
expr.position +
|
|
109
115
|
expr.field.length +
|
|
@@ -124,6 +130,8 @@ const validateFieldValue = (
|
|
|
124
130
|
const schema = schemas.get(expr.prefix.toLowerCase());
|
|
125
131
|
if (schema?.type === "number" || schema?.type === "date") {
|
|
126
132
|
errors.push({
|
|
133
|
+
code: SearchQueryErrorCode.WILDCARD_NOT_ALLOWED,
|
|
134
|
+
value: schema.type,
|
|
127
135
|
message: `Wildcards are not allowed for ${schema.type} fields`,
|
|
128
136
|
position: expr.position,
|
|
129
137
|
length: expr.length,
|
|
@@ -142,6 +150,8 @@ const validateFieldValue = (
|
|
|
142
150
|
if (!allowedFields.has(fieldName.toLowerCase()) && colonIndex > 0) {
|
|
143
151
|
errors.push({
|
|
144
152
|
message: `Invalid field: "${fieldName}"`,
|
|
153
|
+
code: SearchQueryErrorCode.INVALID_FIELD_NAME,
|
|
154
|
+
value: fieldName,
|
|
145
155
|
position: expr.position,
|
|
146
156
|
length: colonIndex,
|
|
147
157
|
});
|
|
@@ -150,6 +160,7 @@ const validateFieldValue = (
|
|
|
150
160
|
if (!value) {
|
|
151
161
|
errors.push({
|
|
152
162
|
message: "Expected field value",
|
|
163
|
+
code: SearchQueryErrorCode.EXPECTED_FIELD_VALUE,
|
|
153
164
|
position: expr.position,
|
|
154
165
|
length: colonIndex + 1,
|
|
155
166
|
});
|
|
@@ -159,6 +170,7 @@ const validateFieldValue = (
|
|
|
159
170
|
if (value.startsWith(":")) {
|
|
160
171
|
errors.push({
|
|
161
172
|
message: "Missing field name",
|
|
173
|
+
code: SearchQueryErrorCode.MISSING_FIELD_NAME,
|
|
162
174
|
position: expr.position,
|
|
163
175
|
length: value.length + colonIndex + 1,
|
|
164
176
|
});
|
|
@@ -175,6 +187,7 @@ const validateFieldValue = (
|
|
|
175
187
|
if (value === ".." || value.includes("...")) {
|
|
176
188
|
errors.push({
|
|
177
189
|
message: "Invalid range format",
|
|
190
|
+
code: SearchQueryErrorCode.INVALID_RANGE_FORMAT,
|
|
178
191
|
position: valueStartPosition,
|
|
179
192
|
length: value.length,
|
|
180
193
|
});
|
|
@@ -194,6 +207,7 @@ const validateFieldValue = (
|
|
|
194
207
|
if (invalidOp.test(value)) {
|
|
195
208
|
errors.push({
|
|
196
209
|
message: "Invalid range operator",
|
|
210
|
+
code: SearchQueryErrorCode.INVALID_RANGE_OPERATOR,
|
|
197
211
|
position: valueStartPosition,
|
|
198
212
|
length: 3,
|
|
199
213
|
});
|
|
@@ -203,6 +217,7 @@ const validateFieldValue = (
|
|
|
203
217
|
if (!compValue) {
|
|
204
218
|
errors.push({
|
|
205
219
|
message: "Expected range value",
|
|
220
|
+
code: SearchQueryErrorCode.EXPECTED_RANGE_VALUE,
|
|
206
221
|
position: valueStartPosition + operator.length,
|
|
207
222
|
length: 0,
|
|
208
223
|
});
|
|
@@ -240,6 +255,7 @@ const validateFieldValue = (
|
|
|
240
255
|
if (!dateValidator(start) || !dateValidator(end)) {
|
|
241
256
|
errors.push({
|
|
242
257
|
message: "Invalid date format",
|
|
258
|
+
code: SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
243
259
|
position: valueStartPosition,
|
|
244
260
|
length: value.length,
|
|
245
261
|
});
|
|
@@ -252,6 +268,7 @@ const validateFieldValue = (
|
|
|
252
268
|
if (!dateValidator(dateStr)) {
|
|
253
269
|
errors.push({
|
|
254
270
|
message: "Invalid date format",
|
|
271
|
+
code: SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
255
272
|
position: valueStartPosition,
|
|
256
273
|
length: value.length,
|
|
257
274
|
});
|
|
@@ -260,6 +277,7 @@ const validateFieldValue = (
|
|
|
260
277
|
} else if (!dateValidator(value)) {
|
|
261
278
|
errors.push({
|
|
262
279
|
message: "Invalid date format",
|
|
280
|
+
code: SearchQueryErrorCode.INVALID_DATE_FORMAT,
|
|
263
281
|
position: valueStartPosition,
|
|
264
282
|
length: value.length,
|
|
265
283
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InExpression } from "./first-pass-parser";
|
|
2
|
-
import { ValidationError, reservedWords } from "./validator";
|
|
2
|
+
import { ValidationError, reservedWords, SearchQueryErrorCode } from "./validator";
|
|
3
3
|
|
|
4
4
|
export const validateInExpression = (
|
|
5
5
|
expr: InExpression,
|
|
@@ -9,6 +9,7 @@ export const validateInExpression = (
|
|
|
9
9
|
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(expr.field)) {
|
|
10
10
|
errors.push({
|
|
11
11
|
message: "Invalid characters in field name",
|
|
12
|
+
code: SearchQueryErrorCode.INVALID_FIELD_CHARS,
|
|
12
13
|
position: expr.position,
|
|
13
14
|
length: expr.field.length,
|
|
14
15
|
});
|
|
@@ -18,6 +19,8 @@ export const validateInExpression = (
|
|
|
18
19
|
if (reservedWords.has(expr.field.toUpperCase())) {
|
|
19
20
|
errors.push({
|
|
20
21
|
message: `${expr.field} is a reserved word`,
|
|
22
|
+
code: SearchQueryErrorCode.RESERVED_WORD_AS_FIELD,
|
|
23
|
+
value: expr.field,
|
|
21
24
|
position: expr.position,
|
|
22
25
|
length: expr.field.length,
|
|
23
26
|
});
|
|
@@ -28,6 +31,7 @@ export const validateInExpression = (
|
|
|
28
31
|
if (value.includes(",")) {
|
|
29
32
|
errors.push({
|
|
30
33
|
message: "Invalid character in IN value",
|
|
34
|
+
code: SearchQueryErrorCode.INVALID_IN_VALUE,
|
|
31
35
|
position: expr.position + expr.field.length + 3 + index * (value.length + 1),
|
|
32
36
|
length: value.length,
|
|
33
37
|
});
|