cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.62.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/strict.go

210lines · modecode

1package parser
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7
8 "github.com/prometheus/common/model"
9 "gopkg.in/yaml.v3"
10)
11
12// https://github.com/go-yaml/yaml/blob/v3.0.1/resolve.go#L70-L81
13const (
14 nullTag = "!!null"
15 boolTag = "!!bool"
16 strTag = "!!str"
17 intTag = "!!int"
18 floatTag = "!!float"
19 timestampTag = "!!timestamp"
20 seqTag = "!!seq"
21 mapTag = "!!map"
22 binaryTag = "!!binary"
23 mergeTag = "!!merge"
24)
25
26func describeTag(tag string) string {
27 switch tag {
28 case strTag:
29 return "string"
30 case intTag:
31 return "integer"
32 case seqTag:
33 return "list"
34 case mapTag:
35 return "mapping"
36 case binaryTag:
37 return "binary data"
38 default:
39 return strings.TrimLeft(tag, "!")
40 }
41}
42
43func parseGroups(content []byte, doc *yaml.Node) (rules []Rule, err ParseError) {
44 names := map[string]struct{}{}
45
46 for _, node := range unpackNodes(doc) {
47 if !isTag(node.ShortTag(), mapTag) {
48 return nil, ParseError{
49 Line: node.Line,
50 Err: fmt.Errorf("top level field must be a groups key, got %s", describeTag(node.ShortTag())),
51 }
52 }
53
54 for _, entry := range mappingNodes(node) {
55 if entry.key.ShortTag() != strTag {
56 return nil, ParseError{
57 Line: entry.key.Line,
58 Err: fmt.Errorf("groups key must be a %s, got a %s", describeTag(strTag), describeTag(entry.key.ShortTag())),
59 }
60 }
61 if entry.key.Value != "groups" {
62 return nil, ParseError{
63 Line: entry.key.Line,
64 Err: fmt.Errorf("unexpected key %s", entry.key.Value),
65 }
66 }
67 if !isTag(entry.val.ShortTag(), seqTag) {
68 return nil, ParseError{
69 Line: entry.key.Line,
70 Err: fmt.Errorf("groups value must be a %s, got %s", describeTag(seqTag), describeTag(entry.val.ShortTag())),
71 }
72 }
73 for _, group := range unpackNodes(entry.val) {
74 name, r, err := parseGroup(content, group)
75 if err.Err != nil {
76 return rules, err
77 }
78 if _, ok := names[name]; ok {
79 return nil, ParseError{
80 Line: group.Line,
81 Err: errors.New("duplicated group name"),
82 }
83 }
84 names[name] = struct{}{}
85 rules = append(rules, r...)
86 }
87 }
88 }
89 return rules, ParseError{}
90}
91
92func parseGroup(content []byte, group *yaml.Node) (name string, rules []Rule, err ParseError) {
93 if !isTag(group.ShortTag(), mapTag) {
94 return "", nil, ParseError{
95 Line: group.Line,
96 Err: fmt.Errorf("group must be a %s, got %s", describeTag(mapTag), describeTag(group.ShortTag())),
97 }
98 }
99
100 setKeys := make(map[string]struct{}, len(group.Content))
101
102 for _, entry := range mappingNodes(group) {
103 switch entry.key.Value {
104 case "name":
105 if entry.val.Kind != yaml.ScalarNode || entry.val.ShortTag() != strTag {
106 return "", nil, ParseError{
107 Line: entry.key.Line,
108 Err: fmt.Errorf("group name must be a %s, got %s", describeTag(strTag), describeTag(entry.val.ShortTag())),
109 }
110 }
111 if entry.val.Value == "" {
112 return "", nil, ParseError{
113 Line: entry.key.Line,
114 Err: errors.New("group name cannot be empty"),
115 }
116 }
117 name = entry.val.Value
118 case "interval", "query_offset":
119 if entry.val.Kind != yaml.ScalarNode || entry.val.ShortTag() != strTag {
120 return "", nil, ParseError{
121 Line: entry.key.Line,
122 Err: fmt.Errorf("group %s must be a %s, got %s", entry.key.Value, describeTag(strTag), describeTag(entry.val.ShortTag())),
123 }
124 }
125 if _, err := model.ParseDuration(entry.val.Value); err != nil {
126 return "", nil, ParseError{
127 Line: entry.key.Line,
128 Err: fmt.Errorf("invalid %s value: %w", entry.key.Value, err),
129 }
130 }
131 case "limit":
132 if entry.val.Kind != yaml.ScalarNode || entry.val.ShortTag() != intTag {
133 return "", nil, ParseError{
134 Line: entry.key.Line,
135 Err: fmt.Errorf("group limit must be a %s, got %s", describeTag(intTag), describeTag(entry.val.ShortTag())),
136 }
137 }
138 case "rules":
139 if !isTag(entry.val.ShortTag(), seqTag) {
140 return "", nil, ParseError{
141 Line: entry.key.Line,
142 Err: fmt.Errorf("rules must be a %s, got %s", describeTag(seqTag), describeTag(entry.val.ShortTag())),
143 }
144 }
145 for _, rule := range unpackNodes(entry.val) {
146 r, err := parseRuleStrict(content, rule)
147 if err.Err != nil {
148 return "", nil, err
149 }
150 rules = append(rules, r)
151 }
152 default:
153 return "", nil, ParseError{
154 Line: entry.key.Line,
155 Err: fmt.Errorf("invalid group key %s", entry.key.Value),
156 }
157 }
158
159 if _, ok := setKeys[entry.key.Value]; ok {
160 return "", nil, ParseError{
161 Line: entry.key.Line,
162 Err: fmt.Errorf("duplicated key %s", entry.key.Value),
163 }
164 }
165 setKeys[entry.key.Value] = struct{}{}
166 }
167
168 if _, ok := setKeys["rules"]; ok {
169 if _, ok := setKeys["name"]; !ok {
170 return "", nil, ParseError{
171 Line: group.Line,
172 Err: errors.New("incomplete group definition, name is required and must be set"),
173 }
174 }
175 }
176
177 return name, rules, ParseError{}
178}
179
180func parseRuleStrict(content []byte, rule *yaml.Node) (Rule, ParseError) {
181 if !isTag(rule.ShortTag(), mapTag) {
182 return Rule{}, ParseError{
183 Line: rule.Line,
184 Err: fmt.Errorf("rule definion must be a %s, got %s", describeTag(mapTag), describeTag(rule.ShortTag())),
185 }
186 }
187
188 for i, node := range unpackNodes(rule) {
189 if i%2 != 0 {
190 continue
191 }
192 switch node.Value {
193 case "record":
194 case "alert":
195 case "expr":
196 case "for":
197 case "keep_firing_for":
198 case "labels":
199 case "annotations":
200 default:
201 return Rule{}, ParseError{
202 Line: node.Line,
203 Err: fmt.Errorf("invalid rule key %s", node.Value),
204 }
205 }
206 }
207
208 r, _ := parseRule(content, rule, 0)
209 return r, ParseError{}
210}
211