cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.79.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/decode.go

156lines · modecode

1package parser
2
3import (
4 "errors"
5 "slices"
6 "sync"
7
8 promParser "github.com/prometheus/prometheus/promql/parser"
9
10 "github.com/cloudflare/pint/internal/parser/source"
11)
12
13type PromQLExpr struct {
14 syntaxError error
15 Value *YamlNode
16 query *PromQLNode
17 mu *sync.Mutex
18 source []source.Source
19 hasSource bool
20}
21
22func (pn *PromQLExpr) parse() {
23 if pn.query == nil {
24 pn.query, pn.syntaxError = DecodeExpr(pn.Value.Value)
25 }
26}
27
28func (pn *PromQLExpr) Source() []source.Source {
29 pn.mu.Lock()
30 defer pn.mu.Unlock()
31 pn.parse()
32 if !pn.hasSource {
33 pn.source = source.LabelsSource(pn.Value.Value, pn.query.Expr)
34 pn.hasSource = true
35 }
36 return pn.source
37}
38
39func (pn *PromQLExpr) Query() *PromQLNode {
40 pn.mu.Lock()
41 defer pn.mu.Unlock()
42 pn.parse()
43 return pn.query
44}
45
46func (pn *PromQLExpr) SyntaxError() error {
47 pn.mu.Lock()
48 defer pn.mu.Unlock()
49 pn.parse()
50 return pn.syntaxError
51}
52
53// PromQLNode is used to turn the parsed PromQL query expression into a tree.
54// This allows us to walk the tree up & down and look for either parents
55// or children of specific type. Which is useful if you, for example,
56// want to check if all vector selectors are wrapped inside function
57// calls etc.
58type PromQLNode struct {
59 Parent *PromQLNode
60 Expr promParser.Node
61 Children []*PromQLNode
62}
63
64// Tree takes a parsed PromQL node and turns it into a Node
65// instance with parent and children populated.
66func tree(expr promParser.Node, parent *PromQLNode) *PromQLNode {
67 n := PromQLNode{
68 Parent: parent,
69 Expr: expr,
70 Children: nil,
71 }
72 for _, child := range promParser.Children(expr) {
73 n.Children = append(n.Children, tree(child, &n))
74 }
75
76 return &n
77}
78
79// WalkUpExpr allows to iterate a promQLNode node looking for
80// parents of specific type.
81// Prometheus parser returns interfaces which makes it more difficult
82// to figure out what kind of node we're dealing with, hence this
83// helper takes a type parameter it tries to cast.
84// It starts by checking the node passed to it and then walks
85// up by visiting all parent nodes.
86func WalkUpExpr[T promParser.Node](node *PromQLNode) (nodes []*PromQLNode) {
87 if node == nil {
88 return nodes
89 }
90 if _, ok := node.Expr.(T); ok {
91 nodes = append(nodes, node)
92 }
93 if node.Parent != nil {
94 nodes = append(nodes, WalkUpExpr[T](node.Parent)...)
95 }
96 return nodes
97}
98
99// WalkDownExpr works just like WalkUpExpr but it walks the tree
100// down, visiting all children.
101// It also starts by checking the node passed to it before walking
102// down the tree.
103func WalkDownExpr[T promParser.Node](node *PromQLNode) (nodes []*PromQLNode) {
104 if _, ok := node.Expr.(T); ok {
105 nodes = append(nodes, node)
106 }
107 for _, child := range node.Children {
108 nodes = append(nodes, WalkDownExpr[T](child)...)
109 }
110 return nodes
111}
112
113// WalkUpParent works like WalkUpExpr but checks the parent
114// (if present) instead of the node itself.
115// It returns the nodes where the parent is of given type.
116func WalkUpParent[T promParser.Node](node *PromQLNode) (nodes []*PromQLNode) {
117 if node == nil || node.Parent == nil {
118 return nodes
119 }
120 if _, ok := node.Parent.Expr.(T); ok {
121 nodes = append(nodes, node)
122 }
123 if node.Parent != nil {
124 nodes = append(nodes, WalkUpParent[T](node.Parent)...)
125 }
126 return nodes
127}
128
129func DecodeExpr(expr string) (*PromQLNode, error) {
130 node, err := promParser.ParseExpr(expr)
131 if err != nil {
132 var errorList promParser.ParseErrors
133 if errors.As(err, &errorList) {
134 // Find the error pointing at the shortest query fragment.
135 slices.SortFunc(errorList, func(a, b promParser.ParseErr) int {
136 ar := a.PositionRange.End - a.PositionRange.Start
137 br := b.PositionRange.End - b.PositionRange.Start
138 switch {
139 case ar < br:
140 return -1
141 case ar > br:
142 return 1
143 default:
144 return 0
145 }
146 })
147 for _, el := range errorList {
148 if el.PositionRange.Start > 0 && el.PositionRange.End > 0 {
149 return nil, promParser.ParseErrors{el}
150 }
151 }
152 }
153 return nil, err
154 }
155 return tree(node, nil), nil
156}
157