cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.80.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/parser/parser_test.go

6326lines · modecode

1package parser_test
2
3import (
4 "bufio"
5 "bytes"
6 "errors"
7 "os"
8 "strconv"
9 "testing"
10 "time"
11
12 "github.com/prometheus/common/model"
13 "github.com/stretchr/testify/require"
14
15 "github.com/cloudflare/pint/internal/comments"
16 "github.com/cloudflare/pint/internal/diags"
17 "github.com/cloudflare/pint/internal/parser"
18
19 "github.com/google/go-cmp/cmp"
20 "github.com/google/go-cmp/cmp/cmpopts"
21)
22
23func TestParse(t *testing.T) {
24 type testCaseT struct {
25 input []byte
26 output parser.File
27 strict bool
28 schema parser.Schema
29 names model.ValidationScheme
30 }
31
32 testCases := []testCaseT{
33 {
34 input: nil,
35 output: parser.File{
36 IsRelaxed: true,
37 },
38 },
39 {
40 input: []byte{},
41 output: parser.File{
42 IsRelaxed: true,
43 },
44 },
45 {
46 input: []byte(""),
47 output: parser.File{
48 IsRelaxed: true,
49 },
50 },
51 {
52 input: []byte("\n"),
53 output: parser.File{
54 IsRelaxed: true,
55 },
56 },
57 {
58 input: []byte("\n\n\n"),
59 output: parser.File{
60 IsRelaxed: true,
61 },
62 },
63 {
64 input: []byte("---"),
65 output: parser.File{
66 IsRelaxed: true,
67 },
68 },
69 {
70 input: []byte("---\n"),
71 output: parser.File{
72 IsRelaxed: true,
73 },
74 },
75 {
76 input: []byte("\n---\n\n---\n"),
77 output: parser.File{
78 IsRelaxed: true,
79 },
80 },
81 {
82 input: []byte("\n---\n\n---\n---"),
83 output: parser.File{
84 IsRelaxed: true,
85 },
86 },
87 {
88 input: []byte(string("! !00 \xf6")),
89 output: parser.File{
90 IsRelaxed: true,
91 Error: parser.ParseError{
92 Err: errors.New("yaml: incomplete UTF-8 octet sequence"),
93 Line: 1,
94 },
95 },
96 },
97 {
98 input: []byte("- 0: 0\n 00000000: 000000\n 000000:00000000000: 00000000\n 00000000000:000000: 0000000000000000000000000000000000\n 000000: 0000000\n expr: |"),
99 output: parser.File{
100 IsRelaxed: true,
101 Groups: []parser.Group{
102 {
103 Rules: []parser.Rule{
104 {
105 Lines: diags.LineRange{First: 1, Last: 6},
106 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 6},
107 },
108 },
109 },
110 },
111 },
112 },
113 {
114 input: []byte("- record: |\n multiline\n"),
115 output: parser.File{
116 IsRelaxed: true,
117 Groups: []parser.Group{
118 {
119 Rules: []parser.Rule{
120 {
121 Lines: diags.LineRange{First: 1, Last: 2},
122 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 2},
123 },
124 },
125 },
126 },
127 },
128 },
129 {
130 input: []byte(`---
131- expr: foo
132 record: foo
133---
134- expr: bar
135`),
136 output: parser.File{
137 IsRelaxed: true,
138 Groups: []parser.Group{
139 {
140 Rules: []parser.Rule{
141 {
142 RecordingRule: &parser.RecordingRule{
143 Expr: parser.PromQLExpr{
144 Value: &parser.YamlNode{
145 Value: "foo",
146 Pos: diags.PositionRanges{
147 {Line: 2, FirstColumn: 9, LastColumn: 11},
148 },
149 },
150 },
151 Record: parser.YamlNode{
152 Value: "foo",
153 Pos: diags.PositionRanges{
154 {Line: 3, FirstColumn: 11, LastColumn: 13},
155 },
156 },
157 },
158 Lines: diags.LineRange{First: 2, Last: 3},
159 },
160 },
161 },
162 {
163 Rules: []parser.Rule{
164 {
165 Lines: diags.LineRange{First: 5, Last: 5},
166 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 5},
167 },
168 },
169 },
170 },
171 },
172 },
173 {
174 input: []byte("- expr: foo\n"),
175 output: parser.File{
176 IsRelaxed: true,
177 Groups: []parser.Group{
178 {
179 Rules: []parser.Rule{
180 {
181 Lines: diags.LineRange{First: 1, Last: 1},
182 Error: parser.ParseError{Err: errors.New("incomplete rule, no alert or record key"), Line: 1},
183 },
184 },
185 },
186 },
187 },
188 },
189 {
190 input: []byte("- alert: foo\n"),
191 output: parser.File{
192 IsRelaxed: true,
193 Groups: []parser.Group{
194 {
195 Rules: []parser.Rule{
196 {
197 Lines: diags.LineRange{First: 1, Last: 1},
198 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
199 },
200 },
201 },
202 },
203 },
204 },
205 {
206 input: []byte("- alert: foo\n record: foo\n"),
207 output: parser.File{
208 IsRelaxed: true,
209 Groups: []parser.Group{
210 {
211 Rules: []parser.Rule{
212 {
213 Lines: diags.LineRange{First: 1, Last: 2},
214 Error: parser.ParseError{Err: errors.New("got both record and alert keys in a single rule"), Line: 1},
215 },
216 },
217 },
218 },
219 },
220 },
221 {
222 input: []byte("- record: foo\n labels:\n foo: bar\n"),
223 output: parser.File{
224 IsRelaxed: true,
225 Groups: []parser.Group{
226 {
227 Rules: []parser.Rule{
228 {
229 Lines: diags.LineRange{First: 1, Last: 3},
230 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
231 },
232 },
233 },
234 },
235 },
236 },
237 {
238 input: []byte("- record: - foo\n"),
239 output: parser.File{
240 IsRelaxed: true,
241 Error: parser.ParseError{
242 Err: errors.New("yaml: block sequence entries are not allowed in this context"),
243 Line: 1,
244 },
245 },
246 },
247 {
248 input: []byte("- record: foo expr: sum(\n"),
249 output: parser.File{
250 IsRelaxed: true,
251 Error: parser.ParseError{
252 Err: errors.New("yaml: mapping values are not allowed in this context"),
253 Line: 1,
254 },
255 },
256 },
257 {
258 input: []byte("- record\n\texpr: foo\n"),
259 output: parser.File{
260 IsRelaxed: true,
261 Error: parser.ParseError{
262 Err: errors.New("found a tab character that violates indentation"),
263 Line: 2,
264 },
265 },
266 },
267 {
268 input: []byte(`
269- record: foo
270 expr: bar
271 expr: bar
272`),
273 output: parser.File{
274 IsRelaxed: true,
275 Groups: []parser.Group{
276 {
277 Rules: []parser.Rule{
278 {
279 Lines: diags.LineRange{First: 2, Last: 4},
280 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 4},
281 },
282 },
283 },
284 },
285 },
286 },
287 {
288 input: []byte(`
289- record: foo
290 expr: bar
291 record: bar
292`),
293 output: parser.File{
294 IsRelaxed: true,
295 Groups: []parser.Group{
296 {
297 Rules: []parser.Rule{
298 {
299 Lines: diags.LineRange{First: 2, Last: 4},
300 Error: parser.ParseError{Err: errors.New("duplicated record key"), Line: 4},
301 },
302 },
303 },
304 },
305 },
306 },
307 {
308 input: []byte(`
309- alert: foo
310 alert: bar
311 expr: bar
312`),
313 output: parser.File{
314 IsRelaxed: true,
315 Groups: []parser.Group{
316 {
317 Rules: []parser.Rule{
318 {
319 Lines: diags.LineRange{First: 2, Last: 3},
320 Error: parser.ParseError{Err: errors.New("duplicated alert key"), Line: 3},
321 },
322 },
323 },
324 },
325 },
326 },
327 {
328 input: []byte(`
329- alert: foo
330 for: 5m
331 expr: bar
332 for: 1m
333`),
334 output: parser.File{
335 IsRelaxed: true,
336 Groups: []parser.Group{
337 {
338 Rules: []parser.Rule{
339 {
340 Lines: diags.LineRange{First: 2, Last: 5},
341 Error: parser.ParseError{Err: errors.New("duplicated for key"), Line: 5},
342 },
343 },
344 },
345 },
346 },
347 },
348 {
349 input: []byte(`
350- alert: foo
351 keep_firing_for: 5m
352 expr: bar
353 keep_firing_for: 1m
354`),
355 output: parser.File{
356 IsRelaxed: true,
357 Groups: []parser.Group{
358 {
359 Rules: []parser.Rule{
360 {
361 Lines: diags.LineRange{First: 2, Last: 5},
362 Error: parser.ParseError{Err: errors.New("duplicated keep_firing_for key"), Line: 5},
363 },
364 },
365 },
366 },
367 },
368 },
369 {
370 input: []byte(`
371- alert: foo
372 labels: {}
373 expr: bar
374 labels: {}
375`),
376 output: parser.File{
377 IsRelaxed: true,
378 Groups: []parser.Group{
379 {
380 Rules: []parser.Rule{
381 {
382 Lines: diags.LineRange{First: 2, Last: 5},
383 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
384 },
385 },
386 },
387 },
388 },
389 },
390 {
391 input: []byte(`
392- record: foo
393 labels: {}
394 expr: bar
395 labels: {}
396`),
397 output: parser.File{
398 IsRelaxed: true,
399 Groups: []parser.Group{
400 {
401 Rules: []parser.Rule{
402 {
403 Lines: diags.LineRange{First: 2, Last: 5},
404 Error: parser.ParseError{Err: errors.New("duplicated labels key"), Line: 5},
405 },
406 },
407 },
408 },
409 },
410 },
411 {
412 input: []byte(`
413- alert: foo
414 annotations: {}
415 expr: bar
416 annotations: {}
417`),
418 output: parser.File{
419 IsRelaxed: true,
420 Groups: []parser.Group{
421 {
422 Rules: []parser.Rule{
423 {
424 Lines: diags.LineRange{First: 2, Last: 5},
425 Error: parser.ParseError{Err: errors.New("duplicated annotations key"), Line: 5},
426 },
427 },
428 },
429 },
430 },
431 },
432 {
433 input: []byte("- record: foo\n expr: foo\n extra: true\n"),
434 output: parser.File{
435 IsRelaxed: true,
436 Groups: []parser.Group{
437 {
438 Rules: []parser.Rule{
439 {
440 Lines: diags.LineRange{First: 1, Last: 3},
441 Error: parser.ParseError{Err: errors.New("invalid key(s) found: extra"), Line: 3},
442 },
443 },
444 },
445 },
446 },
447 },
448 {
449 input: []byte(`- record: foo
450 expr: foo offset 10m
451`),
452 output: parser.File{
453 IsRelaxed: true,
454 Groups: []parser.Group{
455 {
456 Rules: []parser.Rule{
457 {
458 Lines: diags.LineRange{First: 1, Last: 2},
459 RecordingRule: &parser.RecordingRule{
460 Record: parser.YamlNode{
461 Value: "foo",
462 Pos: diags.PositionRanges{
463 {Line: 1, FirstColumn: 11, LastColumn: 13},
464 },
465 },
466 Expr: parser.PromQLExpr{
467 Value: &parser.YamlNode{
468 Pos: diags.PositionRanges{
469 {Line: 2, FirstColumn: 9, LastColumn: 22},
470 },
471 Value: "foo offset 10m",
472 },
473 },
474 },
475 },
476 },
477 },
478 },
479 },
480 },
481 {
482 input: []byte("- record: foo\n expr: foo offset -10m\n"),
483 output: parser.File{
484 IsRelaxed: true,
485 Groups: []parser.Group{
486 {
487 Rules: []parser.Rule{
488 {
489 Lines: diags.LineRange{First: 1, Last: 2},
490 RecordingRule: &parser.RecordingRule{
491 Record: parser.YamlNode{
492 Value: "foo",
493 Pos: diags.PositionRanges{
494 {Line: 1, FirstColumn: 11, LastColumn: 13},
495 },
496 },
497 Expr: parser.PromQLExpr{
498 Value: &parser.YamlNode{
499 Value: "foo offset -10m",
500 Pos: diags.PositionRanges{
501 {Line: 2, FirstColumn: 9, LastColumn: 23},
502 },
503 },
504 },
505 },
506 },
507 },
508 },
509 },
510 },
511 },
512 {
513 input: []byte(`
514# pint disable head comment
515- record: foo # pint disable record comment
516 expr: foo offset 10m # pint disable expr comment
517 # pint disable pre-labels comment
518 labels:
519 # pint disable pre-foo comment
520 foo: bar
521 # pint disable post-foo comment
522 bob: alice
523 # pint disable foot comment
524`),
525 output: parser.File{
526 IsRelaxed: true,
527 Groups: []parser.Group{
528 {
529 Rules: []parser.Rule{
530 {
531 Lines: diags.LineRange{First: 3, Last: 10},
532 Comments: []comments.Comment{
533 {
534 Type: comments.DisableType,
535 Value: comments.Disable{Match: "head comment"},
536 },
537 {
538 Type: comments.DisableType,
539 Value: comments.Disable{Match: "record comment"},
540 },
541 {
542 Type: comments.DisableType,
543 Value: comments.Disable{Match: "expr comment"},
544 },
545 {
546 Type: comments.DisableType,
547 Value: comments.Disable{Match: "pre-labels comment"},
548 },
549 {
550 Type: comments.DisableType,
551 Value: comments.Disable{Match: "foot comment"},
552 },
553 {
554 Type: comments.DisableType,
555 Value: comments.Disable{Match: "pre-foo comment"},
556 },
557 {
558 Type: comments.DisableType,
559 Value: comments.Disable{Match: "post-foo comment"},
560 },
561 },
562 RecordingRule: &parser.RecordingRule{
563 Record: parser.YamlNode{
564 Value: "foo",
565 Pos: diags.PositionRanges{
566 {Line: 3, FirstColumn: 11, LastColumn: 13},
567 },
568 },
569 Expr: parser.PromQLExpr{
570 Value: &parser.YamlNode{
571 Value: "foo offset 10m",
572 Pos: diags.PositionRanges{
573 {Line: 4, FirstColumn: 9, LastColumn: 22},
574 },
575 },
576 },
577 Labels: &parser.YamlMap{
578 Key: &parser.YamlNode{
579 Value: "labels",
580 Pos: diags.PositionRanges{
581 {Line: 6, FirstColumn: 3, LastColumn: 8},
582 },
583 },
584 Items: []*parser.YamlKeyValue{
585 {
586 Key: &parser.YamlNode{
587 Value: "foo",
588 Pos: diags.PositionRanges{
589 {Line: 8, FirstColumn: 5, LastColumn: 7},
590 },
591 },
592 Value: &parser.YamlNode{
593 Value: "bar",
594 Pos: diags.PositionRanges{
595 {Line: 8, FirstColumn: 10, LastColumn: 12},
596 },
597 },
598 },
599 {
600 Key: &parser.YamlNode{
601 Value: "bob",
602 Pos: diags.PositionRanges{
603 {Line: 10, FirstColumn: 5, LastColumn: 7},
604 },
605 },
606 Value: &parser.YamlNode{
607 Value: "alice",
608 Pos: diags.PositionRanges{
609 {Line: 10, FirstColumn: 10, LastColumn: 14},
610 },
611 },
612 },
613 },
614 },
615 },
616 },
617 },
618 },
619 },
620 },
621 },
622 {
623 input: []byte("- record: foo\n expr: foo[5m] offset 10m\n"),
624 output: parser.File{
625 IsRelaxed: true,
626 Groups: []parser.Group{
627 {
628 Rules: []parser.Rule{
629 {
630 Lines: diags.LineRange{First: 1, Last: 2},
631 RecordingRule: &parser.RecordingRule{
632 Record: parser.YamlNode{
633 Value: "foo",
634 Pos: diags.PositionRanges{
635 {Line: 1, FirstColumn: 11, LastColumn: 13},
636 },
637 },
638 Expr: parser.PromQLExpr{
639 Value: &parser.YamlNode{
640 Value: "foo[5m] offset 10m",
641 Pos: diags.PositionRanges{
642 {Line: 2, FirstColumn: 9, LastColumn: 26},
643 },
644 },
645 },
646 },
647 },
648 },
649 },
650 },
651 },
652 },
653 {
654 input: []byte(`
655- record: name
656 expr: sum(foo)
657 labels:
658 foo: bar
659 bob: alice
660`),
661 output: parser.File{
662 IsRelaxed: true,
663 Groups: []parser.Group{
664 {
665 Rules: []parser.Rule{
666 {
667 Lines: diags.LineRange{First: 2, Last: 6},
668 RecordingRule: &parser.RecordingRule{
669 Record: parser.YamlNode{
670 Value: "name",
671 Pos: diags.PositionRanges{
672 {Line: 2, FirstColumn: 11, LastColumn: 14},
673 },
674 },
675 Expr: parser.PromQLExpr{
676 Value: &parser.YamlNode{
677 Value: "sum(foo)",
678 Pos: diags.PositionRanges{
679 {Line: 3, FirstColumn: 9, LastColumn: 16},
680 },
681 },
682 },
683 Labels: &parser.YamlMap{
684 Key: &parser.YamlNode{
685 Value: "labels",
686 Pos: diags.PositionRanges{
687 {Line: 4, FirstColumn: 3, LastColumn: 8},
688 },
689 },
690 Items: []*parser.YamlKeyValue{
691 {
692 Key: &parser.YamlNode{
693 Value: "foo",
694 Pos: diags.PositionRanges{
695 {Line: 5, FirstColumn: 5, LastColumn: 7},
696 },
697 },
698 Value: &parser.YamlNode{
699 Value: "bar",
700 Pos: diags.PositionRanges{
701 {Line: 5, FirstColumn: 10, LastColumn: 12},
702 },
703 },
704 },
705 {
706 Key: &parser.YamlNode{
707 Value: "bob",
708 Pos: diags.PositionRanges{
709 {Line: 6, FirstColumn: 5, LastColumn: 7},
710 },
711 },
712 Value: &parser.YamlNode{
713 Value: "alice",
714 Pos: diags.PositionRanges{
715 {Line: 6, FirstColumn: 10, LastColumn: 14},
716 },
717 },
718 },
719 },
720 },
721 },
722 },
723 },
724 },
725 },
726 },
727 },
728 {
729 input: []byte(`
730groups:
731- name: custom_rules
732 rules:
733 - record: name
734 expr: sum(foo)
735 labels:
736 foo: bar
737 bob: alice
738`),
739 output: parser.File{
740 IsRelaxed: true,
741 Groups: []parser.Group{
742 {
743 Name: parser.YamlNode{
744 Value: "custom_rules",
745 Pos: diags.PositionRanges{
746 {Line: 3, FirstColumn: 9, LastColumn: 20},
747 },
748 },
749 Rules: []parser.Rule{
750 {
751 Lines: diags.LineRange{First: 5, Last: 9},
752 RecordingRule: &parser.RecordingRule{
753 Record: parser.YamlNode{
754 Value: "name",
755 Pos: diags.PositionRanges{
756 {Line: 5, FirstColumn: 15, LastColumn: 18},
757 },
758 },
759 Expr: parser.PromQLExpr{
760 Value: &parser.YamlNode{
761 Value: "sum(foo)",
762 Pos: diags.PositionRanges{
763 {Line: 6, FirstColumn: 13, LastColumn: 20},
764 },
765 },
766 },
767 Labels: &parser.YamlMap{
768 Key: &parser.YamlNode{
769 Value: "labels",
770 Pos: diags.PositionRanges{
771 {Line: 7, FirstColumn: 7, LastColumn: 12},
772 },
773 },
774 Items: []*parser.YamlKeyValue{
775 {
776 Key: &parser.YamlNode{
777 Value: "foo",
778 Pos: diags.PositionRanges{
779 {Line: 8, FirstColumn: 9, LastColumn: 11},
780 },
781 },
782 Value: &parser.YamlNode{
783 Value: "bar",
784 Pos: diags.PositionRanges{
785 {Line: 8, FirstColumn: 14, LastColumn: 16},
786 },
787 },
788 },
789 {
790 Key: &parser.YamlNode{
791 Value: "bob",
792 Pos: diags.PositionRanges{
793 {Line: 9, FirstColumn: 9, LastColumn: 11},
794 },
795 },
796 Value: &parser.YamlNode{
797 Value: "alice",
798 Pos: diags.PositionRanges{
799 {Line: 9, FirstColumn: 14, LastColumn: 18},
800 },
801 },
802 },
803 },
804 },
805 },
806 },
807 },
808 },
809 },
810 },
811 },
812 {
813 input: []byte(`- alert: Down
814 expr: |
815 up == 0
816 for: |+
817 11m
818 labels:
819 severity: critical
820 annotations:
821 uri: https://docs.example.com/down.html
822
823- record: foo
824 expr: |-
825 bar
826 /
827 baz > 1
828 labels: {}
829`),
830 output: parser.File{
831 IsRelaxed: true,
832 Groups: []parser.Group{
833 {
834 Rules: []parser.Rule{
835 {
836 Lines: diags.LineRange{First: 1, Last: 9},
837 AlertingRule: &parser.AlertingRule{
838 Alert: parser.YamlNode{
839 Value: "Down",
840 Pos: diags.PositionRanges{
841 {Line: 1, FirstColumn: 10, LastColumn: 13},
842 },
843 },
844 Expr: parser.PromQLExpr{
845 Value: &parser.YamlNode{
846 Value: "up == 0\n",
847 Pos: diags.PositionRanges{
848 {Line: 3, FirstColumn: 5, LastColumn: 11},
849 },
850 },
851 },
852 For: &parser.YamlDuration{
853 Raw: "11m\n",
854 ParseError: errors.New(`unknown unit "m\n" in duration "11m\n"`),
855 Pos: diags.PositionRanges{
856 {Line: 5, FirstColumn: 5, LastColumn: 7},
857 },
858 },
859 Labels: &parser.YamlMap{
860 Key: &parser.YamlNode{
861 Value: "labels",
862 Pos: diags.PositionRanges{
863 {Line: 6, FirstColumn: 3, LastColumn: 8},
864 },
865 },
866 Items: []*parser.YamlKeyValue{
867 {
868 Key: &parser.YamlNode{
869 Value: "severity",
870 Pos: diags.PositionRanges{
871 {Line: 7, FirstColumn: 5, LastColumn: 12},
872 },
873 },
874 Value: &parser.YamlNode{
875 Value: "critical",
876 Pos: diags.PositionRanges{
877 {Line: 7, FirstColumn: 15, LastColumn: 22},
878 },
879 },
880 },
881 },
882 },
883 Annotations: &parser.YamlMap{
884 Key: &parser.YamlNode{
885 Value: "annotations",
886 Pos: diags.PositionRanges{
887 {Line: 8, FirstColumn: 3, LastColumn: 13},
888 },
889 },
890 Items: []*parser.YamlKeyValue{
891 {
892 Key: &parser.YamlNode{
893 Value: "uri",
894 Pos: diags.PositionRanges{
895 {Line: 9, FirstColumn: 5, LastColumn: 7},
896 },
897 },
898 Value: &parser.YamlNode{
899 Value: "https://docs.example.com/down.html",
900 Pos: diags.PositionRanges{
901 {Line: 9, FirstColumn: 10, LastColumn: 43},
902 },
903 },
904 },
905 },
906 },
907 },
908 },
909 {
910 Lines: diags.LineRange{First: 11, Last: 16},
911 RecordingRule: &parser.RecordingRule{
912 Record: parser.YamlNode{
913 Value: "foo",
914 Pos: diags.PositionRanges{
915 {Line: 11, FirstColumn: 11, LastColumn: 13},
916 },
917 },
918 Expr: parser.PromQLExpr{
919 Value: &parser.YamlNode{
920 Value: "bar\n/\nbaz > 1",
921 Pos: diags.PositionRanges{
922 {Line: 13, FirstColumn: 5, LastColumn: 8},
923 {Line: 14, FirstColumn: 5, LastColumn: 6},
924 {Line: 15, FirstColumn: 5, LastColumn: 11},
925 },
926 },
927 },
928 Labels: &parser.YamlMap{
929 Key: &parser.YamlNode{
930 Value: "labels",
931 Pos: diags.PositionRanges{
932 {Line: 16, FirstColumn: 3, LastColumn: 8},
933 },
934 },
935 },
936 },
937 },
938 },
939 },
940 },
941 },
942 },
943 {
944 input: []byte(`- alert: Foo
945 expr:
946 (
947 xxx
948 -
949 yyy
950 ) * bar > 0
951 and on(instance, device) baz
952 for: 30m
953`),
954 output: parser.File{
955 IsRelaxed: true,
956 Groups: []parser.Group{
957 {
958 Rules: []parser.Rule{
959 {
960 Lines: diags.LineRange{First: 1, Last: 9},
961 AlertingRule: &parser.AlertingRule{
962 Alert: parser.YamlNode{
963 Value: "Foo",
964 Pos: diags.PositionRanges{
965 {Line: 1, FirstColumn: 10, LastColumn: 12},
966 },
967 },
968 Expr: parser.PromQLExpr{
969 Value: &parser.YamlNode{
970 Value: "( xxx - yyy ) * bar > 0 and on(instance, device) baz",
971 Pos: diags.PositionRanges{
972 {Line: 3, FirstColumn: 5, LastColumn: 6},
973 {Line: 4, FirstColumn: 7, LastColumn: 10},
974 {Line: 5, FirstColumn: 7, LastColumn: 8},
975 {Line: 6, FirstColumn: 7, LastColumn: 10},
976 {Line: 7, FirstColumn: 5, LastColumn: 16},
977 {Line: 8, FirstColumn: 5, LastColumn: 32},
978 },
979 },
980 },
981 For: &parser.YamlDuration{
982 Value: time.Minute * 30,
983 Raw: "30m",
984 Pos: diags.PositionRanges{
985 {Line: 9, FirstColumn: 8, LastColumn: 10},
986 },
987 },
988 },
989 },
990 },
991 },
992 },
993 },
994 },
995 {
996 input: []byte(`---
997kind: ConfigMap
998apiVersion: v1
999metadata:
1000 name: example-app-alerts
1001 labels:
1002 app: example-app
1003data:
1004 alerts: |
1005 groups:
1006 - name: example-app-alerts
1007 rules:
1008 - alert: Example_High_Restart_Rate
1009 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
1010---
1011kind: ConfigMap
1012apiVersion: v1
1013metadata:
1014 name: other
1015 labels:
1016 app: other
1017data:
1018 alerts: |
1019
1020 groups:
1021 - name: other alerts
1022 rules:
1023 - alert: Example_High_Restart_Rate
1024 expr: "1"
1025
1026`),
1027 output: parser.File{
1028 IsRelaxed: true,
1029 Groups: []parser.Group{
1030 {
1031 Name: parser.YamlNode{
1032 Value: "example-app-alerts",
1033 Pos: diags.PositionRanges{
1034 {Line: 2, FirstColumn: 11, LastColumn: 28},
1035 },
1036 },
1037 Rules: []parser.Rule{
1038 {
1039 Lines: diags.LineRange{First: 13, Last: 14},
1040 AlertingRule: &parser.AlertingRule{
1041 Alert: parser.YamlNode{
1042 Value: "Example_High_Restart_Rate",
1043 Pos: diags.PositionRanges{
1044 {Line: 13, FirstColumn: 20, LastColumn: 44},
1045 },
1046 },
1047 Expr: parser.PromQLExpr{
1048 Value: &parser.YamlNode{
1049 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
1050 Pos: diags.PositionRanges{
1051 {Line: 14, FirstColumn: 19, LastColumn: 109},
1052 },
1053 },
1054 },
1055 },
1056 },
1057 },
1058 },
1059 {
1060 Name: parser.YamlNode{
1061 Value: "other alerts",
1062 Pos: diags.PositionRanges{
1063 {Line: 3, FirstColumn: 11, LastColumn: 22},
1064 },
1065 },
1066 Rules: []parser.Rule{
1067 {
1068 Lines: diags.LineRange{First: 28, Last: 29},
1069 AlertingRule: &parser.AlertingRule{
1070 Alert: parser.YamlNode{
1071 Value: "Example_High_Restart_Rate",
1072 Pos: diags.PositionRanges{
1073 {Line: 28, FirstColumn: 16, LastColumn: 40},
1074 },
1075 },
1076 Expr: parser.PromQLExpr{
1077 Value: &parser.YamlNode{
1078 Value: "1",
1079 Pos: diags.PositionRanges{
1080 {Line: 29, FirstColumn: 16, LastColumn: 16},
1081 },
1082 },
1083 },
1084 },
1085 },
1086 },
1087 },
1088 },
1089 },
1090 },
1091 {
1092 input: []byte(`---
1093kind: ConfigMap
1094apiVersion: v1
1095metadata:
1096 name: example-app-alerts
1097 labels:
1098 app: example-app
1099data:
1100 alerts: |
1101 groups:
1102 - name: example-app-alerts
1103 rules:
1104 - alert: Example_Is_Down
1105 expr: kube_deployment_status_replicas_available{namespace="example-app"} < 1
1106 for: 5m
1107 labels:
1108 priority: "2"
1109 environment: production
1110 annotations:
1111 summary: "No replicas for Example have been running for 5 minutes"
1112
1113 - alert: Example_High_Restart_Rate
1114 expr: sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )
1115`),
1116 output: parser.File{
1117 IsRelaxed: true,
1118 Groups: []parser.Group{
1119 {
1120 Name: parser.YamlNode{
1121 Value: "example-app-alerts",
1122 Pos: diags.PositionRanges{
1123 {Line: 2, FirstColumn: 11, LastColumn: 28},
1124 },
1125 },
1126 Rules: []parser.Rule{
1127 {
1128 Lines: diags.LineRange{First: 13, Last: 20},
1129 AlertingRule: &parser.AlertingRule{
1130 Alert: parser.YamlNode{
1131 Value: "Example_Is_Down",
1132 Pos: diags.PositionRanges{
1133 {Line: 13, FirstColumn: 20, LastColumn: 34},
1134 },
1135 },
1136 Expr: parser.PromQLExpr{
1137 Value: &parser.YamlNode{
1138 Value: `kube_deployment_status_replicas_available{namespace="example-app"} < 1`,
1139 Pos: diags.PositionRanges{
1140 {Line: 14, FirstColumn: 19, LastColumn: 88},
1141 },
1142 },
1143 },
1144 For: &parser.YamlDuration{
1145 Value: time.Minute * 5,
1146 Raw: "5m",
1147 Pos: diags.PositionRanges{
1148 {Line: 15, FirstColumn: 18, LastColumn: 19},
1149 },
1150 },
1151 Labels: &parser.YamlMap{
1152 Key: &parser.YamlNode{
1153 Value: "labels",
1154 Pos: diags.PositionRanges{
1155 {Line: 16, FirstColumn: 13, LastColumn: 18},
1156 },
1157 },
1158 Items: []*parser.YamlKeyValue{
1159 {
1160 Key: &parser.YamlNode{
1161 Value: "priority",
1162 Pos: diags.PositionRanges{
1163 {Line: 17, FirstColumn: 15, LastColumn: 22},
1164 },
1165 },
1166 Value: &parser.YamlNode{
1167 Value: "2",
1168 Pos: diags.PositionRanges{
1169 {Line: 17, FirstColumn: 26, LastColumn: 26},
1170 },
1171 },
1172 },
1173 {
1174 Key: &parser.YamlNode{
1175 Value: "environment",
1176 Pos: diags.PositionRanges{
1177 {Line: 18, FirstColumn: 15, LastColumn: 25},
1178 },
1179 },
1180 Value: &parser.YamlNode{
1181 Value: "production",
1182 Pos: diags.PositionRanges{
1183 {Line: 18, FirstColumn: 28, LastColumn: 37},
1184 },
1185 },
1186 },
1187 },
1188 },
1189 Annotations: &parser.YamlMap{
1190 Key: &parser.YamlNode{
1191 Value: "annotations",
1192 Pos: diags.PositionRanges{
1193 {Line: 19, FirstColumn: 13, LastColumn: 23},
1194 },
1195 },
1196 Items: []*parser.YamlKeyValue{
1197 {
1198 Key: &parser.YamlNode{
1199 Value: "summary",
1200 Pos: diags.PositionRanges{
1201 {Line: 20, FirstColumn: 15, LastColumn: 21},
1202 },
1203 },
1204 Value: &parser.YamlNode{
1205 Value: "No replicas for Example have been running for 5 minutes",
1206 Pos: diags.PositionRanges{
1207 {Line: 20, FirstColumn: 25, LastColumn: 79},
1208 },
1209 },
1210 },
1211 },
1212 },
1213 },
1214 },
1215 {
1216 Lines: diags.LineRange{First: 22, Last: 23},
1217 AlertingRule: &parser.AlertingRule{
1218 Alert: parser.YamlNode{
1219 Value: "Example_High_Restart_Rate",
1220 Pos: diags.PositionRanges{
1221 {Line: 22, FirstColumn: 20, LastColumn: 44},
1222 },
1223 },
1224 Expr: parser.PromQLExpr{
1225 Value: &parser.YamlNode{
1226 Value: `sum(rate(kube_pod_container_status_restarts_total{namespace="example-app"}[5m])) > ( 3/60 )`,
1227 Pos: diags.PositionRanges{
1228 {Line: 23, FirstColumn: 19, LastColumn: 109},
1229 },
1230 },
1231 },
1232 },
1233 },
1234 },
1235 },
1236 },
1237 },
1238 },
1239 {
1240 input: []byte(`groups:
1241- name: "haproxy.api_server.rules"
1242 rules:
1243 - alert: HaproxyServerHealthcheckFailure
1244 expr: increase(haproxy_server_check_failures_total[15m]) > 100
1245 for: 5m
1246 labels:
1247 severity: 24x7
1248 annotations:
1249 summary: "HAProxy server healthcheck failure (instance {{ $labels.instance }})"
1250 description: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}"
1251`),
1252 output: parser.File{
1253 IsRelaxed: true,
1254 Groups: []parser.Group{
1255 {
1256 Name: parser.YamlNode{
1257 Value: "haproxy.api_server.rules",
1258 Pos: diags.PositionRanges{
1259 {Line: 2, FirstColumn: 10, LastColumn: 33},
1260 },
1261 },
1262 Rules: []parser.Rule{
1263 {
1264 Lines: diags.LineRange{First: 4, Last: 11},
1265 AlertingRule: &parser.AlertingRule{
1266 Alert: parser.YamlNode{
1267 Value: "HaproxyServerHealthcheckFailure",
1268 Pos: diags.PositionRanges{
1269 {Line: 4, FirstColumn: 12, LastColumn: 42},
1270 },
1271 },
1272 Expr: parser.PromQLExpr{
1273 Value: &parser.YamlNode{
1274 Value: "increase(haproxy_server_check_failures_total[15m]) > 100",
1275 Pos: diags.PositionRanges{
1276 {Line: 5, FirstColumn: 11, LastColumn: 66},
1277 },
1278 },
1279 },
1280 For: &parser.YamlDuration{
1281 Value: time.Minute * 5,
1282 Raw: "5m",
1283 Pos: diags.PositionRanges{
1284 {Line: 6, FirstColumn: 10, LastColumn: 11},
1285 },
1286 },
1287 Labels: &parser.YamlMap{
1288 Key: &parser.YamlNode{
1289 Value: "labels",
1290 Pos: diags.PositionRanges{
1291 {Line: 7, FirstColumn: 5, LastColumn: 10},
1292 },
1293 },
1294 Items: []*parser.YamlKeyValue{
1295 {
1296 Key: &parser.YamlNode{
1297 Value: "severity",
1298 Pos: diags.PositionRanges{
1299 {Line: 8, FirstColumn: 7, LastColumn: 14},
1300 },
1301 },
1302 Value: &parser.YamlNode{
1303 Value: "24x7",
1304 Pos: diags.PositionRanges{
1305 {Line: 8, FirstColumn: 17, LastColumn: 20},
1306 },
1307 },
1308 },
1309 },
1310 },
1311 Annotations: &parser.YamlMap{
1312 Key: &parser.YamlNode{
1313 Value: "annotations",
1314 Pos: diags.PositionRanges{
1315 {Line: 9, FirstColumn: 5, LastColumn: 15},
1316 },
1317 },
1318 Items: []*parser.YamlKeyValue{
1319 {
1320 Key: &parser.YamlNode{
1321 Value: "summary",
1322 Pos: diags.PositionRanges{
1323 {Line: 10, FirstColumn: 7, LastColumn: 13},
1324 },
1325 },
1326 Value: &parser.YamlNode{
1327 Value: "HAProxy server healthcheck failure (instance {{ $labels.instance }})",
1328 Pos: diags.PositionRanges{
1329 {Line: 10, FirstColumn: 17, LastColumn: 84},
1330 },
1331 },
1332 },
1333 {
1334 Key: &parser.YamlNode{
1335 Value: "description",
1336 Pos: diags.PositionRanges{
1337 {Line: 11, FirstColumn: 7, LastColumn: 17},
1338 },
1339 },
1340 Value: &parser.YamlNode{
1341 Value: "Some server healthcheck are failing on {{ $labels.server }}\n VALUE = {{ $value }}\n LABELS: {{ $labels }}",
1342 Pos: diags.PositionRanges{
1343 {Line: 11, FirstColumn: 21, LastColumn: 79},
1344 },
1345 },
1346 },
1347 },
1348 },
1349 },
1350 },
1351 },
1352 },
1353 },
1354 },
1355 },
1356 {
1357 input: []byte(`groups:
1358- name: certmanager
1359 rules:
1360 # pint disable before recordAnchor
1361 - &recordAnchor # pint disable recordAnchor
1362 record: name1 # pint disable name1
1363 expr: expr1 # pint disable expr1
1364 # pint disable after expr1
1365 - <<: *recordAnchor
1366 expr: expr2
1367 - <<: *recordAnchor
1368`),
1369 output: parser.File{
1370 IsRelaxed: true,
1371 Groups: []parser.Group{
1372 {
1373 Name: parser.YamlNode{
1374 Value: "certmanager",
1375 Pos: diags.PositionRanges{
1376 {Line: 2, FirstColumn: 9, LastColumn: 19},
1377 },
1378 },
1379 Rules: []parser.Rule{
1380 {
1381 Lines: diags.LineRange{First: 6, Last: 7},
1382 Comments: []comments.Comment{
1383 {
1384 Type: comments.DisableType,
1385 Value: comments.Disable{Match: "before recordAnchor"},
1386 },
1387 {
1388 Type: comments.DisableType,
1389 Value: comments.Disable{Match: "recordAnchor"},
1390 },
1391 {
1392 Type: comments.DisableType,
1393 Value: comments.Disable{Match: "name1"},
1394 },
1395 {
1396 Type: comments.DisableType,
1397 Value: comments.Disable{Match: "after expr1"},
1398 },
1399 {
1400 Type: comments.DisableType,
1401 Value: comments.Disable{Match: "expr1"},
1402 },
1403 },
1404 RecordingRule: &parser.RecordingRule{
1405 Record: parser.YamlNode{
1406 Value: "name1",
1407 Pos: diags.PositionRanges{
1408 {Line: 6, FirstColumn: 13, LastColumn: 17},
1409 },
1410 },
1411 Expr: parser.PromQLExpr{
1412 Value: &parser.YamlNode{
1413 Value: "expr1",
1414 Pos: diags.PositionRanges{
1415 {Line: 7, FirstColumn: 11, LastColumn: 15},
1416 },
1417 },
1418 },
1419 },
1420 },
1421 {
1422 Comments: []comments.Comment{
1423 {
1424 Type: comments.DisableType,
1425 Value: comments.Disable{Match: "before recordAnchor"},
1426 },
1427 {
1428 Type: comments.DisableType,
1429 Value: comments.Disable{Match: "recordAnchor"},
1430 },
1431 {
1432 Type: comments.DisableType,
1433 Value: comments.Disable{Match: "name1"},
1434 },
1435 },
1436 Lines: diags.LineRange{First: 6, Last: 10},
1437 RecordingRule: &parser.RecordingRule{
1438 Record: parser.YamlNode{
1439 Value: "name1",
1440 Pos: diags.PositionRanges{
1441 {Line: 6, FirstColumn: 13, LastColumn: 17},
1442 },
1443 },
1444 Expr: parser.PromQLExpr{
1445 Value: &parser.YamlNode{
1446 Value: "expr2",
1447 Pos: diags.PositionRanges{
1448 {Line: 10, FirstColumn: 11, LastColumn: 15},
1449 },
1450 },
1451 },
1452 },
1453 },
1454 {
1455 Comments: []comments.Comment{
1456 {
1457 Type: comments.DisableType,
1458 Value: comments.Disable{Match: "before recordAnchor"},
1459 },
1460 {
1461 Type: comments.DisableType,
1462 Value: comments.Disable{Match: "recordAnchor"},
1463 },
1464 {
1465 Type: comments.DisableType,
1466 Value: comments.Disable{Match: "name1"},
1467 },
1468 {
1469 Type: comments.DisableType,
1470 Value: comments.Disable{Match: "after expr1"},
1471 },
1472 {
1473 Type: comments.DisableType,
1474 Value: comments.Disable{Match: "expr1"},
1475 },
1476 },
1477 Lines: diags.LineRange{First: 6, Last: 7},
1478 RecordingRule: &parser.RecordingRule{
1479 Record: parser.YamlNode{
1480 Value: "name1",
1481 Pos: diags.PositionRanges{
1482 {Line: 6, FirstColumn: 13, LastColumn: 17},
1483 },
1484 },
1485 Expr: parser.PromQLExpr{
1486 Value: &parser.YamlNode{
1487 Value: "expr1",
1488 Pos: diags.PositionRanges{
1489 {Line: 7, FirstColumn: 11, LastColumn: 15},
1490 },
1491 },
1492 },
1493 },
1494 },
1495 },
1496 },
1497 },
1498 },
1499 },
1500 {
1501 input: []byte(`groups:
1502- name: certmanager
1503 rules:
1504 - record: name1
1505 expr: expr1
1506 labels: &labelsAnchor
1507 label1: val1
1508 label2: val2
1509 - record: name2
1510 expr: expr2
1511 labels: *labelsAnchor
1512 # pint disable foot comment
1513`),
1514 output: parser.File{
1515 IsRelaxed: true,
1516 Groups: []parser.Group{
1517 {
1518 Name: parser.YamlNode{
1519 Value: "certmanager",
1520 Pos: diags.PositionRanges{
1521 {Line: 2, FirstColumn: 9, LastColumn: 19},
1522 },
1523 },
1524 Rules: []parser.Rule{
1525 {
1526 Lines: diags.LineRange{First: 4, Last: 8},
1527 RecordingRule: &parser.RecordingRule{
1528 Record: parser.YamlNode{
1529 Value: "name1",
1530 Pos: diags.PositionRanges{
1531 {Line: 4, FirstColumn: 13, LastColumn: 17},
1532 },
1533 },
1534 Expr: parser.PromQLExpr{
1535 Value: &parser.YamlNode{
1536 Value: "expr1",
1537 Pos: diags.PositionRanges{
1538 {Line: 5, FirstColumn: 11, LastColumn: 15},
1539 },
1540 },
1541 },
1542 Labels: &parser.YamlMap{
1543 Key: &parser.YamlNode{
1544 Value: "labels",
1545 Pos: diags.PositionRanges{
1546 {Line: 6, FirstColumn: 5, LastColumn: 10},
1547 },
1548 },
1549 Items: []*parser.YamlKeyValue{
1550 {
1551 Key: &parser.YamlNode{
1552 Value: "label1",
1553 Pos: diags.PositionRanges{
1554 {Line: 7, FirstColumn: 7, LastColumn: 12},
1555 },
1556 },
1557 Value: &parser.YamlNode{
1558 Value: "val1",
1559 Pos: diags.PositionRanges{
1560 {Line: 7, FirstColumn: 15, LastColumn: 18},
1561 },
1562 },
1563 },
1564 {
1565 Key: &parser.YamlNode{
1566 Value: "label2",
1567 Pos: diags.PositionRanges{
1568 {Line: 8, FirstColumn: 7, LastColumn: 12},
1569 },
1570 },
1571 Value: &parser.YamlNode{
1572 Value: "val2",
1573 Pos: diags.PositionRanges{
1574 {Line: 8, FirstColumn: 15, LastColumn: 18},
1575 },
1576 },
1577 },
1578 },
1579 },
1580 },
1581 },
1582 {
1583 Comments: []comments.Comment{
1584 {
1585 Type: comments.DisableType,
1586 Value: comments.Disable{Match: "foot comment"},
1587 },
1588 },
1589 Lines: diags.LineRange{First: 9, Last: 11},
1590 RecordingRule: &parser.RecordingRule{
1591 Record: parser.YamlNode{
1592 Value: "name2",
1593 Pos: diags.PositionRanges{
1594 {Line: 9, FirstColumn: 13, LastColumn: 17},
1595 },
1596 },
1597 Expr: parser.PromQLExpr{
1598 Value: &parser.YamlNode{
1599 Value: "expr2",
1600 Pos: diags.PositionRanges{
1601 {Line: 10, FirstColumn: 11, LastColumn: 15},
1602 },
1603 },
1604 },
1605 Labels: &parser.YamlMap{
1606 Key: &parser.YamlNode{
1607 Value: "labels",
1608 Pos: diags.PositionRanges{
1609 {Line: 11, FirstColumn: 5, LastColumn: 10},
1610 },
1611 },
1612 Items: []*parser.YamlKeyValue{
1613 {
1614 Key: &parser.YamlNode{
1615 Value: "label1",
1616 Pos: diags.PositionRanges{
1617 {Line: 7, FirstColumn: 7, LastColumn: 12},
1618 },
1619 },
1620 Value: &parser.YamlNode{
1621 Value: "val1",
1622 Pos: diags.PositionRanges{
1623 {Line: 7, FirstColumn: 15, LastColumn: 18},
1624 },
1625 },
1626 },
1627 {
1628 Key: &parser.YamlNode{
1629 Value: "label2",
1630 Pos: diags.PositionRanges{
1631 {Line: 8, FirstColumn: 7, LastColumn: 12},
1632 },
1633 },
1634 Value: &parser.YamlNode{
1635 Value: "val2",
1636 Pos: diags.PositionRanges{
1637 {Line: 8, FirstColumn: 15, LastColumn: 18},
1638 },
1639 },
1640 },
1641 },
1642 },
1643 },
1644 },
1645 },
1646 },
1647 },
1648 },
1649 },
1650 {
1651 input: []byte("- alert:\n expr: vector(1)\n"),
1652 output: parser.File{
1653 IsRelaxed: true,
1654 Groups: []parser.Group{
1655 {
1656 Rules: []parser.Rule{
1657 {
1658 Lines: diags.LineRange{First: 1, Last: 2},
1659 Error: parser.ParseError{Err: errors.New("alert value cannot be empty"), Line: 1},
1660 },
1661 },
1662 },
1663 },
1664 },
1665 },
1666 {
1667 input: []byte("- alert: foo\n expr:\n"),
1668 output: parser.File{
1669 IsRelaxed: true,
1670 Groups: []parser.Group{
1671 {
1672 Rules: []parser.Rule{
1673 {
1674 Lines: diags.LineRange{First: 1, Last: 2},
1675 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1676 },
1677 },
1678 },
1679 },
1680 },
1681 },
1682 {
1683 input: []byte("- alert: foo\n"),
1684 output: parser.File{
1685 IsRelaxed: true,
1686 Groups: []parser.Group{
1687 {
1688 Rules: []parser.Rule{
1689 {
1690 Lines: diags.LineRange{First: 1, Last: 1},
1691 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1692 },
1693 },
1694 },
1695 },
1696 },
1697 },
1698 {
1699 input: []byte("- record:\n expr:\n"),
1700 output: parser.File{
1701 IsRelaxed: true,
1702 Groups: []parser.Group{
1703 {
1704 Rules: []parser.Rule{
1705 {
1706 Lines: diags.LineRange{First: 1, Last: 2},
1707 Error: parser.ParseError{Err: errors.New("record value cannot be empty"), Line: 1},
1708 },
1709 },
1710 },
1711 },
1712 },
1713 },
1714 {
1715 input: []byte("- record: foo\n expr:\n"),
1716 output: parser.File{
1717 IsRelaxed: true,
1718 Groups: []parser.Group{
1719 {
1720 Rules: []parser.Rule{
1721 {
1722 Lines: diags.LineRange{First: 1, Last: 2},
1723 Error: parser.ParseError{Err: errors.New("expr value cannot be empty"), Line: 2},
1724 },
1725 },
1726 },
1727 },
1728 },
1729 },
1730 {
1731 input: []byte("- record: foo\n"),
1732 output: parser.File{
1733 IsRelaxed: true,
1734 Groups: []parser.Group{
1735 {
1736 Rules: []parser.Rule{
1737 {
1738 Lines: diags.LineRange{First: 1, Last: 1},
1739 Error: parser.ParseError{Err: errors.New("missing expr key"), Line: 1},
1740 },
1741 },
1742 },
1743 },
1744 },
1745 },
1746 {
1747 input: []byte(string(`
1748# pint file/owner bob
1749# pint ignore/begin
1750# pint ignore/end
1751# pint disable up
1752
1753- record: foo
1754 expr: up
1755
1756# pint file/owner alice
1757
1758- record: foo
1759 expr: up
1760
1761# pint ignore/next-line
1762`)),
1763 output: parser.File{
1764 IsRelaxed: true,
1765 Comments: []comments.Comment{
1766 {
1767 Type: comments.FileOwnerType,
1768 Value: comments.Owner{
1769 Name: "bob",
1770 Line: 2,
1771 },
1772 },
1773 {
1774 Type: comments.FileOwnerType,
1775 Value: comments.Owner{
1776 Name: "alice",
1777 Line: 10,
1778 },
1779 },
1780 },
1781 Groups: []parser.Group{
1782 {
1783 Rules: []parser.Rule{
1784 {
1785 Lines: diags.LineRange{First: 7, Last: 8},
1786 RecordingRule: &parser.RecordingRule{
1787 Record: parser.YamlNode{
1788 Value: "foo",
1789 Pos: diags.PositionRanges{
1790 {Line: 7, FirstColumn: 11, LastColumn: 13},
1791 },
1792 },
1793 Expr: parser.PromQLExpr{
1794 Value: &parser.YamlNode{
1795 Value: "up",
1796 Pos: diags.PositionRanges{
1797 {Line: 8, FirstColumn: 9, LastColumn: 10},
1798 },
1799 },
1800 },
1801 },
1802 },
1803 {
1804 Lines: diags.LineRange{First: 12, Last: 13},
1805 RecordingRule: &parser.RecordingRule{
1806 Record: parser.YamlNode{
1807 Value: "foo",
1808 Pos: diags.PositionRanges{
1809 {Line: 12, FirstColumn: 11, LastColumn: 13},
1810 },
1811 },
1812 Expr: parser.PromQLExpr{
1813 Value: &parser.YamlNode{
1814 Value: "up",
1815 Pos: diags.PositionRanges{
1816 {Line: 13, FirstColumn: 9, LastColumn: 10},
1817 },
1818 },
1819 },
1820 },
1821 },
1822 },
1823 },
1824 },
1825 },
1826 },
1827 {
1828 input: []byte(string(`
1829- alert: Template
1830 expr: &expr up == 0
1831 labels:
1832 notify: &maybe_escalate_notify chat-alerts
1833- alert: Service Down
1834 expr: *expr
1835 labels:
1836 notify: *maybe_escalate_notify
1837 summary: foo
1838`)),
1839 output: parser.File{
1840 IsRelaxed: true,
1841 Groups: []parser.Group{
1842 {
1843 Rules: []parser.Rule{
1844 {
1845 Lines: diags.LineRange{First: 2, Last: 5},
1846 AlertingRule: &parser.AlertingRule{
1847 Alert: parser.YamlNode{
1848 Value: "Template",
1849 Pos: diags.PositionRanges{
1850 {Line: 2, FirstColumn: 10, LastColumn: 17},
1851 },
1852 },
1853 Expr: parser.PromQLExpr{
1854 Value: &parser.YamlNode{
1855 Value: "up == 0",
1856 Pos: diags.PositionRanges{
1857 {Line: 3, FirstColumn: 15, LastColumn: 21},
1858 },
1859 },
1860 },
1861 Labels: &parser.YamlMap{
1862 Key: &parser.YamlNode{
1863 Value: "labels",
1864 Pos: diags.PositionRanges{
1865 {Line: 4, FirstColumn: 3, LastColumn: 8},
1866 },
1867 },
1868 Items: []*parser.YamlKeyValue{
1869 {
1870 Key: &parser.YamlNode{
1871 Value: "notify",
1872 Pos: diags.PositionRanges{
1873 {Line: 5, FirstColumn: 5, LastColumn: 10},
1874 },
1875 },
1876 Value: &parser.YamlNode{
1877 Value: "chat-alerts",
1878 Pos: diags.PositionRanges{
1879 {Line: 5, FirstColumn: 22, LastColumn: 22},
1880 {Line: 5, FirstColumn: 37, LastColumn: 46},
1881 },
1882 },
1883 },
1884 },
1885 },
1886 },
1887 },
1888 {
1889 Lines: diags.LineRange{First: 6, Last: 10},
1890 AlertingRule: &parser.AlertingRule{
1891 Alert: parser.YamlNode{
1892 Value: "Service Down",
1893 Pos: diags.PositionRanges{
1894 {Line: 6, FirstColumn: 10, LastColumn: 21},
1895 },
1896 },
1897 Expr: parser.PromQLExpr{
1898 Value: &parser.YamlNode{
1899 Value: "up == 0",
1900 Pos: diags.PositionRanges{
1901 {Line: 7, FirstColumn: 10, LastColumn: 13}, // points at anchor
1902 },
1903 },
1904 },
1905 Labels: &parser.YamlMap{
1906 Key: &parser.YamlNode{
1907 Value: "labels",
1908 Pos: diags.PositionRanges{
1909 {Line: 8, FirstColumn: 3, LastColumn: 8},
1910 },
1911 },
1912 Items: []*parser.YamlKeyValue{
1913 {
1914 Key: &parser.YamlNode{
1915 Value: "notify",
1916 Pos: diags.PositionRanges{
1917 {Line: 9, FirstColumn: 5, LastColumn: 10},
1918 },
1919 },
1920 Value: &parser.YamlNode{
1921 Value: "chat-alerts",
1922 Pos: diags.PositionRanges{
1923 {Line: 9, FirstColumn: 14, LastColumn: 34},
1924 },
1925 },
1926 },
1927 {
1928 Key: &parser.YamlNode{
1929 Value: "summary",
1930 Pos: diags.PositionRanges{
1931 {Line: 10, FirstColumn: 5, LastColumn: 11},
1932 },
1933 },
1934 Value: &parser.YamlNode{
1935 Value: "foo",
1936 Pos: diags.PositionRanges{
1937 {Line: 10, FirstColumn: 14, LastColumn: 16},
1938 },
1939 },
1940 },
1941 },
1942 },
1943 },
1944 },
1945 },
1946 },
1947 },
1948 },
1949 },
1950 {
1951 input: []byte(`
1952- record: invalid metric name
1953 expr: bar
1954`),
1955 output: parser.File{
1956 IsRelaxed: true,
1957 Groups: []parser.Group{
1958 {
1959 Rules: []parser.Rule{
1960 {
1961 Lines: diags.LineRange{First: 2, Last: 3},
1962 Error: parser.ParseError{Err: errors.New("invalid recording rule name: invalid metric name"), Line: 2},
1963 },
1964 },
1965 },
1966 },
1967 },
1968 },
1969 {
1970 input: []byte(`
1971- record: utf-8 enabled name
1972 expr: bar
1973 labels:
1974 "a b c": bar
1975`),
1976 names: model.UTF8Validation,
1977 output: parser.File{
1978 IsRelaxed: true,
1979 Groups: []parser.Group{
1980 {
1981 Rules: []parser.Rule{
1982 {
1983 Lines: diags.LineRange{First: 2, Last: 5},
1984 RecordingRule: &parser.RecordingRule{
1985 Record: parser.YamlNode{
1986 Value: "utf-8 enabled name",
1987 Pos: diags.PositionRanges{
1988 {Line: 2, FirstColumn: 11, LastColumn: 28},
1989 },
1990 },
1991 Expr: parser.PromQLExpr{
1992 Value: &parser.YamlNode{
1993 Value: "bar",
1994 Pos: diags.PositionRanges{
1995 {Line: 3, FirstColumn: 9, LastColumn: 11},
1996 },
1997 },
1998 },
1999 Labels: &parser.YamlMap{
2000 Key: &parser.YamlNode{
2001 Value: "labels",
2002 Pos: diags.PositionRanges{
2003 {Line: 4, FirstColumn: 3, LastColumn: 8},
2004 },
2005 },
2006 Items: []*parser.YamlKeyValue{
2007 {
2008 Key: &parser.YamlNode{
2009 Value: "a b c",
2010 Pos: diags.PositionRanges{
2011 {Line: 5, FirstColumn: 6, LastColumn: 10},
2012 },
2013 },
2014 Value: &parser.YamlNode{
2015 Value: "bar",
2016 Pos: diags.PositionRanges{
2017 {Line: 5, FirstColumn: 14, LastColumn: 16},
2018 },
2019 },
2020 },
2021 },
2022 },
2023 },
2024 },
2025 },
2026 },
2027 },
2028 },
2029 },
2030 {
2031 input: []byte(`
2032- record: foo
2033 expr: bar
2034 labels:
2035 "foo bar": yes
2036`),
2037 output: parser.File{
2038 IsRelaxed: true,
2039 Groups: []parser.Group{
2040 {
2041 Rules: []parser.Rule{
2042 {
2043 Lines: diags.LineRange{First: 2, Last: 5},
2044 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
2045 },
2046 },
2047 },
2048 },
2049 },
2050 },
2051 {
2052 input: []byte(`
2053- alert: foo
2054 expr: bar
2055 labels:
2056 "foo bar": yes
2057`),
2058 output: parser.File{
2059 IsRelaxed: true,
2060 Groups: []parser.Group{
2061 {
2062 Rules: []parser.Rule{
2063 {
2064 Lines: diags.LineRange{First: 2, Last: 5},
2065 Error: parser.ParseError{Err: errors.New("invalid label name: foo bar"), Line: 5},
2066 },
2067 },
2068 },
2069 },
2070 },
2071 },
2072 {
2073 input: []byte(`
2074- alert: foo
2075 expr: bar
2076 labels:
2077 "{{ $value }}": yes
2078`),
2079 output: parser.File{
2080 IsRelaxed: true,
2081 Groups: []parser.Group{
2082 {
2083 Rules: []parser.Rule{
2084 {
2085 Lines: diags.LineRange{First: 2, Last: 5},
2086 Error: parser.ParseError{Err: errors.New("invalid label name: {{ $value }}"), Line: 5},
2087 },
2088 },
2089 },
2090 },
2091 },
2092 },
2093 {
2094 input: []byte(`
2095- alert: foo
2096 expr: bar
2097 annotations:
2098 "foo bar": yes
2099`),
2100 output: parser.File{
2101 IsRelaxed: true,
2102 Groups: []parser.Group{
2103 {
2104 Rules: []parser.Rule{
2105 {
2106 Lines: diags.LineRange{First: 2, Last: 5},
2107 Error: parser.ParseError{Err: errors.New("invalid annotation name: foo bar"), Line: 5},
2108 },
2109 },
2110 },
2111 },
2112 },
2113 },
2114 {
2115 input: []byte(`
2116- alert: foo
2117 expr: bar
2118 labels:
2119 foo: ` + string("\xed\xbf\xbf")),
2120 // Label values are invalid only if they aren't valid UTF-8 strings
2121 // which also makes them unparsable by YAML.
2122 output: parser.File{
2123 IsRelaxed: true,
2124 Error: parser.ParseError{
2125 Err: errors.New("yaml: invalid Unicode character"),
2126 Line: 1,
2127 },
2128 },
2129 },
2130 {
2131 input: []byte(`
2132- alert: foo
2133 expr: bar
2134 annotations:
2135 "{{ $value }}": yes
2136`),
2137 output: parser.File{
2138 IsRelaxed: true,
2139 Groups: []parser.Group{
2140 {
2141 Rules: []parser.Rule{
2142 {
2143 Lines: diags.LineRange{First: 2, Last: 5},
2144 Error: parser.ParseError{Err: errors.New("invalid annotation name: {{ $value }}"), Line: 5},
2145 },
2146 },
2147 },
2148 },
2149 },
2150 },
2151 {
2152 input: []byte(`
2153- record: foo
2154 expr: bar
2155 keep_firing_for: 5m
2156`),
2157 output: parser.File{
2158 IsRelaxed: true,
2159 Groups: []parser.Group{
2160 {
2161 Rules: []parser.Rule{
2162 {
2163 Lines: diags.LineRange{First: 2, Last: 4},
2164 Error: parser.ParseError{Err: errors.New("invalid field 'keep_firing_for' in recording rule"), Line: 4},
2165 },
2166 },
2167 },
2168 },
2169 },
2170 },
2171 {
2172 input: []byte(`
2173- record: foo
2174 expr: bar
2175 for: 5m
2176`),
2177 output: parser.File{
2178 IsRelaxed: true,
2179 Groups: []parser.Group{
2180 {
2181 Rules: []parser.Rule{
2182 {
2183 Lines: diags.LineRange{First: 2, Last: 4},
2184 Error: parser.ParseError{Err: errors.New("invalid field 'for' in recording rule"), Line: 4},
2185 },
2186 },
2187 },
2188 },
2189 },
2190 },
2191 {
2192 input: []byte(`
2193- record: foo
2194 expr: bar
2195 annotations:
2196 foo: bar
2197`),
2198 output: parser.File{
2199 IsRelaxed: true,
2200 Groups: []parser.Group{
2201 {
2202 Rules: []parser.Rule{
2203 {
2204 Lines: diags.LineRange{First: 2, Last: 5},
2205 Error: parser.ParseError{Err: errors.New("invalid field 'annotations' in recording rule"), Line: 4},
2206 },
2207 },
2208 },
2209 },
2210 },
2211 },
2212 // Tag tests
2213 {
2214 input: []byte(`
2215- record: 5
2216 expr: bar
2217`),
2218 output: parser.File{
2219 IsRelaxed: true,
2220 Groups: []parser.Group{
2221 {
2222 Rules: []parser.Rule{
2223 {
2224 Lines: diags.LineRange{First: 2, Last: 3},
2225 Error: parser.ParseError{Err: errors.New("record value must be a string, got integer instead"), Line: 2},
2226 },
2227 },
2228 },
2229 },
2230 },
2231 },
2232 {
2233 input: []byte(`
2234- alert: 5
2235 expr: bar
2236`),
2237 output: parser.File{
2238 IsRelaxed: true,
2239 Groups: []parser.Group{
2240 {
2241 Rules: []parser.Rule{
2242 {
2243 Lines: diags.LineRange{First: 2, Last: 3},
2244 Error: parser.ParseError{Err: errors.New("alert value must be a string, got integer instead"), Line: 2},
2245 },
2246 },
2247 },
2248 },
2249 },
2250 },
2251 {
2252 input: []byte(`
2253- record: foo
2254 expr: 5
2255`),
2256 output: parser.File{
2257 IsRelaxed: true,
2258 Groups: []parser.Group{
2259 {
2260 Rules: []parser.Rule{
2261 {
2262 Lines: diags.LineRange{First: 2, Last: 3},
2263 Error: parser.ParseError{Err: errors.New("expr value must be a string, got integer instead"), Line: 3},
2264 },
2265 },
2266 },
2267 },
2268 },
2269 },
2270 {
2271 input: []byte(`
2272- alert: foo
2273 expr: bar
2274 for: 5
2275`),
2276 output: parser.File{
2277 IsRelaxed: true,
2278 Groups: []parser.Group{
2279 {
2280 Rules: []parser.Rule{
2281 {
2282 Lines: diags.LineRange{First: 2, Last: 4},
2283 Error: parser.ParseError{Err: errors.New("for value must be a string, got integer instead"), Line: 4},
2284 },
2285 },
2286 },
2287 },
2288 },
2289 },
2290 {
2291 input: []byte(`
2292- alert: foo
2293 expr: bar
2294 keep_firing_for: 5
2295`),
2296 output: parser.File{
2297 IsRelaxed: true,
2298 Groups: []parser.Group{
2299 {
2300 Rules: []parser.Rule{
2301 {
2302 Lines: diags.LineRange{First: 2, Last: 4},
2303 Error: parser.ParseError{Err: errors.New("keep_firing_for value must be a string, got integer instead"), Line: 4},
2304 },
2305 },
2306 },
2307 },
2308 },
2309 },
2310 {
2311 input: []byte(`
2312- record: foo
2313 expr: bar
2314 labels: []
2315`),
2316 output: parser.File{
2317 IsRelaxed: true,
2318 Groups: []parser.Group{
2319 {
2320 Rules: []parser.Rule{
2321 {
2322 Lines: diags.LineRange{First: 2, Last: 4},
2323 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got list instead"), Line: 4},
2324 },
2325 },
2326 },
2327 },
2328 },
2329 },
2330 {
2331 input: []byte(`
2332- alert: foo
2333 expr: bar
2334 labels: {}
2335 annotations: []
2336`),
2337 output: parser.File{
2338 IsRelaxed: true,
2339 Groups: []parser.Group{
2340 {
2341 Rules: []parser.Rule{
2342 {
2343 Lines: diags.LineRange{First: 2, Last: 5},
2344 Error: parser.ParseError{Err: errors.New("annotations value must be a mapping, got list instead"), Line: 5},
2345 },
2346 },
2347 },
2348 },
2349 },
2350 },
2351 {
2352 input: []byte(`
2353- alert: foo
2354 expr: bar
2355 labels:
2356 foo: 3
2357 annotations:
2358 bar: "5"
2359`),
2360 output: parser.File{
2361 IsRelaxed: true,
2362 Groups: []parser.Group{
2363 {
2364 Rules: []parser.Rule{
2365 {
2366 Lines: diags.LineRange{First: 2, Last: 7},
2367 Error: parser.ParseError{Err: errors.New("labels foo value must be a string, got integer instead"), Line: 5},
2368 },
2369 },
2370 },
2371 },
2372 },
2373 },
2374 {
2375 input: []byte(`
2376- alert: foo
2377 expr: bar
2378 labels: {}
2379 annotations:
2380 foo: "3"
2381 bar: 5
2382`),
2383 output: parser.File{
2384 IsRelaxed: true,
2385 Groups: []parser.Group{
2386 {
2387 Rules: []parser.Rule{
2388 {
2389 Lines: diags.LineRange{First: 2, Last: 7},
2390 Error: parser.ParseError{Err: errors.New("annotations bar value must be a string, got integer instead"), Line: 7},
2391 },
2392 },
2393 },
2394 },
2395 },
2396 },
2397 {
2398 input: []byte(`
2399- record: foo
2400 expr: bar
2401 labels: 4
2402`),
2403 output: parser.File{
2404 IsRelaxed: true,
2405 Groups: []parser.Group{
2406 {
2407 Rules: []parser.Rule{
2408 {
2409 Lines: diags.LineRange{First: 2, Last: 4},
2410 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got integer instead"), Line: 4},
2411 },
2412 },
2413 },
2414 },
2415 },
2416 },
2417 {
2418 input: []byte(`
2419- record: foo
2420 expr: bar
2421 labels: true
2422`),
2423 output: parser.File{
2424 IsRelaxed: true,
2425 Groups: []parser.Group{
2426 {
2427 Rules: []parser.Rule{
2428 {
2429 Lines: diags.LineRange{First: 2, Last: 4},
2430 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got bool instead"), Line: 4},
2431 },
2432 },
2433 },
2434 },
2435 },
2436 },
2437 {
2438 input: []byte(`
2439- record: foo
2440 expr: bar
2441 labels: null
2442`),
2443 output: parser.File{
2444 IsRelaxed: true,
2445 Groups: []parser.Group{
2446 {
2447 Rules: []parser.Rule{
2448 {
2449 Lines: diags.LineRange{First: 2, Last: 4},
2450 RecordingRule: &parser.RecordingRule{
2451 Record: parser.YamlNode{
2452 Value: "foo",
2453 Pos: diags.PositionRanges{
2454 {Line: 2, FirstColumn: 11, LastColumn: 13},
2455 },
2456 },
2457 Expr: parser.PromQLExpr{
2458 Value: &parser.YamlNode{
2459 Value: "bar",
2460 Pos: diags.PositionRanges{
2461 {Line: 3, FirstColumn: 9, LastColumn: 11},
2462 },
2463 },
2464 },
2465 Labels: &parser.YamlMap{
2466 Key: &parser.YamlNode{
2467 Value: "labels",
2468 Pos: diags.PositionRanges{
2469 {Line: 4, FirstColumn: 3, LastColumn: 8},
2470 },
2471 },
2472 },
2473 },
2474 },
2475 },
2476 },
2477 },
2478 },
2479 },
2480 {
2481 input: []byte(`
2482- record: true
2483 expr: bar
2484`),
2485 output: parser.File{
2486 IsRelaxed: true,
2487 Groups: []parser.Group{
2488 {
2489 Rules: []parser.Rule{
2490 {
2491 Lines: diags.LineRange{First: 2, Last: 3},
2492 Error: parser.ParseError{Err: errors.New("record value must be a string, got bool instead"), Line: 2},
2493 },
2494 },
2495 },
2496 },
2497 },
2498 },
2499 {
2500 input: []byte(`
2501- record:
2502 query: foo
2503 expr: bar
2504`),
2505 output: parser.File{
2506 IsRelaxed: true,
2507 Groups: []parser.Group{
2508 {
2509 Rules: []parser.Rule{
2510 {
2511 Lines: diags.LineRange{First: 2, Last: 4},
2512 Error: parser.ParseError{Err: errors.New("record value must be a string, got mapping instead"), Line: 3},
2513 },
2514 },
2515 },
2516 },
2517 },
2518 },
2519 {
2520 input: []byte(`
2521- record: foo
2522 expr: bar
2523 labels: some
2524`),
2525 output: parser.File{
2526 IsRelaxed: true,
2527 Groups: []parser.Group{
2528 {
2529 Rules: []parser.Rule{
2530 {
2531 Lines: diags.LineRange{First: 2, Last: 4},
2532 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got string instead"), Line: 4},
2533 },
2534 },
2535 },
2536 },
2537 },
2538 },
2539 {
2540 input: []byte(`
2541- record: foo
2542 expr: bar
2543 labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
2544`),
2545 output: parser.File{
2546 IsRelaxed: true,
2547 Groups: []parser.Group{
2548 {
2549 Rules: []parser.Rule{
2550 {
2551 Lines: diags.LineRange{First: 2, Last: 4},
2552 Error: parser.ParseError{
2553 Err: errors.New("labels value must be a mapping, got binary data instead"),
2554 Line: 4,
2555 },
2556 },
2557 },
2558 },
2559 },
2560 },
2561 },
2562 {
2563 input: []byte(`
2564- alert: foo
2565 expr: bar
2566 for: 1.23
2567`),
2568 output: parser.File{
2569 IsRelaxed: true,
2570 Groups: []parser.Group{
2571 {
2572 Rules: []parser.Rule{
2573 {
2574 Lines: diags.LineRange{First: 2, Last: 4},
2575 Error: parser.ParseError{
2576 Err: errors.New("for value must be a string, got float instead"),
2577 Line: 4,
2578 },
2579 },
2580 },
2581 },
2582 },
2583 },
2584 },
2585 {
2586 input: []byte(`
2587- record: foo
2588 expr: bar
2589 labels: !!garbage "SGVsbG8sIFdvcmxkIQ=="
2590`),
2591 output: parser.File{
2592 IsRelaxed: true,
2593 Groups: []parser.Group{
2594 {
2595 Rules: []parser.Rule{
2596 {
2597 Lines: diags.LineRange{First: 2, Last: 4},
2598 Error: parser.ParseError{Err: errors.New("labels value must be a mapping, got garbage instead"), Line: 4},
2599 },
2600 },
2601 },
2602 },
2603 },
2604 },
2605 {
2606 input: []byte(`
2607- record: foo
2608 expr: bar
2609 labels: !! "SGVsbG8sIFdvcmxkIQ=="
2610`),
2611 output: parser.File{
2612 IsRelaxed: true,
2613 Error: parser.ParseError{
2614 Err: errors.New("did not find expected tag URI"),
2615 Line: 4,
2616 },
2617 },
2618 },
2619 {
2620 input: []byte(`
2621- record: &foo foo
2622 expr: bar
2623 labels: *foo
2624`),
2625 output: parser.File{
2626 IsRelaxed: true,
2627 Groups: []parser.Group{
2628 {
2629 Rules: []parser.Rule{
2630 {
2631 Lines: diags.LineRange{First: 2, Last: 4},
2632 Error: parser.ParseError{
2633 Err: errors.New("labels value must be a mapping, got string instead"),
2634 Line: 4,
2635 },
2636 },
2637 },
2638 },
2639 },
2640 },
2641 },
2642 // Multi-document tests
2643 {
2644 input: []byte(`---
2645- expr: foo
2646 record: foo
2647---
2648- expr: bar
2649`),
2650 output: parser.File{
2651 IsRelaxed: true,
2652 Groups: []parser.Group{
2653 {
2654 Rules: []parser.Rule{
2655 {
2656 Lines: diags.LineRange{First: 2, Last: 3},
2657 RecordingRule: &parser.RecordingRule{
2658 Record: parser.YamlNode{
2659 Value: "foo",
2660 Pos: diags.PositionRanges{
2661 {Line: 3, FirstColumn: 11, LastColumn: 13},
2662 },
2663 },
2664 Expr: parser.PromQLExpr{
2665 Value: &parser.YamlNode{
2666 Value: "foo",
2667 Pos: diags.PositionRanges{
2668 {Line: 2, FirstColumn: 9, LastColumn: 11},
2669 },
2670 },
2671 },
2672 },
2673 },
2674 },
2675 },
2676 {
2677 Rules: []parser.Rule{
2678 {
2679 Lines: diags.LineRange{First: 5, Last: 5},
2680 Error: parser.ParseError{
2681 Err: errors.New("incomplete rule, no alert or record key"),
2682 Line: 5,
2683 },
2684 },
2685 },
2686 },
2687 },
2688 },
2689 },
2690 {
2691 input: []byte(`---
2692- expr: foo
2693 record: foo
2694---
2695- expr: bar
2696 record: bar
2697 expr: bar
2698`),
2699 output: parser.File{
2700 IsRelaxed: true,
2701 Groups: []parser.Group{
2702 {
2703 Rules: []parser.Rule{
2704 {
2705 Lines: diags.LineRange{First: 2, Last: 3},
2706 RecordingRule: &parser.RecordingRule{
2707 Record: parser.YamlNode{
2708 Value: "foo",
2709 Pos: diags.PositionRanges{
2710 {Line: 3, FirstColumn: 11, LastColumn: 13},
2711 },
2712 },
2713 Expr: parser.PromQLExpr{
2714 Value: &parser.YamlNode{
2715 Value: "foo",
2716 Pos: diags.PositionRanges{
2717 {Line: 2, FirstColumn: 9, LastColumn: 11},
2718 },
2719 },
2720 },
2721 },
2722 },
2723 },
2724 },
2725 {
2726 Rules: []parser.Rule{
2727 {
2728 Lines: diags.LineRange{First: 5, Last: 7},
2729 Error: parser.ParseError{Err: errors.New("duplicated expr key"), Line: 7},
2730 },
2731 },
2732 },
2733 },
2734 },
2735 },
2736 {
2737 input: []byte(`---
2738- expr: foo
2739 record: foo
2740---
2741- expr: bar
2742 alert: foo
2743`),
2744 output: parser.File{
2745 IsRelaxed: true,
2746 Groups: []parser.Group{
2747 {
2748 Rules: []parser.Rule{
2749 {
2750 Lines: diags.LineRange{First: 2, Last: 3},
2751 RecordingRule: &parser.RecordingRule{
2752 Record: parser.YamlNode{
2753 Value: "foo",
2754 Pos: diags.PositionRanges{
2755 {Line: 3, FirstColumn: 11, LastColumn: 13},
2756 },
2757 },
2758 Expr: parser.PromQLExpr{
2759 Value: &parser.YamlNode{
2760 Value: "foo",
2761 Pos: diags.PositionRanges{
2762 {Line: 2, FirstColumn: 9, LastColumn: 11},
2763 },
2764 },
2765 },
2766 },
2767 },
2768 },
2769 },
2770 {
2771 Rules: []parser.Rule{
2772 {
2773 Lines: diags.LineRange{First: 5, Last: 6},
2774 AlertingRule: &parser.AlertingRule{
2775 Alert: parser.YamlNode{
2776 Value: "foo",
2777 Pos: diags.PositionRanges{
2778 {Line: 6, FirstColumn: 10, LastColumn: 12},
2779 },
2780 },
2781 Expr: parser.PromQLExpr{
2782 Value: &parser.YamlNode{
2783 Value: "bar",
2784 Pos: diags.PositionRanges{
2785 {Line: 5, FirstColumn: 9, LastColumn: 11},
2786 },
2787 },
2788 },
2789 },
2790 },
2791 },
2792 },
2793 },
2794 },
2795 },
2796 {
2797 input: []byte(`---
2798groups:
2799- name: v1
2800 rules:
2801 - record: up:count
2802 expr: count(up)
2803 labels:
2804 foo:
2805 bar: foo
2806`),
2807 output: parser.File{
2808 IsRelaxed: true,
2809 Groups: []parser.Group{
2810 {
2811 Name: parser.YamlNode{
2812 Value: "v1",
2813 Pos: diags.PositionRanges{
2814 {Line: 3, FirstColumn: 9, LastColumn: 10},
2815 },
2816 },
2817 Rules: []parser.Rule{
2818 {
2819 Lines: diags.LineRange{First: 5, Last: 9},
2820 Error: parser.ParseError{
2821 Err: errors.New("labels foo value must be a string, got mapping instead"),
2822 Line: 9,
2823 },
2824 },
2825 },
2826 },
2827 },
2828 },
2829 },
2830 {
2831 input: []byte(`
2832groups:
2833- name: v1
2834 rules:
2835 - record: up:count
2836 expr: count(up)
2837`),
2838 strict: true,
2839 output: parser.File{
2840 Groups: []parser.Group{
2841 {
2842 Name: parser.YamlNode{
2843 Value: "v1",
2844 Pos: diags.PositionRanges{
2845 {Line: 3, FirstColumn: 9, LastColumn: 10},
2846 },
2847 },
2848 Rules: []parser.Rule{
2849 {
2850 Lines: diags.LineRange{First: 5, Last: 6},
2851 RecordingRule: &parser.RecordingRule{
2852 Record: parser.YamlNode{
2853 Value: "up:count",
2854 Pos: diags.PositionRanges{
2855 {Line: 5, FirstColumn: 13, LastColumn: 20},
2856 },
2857 },
2858 Expr: parser.PromQLExpr{
2859 Value: &parser.YamlNode{
2860 Value: "count(up)",
2861 Pos: diags.PositionRanges{
2862 {Line: 6, FirstColumn: 11, LastColumn: 19},
2863 },
2864 },
2865 },
2866 },
2867 },
2868 },
2869 },
2870 },
2871 },
2872 },
2873 {
2874 input: []byte(`
2875groups:
2876- name: v1
2877 rules:
2878 - record: up:count
2879`),
2880 strict: true,
2881 output: parser.File{
2882 Groups: []parser.Group{
2883 {
2884 Name: parser.YamlNode{
2885 Value: "v1",
2886 Pos: diags.PositionRanges{
2887 {Line: 3, FirstColumn: 9, LastColumn: 10},
2888 },
2889 },
2890 Rules: []parser.Rule{
2891 {
2892 Lines: diags.LineRange{First: 5, Last: 5},
2893 Error: parser.ParseError{
2894 Err: errors.New("missing expr key"),
2895 Line: 5,
2896 },
2897 },
2898 },
2899 },
2900 },
2901 },
2902 },
2903 {
2904 input: []byte(`
2905- record: up:count
2906 expr: count(up)
2907`),
2908 strict: true,
2909 output: parser.File{
2910 Error: parser.ParseError{
2911 Err: errors.New("top level field must be a groups key, got list"),
2912 Line: 2,
2913 },
2914 },
2915 },
2916 {
2917 input: []byte(`
2918rules:
2919 - record: up:count
2920 expr: count(up)
2921`),
2922 strict: true,
2923 output: parser.File{
2924 Error: parser.ParseError{
2925 Err: errors.New("unexpected key rules"),
2926 Line: 2,
2927 },
2928 },
2929 },
2930 {
2931 input: []byte(`
2932groups:
2933 - record: up:count
2934 expr: count(up)
2935`),
2936 strict: true,
2937 output: parser.File{
2938 Groups: []parser.Group{
2939 {
2940 Error: parser.ParseError{
2941 Err: errors.New("invalid group key record"),
2942 Line: 3,
2943 },
2944 },
2945 },
2946 },
2947 },
2948 {
2949 input: []byte(`
2950groups:
2951- rules:
2952 - record: up:count
2953 expr: count(up)
2954`),
2955 strict: true,
2956 output: parser.File{
2957 Groups: []parser.Group{
2958 {
2959 Error: parser.ParseError{
2960 Err: errors.New("incomplete group definition, name is required and must be set"),
2961 Line: 3,
2962 },
2963 Rules: []parser.Rule{
2964 {
2965 Lines: diags.LineRange{First: 4, Last: 5},
2966 RecordingRule: &parser.RecordingRule{
2967 Record: parser.YamlNode{
2968 Value: "up:count",
2969 Pos: diags.PositionRanges{
2970 {Line: 4, FirstColumn: 13, LastColumn: 20},
2971 },
2972 },
2973 Expr: parser.PromQLExpr{
2974 Value: &parser.YamlNode{
2975 Value: "count(up)",
2976 Pos: diags.PositionRanges{
2977 {Line: 5, FirstColumn: 11, LastColumn: 19},
2978 },
2979 },
2980 },
2981 },
2982 },
2983 },
2984 },
2985 },
2986 },
2987 },
2988 {
2989 input: []byte(`
2990groups:
2991- name: foo
2992`),
2993 strict: true,
2994 output: parser.File{
2995 Groups: []parser.Group{
2996 {
2997 Name: parser.YamlNode{
2998 Value: "foo",
2999 Pos: diags.PositionRanges{
3000 {Line: 3, FirstColumn: 9, LastColumn: 11},
3001 },
3002 },
3003 },
3004 },
3005 },
3006 },
3007 {
3008 input: []byte(`
3009groups: {}
3010`),
3011 strict: true,
3012 output: parser.File{
3013 Error: parser.ParseError{
3014 Err: errors.New("groups value must be a list, got mapping"),
3015 Line: 2,
3016 },
3017 },
3018 },
3019 {
3020 input: []byte(`
3021groups:
3022- name: []
3023`),
3024 strict: true,
3025 output: parser.File{
3026 Groups: []parser.Group{
3027 {
3028 Error: parser.ParseError{
3029 Err: errors.New("group name must be a string, got list"),
3030 Line: 3,
3031 },
3032 },
3033 },
3034 },
3035 },
3036 {
3037 input: []byte(`
3038groups:
3039- name: foo
3040 name: bar
3041 name: bob
3042`),
3043 strict: true,
3044 output: parser.File{
3045 Groups: []parser.Group{
3046 {
3047 Name: parser.YamlNode{
3048 Value: "bar",
3049 Pos: diags.PositionRanges{
3050 {Line: 4, FirstColumn: 9, LastColumn: 11},
3051 },
3052 },
3053 Error: parser.ParseError{
3054 Err: errors.New("duplicated key name"),
3055 Line: 4,
3056 },
3057 },
3058 },
3059 },
3060 },
3061 {
3062 input: []byte(`
3063groups:
3064- name: v1
3065 rules:
3066 rules:
3067 - record: up:count
3068 expr: count(up)
3069`),
3070 strict: true,
3071 output: parser.File{
3072 Groups: []parser.Group{
3073 {
3074 Name: parser.YamlNode{
3075 Value: "v1",
3076 Pos: diags.PositionRanges{
3077 {Line: 3, FirstColumn: 9, LastColumn: 10},
3078 },
3079 },
3080 Error: parser.ParseError{
3081 Err: errors.New("rules must be a list, got mapping"),
3082 Line: 4,
3083 },
3084 },
3085 },
3086 },
3087 },
3088 {
3089 input: []byte(`
3090groups:
3091- name: v1
3092 rules:
3093 - rules:
3094 - record: up:count
3095 expr: count(up)
3096`),
3097 strict: true,
3098 output: parser.File{
3099 Groups: []parser.Group{
3100 {
3101 Name: parser.YamlNode{
3102 Value: "v1",
3103 Pos: diags.PositionRanges{
3104 {Line: 3, FirstColumn: 9, LastColumn: 10},
3105 },
3106 },
3107 Rules: []parser.Rule{
3108 {
3109 Error: parser.ParseError{
3110 Err: errors.New("invalid rule key rules"),
3111 Line: 5,
3112 },
3113 },
3114 },
3115 },
3116 },
3117 },
3118 },
3119 {
3120 input: []byte(`
3121groups:
3122- name: v1
3123 rules:
3124 - rules:
3125 - record: up:count
3126 expr: count(up)
3127`),
3128 strict: true,
3129 output: parser.File{
3130 Error: parser.ParseError{
3131 Err: errors.New("found a tab character that violates indentation"),
3132 Line: 6,
3133 },
3134 },
3135 },
3136 {
3137 input: []byte(`
3138---
3139groups:
3140- name: v1
3141 rules:
3142 - record: up:count
3143 expr: count(up)
3144---
3145groups:
3146- name: v1
3147 rules:
3148 - rules:
3149 - record: up:count
3150 expr: count(up)
3151`),
3152 strict: true,
3153 output: parser.File{
3154 Groups: []parser.Group{
3155 {
3156 Name: parser.YamlNode{
3157 Value: "v1",
3158 Pos: diags.PositionRanges{
3159 {Line: 4, FirstColumn: 9, LastColumn: 10},
3160 },
3161 },
3162 Rules: []parser.Rule{
3163 {
3164 Lines: diags.LineRange{First: 6, Last: 7},
3165 RecordingRule: &parser.RecordingRule{
3166 Record: parser.YamlNode{
3167 Value: "up:count",
3168 Pos: diags.PositionRanges{
3169 {Line: 6, FirstColumn: 15, LastColumn: 22},
3170 },
3171 },
3172 Expr: parser.PromQLExpr{
3173 Value: &parser.YamlNode{
3174 Value: "count(up)",
3175 Pos: diags.PositionRanges{
3176 {Line: 7, FirstColumn: 13, LastColumn: 21},
3177 },
3178 },
3179 },
3180 },
3181 },
3182 },
3183 },
3184 {
3185 Name: parser.YamlNode{
3186 Value: "v1",
3187 Pos: diags.PositionRanges{
3188 {Line: 10, FirstColumn: 9, LastColumn: 10},
3189 },
3190 },
3191 Rules: []parser.Rule{
3192 {
3193 Error: parser.ParseError{
3194 Err: errors.New("invalid rule key rules"),
3195 Line: 12,
3196 },
3197 },
3198 },
3199 },
3200 },
3201 Error: parser.ParseError{
3202 Line: 8,
3203 Err: errors.New("multi-document YAML files are not allowed"),
3204 },
3205 },
3206 },
3207 {
3208 input: []byte(`
3209---
3210groups: []
3211---
3212groups:
3213- name: foo
3214 rules:
3215 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
3216 record: foo
3217 expr: foo
3218`),
3219 strict: true,
3220 output: parser.File{
3221 Groups: []parser.Group{
3222 {
3223 Name: parser.YamlNode{
3224 Value: "foo",
3225 Pos: diags.PositionRanges{
3226 {Line: 6, FirstColumn: 9, LastColumn: 11},
3227 },
3228 },
3229 Rules: []parser.Rule{
3230 {
3231 Lines: diags.LineRange{First: 8, Last: 10},
3232 Error: parser.ParseError{
3233 Line: 8,
3234 Err: errors.New("labels value must be a mapping, got binary data instead"),
3235 },
3236 },
3237 },
3238 },
3239 },
3240 Error: parser.ParseError{
3241 Line: 4,
3242 Err: errors.New("multi-document YAML files are not allowed"),
3243 },
3244 },
3245 },
3246 {
3247 input: []byte("[]"),
3248 strict: true,
3249 output: parser.File{
3250 Error: parser.ParseError{
3251 Err: errors.New("top level field must be a groups key, got list"),
3252 Line: 1,
3253 },
3254 },
3255 },
3256 {
3257 input: []byte("\n\n[]"),
3258 strict: true,
3259 output: parser.File{
3260 Error: parser.ParseError{
3261 Err: errors.New("top level field must be a groups key, got list"),
3262 Line: 3,
3263 },
3264 },
3265 },
3266 {
3267 input: []byte("groups: {}"),
3268 strict: true,
3269 output: parser.File{
3270 Error: parser.ParseError{
3271 Err: errors.New("groups value must be a list, got mapping"),
3272 Line: 1,
3273 },
3274 },
3275 },
3276 {
3277 input: []byte("groups: []"),
3278 strict: true,
3279 },
3280 {
3281 input: []byte("xgroups: {}"),
3282 strict: true,
3283 output: parser.File{
3284 Error: parser.ParseError{
3285 Err: errors.New("unexpected key xgroups"),
3286 Line: 1,
3287 },
3288 },
3289 },
3290 {
3291 input: []byte("\nbob\n"),
3292 strict: true,
3293 output: parser.File{
3294 Error: parser.ParseError{
3295 Err: errors.New("top level field must be a groups key, got string"),
3296 Line: 2,
3297 },
3298 },
3299 },
3300 {
3301 input: []byte(`groups: []
3302
3303rules: []
3304`),
3305 strict: true,
3306 output: parser.File{
3307 Error: parser.ParseError{
3308 Err: errors.New("unexpected key rules"),
3309 Line: 3,
3310 },
3311 },
3312 },
3313 {
3314 input: []byte(`
3315groups:
3316- name: foo
3317 rules: []
3318`),
3319 strict: true,
3320 output: parser.File{
3321 Groups: []parser.Group{
3322 {
3323 Name: parser.YamlNode{
3324 Value: "foo",
3325 Pos: diags.PositionRanges{
3326 {Line: 3, FirstColumn: 9, LastColumn: 11},
3327 },
3328 },
3329 },
3330 },
3331 },
3332 },
3333 {
3334 input: []byte(`
3335groups:
3336- name:
3337 rules: []
3338`),
3339 strict: true,
3340 output: parser.File{
3341 Groups: []parser.Group{
3342 {
3343 Error: parser.ParseError{
3344 Err: errors.New("group name must be a string, got null"),
3345 Line: 3,
3346 },
3347 },
3348 },
3349 },
3350 },
3351 {
3352 input: []byte(`
3353groups:
3354- name: foo
3355 rules:
3356 - record: foo
3357 expr: sum(up)
3358 labels:
3359 job: foo
3360`),
3361 strict: true,
3362 output: parser.File{
3363 Groups: []parser.Group{
3364 {
3365 Name: parser.YamlNode{
3366 Value: "foo",
3367 Pos: diags.PositionRanges{
3368 {Line: 3, FirstColumn: 9, LastColumn: 11},
3369 },
3370 },
3371 Rules: []parser.Rule{
3372 {
3373 Lines: diags.LineRange{First: 5, Last: 8},
3374 RecordingRule: &parser.RecordingRule{
3375 Record: parser.YamlNode{
3376 Value: "foo",
3377 Pos: diags.PositionRanges{
3378 {Line: 5, FirstColumn: 13, LastColumn: 15},
3379 },
3380 },
3381 Expr: parser.PromQLExpr{
3382 Value: &parser.YamlNode{
3383 Value: "sum(up)",
3384 Pos: diags.PositionRanges{
3385 {Line: 6, FirstColumn: 11, LastColumn: 17},
3386 },
3387 },
3388 },
3389 Labels: &parser.YamlMap{
3390 Key: &parser.YamlNode{
3391 Value: "labels",
3392 Pos: diags.PositionRanges{
3393 {Line: 7, FirstColumn: 5, LastColumn: 10},
3394 },
3395 },
3396 Items: []*parser.YamlKeyValue{
3397 {
3398 Key: &parser.YamlNode{
3399 Value: "job",
3400 Pos: diags.PositionRanges{
3401 {Line: 8, FirstColumn: 7, LastColumn: 9},
3402 },
3403 },
3404 Value: &parser.YamlNode{
3405 Value: "foo",
3406 Pos: diags.PositionRanges{
3407 {Line: 8, FirstColumn: 12, LastColumn: 14},
3408 },
3409 },
3410 },
3411 },
3412 },
3413 },
3414 },
3415 },
3416 },
3417 },
3418 },
3419 },
3420 {
3421 input: []byte(`
3422groups:
3423- name: foo
3424 rules:
3425 - record: foo
3426 expr: sum(up)
3427 xxx: 1
3428 labels:
3429 job: foo
3430`),
3431 strict: true,
3432 output: parser.File{
3433 Groups: []parser.Group{
3434 {
3435 Name: parser.YamlNode{
3436 Value: "foo",
3437 Pos: diags.PositionRanges{
3438 {Line: 3, FirstColumn: 9, LastColumn: 11},
3439 },
3440 },
3441 Rules: []parser.Rule{
3442 {
3443 Error: parser.ParseError{
3444 Err: errors.New("invalid rule key xxx"),
3445 Line: 7,
3446 },
3447 },
3448 },
3449 },
3450 },
3451 },
3452 },
3453 {
3454 input: []byte(`
3455groups:
3456- name: foo
3457 rules:
3458 record: foo
3459 expr: sum(up)
3460 xxx: 1
3461 labels:
3462 job: foo
3463`),
3464 strict: true,
3465 output: parser.File{
3466 Groups: []parser.Group{
3467 {
3468 Name: parser.YamlNode{
3469 Value: "foo",
3470 Pos: diags.PositionRanges{
3471 {Line: 3, FirstColumn: 9, LastColumn: 11},
3472 },
3473 },
3474 Error: parser.ParseError{
3475 Err: errors.New("rules must be a list, got mapping"),
3476 Line: 4,
3477 },
3478 },
3479 },
3480 },
3481 },
3482 {
3483 input: []byte(`
3484groups:
3485- name: foo
3486 rules:
3487 - record: foo
3488 expr: sum(up)
3489 labels:
3490 job:
3491 foo: bar
3492`),
3493 strict: true,
3494 output: parser.File{
3495 Groups: []parser.Group{
3496 {
3497 Name: parser.YamlNode{
3498 Value: "foo",
3499 Pos: diags.PositionRanges{
3500 {Line: 3, FirstColumn: 9, LastColumn: 11},
3501 },
3502 },
3503 Rules: []parser.Rule{
3504 {
3505 Lines: diags.LineRange{First: 5, Last: 9},
3506 Error: parser.ParseError{
3507 Line: 9,
3508 Err: errors.New("labels job value must be a string, got mapping instead"),
3509 },
3510 },
3511 },
3512 },
3513 },
3514 },
3515 },
3516 {
3517 input: []byte(`
3518groups:
3519- name: foo
3520 rules:
3521 - record: foo
3522 expr:
3523 sum: sum(up)
3524`),
3525 strict: true,
3526 output: parser.File{
3527 Groups: []parser.Group{
3528 {
3529 Name: parser.YamlNode{
3530 Value: "foo",
3531 Pos: diags.PositionRanges{
3532 {Line: 3, FirstColumn: 9, LastColumn: 11},
3533 },
3534 },
3535 Rules: []parser.Rule{
3536 {
3537 Lines: diags.LineRange{First: 5, Last: 7},
3538 Error: parser.ParseError{
3539 Line: 7,
3540 Err: errors.New("expr value must be a string, got mapping instead"),
3541 },
3542 },
3543 },
3544 },
3545 },
3546 },
3547 },
3548 {
3549 input: []byte(`
3550groups:
3551- name: foo
3552 rules: []
3553- name: foo
3554 rules: []
3555`),
3556 strict: true,
3557 output: parser.File{
3558 Error: parser.ParseError{
3559 Err: errors.New("duplicated group name"),
3560 Line: 5,
3561 },
3562 },
3563 },
3564 {
3565 input: []byte(`
3566groups:
3567- name: foo
3568 rules:
3569 - record: foo
3570 expr: sum(up)
3571 labels:
3572 foo: bob
3573 foo: bar
3574`),
3575 strict: true,
3576 output: parser.File{
3577 Groups: []parser.Group{
3578 {
3579 Name: parser.YamlNode{
3580 Value: "foo",
3581 Pos: diags.PositionRanges{
3582 {Line: 3, FirstColumn: 9, LastColumn: 11},
3583 },
3584 },
3585 Rules: []parser.Rule{
3586 {
3587 Lines: diags.LineRange{First: 8, Last: 9},
3588 Error: parser.ParseError{
3589 Line: 9,
3590 Err: errors.New("duplicated labels key foo"),
3591 },
3592 },
3593 },
3594 },
3595 },
3596 },
3597 },
3598 {
3599 input: []byte(`
3600groups:
3601- name: v2
3602 rules:
3603 - record: up:count
3604 expr: count(up)
3605 expr: sum(up)`),
3606 strict: true,
3607 output: parser.File{
3608 Groups: []parser.Group{
3609 {
3610 Name: parser.YamlNode{
3611 Value: "v2",
3612 Pos: diags.PositionRanges{
3613 {Line: 3, FirstColumn: 9, LastColumn: 10},
3614 },
3615 },
3616 Rules: []parser.Rule{
3617 {
3618 Lines: diags.LineRange{First: 5, Last: 7},
3619 Error: parser.ParseError{
3620 Line: 7,
3621 Err: errors.New("duplicated expr key"),
3622 },
3623 },
3624 },
3625 },
3626 },
3627 },
3628 },
3629 {
3630 input: []byte(`
3631groups:
3632- name: v2
3633 rules:
3634 - record: up:count
3635 expr: count(up)
3636bogus: 1
3637`),
3638 strict: true,
3639 output: parser.File{
3640 Error: parser.ParseError{
3641 Err: errors.New("unexpected key bogus"),
3642 Line: 7,
3643 },
3644 },
3645 },
3646 {
3647 input: []byte(`
3648groups:
3649- name: v2
3650 rules:
3651 - record: up:count
3652 expr: count(up)
3653 bogus: 1
3654`),
3655 strict: true,
3656 output: parser.File{
3657 Groups: []parser.Group{
3658 {
3659 Name: parser.YamlNode{
3660 Value: "v2",
3661 Pos: diags.PositionRanges{
3662 {Line: 3, FirstColumn: 9, LastColumn: 10},
3663 },
3664 },
3665 Rules: []parser.Rule{
3666 {
3667 Error: parser.ParseError{
3668 Err: errors.New("invalid rule key bogus"),
3669 Line: 7,
3670 },
3671 },
3672 },
3673 },
3674 },
3675 },
3676 },
3677 {
3678 input: []byte(`
3679groups:
3680- name: v2
3681 rules:
3682 - alert: up:count
3683 for: 5m
3684 keep_firing_for: 5m
3685 expr: count(up)
3686 labels: {}
3687 annotations: {}
3688 bogus: 1
3689`),
3690 strict: true,
3691 output: parser.File{
3692 Groups: []parser.Group{
3693 {
3694 Name: parser.YamlNode{
3695 Value: "v2",
3696 Pos: diags.PositionRanges{
3697 {Line: 3, FirstColumn: 9, LastColumn: 10},
3698 },
3699 },
3700 Rules: []parser.Rule{
3701 {
3702 Error: parser.ParseError{
3703 Err: errors.New("invalid rule key bogus"),
3704 Line: 11,
3705 },
3706 },
3707 },
3708 },
3709 },
3710 },
3711 },
3712 {
3713 input: []byte(`
3714groups:
3715
3716- name: CloudflareKafkaZookeeperExporter
3717
3718 rules:
3719`),
3720 strict: true,
3721 output: parser.File{
3722 Groups: []parser.Group{
3723 {
3724 Name: parser.YamlNode{
3725 Value: "CloudflareKafkaZookeeperExporter",
3726 Pos: diags.PositionRanges{
3727 {Line: 4, FirstColumn: 9, LastColumn: 40},
3728 },
3729 },
3730 },
3731 },
3732 },
3733 },
3734 {
3735 input: []byte(`
3736groups:
3737- name: foo
3738 rules:
3739 expr: 1
3740`),
3741 strict: true,
3742 output: parser.File{
3743 Groups: []parser.Group{
3744 {
3745 Name: parser.YamlNode{
3746 Value: "foo",
3747 Pos: diags.PositionRanges{
3748 {Line: 3, FirstColumn: 9, LastColumn: 11},
3749 },
3750 },
3751 Error: parser.ParseError{
3752 Err: errors.New("rules must be a list, got mapping"),
3753 Line: 4,
3754 },
3755 },
3756 },
3757 },
3758 },
3759 {
3760 input: []byte(`
3761groups:
3762- name: foo
3763 rules:
3764 - expr: 1
3765`),
3766 strict: true,
3767 output: parser.File{
3768 Groups: []parser.Group{
3769 {
3770 Name: parser.YamlNode{
3771 Value: "foo",
3772 Pos: diags.PositionRanges{
3773 {Line: 3, FirstColumn: 9, LastColumn: 11},
3774 },
3775 },
3776 Rules: []parser.Rule{
3777 {
3778 Lines: diags.LineRange{First: 5, Last: 5},
3779 Error: parser.ParseError{
3780 Line: 5,
3781 Err: errors.New("incomplete rule, no alert or record key"),
3782 },
3783 },
3784 },
3785 },
3786 },
3787 },
3788 },
3789 {
3790 input: []byte(`
3791groups:
3792- name: foo
3793 rules:
3794 - expr: null
3795`),
3796 strict: true,
3797 output: parser.File{
3798 Groups: []parser.Group{
3799 {
3800 Name: parser.YamlNode{
3801 Value: "foo",
3802 Pos: diags.PositionRanges{
3803 {Line: 3, FirstColumn: 9, LastColumn: 11},
3804 },
3805 },
3806 Rules: []parser.Rule{
3807 {
3808 Lines: diags.LineRange{First: 5, Last: 5},
3809 Error: parser.ParseError{
3810 Line: 5,
3811 Err: errors.New("incomplete rule, no alert or record key"),
3812 },
3813 },
3814 },
3815 },
3816 },
3817 },
3818 },
3819 {
3820 input: []byte(`
3821groups:
3822- name: foo
3823 rules:
3824 - 1: null
3825`),
3826 strict: true,
3827 output: parser.File{
3828 Groups: []parser.Group{
3829 {
3830 Name: parser.YamlNode{
3831 Value: "foo",
3832 Pos: diags.PositionRanges{
3833 {Line: 3, FirstColumn: 9, LastColumn: 11},
3834 },
3835 },
3836 Rules: []parser.Rule{
3837 {
3838 Error: parser.ParseError{
3839 Err: errors.New("invalid rule key 1"),
3840 Line: 5,
3841 },
3842 },
3843 },
3844 },
3845 },
3846 },
3847 },
3848 {
3849 input: []byte(`
3850groups:
3851- name: foo
3852 rules:
3853 - true: !!binary "SGVsbG8sIFdvcmxkIQ=="
3854`),
3855 strict: true,
3856 output: parser.File{
3857 Groups: []parser.Group{
3858 {
3859 Name: parser.YamlNode{
3860 Value: "foo",
3861 Pos: diags.PositionRanges{
3862 {Line: 3, FirstColumn: 9, LastColumn: 11},
3863 },
3864 },
3865 Rules: []parser.Rule{
3866 {
3867 Error: parser.ParseError{
3868 Err: errors.New("invalid rule key true"),
3869 Line: 5,
3870 },
3871 },
3872 },
3873 },
3874 },
3875 },
3876 },
3877 {
3878 input: []byte(`
3879groups:
3880- name: foo
3881 rules:
3882 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
3883`),
3884 strict: true,
3885 output: parser.File{
3886 Groups: []parser.Group{
3887 {
3888 Name: parser.YamlNode{
3889 Value: "foo",
3890 Pos: diags.PositionRanges{
3891 {Line: 3, FirstColumn: 9, LastColumn: 11},
3892 },
3893 },
3894 Rules: []parser.Rule{
3895 {
3896 Lines: diags.LineRange{First: 5, Last: 5},
3897 Error: parser.ParseError{
3898 Line: 5,
3899 Err: errors.New("incomplete rule, no alert or record key"),
3900 },
3901 },
3902 },
3903 },
3904 },
3905 },
3906 },
3907 {
3908 input: []byte(`
3909groups:
3910- name: foo
3911 rules:
3912 - expr: !!binary "SGVsbG8sIFdvcmxkIQ=="
3913 record: foo
3914`),
3915 strict: true,
3916 output: parser.File{
3917 Groups: []parser.Group{
3918 {
3919 Name: parser.YamlNode{
3920 Value: "foo",
3921 Pos: diags.PositionRanges{
3922 {Line: 3, FirstColumn: 9, LastColumn: 11},
3923 },
3924 },
3925 Rules: []parser.Rule{
3926 {
3927 Lines: diags.LineRange{First: 5, Last: 6},
3928 Error: parser.ParseError{
3929 Line: 5,
3930 Err: errors.New("expr value must be a string, got binary data instead"),
3931 },
3932 },
3933 },
3934 },
3935 },
3936 },
3937 },
3938 {
3939 input: []byte(`
3940groups:
3941- name: foo
3942 rules:
3943 - labels: !!binary "SGVsbG8sIFdvcmxkIQ=="
3944 record: foo
3945 expr: foo
3946`),
3947 strict: true,
3948 output: parser.File{
3949 Groups: []parser.Group{
3950 {
3951 Name: parser.YamlNode{
3952 Value: "foo",
3953 Pos: diags.PositionRanges{
3954 {Line: 3, FirstColumn: 9, LastColumn: 11},
3955 },
3956 },
3957 Rules: []parser.Rule{
3958 {
3959 Lines: diags.LineRange{First: 5, Last: 7},
3960 Error: parser.ParseError{
3961 Line: 5,
3962 Err: errors.New("labels value must be a mapping, got binary data instead"),
3963 },
3964 },
3965 },
3966 },
3967 },
3968 },
3969 },
3970 {
3971 input: []byte(`
3972---
3973groups:
3974- name: foo
3975 rules:
3976 - record: foo
3977 expr: bar
3978---
3979groups:
3980- name: foo
3981 rules:
3982 - record: foo
3983 expr: bar
3984`),
3985 strict: true,
3986 output: parser.File{
3987 Error: parser.ParseError{
3988 Line: 8,
3989 Err: errors.New("multi-document YAML files are not allowed"),
3990 },
3991 Groups: []parser.Group{
3992 {
3993 Name: parser.YamlNode{
3994 Value: "foo",
3995 Pos: diags.PositionRanges{
3996 {Line: 4, FirstColumn: 9, LastColumn: 11},
3997 },
3998 },
3999 Rules: []parser.Rule{
4000 {
4001 Lines: diags.LineRange{First: 6, Last: 7},
4002 RecordingRule: &parser.RecordingRule{
4003 Record: parser.YamlNode{
4004 Value: "foo",
4005 Pos: diags.PositionRanges{
4006 {Line: 6, FirstColumn: 15, LastColumn: 17},
4007 },
4008 },
4009 Expr: parser.PromQLExpr{
4010 Value: &parser.YamlNode{
4011 Value: "bar",
4012 Pos: diags.PositionRanges{
4013 {Line: 7, FirstColumn: 13, LastColumn: 15},
4014 },
4015 },
4016 },
4017 },
4018 },
4019 },
4020 },
4021 {
4022 Name: parser.YamlNode{
4023 Value: "foo",
4024 Pos: diags.PositionRanges{
4025 {Line: 10, FirstColumn: 9, LastColumn: 11},
4026 },
4027 },
4028 Rules: []parser.Rule{
4029 {
4030 Lines: diags.LineRange{First: 12, Last: 13},
4031 RecordingRule: &parser.RecordingRule{
4032 Record: parser.YamlNode{
4033 Value: "foo",
4034 Pos: diags.PositionRanges{
4035 {Line: 12, FirstColumn: 15, LastColumn: 17},
4036 },
4037 },
4038 Expr: parser.PromQLExpr{
4039 Value: &parser.YamlNode{
4040 Value: "bar",
4041 Pos: diags.PositionRanges{
4042 {Line: 13, FirstColumn: 13, LastColumn: 15},
4043 },
4044 },
4045 },
4046 },
4047 },
4048 },
4049 },
4050 },
4051 },
4052 },
4053 {
4054 input: []byte(`
4055groups:
4056- name: foo
4057 rules:
4058 - record: foo
4059 expr: foo
4060 expr: foo
4061`),
4062 strict: true,
4063 output: parser.File{
4064 Groups: []parser.Group{
4065 {
4066 Name: parser.YamlNode{
4067 Value: "foo",
4068 Pos: diags.PositionRanges{
4069 {Line: 3, FirstColumn: 9, LastColumn: 11},
4070 },
4071 },
4072 Rules: []parser.Rule{
4073 {
4074 Lines: diags.LineRange{First: 5, Last: 7},
4075 Error: parser.ParseError{
4076 Line: 7,
4077 Err: errors.New("duplicated expr key"),
4078 },
4079 },
4080 },
4081 },
4082 },
4083 },
4084 },
4085 {
4086 input: []byte(`
4087groups:
4088- name: foo
4089 rules:
4090 - record: foo
4091 keep_firing_for: 1m
4092 keep_firing_for: 2m
4093`),
4094 strict: true,
4095 output: parser.File{
4096 Groups: []parser.Group{
4097 {
4098 Name: parser.YamlNode{
4099 Value: "foo",
4100 Pos: diags.PositionRanges{
4101 {Line: 3, FirstColumn: 9, LastColumn: 11},
4102 },
4103 },
4104 Rules: []parser.Rule{
4105 {
4106 Lines: diags.LineRange{First: 5, Last: 7},
4107 Error: parser.ParseError{
4108 Line: 7,
4109 Err: errors.New("duplicated keep_firing_for key"),
4110 },
4111 },
4112 },
4113 },
4114 },
4115 },
4116 },
4117 {
4118 input: []byte(`
4119groups:
4120- name: foo
4121 rules:
4122 - record: foo
4123 keep_firing_for: 1m
4124 record: 2m
4125`),
4126 strict: true,
4127 output: parser.File{
4128 Groups: []parser.Group{
4129 {
4130 Name: parser.YamlNode{
4131 Value: "foo",
4132 Pos: diags.PositionRanges{
4133 {Line: 3, FirstColumn: 9, LastColumn: 11},
4134 },
4135 },
4136 Rules: []parser.Rule{
4137 {
4138 Lines: diags.LineRange{First: 5, Last: 7},
4139 Error: parser.ParseError{
4140 Line: 7,
4141 Err: errors.New("duplicated record key"),
4142 },
4143 },
4144 },
4145 },
4146 },
4147 },
4148 },
4149 {
4150 input: []byte(`
4151groups:
4152- name: foo
4153 rules:
4154 - []
4155`),
4156 strict: true,
4157 output: parser.File{
4158 Groups: []parser.Group{
4159 {
4160 Name: parser.YamlNode{
4161 Value: "foo",
4162 Pos: diags.PositionRanges{
4163 {Line: 3, FirstColumn: 9, LastColumn: 11},
4164 },
4165 },
4166 Rules: []parser.Rule{
4167 {
4168 Error: parser.ParseError{
4169 Err: errors.New("rule definition must be a mapping, got list"),
4170 Line: 5,
4171 },
4172 },
4173 },
4174 },
4175 },
4176 },
4177 },
4178 {
4179 input: []byte(`
41801: 0
4181`),
4182 strict: true,
4183 output: parser.File{
4184 Error: parser.ParseError{
4185 Err: errors.New("groups key must be a string, got a integer"),
4186 Line: 2,
4187 },
4188 },
4189 },
4190 {
4191 input: []byte(`
4192true: 0
4193`),
4194 strict: true,
4195 output: parser.File{
4196 Error: parser.ParseError{
4197 Err: errors.New("groups key must be a string, got a bool"),
4198 Line: 2,
4199 },
4200 },
4201 },
4202 {
4203 input: []byte(`
4204groups: !!binary "SGVsbG8sIFdvcmxkIQ=="
4205`),
4206 strict: true,
4207 output: parser.File{
4208 Error: parser.ParseError{
4209 Err: errors.New("groups value must be a list, got binary data"),
4210 Line: 2,
4211 },
4212 },
4213 },
4214 {
4215 input: []byte(`
4216groups:
4217 - true: null"
4218`),
4219 strict: true,
4220 output: parser.File{
4221 Groups: []parser.Group{
4222 {
4223 Error: parser.ParseError{
4224 Err: errors.New("invalid group key true"),
4225 Line: 3,
4226 },
4227 },
4228 },
4229 },
4230 },
4231 {
4232 input: []byte(`
4233!!!binary "groups": true"
4234`),
4235 strict: true,
4236 output: parser.File{
4237 Error: parser.ParseError{
4238 Err: errors.New("groups key must be a string, got a binary"),
4239 Line: 2,
4240 },
4241 },
4242 },
4243 {
4244 input: []byte("[]"),
4245 strict: true,
4246 output: parser.File{
4247 Error: parser.ParseError{
4248 Err: errors.New("top level field must be a groups key, got list"),
4249 Line: 1,
4250 },
4251 },
4252 },
4253 {
4254 input: []byte(`
4255groups:
4256 - true
4257`),
4258 strict: true,
4259 output: parser.File{
4260 Groups: []parser.Group{
4261 {
4262 Error: parser.ParseError{
4263 Err: errors.New("group must be a mapping, got bool"),
4264 Line: 3,
4265 },
4266 },
4267 },
4268 },
4269 },
4270 {
4271 input: []byte(`
4272groups:
4273 - name:
4274 rules: []
4275`),
4276 strict: true,
4277 output: parser.File{
4278 Groups: []parser.Group{
4279 {
4280 Error: parser.ParseError{
4281 Err: errors.New("group name must be a string, got null"),
4282 Line: 3,
4283 },
4284 },
4285 },
4286 },
4287 },
4288 {
4289 input: []byte(`
4290groups:
4291 - name: ""
4292 rules: []
4293`),
4294 strict: true,
4295 output: parser.File{
4296 Groups: []parser.Group{
4297 {
4298 Error: parser.ParseError{
4299 Err: errors.New("group name cannot be empty"),
4300 Line: 3,
4301 },
4302 },
4303 },
4304 },
4305 },
4306 {
4307 input: []byte(`
4308groups:
4309 - name: 1
4310 rules: []
4311`),
4312 strict: true,
4313 output: parser.File{
4314 Groups: []parser.Group{
4315 {
4316 Error: parser.ParseError{
4317 Err: errors.New("group name must be a string, got integer"),
4318 Line: 3,
4319 },
4320 },
4321 },
4322 },
4323 },
4324 {
4325 input: []byte(`
4326groups:
4327 - name: foo
4328 interval: 1
4329 rules: []
4330`),
4331 strict: true,
4332 output: parser.File{
4333 Groups: []parser.Group{
4334 {
4335 Name: parser.YamlNode{
4336 Value: "foo",
4337 Pos: diags.PositionRanges{
4338 {Line: 3, FirstColumn: 11, LastColumn: 13},
4339 },
4340 },
4341 Error: parser.ParseError{
4342 Err: errors.New("group interval must be a string, got integer"),
4343 Line: 4,
4344 },
4345 },
4346 },
4347 },
4348 },
4349 {
4350 input: []byte(`
4351groups:
4352 - name: foo
4353 interval: xxx
4354 rules: []
4355`),
4356 strict: true,
4357 output: parser.File{
4358 Groups: []parser.Group{
4359 {
4360 Name: parser.YamlNode{
4361 Value: "foo",
4362 Pos: diags.PositionRanges{
4363 {Line: 3, FirstColumn: 11, LastColumn: 13},
4364 },
4365 },
4366 Interval: &parser.YamlDuration{
4367 ParseError: errors.New(`not a valid duration string: "xxx"`),
4368 Raw: "xxx",
4369 Pos: diags.PositionRanges{
4370 {Line: 4, FirstColumn: 15, LastColumn: 17},
4371 },
4372 },
4373 Error: parser.ParseError{
4374 Err: errors.New("invalid interval value: not a valid duration string: \"xxx\""),
4375 Line: 4,
4376 },
4377 },
4378 },
4379 },
4380 },
4381 {
4382 input: []byte(`
4383groups:
4384 - name: foo
4385 query_offset: 1
4386 rules: []
4387`),
4388 strict: true,
4389 output: parser.File{
4390 Groups: []parser.Group{
4391 {
4392 Name: parser.YamlNode{
4393 Value: "foo",
4394 Pos: diags.PositionRanges{
4395 {Line: 3, FirstColumn: 11, LastColumn: 13},
4396 },
4397 },
4398 Error: parser.ParseError{
4399 Err: errors.New("group query_offset must be a string, got integer"),
4400 Line: 4,
4401 },
4402 },
4403 },
4404 },
4405 },
4406 {
4407 input: []byte(`
4408groups:
4409 - name: foo
4410 query_offset: xxx
4411 rules: []
4412`),
4413 strict: true,
4414 output: parser.File{
4415 Groups: []parser.Group{
4416 {
4417 Name: parser.YamlNode{
4418 Value: "foo",
4419 Pos: diags.PositionRanges{
4420 {Line: 3, FirstColumn: 11, LastColumn: 13},
4421 },
4422 },
4423 QueryOffset: &parser.YamlDuration{
4424 ParseError: errors.New(`not a valid duration string: "xxx"`),
4425 Raw: "xxx",
4426 Pos: diags.PositionRanges{
4427 {Line: 4, FirstColumn: 19, LastColumn: 21},
4428 },
4429 },
4430 Error: parser.ParseError{
4431 Err: errors.New("invalid query_offset value: not a valid duration string: \"xxx\""),
4432 Line: 4,
4433 },
4434 },
4435 },
4436 },
4437 },
4438 {
4439 input: []byte(`
4440groups:
4441 - name: foo
4442 query_offset: 1m
4443 limit: abc
4444 rules: []
4445`),
4446 strict: true,
4447 output: parser.File{
4448 Groups: []parser.Group{
4449 {
4450 Name: parser.YamlNode{
4451 Value: "foo",
4452 Pos: diags.PositionRanges{
4453 {Line: 3, FirstColumn: 11, LastColumn: 13},
4454 },
4455 },
4456 QueryOffset: &parser.YamlDuration{
4457 Value: time.Minute,
4458 Raw: "1m",
4459 Pos: diags.PositionRanges{
4460 {Line: 4, FirstColumn: 19, LastColumn: 20},
4461 },
4462 },
4463 Error: parser.ParseError{
4464 Err: errors.New("group limit must be a integer, got string"),
4465 Line: 5,
4466 },
4467 },
4468 },
4469 },
4470 },
4471 {
4472 input: []byte(`
4473groups:
4474- name: v2
4475 rules:
4476 - alert: up:count
4477 for: 5m &timeout
4478 keep_firing_for: **timeout
4479 expr: count(up)
4480 labels: {}
4481 annotations: {}
4482 bogus: 1
4483`),
4484 strict: true,
4485 output: parser.File{
4486 Error: parser.ParseError{
4487 Err: errors.New("did not find expected alphabetic or numeric character"),
4488 Line: 7,
4489 },
4490 },
4491 },
4492 {
4493 // Non-strict parser accepts any scalar value for limit and records
4494 // the strconv error on the YamlInt itself.
4495 input: []byte(`
4496groups:
4497- name: foo
4498 limit: abc
4499 rules: []
4500`),
4501 output: parser.File{
4502 IsRelaxed: true,
4503 Groups: []parser.Group{
4504 {
4505 Name: parser.YamlNode{
4506 Value: "foo",
4507 Pos: diags.PositionRanges{
4508 {Line: 3, FirstColumn: 9, LastColumn: 11},
4509 },
4510 },
4511 Limit: &parser.YamlInt{
4512 ParseError: errors.New(`strconv.Atoi: parsing "abc": invalid syntax`),
4513 Raw: "abc",
4514 Pos: diags.PositionRanges{
4515 {Line: 4, FirstColumn: 10, LastColumn: 12},
4516 },
4517 },
4518 },
4519 },
4520 },
4521 },
4522 {
4523 input: []byte(`
4524groups:
4525- name: v2
4526 rules:
4527 - alert: up:count
4528 for: &for 1
4529 keep_firing_for: *for
4530 expr: count(up)
4531 labels: {}
4532 annotations: {}
4533`),
4534 strict: true,
4535 output: parser.File{
4536 Groups: []parser.Group{
4537 {
4538 Name: parser.YamlNode{
4539 Value: "v2",
4540 Pos: diags.PositionRanges{
4541 {Line: 3, FirstColumn: 9, LastColumn: 10},
4542 },
4543 },
4544 Rules: []parser.Rule{
4545 {
4546 Lines: diags.LineRange{First: 5, Last: 10},
4547 Error: parser.ParseError{
4548 Line: 6,
4549 Err: errors.New("for value must be a string, got integer instead"),
4550 },
4551 },
4552 },
4553 },
4554 },
4555 },
4556 },
4557 {
4558 input: []byte(`
4559groups:
4560- name: v2
4561 limit: &for 1
4562 rules:
4563 - alert: up:count
4564 keep_firing_for: *for
4565 expr: count(up)
4566 labels: {}
4567 annotations: {}
4568`),
4569 strict: true,
4570 output: parser.File{
4571 Groups: []parser.Group{
4572 {
4573 Name: parser.YamlNode{
4574 Value: "v2",
4575 Pos: diags.PositionRanges{
4576 {Line: 3, FirstColumn: 9, LastColumn: 10},
4577 },
4578 },
4579 Limit: &parser.YamlInt{
4580 Value: 1,
4581 Raw: "1",
4582 Pos: diags.PositionRanges{
4583 {Line: 4, FirstColumn: 15, LastColumn: 15},
4584 },
4585 },
4586 Rules: []parser.Rule{
4587 {
4588 Lines: diags.LineRange{First: 6, Last: 10},
4589 Error: parser.ParseError{
4590 Line: 7,
4591 Err: errors.New("keep_firing_for value must be a string, got integer instead"),
4592 },
4593 },
4594 },
4595 },
4596 },
4597 },
4598 },
4599 {
4600 input: []byte(`
4601groups:
4602- name: "{{ source }}"
4603 rules:
4604# pint ignore/begin
4605
4606# pint ignore/end
4607`),
4608 strict: true,
4609 output: parser.File{
4610 Groups: []parser.Group{
4611 {
4612 Name: parser.YamlNode{
4613 Value: "{{ source }}",
4614 Pos: diags.PositionRanges{
4615 {Line: 3, FirstColumn: 10, LastColumn: 21},
4616 },
4617 },
4618 },
4619 },
4620 },
4621 },
4622 {
4623 input: []byte(`
4624groups:
4625- name: foo
4626 rules:
4627 - record: foo
4628 expr: |
4629 {"up"}
4630`),
4631 output: parser.File{
4632 IsRelaxed: true,
4633 Groups: []parser.Group{
4634 {
4635 Name: parser.YamlNode{
4636 Value: "foo",
4637 Pos: diags.PositionRanges{
4638 {Line: 3, FirstColumn: 9, LastColumn: 11},
4639 },
4640 },
4641 Rules: []parser.Rule{
4642 {
4643 Lines: diags.LineRange{First: 5, Last: 7},
4644 RecordingRule: &parser.RecordingRule{
4645 Record: parser.YamlNode{
4646 Value: "foo",
4647 Pos: diags.PositionRanges{
4648 {Line: 5, FirstColumn: 13, LastColumn: 15},
4649 },
4650 },
4651 Expr: parser.PromQLExpr{
4652 Value: &parser.YamlNode{
4653 Value: "{\"up\"}\n",
4654 Pos: diags.PositionRanges{
4655 {Line: 7, FirstColumn: 7, LastColumn: 12},
4656 },
4657 },
4658 },
4659 },
4660 },
4661 },
4662 },
4663 },
4664 },
4665 },
4666 {
4667 input: []byte(`
4668groups:
4669- name: foo
4670 rules:
4671 - record: foo
4672 expr: |
4673 {'up'}
4674`),
4675 output: parser.File{
4676 IsRelaxed: true,
4677 Groups: []parser.Group{
4678 {
4679 Name: parser.YamlNode{
4680 Value: "foo",
4681 Pos: diags.PositionRanges{
4682 {Line: 3, FirstColumn: 9, LastColumn: 11},
4683 },
4684 },
4685 Rules: []parser.Rule{
4686 {
4687 Lines: diags.LineRange{First: 5, Last: 7},
4688 RecordingRule: &parser.RecordingRule{
4689 Record: parser.YamlNode{
4690 Value: "foo",
4691 Pos: diags.PositionRanges{
4692 {Line: 5, FirstColumn: 13, LastColumn: 15},
4693 },
4694 },
4695 Expr: parser.PromQLExpr{
4696 Value: &parser.YamlNode{
4697 Value: "{'up'}\n",
4698 Pos: diags.PositionRanges{
4699 {Line: 7, FirstColumn: 7, LastColumn: 12},
4700 },
4701 },
4702 },
4703 },
4704 },
4705 },
4706 },
4707 },
4708 },
4709 },
4710 {
4711 input: []byte(`
4712groups:
4713- name: mygroup
4714 partial_response_strategy: bob
4715 rules:
4716 - record: up:count
4717 expr: count(up)
4718`),
4719 strict: true,
4720 output: parser.File{
4721 Groups: []parser.Group{
4722 {
4723 Name: parser.YamlNode{
4724 Value: "mygroup",
4725 Pos: diags.PositionRanges{
4726 {Line: 3, FirstColumn: 9, LastColumn: 15},
4727 },
4728 },
4729 Error: parser.ParseError{
4730 Err: errors.New("partial_response_strategy is only valid when parser is configured to use the Thanos rule schema"),
4731 Line: 4,
4732 },
4733 },
4734 },
4735 },
4736 },
4737 {
4738 input: []byte(`
4739groups:
4740- name: mygroup
4741 partial_response_strategy: warn
4742 rules:
4743 - record: up:count
4744 expr: count(up)
4745`),
4746 strict: true,
4747 schema: parser.ThanosSchema,
4748 output: parser.File{
4749 Groups: []parser.Group{
4750 {
4751 Name: parser.YamlNode{
4752 Value: "mygroup",
4753 Pos: diags.PositionRanges{
4754 {Line: 3, FirstColumn: 9, LastColumn: 15},
4755 },
4756 },
4757 Rules: []parser.Rule{
4758 {
4759 Lines: diags.LineRange{First: 6, Last: 7},
4760 RecordingRule: &parser.RecordingRule{
4761 Record: parser.YamlNode{
4762 Value: "up:count",
4763 Pos: diags.PositionRanges{
4764 {Line: 6, FirstColumn: 13, LastColumn: 20},
4765 },
4766 },
4767 Expr: parser.PromQLExpr{
4768 Value: &parser.YamlNode{
4769 Value: "count(up)",
4770 Pos: diags.PositionRanges{
4771 {Line: 7, FirstColumn: 11, LastColumn: 19},
4772 },
4773 },
4774 },
4775 },
4776 },
4777 },
4778 },
4779 },
4780 },
4781 },
4782 {
4783 input: []byte(`
4784groups:
4785- name: mygroup
4786 partial_response_strategy: abort
4787 rules:
4788 - record: up:count
4789 expr: count(up)
4790`),
4791 strict: true,
4792 schema: parser.ThanosSchema,
4793 output: parser.File{
4794 Groups: []parser.Group{
4795 {
4796 Name: parser.YamlNode{
4797 Value: "mygroup",
4798 Pos: diags.PositionRanges{
4799 {Line: 3, FirstColumn: 9, LastColumn: 15},
4800 },
4801 },
4802 Rules: []parser.Rule{
4803 {
4804 Lines: diags.LineRange{First: 6, Last: 7},
4805 RecordingRule: &parser.RecordingRule{
4806 Record: parser.YamlNode{
4807 Value: "up:count",
4808 Pos: diags.PositionRanges{
4809 {Line: 6, FirstColumn: 13, LastColumn: 20},
4810 },
4811 },
4812 Expr: parser.PromQLExpr{
4813 Value: &parser.YamlNode{
4814 Value: "count(up)",
4815 Pos: diags.PositionRanges{
4816 {Line: 7, FirstColumn: 11, LastColumn: 19},
4817 },
4818 },
4819 },
4820 },
4821 },
4822 },
4823 },
4824 },
4825 },
4826 },
4827 {
4828 input: []byte(`
4829groups:
4830- name: mygroup
4831 partial_response_strategy: abort
4832 rules:
4833 - record: up:count
4834 expr: count(up)
4835`),
4836 strict: false,
4837 schema: parser.PrometheusSchema,
4838 output: parser.File{
4839 IsRelaxed: true,
4840 Groups: []parser.Group{
4841 {
4842 Name: parser.YamlNode{
4843 Value: "mygroup",
4844 Pos: diags.PositionRanges{
4845 {Line: 3, FirstColumn: 9, LastColumn: 15},
4846 },
4847 },
4848 Rules: []parser.Rule{
4849 {
4850 Lines: diags.LineRange{First: 6, Last: 7},
4851 RecordingRule: &parser.RecordingRule{
4852 Record: parser.YamlNode{
4853 Value: "up:count",
4854 Pos: diags.PositionRanges{
4855 {Line: 6, FirstColumn: 13, LastColumn: 20},
4856 },
4857 },
4858 Expr: parser.PromQLExpr{
4859 Value: &parser.YamlNode{
4860 Value: "count(up)",
4861 Pos: diags.PositionRanges{
4862 {Line: 7, FirstColumn: 11, LastColumn: 19},
4863 },
4864 },
4865 },
4866 },
4867 },
4868 },
4869 },
4870 },
4871 },
4872 },
4873 {
4874 input: []byte(`
4875groups:
4876- name: mygroup
4877 partial_response_strategy: bob
4878 rules:
4879 - record: up:count
4880 expr: count(up)
4881`),
4882 strict: true,
4883 schema: parser.ThanosSchema,
4884 output: parser.File{
4885 Groups: []parser.Group{
4886 {
4887 Name: parser.YamlNode{
4888 Value: "mygroup",
4889 Pos: diags.PositionRanges{
4890 {Line: 3, FirstColumn: 9, LastColumn: 15},
4891 },
4892 },
4893 Error: parser.ParseError{
4894 Err: errors.New("invalid partial_response_strategy value: bob"),
4895 Line: 4,
4896 },
4897 },
4898 },
4899 },
4900 },
4901 {
4902 input: []byte(`
4903groups:
4904- name: mygroup
4905 partial_response_strategy: 1
4906 rules:
4907 - record: up:count
4908 expr: count(up)
4909`),
4910 strict: true,
4911 schema: parser.ThanosSchema,
4912 output: parser.File{
4913 Groups: []parser.Group{
4914 {
4915 Name: parser.YamlNode{
4916 Value: "mygroup",
4917 Pos: diags.PositionRanges{
4918 {Line: 3, FirstColumn: 9, LastColumn: 15},
4919 },
4920 },
4921 Error: parser.ParseError{
4922 Err: errors.New("partial_response_strategy must be a string, got integer"),
4923 Line: 4,
4924 },
4925 },
4926 },
4927 },
4928 },
4929 {
4930 input: []byte(`
4931- alert: Multi Line
4932 expr: foo
4933 AND ON (instance)
4934 bar
4935`),
4936 strict: false,
4937 schema: parser.PrometheusSchema,
4938 output: parser.File{
4939 IsRelaxed: true,
4940 Groups: []parser.Group{
4941 {
4942 Rules: []parser.Rule{
4943 {
4944 Lines: diags.LineRange{First: 2, Last: 5},
4945 AlertingRule: &parser.AlertingRule{
4946 Alert: parser.YamlNode{
4947 Value: "Multi Line",
4948 Pos: diags.PositionRanges{
4949 {Line: 2, FirstColumn: 10, LastColumn: 19},
4950 },
4951 },
4952 Expr: parser.PromQLExpr{
4953 Value: &parser.YamlNode{
4954 Value: "foo AND ON (instance) bar",
4955 Pos: diags.PositionRanges{
4956 {Line: 3, FirstColumn: 9, LastColumn: 12},
4957 {Line: 4, FirstColumn: 11, LastColumn: 28},
4958 {Line: 5, FirstColumn: 11, LastColumn: 13},
4959 },
4960 },
4961 },
4962 },
4963 },
4964 },
4965 },
4966 },
4967 },
4968 },
4969 {
4970 input: []byte(`
4971 - alert: FooBar
4972 expr: >-
4973 count(
4974 foo
4975 or
4976 bar
4977 ) > 0
4978`),
4979 strict: false,
4980 schema: parser.PrometheusSchema,
4981 output: parser.File{
4982 IsRelaxed: true,
4983 Groups: []parser.Group{
4984 {
4985 Rules: []parser.Rule{
4986 {
4987 Lines: diags.LineRange{First: 2, Last: 8},
4988 AlertingRule: &parser.AlertingRule{
4989 Alert: parser.YamlNode{
4990 Value: "FooBar",
4991 Pos: diags.PositionRanges{
4992 {Line: 2, FirstColumn: 12, LastColumn: 17},
4993 },
4994 },
4995 Expr: parser.PromQLExpr{
4996 Value: &parser.YamlNode{
4997 Value: "count(\n foo\n or\n bar\n) > 0",
4998 Pos: diags.PositionRanges{
4999 {Line: 4, FirstColumn: 7, LastColumn: 13},
5000 {Line: 5, FirstColumn: 7, LastColumn: 12},
5001 {Line: 6, FirstColumn: 7, LastColumn: 11},
5002 {Line: 7, FirstColumn: 7, LastColumn: 12},
5003 {Line: 8, FirstColumn: 7, LastColumn: 11},
5004 },
5005 },
5006 },
5007 },
5008 },
5009 },
5010 },
5011 },
5012 },
5013 },
5014 {
5015 input: []byte(`
5016 - alert: FooBar
5017 expr: >-
5018 aaaaaaaaaaaaaaaaaaaaaaaa
5019 AND ON (colo_id) bbbbbbbbbbb
5020 > 2
5021 for: 1m
5022`),
5023 strict: false,
5024 schema: parser.PrometheusSchema,
5025 output: parser.File{
5026 IsRelaxed: true,
5027 Groups: []parser.Group{
5028 {
5029 Rules: []parser.Rule{
5030 {
5031 Lines: diags.LineRange{First: 2, Last: 7},
5032 AlertingRule: &parser.AlertingRule{
5033 Alert: parser.YamlNode{
5034 Value: "FooBar",
5035 Pos: diags.PositionRanges{
5036 {Line: 2, FirstColumn: 12, LastColumn: 17},
5037 },
5038 },
5039 Expr: parser.PromQLExpr{
5040 Value: &parser.YamlNode{
5041 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
5042 Pos: diags.PositionRanges{
5043 {Line: 4, FirstColumn: 7, LastColumn: 31},
5044 {Line: 5, FirstColumn: 7, LastColumn: 35},
5045 {Line: 6, FirstColumn: 7, LastColumn: 9},
5046 },
5047 },
5048 },
5049 For: &parser.YamlDuration{
5050 Value: time.Minute,
5051 Raw: "1m",
5052 Pos: diags.PositionRanges{
5053 {Line: 7, FirstColumn: 10, LastColumn: 11},
5054 },
5055 },
5056 },
5057 },
5058 },
5059 },
5060 },
5061 },
5062 },
5063 {
5064 input: []byte(`
5065 - alert: FooBar
5066 expr: 'aaaaaaaaaaaaaaaaaaaaaaaa
5067 AND ON (colo_id) bbbbbbbbbbb
5068 > 2'
5069 for: 1m
5070`),
5071 strict: false,
5072 schema: parser.PrometheusSchema,
5073 output: parser.File{
5074 IsRelaxed: true,
5075 Groups: []parser.Group{
5076 {
5077 Rules: []parser.Rule{
5078 {
5079 Lines: diags.LineRange{First: 2, Last: 6},
5080 AlertingRule: &parser.AlertingRule{
5081 Alert: parser.YamlNode{
5082 Value: "FooBar",
5083 Pos: diags.PositionRanges{
5084 {Line: 2, FirstColumn: 12, LastColumn: 17},
5085 },
5086 },
5087 Expr: parser.PromQLExpr{
5088 Value: &parser.YamlNode{
5089 Value: "aaaaaaaaaaaaaaaaaaaaaaaa AND ON (colo_id) bbbbbbbbbbb > 2",
5090 Pos: diags.PositionRanges{
5091 {Line: 3, FirstColumn: 12, LastColumn: 36},
5092 {Line: 4, FirstColumn: 11, LastColumn: 39},
5093 {Line: 5, FirstColumn: 11, LastColumn: 13},
5094 },
5095 },
5096 },
5097 For: &parser.YamlDuration{
5098 Value: time.Minute,
5099 Raw: "1m",
5100 Pos: diags.PositionRanges{
5101 {Line: 6, FirstColumn: 10, LastColumn: 11},
5102 },
5103 },
5104 },
5105 },
5106 },
5107 },
5108 },
5109 },
5110 },
5111 {
5112 input: []byte(`
5113groups:
5114- name: foo
5115 rules:
5116 - record: colo:foo:sum
5117 expr: sum without (instance) ( rate(my_metric[2m]) * on (instance)
5118 group_left (hardware_generation, hms_scope, sliver) (instance:metadata{})
5119 )
5120`),
5121 strict: false,
5122 schema: parser.PrometheusSchema,
5123 output: parser.File{
5124 IsRelaxed: true,
5125 Groups: []parser.Group{
5126 {
5127 Name: parser.YamlNode{
5128 Value: "foo",
5129 Pos: diags.PositionRanges{
5130 {Line: 3, FirstColumn: 9, LastColumn: 11},
5131 },
5132 },
5133 Rules: []parser.Rule{
5134 {
5135 Lines: diags.LineRange{First: 5, Last: 8},
5136 RecordingRule: &parser.RecordingRule{
5137 Record: parser.YamlNode{
5138 Value: "colo:foo:sum",
5139 Pos: diags.PositionRanges{
5140 {Line: 5, FirstColumn: 13, LastColumn: 24},
5141 },
5142 },
5143 Expr: parser.PromQLExpr{
5144 Value: &parser.YamlNode{
5145 Value: "sum without (instance) ( rate(my_metric[2m]) * on (instance) group_left (hardware_generation, hms_scope, sliver) (instance:metadata{}) )",
5146 Pos: diags.PositionRanges{
5147 {Line: 6, FirstColumn: 11, LastColumn: 71},
5148 {Line: 7, FirstColumn: 7, LastColumn: 80},
5149 {Line: 8, FirstColumn: 7, LastColumn: 7},
5150 },
5151 },
5152 },
5153 },
5154 },
5155 },
5156 },
5157 },
5158 },
5159 },
5160 {
5161 input: []byte(`
5162groups:
5163- name: "{{ source }}"
5164 rules:
5165 # AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
5166 # BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
5167 - record: some_long_name
5168 labels:
5169 metricssource: receiver
5170 expr:
5171 clamp_max(
5172 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) /
5173 sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)),
5174 2
5175 )
5176`),
5177 strict: true,
5178 schema: parser.PrometheusSchema,
5179 output: parser.File{
5180 Groups: []parser.Group{
5181 {
5182 Name: parser.YamlNode{
5183 Value: "{{ source }}",
5184 Pos: diags.PositionRanges{
5185 {Line: 3, FirstColumn: 10, LastColumn: 21},
5186 },
5187 },
5188 Rules: []parser.Rule{
5189 {
5190 Lines: diags.LineRange{First: 7, Last: 15},
5191 RecordingRule: &parser.RecordingRule{
5192 Record: parser.YamlNode{
5193 Value: "some_long_name",
5194 Pos: diags.PositionRanges{
5195 {Line: 7, FirstColumn: 13, LastColumn: 26},
5196 },
5197 },
5198 Labels: &parser.YamlMap{
5199 Key: &parser.YamlNode{
5200 Value: "labels",
5201 Pos: diags.PositionRanges{
5202 {Line: 8, FirstColumn: 5, LastColumn: 10},
5203 },
5204 },
5205 Items: []*parser.YamlKeyValue{
5206 {
5207 Key: &parser.YamlNode{
5208 Value: "metricssource",
5209 Pos: diags.PositionRanges{
5210 {Line: 9, FirstColumn: 7, LastColumn: 19},
5211 },
5212 },
5213 Value: &parser.YamlNode{
5214 Value: "receiver",
5215 Pos: diags.PositionRanges{
5216 {Line: 9, FirstColumn: 22, LastColumn: 29},
5217 },
5218 },
5219 },
5220 },
5221 },
5222 Expr: parser.PromQLExpr{
5223 Value: &parser.YamlNode{
5224 Value: `clamp_max( sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m])) / sum(rate(my_metric_long_name_total{output="control:output:kafka:/:requests:http-b:http_requests_control_sample"}[5m] offset 30m)), 2 )`,
5225 Pos: diags.PositionRanges{
5226 {Line: 11, FirstColumn: 7, LastColumn: 17},
5227 {Line: 12, FirstColumn: 9, LastColumn: 129},
5228 {Line: 13, FirstColumn: 9, LastColumn: 139},
5229 {Line: 14, FirstColumn: 9, LastColumn: 10},
5230 {Line: 15, FirstColumn: 7, LastColumn: 7},
5231 },
5232 },
5233 },
5234 },
5235 },
5236 },
5237 },
5238 },
5239 },
5240 },
5241 {
5242 input: []byte(`
5243groups:
5244- name: "{{ source }}"
5245 rules:
5246
5247 - alert: Director_Is_Not_Advertising_Any_Routes
5248 expr: |
5249 sum without (name) (
5250 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
5251 * on (instance) group_left (profile,cluster)
5252 cf_node_role{kubernetes_role="director",role="kubernetes"}
5253 ) <= 0
5254 for: 1m
5255`),
5256 strict: true,
5257 schema: parser.PrometheusSchema,
5258 output: parser.File{
5259 Groups: []parser.Group{
5260 {
5261 Name: parser.YamlNode{
5262 Value: "{{ source }}",
5263 Pos: diags.PositionRanges{
5264 {Line: 3, FirstColumn: 10, LastColumn: 21},
5265 },
5266 },
5267 Rules: []parser.Rule{
5268 {
5269 Lines: diags.LineRange{First: 6, Last: 13},
5270 AlertingRule: &parser.AlertingRule{
5271 Alert: parser.YamlNode{
5272 Value: "Director_Is_Not_Advertising_Any_Routes",
5273 Pos: diags.PositionRanges{
5274 {Line: 6, FirstColumn: 12, LastColumn: 49},
5275 },
5276 },
5277 Expr: parser.PromQLExpr{
5278 Value: &parser.YamlNode{
5279 Value: `sum without (name) (
5280 bird_protocol_prefix_export_count{ip_version="4",name=~".*external.*",proto!="Kernel"}
5281 * on (instance) group_left (profile,cluster)
5282 cf_node_role{kubernetes_role="director",role="kubernetes"}
5283) <= 0
5284`,
5285 Pos: diags.PositionRanges{
5286 {Line: 8, FirstColumn: 9, LastColumn: 29},
5287 {Line: 9, FirstColumn: 9, LastColumn: 99},
5288 {Line: 10, FirstColumn: 9, LastColumn: 55},
5289 {Line: 11, FirstColumn: 9, LastColumn: 71},
5290 {Line: 12, FirstColumn: 9, LastColumn: 14},
5291 },
5292 },
5293 },
5294 For: &parser.YamlDuration{
5295 Value: time.Minute,
5296 Raw: "1m",
5297 Pos: diags.PositionRanges{
5298 {Line: 13, FirstColumn: 10, LastColumn: 11},
5299 },
5300 },
5301 },
5302 },
5303 },
5304 },
5305 },
5306 },
5307 },
5308 {
5309 input: []byte(`
5310groups:
5311- name: xxx
5312 interval: 3m
5313 rules: []
5314`),
5315 strict: true,
5316 output: parser.File{
5317 Groups: []parser.Group{
5318 {
5319 Name: parser.YamlNode{
5320 Value: "xxx",
5321 Pos: diags.PositionRanges{
5322 {Line: 3, FirstColumn: 9, LastColumn: 11},
5323 },
5324 },
5325 Interval: &parser.YamlDuration{
5326 Value: time.Minute * 3,
5327 Raw: "3m",
5328 Pos: diags.PositionRanges{
5329 {Line: 4, FirstColumn: 13, LastColumn: 14},
5330 },
5331 },
5332 },
5333 },
5334 },
5335 },
5336 {
5337 input: []byte(`
5338groups:
5339- name: xxx
5340 interval: 3m
5341 rules: []
5342`),
5343 output: parser.File{
5344 IsRelaxed: true,
5345 Groups: []parser.Group{
5346 {
5347 Name: parser.YamlNode{
5348 Value: "xxx",
5349 Pos: diags.PositionRanges{
5350 {Line: 3, FirstColumn: 9, LastColumn: 11},
5351 },
5352 },
5353 Interval: &parser.YamlDuration{
5354 Value: time.Minute * 3,
5355 Raw: "3m",
5356 Pos: diags.PositionRanges{
5357 {Line: 4, FirstColumn: 13, LastColumn: 14},
5358 },
5359 },
5360 },
5361 },
5362 },
5363 },
5364 {
5365 input: []byte(`
5366groups:
5367- name: xxx
5368 interval: 3m
5369 rules: []
5370---
5371groups:
5372- name: yyy
5373 interval: 2m
5374 rules: []
5375`),
5376 output: parser.File{
5377 IsRelaxed: true,
5378 Groups: []parser.Group{
5379 {
5380 Name: parser.YamlNode{
5381 Value: "xxx",
5382 Pos: diags.PositionRanges{
5383 {Line: 3, FirstColumn: 9, LastColumn: 11},
5384 },
5385 },
5386 Interval: &parser.YamlDuration{
5387 Value: time.Minute * 3,
5388 Raw: "3m",
5389 Pos: diags.PositionRanges{
5390 {Line: 4, FirstColumn: 13, LastColumn: 14},
5391 },
5392 },
5393 },
5394 {
5395 Name: parser.YamlNode{
5396 Value: "yyy",
5397 Pos: diags.PositionRanges{
5398 {Line: 8, FirstColumn: 9, LastColumn: 11},
5399 },
5400 },
5401 Interval: &parser.YamlDuration{
5402 Value: time.Minute * 2,
5403 Raw: "2m",
5404 Pos: diags.PositionRanges{
5405 {Line: 9, FirstColumn: 13, LastColumn: 14},
5406 },
5407 },
5408 },
5409 },
5410 },
5411 },
5412 {
5413 input: []byte(`
5414groups:
5415- name: xxx
5416 interval: 3m
5417 labels:
5418 foo: bar
5419 rules: []
5420`),
5421 strict: true,
5422 output: parser.File{
5423 Groups: []parser.Group{
5424 {
5425 Name: parser.YamlNode{
5426 Value: "xxx",
5427 Pos: diags.PositionRanges{
5428 {Line: 3, FirstColumn: 9, LastColumn: 11},
5429 },
5430 },
5431 Interval: &parser.YamlDuration{
5432 Value: time.Minute * 3,
5433 Raw: "3m",
5434 Pos: diags.PositionRanges{
5435 {Line: 4, FirstColumn: 13, LastColumn: 14},
5436 },
5437 },
5438 Labels: &parser.YamlMap{
5439 Key: &parser.YamlNode{
5440 Value: "labels",
5441 Pos: diags.PositionRanges{
5442 {Line: 5, FirstColumn: 3, LastColumn: 8},
5443 },
5444 },
5445 Items: []*parser.YamlKeyValue{
5446 {
5447 Key: &parser.YamlNode{
5448 Value: "foo",
5449 Pos: diags.PositionRanges{
5450 {Line: 6, FirstColumn: 5, LastColumn: 7},
5451 },
5452 },
5453 Value: &parser.YamlNode{
5454 Value: "bar",
5455 Pos: diags.PositionRanges{
5456 {Line: 6, FirstColumn: 10, LastColumn: 12},
5457 },
5458 },
5459 },
5460 },
5461 },
5462 },
5463 },
5464 },
5465 },
5466 {
5467 input: []byte(`
5468groups:
5469- name: xxx
5470 labels:
5471 - foo: bar
5472 rules: []
5473`),
5474 strict: true,
5475 output: parser.File{
5476 Groups: []parser.Group{
5477 {
5478 Name: parser.YamlNode{
5479 Value: "xxx",
5480 Pos: diags.PositionRanges{
5481 {Line: 3, FirstColumn: 9, LastColumn: 11},
5482 },
5483 },
5484 Error: parser.ParseError{
5485 Err: errors.New("group labels must be a mapping, got list"),
5486 Line: 4,
5487 },
5488 },
5489 },
5490 },
5491 },
5492 {
5493 input: []byte(`
5494groups:
5495- name: xxx
5496 labels:
5497 foo: 1
5498 rules: []
5499`),
5500 strict: true,
5501 output: parser.File{
5502 Groups: []parser.Group{
5503 {
5504 Name: parser.YamlNode{
5505 Value: "xxx",
5506 Pos: diags.PositionRanges{
5507 {Line: 3, FirstColumn: 9, LastColumn: 11},
5508 },
5509 },
5510 Error: parser.ParseError{
5511 Err: errors.New("labels foo value must be a string, got integer instead"),
5512 Line: 5,
5513 },
5514 },
5515 },
5516 },
5517 },
5518 {
5519 input: []byte(`
5520groups:
5521- name: xxx
5522 labels:
5523 foo: bar
5524 bob: foo
5525 foo: bob
5526 rules: []
5527`),
5528 strict: true,
5529 output: parser.File{
5530 Groups: []parser.Group{
5531 {
5532 Name: parser.YamlNode{
5533 Value: "xxx",
5534 Pos: diags.PositionRanges{
5535 {Line: 3, FirstColumn: 9, LastColumn: 11},
5536 },
5537 },
5538 Error: parser.ParseError{
5539 Err: errors.New("duplicated labels key foo"),
5540 Line: 7,
5541 },
5542 },
5543 },
5544 },
5545 },
5546 {
5547 input: []byte(`
5548groups:
5549- name: xxx
5550 interval: 3m
5551 query_offset: 1s
5552 limit: 5
5553 labels:
5554 foo: bar
5555 rules: []
5556`),
5557 output: parser.File{
5558 IsRelaxed: true,
5559 Groups: []parser.Group{
5560 {
5561 Name: parser.YamlNode{
5562 Value: "xxx",
5563 Pos: diags.PositionRanges{
5564 {Line: 3, FirstColumn: 9, LastColumn: 11},
5565 },
5566 },
5567 Interval: &parser.YamlDuration{
5568 Value: time.Minute * 3,
5569 Raw: "3m",
5570 Pos: diags.PositionRanges{
5571 {Line: 4, FirstColumn: 13, LastColumn: 14},
5572 },
5573 },
5574 QueryOffset: &parser.YamlDuration{
5575 Value: time.Second,
5576 Raw: "1s",
5577 Pos: diags.PositionRanges{
5578 {Line: 5, FirstColumn: 17, LastColumn: 18},
5579 },
5580 },
5581 Limit: &parser.YamlInt{
5582 Value: 5,
5583 Raw: "5",
5584 Pos: diags.PositionRanges{
5585 {Line: 6, FirstColumn: 10, LastColumn: 10},
5586 },
5587 },
5588 Labels: &parser.YamlMap{
5589 Key: &parser.YamlNode{
5590 Value: "labels",
5591 Pos: diags.PositionRanges{
5592 {Line: 7, FirstColumn: 3, LastColumn: 8},
5593 },
5594 },
5595 Items: []*parser.YamlKeyValue{
5596 {
5597 Key: &parser.YamlNode{
5598 Value: "foo",
5599 Pos: diags.PositionRanges{
5600 {Line: 8, FirstColumn: 5, LastColumn: 7},
5601 },
5602 },
5603 Value: &parser.YamlNode{
5604 Value: "bar",
5605 Pos: diags.PositionRanges{
5606 {Line: 8, FirstColumn: 10, LastColumn: 12},
5607 },
5608 },
5609 },
5610 },
5611 },
5612 },
5613 },
5614 },
5615 },
5616 {
5617 input: []byte(`
5618groups:
5619- name: xxx
5620 labels:
5621 - foo: bar
5622 rules: []
5623`),
5624 output: parser.File{
5625 IsRelaxed: true,
5626 Groups: []parser.Group{
5627 {
5628 Name: parser.YamlNode{
5629 Value: "xxx",
5630 Pos: diags.PositionRanges{
5631 {Line: 3, FirstColumn: 9, LastColumn: 11},
5632 },
5633 },
5634 Labels: &parser.YamlMap{
5635 Key: &parser.YamlNode{
5636 Value: "labels",
5637 Pos: diags.PositionRanges{
5638 {Line: 4, FirstColumn: 3, LastColumn: 8},
5639 },
5640 },
5641 },
5642 },
5643 },
5644 },
5645 },
5646 {
5647 input: []byte(`
5648groups:
5649- name: xxx
5650 labels:
5651 foo: 1
5652 rules: []
5653`),
5654 output: parser.File{
5655 IsRelaxed: true,
5656 Groups: []parser.Group{
5657 {
5658 Name: parser.YamlNode{
5659 Value: "xxx",
5660 Pos: diags.PositionRanges{
5661 {Line: 3, FirstColumn: 9, LastColumn: 11},
5662 },
5663 },
5664 Labels: &parser.YamlMap{
5665 Key: &parser.YamlNode{
5666 Value: "labels",
5667 Pos: diags.PositionRanges{
5668 {Line: 4, FirstColumn: 3, LastColumn: 8},
5669 },
5670 },
5671 Items: []*parser.YamlKeyValue{
5672 {
5673 Key: &parser.YamlNode{
5674 Value: "foo",
5675 Pos: diags.PositionRanges{
5676 {Line: 5, FirstColumn: 5, LastColumn: 7},
5677 },
5678 },
5679 Value: &parser.YamlNode{
5680 Value: "1",
5681 Pos: diags.PositionRanges{
5682 {Line: 5, FirstColumn: 10, LastColumn: 10},
5683 },
5684 },
5685 },
5686 },
5687 },
5688 },
5689 },
5690 },
5691 },
5692 {
5693 input: []byte(`
5694groups:
5695- name: xxx
5696 labels:
5697 foo: bar
5698 bob: foo
5699 foo: bob
5700 rules: []
5701`),
5702 output: parser.File{
5703 IsRelaxed: true,
5704 Groups: []parser.Group{
5705 {
5706 Name: parser.YamlNode{
5707 Value: "xxx",
5708 Pos: diags.PositionRanges{
5709 {Line: 3, FirstColumn: 9, LastColumn: 11},
5710 },
5711 },
5712 Labels: &parser.YamlMap{
5713 Key: &parser.YamlNode{
5714 Value: "labels",
5715 Pos: diags.PositionRanges{
5716 {Line: 4, FirstColumn: 3, LastColumn: 8},
5717 },
5718 },
5719 Items: []*parser.YamlKeyValue{
5720 {
5721 Key: &parser.YamlNode{
5722 Value: "foo",
5723 Pos: diags.PositionRanges{
5724 {Line: 5, FirstColumn: 5, LastColumn: 7},
5725 },
5726 },
5727 Value: &parser.YamlNode{
5728 Value: "bar",
5729 Pos: diags.PositionRanges{
5730 {Line: 5, FirstColumn: 10, LastColumn: 12},
5731 },
5732 },
5733 },
5734 {
5735 Key: &parser.YamlNode{
5736 Value: "bob",
5737 Pos: diags.PositionRanges{
5738 {Line: 6, FirstColumn: 5, LastColumn: 7},
5739 },
5740 },
5741 Value: &parser.YamlNode{
5742 Value: "foo",
5743 Pos: diags.PositionRanges{
5744 {Line: 6, FirstColumn: 10, LastColumn: 12},
5745 },
5746 },
5747 },
5748 {
5749 Key: &parser.YamlNode{
5750 Value: "foo",
5751 Pos: diags.PositionRanges{
5752 {Line: 7, FirstColumn: 5, LastColumn: 7},
5753 },
5754 },
5755 Value: &parser.YamlNode{
5756 Value: "bob",
5757 Pos: diags.PositionRanges{
5758 {Line: 7, FirstColumn: 10, LastColumn: 12},
5759 },
5760 },
5761 },
5762 },
5763 },
5764 },
5765 },
5766 },
5767 },
5768 {
5769 input: []byte(`
5770- name: xxx
5771 interval: 3m
5772 query_offset: 1s
5773 limit: 5
5774 labels:
5775 foo: bar
5776 rules:
5777 - record: up:count
5778 expr: count(up)
5779`),
5780 output: parser.File{
5781 IsRelaxed: true,
5782 Groups: []parser.Group{
5783 {
5784 Name: parser.YamlNode{
5785 Value: "xxx",
5786 Pos: diags.PositionRanges{
5787 {Line: 2, FirstColumn: 9, LastColumn: 11},
5788 },
5789 },
5790 Interval: &parser.YamlDuration{
5791 Value: time.Minute * 3,
5792 Raw: "3m",
5793 Pos: diags.PositionRanges{
5794 {Line: 3, FirstColumn: 13, LastColumn: 14},
5795 },
5796 },
5797 QueryOffset: &parser.YamlDuration{
5798 Value: time.Second,
5799 Raw: "1s",
5800 Pos: diags.PositionRanges{
5801 {Line: 4, FirstColumn: 17, LastColumn: 18},
5802 },
5803 },
5804 Limit: &parser.YamlInt{
5805 Value: 5,
5806 Raw: "5",
5807 Pos: diags.PositionRanges{
5808 {Line: 5, FirstColumn: 10, LastColumn: 10},
5809 },
5810 },
5811 Labels: &parser.YamlMap{
5812 Key: &parser.YamlNode{
5813 Value: "labels",
5814 Pos: diags.PositionRanges{
5815 {Line: 6, FirstColumn: 3, LastColumn: 8},
5816 },
5817 },
5818 Items: []*parser.YamlKeyValue{
5819 {
5820 Key: &parser.YamlNode{
5821 Value: "foo",
5822 Pos: diags.PositionRanges{
5823 {Line: 7, FirstColumn: 5, LastColumn: 7},
5824 },
5825 },
5826 Value: &parser.YamlNode{
5827 Value: "bar",
5828 Pos: diags.PositionRanges{
5829 {Line: 7, FirstColumn: 10, LastColumn: 12},
5830 },
5831 },
5832 },
5833 },
5834 },
5835 Rules: []parser.Rule{
5836 {
5837 Lines: diags.LineRange{First: 9, Last: 10},
5838 RecordingRule: &parser.RecordingRule{
5839 Record: parser.YamlNode{
5840 Value: "up:count",
5841 Pos: diags.PositionRanges{
5842 {Line: 9, FirstColumn: 13, LastColumn: 20},
5843 },
5844 },
5845 Expr: parser.PromQLExpr{
5846 Value: &parser.YamlNode{
5847 Value: "count(up)",
5848 Pos: diags.PositionRanges{
5849 {Line: 10, FirstColumn: 11, LastColumn: 19},
5850 },
5851 },
5852 },
5853 },
5854 },
5855 },
5856 },
5857 },
5858 },
5859 },
5860 {
5861 input: []byte(`
5862
5863- interval: 3m
5864 query_offset: 1s
5865 limit: 5
5866 labels:
5867 foo: bar
5868 rules:
5869 - record: up:count
5870 expr: count(up)
5871`),
5872 output: parser.File{
5873 IsRelaxed: true,
5874 Groups: []parser.Group{
5875 {
5876 Name: parser.YamlNode{Value: ""},
5877 Rules: []parser.Rule{
5878 {
5879 Lines: diags.LineRange{First: 9, Last: 10},
5880 RecordingRule: &parser.RecordingRule{
5881 Record: parser.YamlNode{
5882 Value: "up:count",
5883 Pos: diags.PositionRanges{
5884 {Line: 9, FirstColumn: 13, LastColumn: 20},
5885 },
5886 },
5887 Expr: parser.PromQLExpr{
5888 Value: &parser.YamlNode{
5889 Value: "count(up)",
5890 Pos: diags.PositionRanges{
5891 {Line: 10, FirstColumn: 11, LastColumn: 19},
5892 },
5893 },
5894 },
5895 },
5896 },
5897 },
5898 },
5899 },
5900 },
5901 },
5902 {
5903 input: []byte(`
5904apiVersion: v1
5905kind: ConfigMap
5906metadata:
5907 labels:
5908 shard: "0"
5909 total-shards: "1"
5910 name: shard-jobs
5911 namespace: foo
5912data:
5913 jobs.yaml: |
5914 jobs:
5915 - name: foo
5916 interval: 1m
5917 queries:
5918 - name: xxx
5919 help: ''
5920 labels:
5921 - account_id
5922 - bucket
5923 - cache_status
5924 mtls_identity:
5925 cert_path: /etc/identity/tls.crt
5926 key_path: /etc/identity/tls.key
5927`),
5928 output: parser.File{
5929 Groups: nil,
5930 IsRelaxed: true,
5931 },
5932 },
5933 {
5934 // Test rule with line comment on the mapping node itself
5935 input: []byte(`- record: foo # pint disable line comment
5936 expr: bar
5937`),
5938 output: parser.File{
5939 IsRelaxed: true,
5940 Groups: []parser.Group{
5941 {
5942 Rules: []parser.Rule{
5943 {
5944 Lines: diags.LineRange{First: 1, Last: 2},
5945 Comments: []comments.Comment{
5946 {
5947 Type: comments.DisableType,
5948 Value: comments.Disable{Match: "line comment"},
5949 },
5950 },
5951 RecordingRule: &parser.RecordingRule{
5952 Record: parser.YamlNode{
5953 Value: "foo",
5954 Pos: diags.PositionRanges{
5955 {Line: 1, FirstColumn: 11, LastColumn: 13},
5956 },
5957 },
5958 Expr: parser.PromQLExpr{
5959 Value: &parser.YamlNode{
5960 Value: "bar",
5961 Pos: diags.PositionRanges{
5962 {Line: 2, FirstColumn: 9, LastColumn: 11},
5963 },
5964 },
5965 },
5966 },
5967 },
5968 },
5969 },
5970 },
5971 },
5972 },
5973 {
5974 // Test flow mapping with line comment (comment attached to parent node)
5975 input: []byte(`- {record: foo, expr: bar} # pint disable flow comment
5976`),
5977 output: parser.File{
5978 IsRelaxed: true,
5979 Groups: []parser.Group{
5980 {
5981 Rules: []parser.Rule{
5982 {
5983 Lines: diags.LineRange{First: 1, Last: 1},
5984 Comments: []comments.Comment{
5985 {
5986 Type: comments.DisableType,
5987 Value: comments.Disable{Match: "flow comment"},
5988 },
5989 },
5990 RecordingRule: &parser.RecordingRule{
5991 Record: parser.YamlNode{
5992 Value: "foo",
5993 Pos: diags.PositionRanges{
5994 {Line: 1, FirstColumn: 12, LastColumn: 14},
5995 },
5996 },
5997 Expr: parser.PromQLExpr{
5998 Value: &parser.YamlNode{
5999 Value: "bar",
6000 Pos: diags.PositionRanges{
6001 {Line: 1, FirstColumn: 23, LastColumn: 25},
6002 },
6003 },
6004 },
6005 },
6006 },
6007 },
6008 },
6009 },
6010 },
6011 },
6012 {
6013 // Test flow mapping with foot comment (comment attached to parent node's FootComment)
6014 input: []byte(`groups:
6015- name: test
6016 rules:
6017 - {record: foo, expr: bar}
6018 # pint disable foot comment
6019`),
6020 output: parser.File{
6021 IsRelaxed: true,
6022 Groups: []parser.Group{
6023 {
6024 Name: parser.YamlNode{
6025 Value: "test",
6026 Pos: diags.PositionRanges{
6027 {Line: 2, FirstColumn: 9, LastColumn: 12},
6028 },
6029 },
6030 Rules: []parser.Rule{
6031 {
6032 Lines: diags.LineRange{First: 4, Last: 4},
6033 Comments: []comments.Comment{
6034 {
6035 Type: comments.DisableType,
6036 Value: comments.Disable{Match: "foot comment"},
6037 },
6038 },
6039 RecordingRule: &parser.RecordingRule{
6040 Record: parser.YamlNode{
6041 Value: "foo",
6042 Pos: diags.PositionRanges{
6043 {Line: 4, FirstColumn: 14, LastColumn: 16},
6044 },
6045 },
6046 Expr: parser.PromQLExpr{
6047 Value: &parser.YamlNode{
6048 Value: "bar",
6049 Pos: diags.PositionRanges{
6050 {Line: 4, FirstColumn: 25, LastColumn: 27},
6051 },
6052 },
6053 },
6054 },
6055 },
6056 },
6057 },
6058 },
6059 },
6060 },
6061 }
6062
6063 alwaysEqual := cmp.Comparer(func(_, _ any) bool { return true })
6064 ignorePrometheusExpr := cmp.FilterValues(func(x, y any) bool {
6065 _, xe := x.(*parser.PromQLNode)
6066 _, ye := y.(*parser.PromQLNode)
6067 return xe || ye
6068 }, alwaysEqual)
6069
6070 cmpErrorText := cmp.Comparer(func(x, y any) bool {
6071 xe := x.(error)
6072 ye := y.(error)
6073 return xe.Error() == ye.Error()
6074 })
6075 sameErrorText := cmp.FilterValues(func(x, y any) bool {
6076 _, xe := x.(error)
6077 _, ye := y.(error)
6078 return xe && ye
6079 }, cmpErrorText)
6080
6081 for i, tc := range testCases {
6082 t.Run(strconv.Itoa(i+1), func(t *testing.T) {
6083 t.Logf("\n--- Content ---%s--- END ---", tc.input)
6084
6085 s := bufio.NewScanner(bytes.NewReader(tc.input))
6086 for s.Scan() {
6087 tc.output.TotalLines++
6088 }
6089
6090 p := parser.NewParser(parser.Options{IsStrict: tc.strict, Schema: tc.schema, Names: tc.names})
6091 file := p.Parse(bytes.NewReader(tc.input))
6092
6093 if diff := cmp.Diff(tc.output, file,
6094 ignorePrometheusExpr,
6095 sameErrorText,
6096 cmpopts.IgnoreUnexported(parser.PromQLExpr{}),
6097 ); diff != "" {
6098 t.Errorf("Parse() returned wrong output (-want +got):\n%s", diff)
6099 return
6100 }
6101 })
6102 }
6103}
6104
6105func TestPromQLExprSyntaxError(t *testing.T) {
6106 type testCaseT struct {
6107 name string
6108 expectedError string
6109 input []byte
6110 schema parser.Schema
6111 names model.ValidationScheme
6112 }
6113
6114 testCases := []testCaseT{
6115 {
6116 name: "invalid label matching operator",
6117 input: []byte(`
6118groups:
6119- name: foo
6120 rules:
6121 - record: foo
6122 expr: |
6123 {'up' == 1}
6124`),
6125 expectedError: `1:8: parse error: unexpected "=" in label matching, expected string`,
6126 schema: parser.PrometheusSchema,
6127 names: model.LegacyValidation,
6128 },
6129 {
6130 name: "unclosed parenthesis",
6131 input: []byte(`
6132groups:
6133- name: foo
6134 rules:
6135 - record: foo
6136 expr: sum(up
6137`),
6138 expectedError: `1:7: parse error: unclosed left parenthesis`,
6139 schema: parser.PrometheusSchema,
6140 names: model.LegacyValidation,
6141 },
6142 {
6143 name: "unclosed brace",
6144 input: []byte(`
6145groups:
6146- name: foo
6147 rules:
6148 - record: foo
6149 expr: up{job="test"
6150`),
6151 expectedError: `1:14: parse error: unexpected end of input inside braces`,
6152 schema: parser.PrometheusSchema,
6153 names: model.LegacyValidation,
6154 },
6155 {
6156 name: "invalid aggregation without grouping",
6157 input: []byte(`
6158groups:
6159- name: foo
6160 rules:
6161 - record: foo
6162 expr: sum without (up)
6163`),
6164 expectedError: `1:17: parse error: unexpected end of input in aggregation`,
6165 schema: parser.PrometheusSchema,
6166 names: model.LegacyValidation,
6167 },
6168 {
6169 name: "bad regex syntax",
6170 input: []byte(`
6171groups:
6172- name: foo
6173 rules:
6174 - record: foo
6175 expr: up{job=~"["}
6176`),
6177 expectedError: `1:4: parse error: error parsing regexp: missing closing ]: ` + "`[`",
6178 schema: parser.PrometheusSchema,
6179 names: model.LegacyValidation,
6180 },
6181 {
6182 name: "invalid binary operator position",
6183 input: []byte(`
6184groups:
6185- name: foo
6186 rules:
6187 - record: foo
6188 expr: up +
6189`),
6190 expectedError: `1:5: parse error: unexpected end of input`,
6191 schema: parser.PrometheusSchema,
6192 names: model.LegacyValidation,
6193 },
6194 {
6195 name: "multiple binary operators",
6196 input: []byte(`
6197groups:
6198- name: foo
6199 rules:
6200 - record: foo
6201 expr: up + * down
6202`),
6203 expectedError: `1:6: parse error: unexpected <op:*>`,
6204 schema: parser.PrometheusSchema,
6205 names: model.LegacyValidation,
6206 },
6207 {
6208 name: "invalid duration syntax",
6209 input: []byte(`
6210groups:
6211- name: foo
6212 rules:
6213 - record: foo
6214 expr: rate(up[5x])
6215`),
6216 expectedError: `1:9: parse error: bad number or duration syntax: "5"`,
6217 schema: parser.PrometheusSchema,
6218 names: model.LegacyValidation,
6219 },
6220 {
6221 name: "missing range selector",
6222 input: []byte(`
6223groups:
6224- name: foo
6225 rules:
6226 - record: foo
6227 expr: rate(up)
6228`),
6229 expectedError: `1:6: parse error: expected type range vector in call to function "rate", got instant vector`,
6230 schema: parser.PrometheusSchema,
6231 names: model.LegacyValidation,
6232 },
6233 {
6234 name: "invalid label name in matcher",
6235 input: []byte(`
6236groups:
6237- name: foo
6238 rules:
6239 - record: foo
6240 expr: up{123="test"}
6241`),
6242 expectedError: `1:4: parse error: unexpected character inside braces: '1'`,
6243 schema: parser.PrometheusSchema,
6244 names: model.LegacyValidation,
6245 },
6246 {
6247 name: "unclosed parenthesis with Thanos schema",
6248 input: []byte(`
6249groups:
6250- name: foo
6251 rules:
6252 - record: foo
6253 expr: sum(up
6254`),
6255 expectedError: `1:7: parse error: unclosed left parenthesis`,
6256 schema: parser.ThanosSchema,
6257 names: model.LegacyValidation,
6258 },
6259 {
6260 name: "invalid binary operator with UTF8 validation",
6261 input: []byte(`
6262groups:
6263- name: foo
6264 rules:
6265 - record: foo
6266 expr: up +
6267`),
6268 expectedError: `1:5: parse error: unexpected end of input`,
6269 schema: parser.PrometheusSchema,
6270 names: model.UTF8Validation,
6271 },
6272 {
6273 name: "bad regex with Thanos schema and UTF8 validation",
6274 input: []byte(`
6275groups:
6276- name: foo
6277 rules:
6278 - record: foo
6279 expr: up{job=~"["}
6280`),
6281 expectedError: `1:4: parse error: error parsing regexp: missing closing ]: ` + "`[`",
6282 schema: parser.ThanosSchema,
6283 names: model.UTF8Validation,
6284 },
6285 }
6286
6287 for _, tc := range testCases {
6288 t.Run(tc.name, func(t *testing.T) {
6289 p := parser.NewParser(parser.Options{Schema: tc.schema, Names: tc.names})
6290 r := bytes.NewReader(tc.input)
6291 file := p.Parse(r)
6292
6293 require.NoError(t, file.Error.Err)
6294 require.NotEmpty(t, file.Groups)
6295 for _, group := range file.Groups {
6296 for _, rule := range group.Rules {
6297 require.NoError(t, rule.Error.Err)
6298 expr := rule.Expr()
6299 err := expr.SyntaxError()
6300 require.Error(t, err)
6301 require.Equal(t, tc.expectedError, err.Error())
6302 }
6303 }
6304 })
6305 }
6306}
6307
6308func BenchmarkParse(b *testing.B) {
6309 data, err := os.ReadFile("testrules.yml")
6310 require.NoError(b, err)
6311
6312 p := parser.NewParser(parser.Options{IsStrict: true, Names: model.LegacyValidation})
6313 for b.Loop() {
6314 b.StopTimer()
6315 r := bytes.NewReader(data)
6316 b.StartTimer()
6317
6318 f := p.Parse(r)
6319
6320 b.StopTimer()
6321 require.Len(b, f.Groups, 90)
6322 require.NoError(b, f.Error.Err)
6323 require.Equal(b, 5501, f.TotalLines)
6324 b.StartTimer()
6325 }
6326}
6327