File: validate.go

package info (click to toggle)
golang-github-hashicorp-go-bexpr 0.1.2-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 356 kB
  • sloc: makefile: 50
file content (142 lines) | stat: -rw-r--r-- 4,135 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package bexpr

import (
	"fmt"
	"regexp"
)

func validateRecurse(ast Expression, fields FieldConfigurations, maxRawValueLength int) (int, error) {
	switch node := ast.(type) {
	case *UnaryExpression:
		switch node.Operator {
		case UnaryOpNot:
			// this is fine
		default:
			return 0, fmt.Errorf("Invalid unary expression operator: %d", node.Operator)
		}

		if node.Operand == nil {
			return 0, fmt.Errorf("Invalid unary expression operand: nil")
		}
		return validateRecurse(node.Operand, fields, maxRawValueLength)
	case *BinaryExpression:
		switch node.Operator {
		case BinaryOpAnd, BinaryOpOr:
			// this is fine
		default:
			return 0, fmt.Errorf("Invalid binary expression operator: %d", node.Operator)
		}

		if node.Left == nil {
			return 0, fmt.Errorf("Invalid left hand side of binary expression: nil")
		} else if node.Right == nil {
			return 0, fmt.Errorf("Invalid right hand side of binary expression: nil")
		}

		leftMatches, err := validateRecurse(node.Left, fields, maxRawValueLength)
		if err != nil {
			return leftMatches, err
		}

		rightMatches, err := validateRecurse(node.Right, fields, maxRawValueLength)
		return leftMatches + rightMatches, err
	case *MatchExpression:
		if len(node.Selector) < 1 {
			return 1, fmt.Errorf("Invalid selector: %q", node.Selector)
		}

		if node.Value != nil && maxRawValueLength != 0 && len(node.Value.Raw) > maxRawValueLength {
			return 1, fmt.Errorf("Value in expression with length %d for selector %q exceeds maximum length of", len(node.Value.Raw), maxRawValueLength)
		}

		// exit early if we have no fields to check against
		if len(fields) < 1 {
			return 1, nil
		}

		configs := fields
		var lastConfig *FieldConfiguration
		// validate the selector
		for idx, field := range node.Selector {
			if fcfg, ok := configs[FieldName(field)]; ok {
				lastConfig = fcfg
				configs = fcfg.SubFields
			} else if fcfg, ok := configs[FieldNameAny]; ok {
				lastConfig = fcfg
				configs = fcfg.SubFields
			} else {
				return 1, fmt.Errorf("Selector %q is not valid", node.Selector[:idx+1])
			}

			// this just verifies that the FieldConfigurations we are using was created properly
			if lastConfig == nil {
				return 1, fmt.Errorf("FieldConfiguration for selector %q is nil", node.Selector[:idx])
			}
		}

		// check the operator
		found := false
		for _, op := range lastConfig.SupportedOperations {
			if op == node.Operator {
				found = true
				break
			}
		}

		if !found {
			return 1, fmt.Errorf("Invalid match operator %q for selector %q", node.Operator, node.Selector)
		}

		// coerce/validate the value
		if node.Value != nil {
			if lastConfig.CoerceFn != nil {
				coerced, err := lastConfig.CoerceFn(node.Value.Raw)
				if err != nil {
					return 1, fmt.Errorf("Failed to coerce value %q for selector %q: %v", node.Value.Raw, node.Selector, err)
				}

				node.Value.Converted = coerced
			}

			if node.Operator == MatchMatches || node.Operator == MatchNotMatches {
				var regRaw string
				if strVal, ok := node.Value.Converted.(string); ok {
					regRaw = strVal
				} else if node.Value.Converted == nil {
					regRaw = node.Value.Raw
				} else {
					return 1, fmt.Errorf("Match operator %q cannot be used with fields whose coercion functions return non string values", node.Operator)
				}

				re, err := regexp.Compile(regRaw)
				if err != nil {
					return 1, fmt.Errorf("Failed to compile regular expression %q: %v", regRaw, err)
				}

				node.Value.Converted = re
			}
		} else {
			switch node.Operator {
			case MatchIsEmpty, MatchIsNotEmpty:
				// these don't require values
			default:
				return 1, fmt.Errorf("Match operator %q requires a non-nil value", node.Operator)
			}
		}
		return 1, nil
	}
	return 0, fmt.Errorf("Cannot validate: Invalid AST")
}

func validate(ast Expression, fields FieldConfigurations, maxMatches, maxRawValueLength int) error {
	matches, err := validateRecurse(ast, fields, maxRawValueLength)
	if err != nil {
		return err
	}

	if maxMatches != 0 && matches > maxMatches {
		return fmt.Errorf("Number of match expressions (%d) exceeds the limit (%d)", matches, maxMatches)
	}

	return nil
}