-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
parser.go
155 lines (130 loc) · 4.01 KB
/
parser.go
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
143
144
145
146
147
148
149
150
151
152
153
154
155
package random
import (
"fmt"
"reflect"
"unicode/utf8"
"github.com/hashicorp/hcl"
"github.com/mitchellh/mapstructure"
)
// ParsePolicy is a convenience function for parsing HCL into a StringGenerator.
// See PolicyParser.ParsePolicy for details.
func ParsePolicy(raw string) (gen StringGenerator, err error) {
parser := PolicyParser{
RuleRegistry: Registry{
Rules: defaultRuleNameMapping,
},
}
return parser.ParsePolicy(raw)
}
// ParsePolicyBytes is a convenience function for parsing HCL into a StringGenerator.
// See PolicyParser.ParsePolicy for details.
func ParsePolicyBytes(raw []byte) (gen StringGenerator, err error) {
return ParsePolicy(string(raw))
}
// PolicyParser parses string generator configuration from HCL.
type PolicyParser struct {
// RuleRegistry maps rule names in HCL to Rule constructors.
RuleRegistry Registry
}
// ParsePolicy parses the provided HCL into a StringGenerator.
func (p PolicyParser) ParsePolicy(raw string) (sg StringGenerator, err error) {
rawData := map[string]interface{}{}
err = hcl.Decode(&rawData, raw)
if err != nil {
return sg, fmt.Errorf("unable to decode: %w", err)
}
// Decode the top level items
gen := StringGenerator{}
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &gen,
DecodeHook: stringToRunesFunc,
})
if err != nil {
return sg, fmt.Errorf("unable to decode configuration: %w", err)
}
err = decoder.Decode(rawData)
if err != nil {
return sg, fmt.Errorf("failed to decode configuration: %w", err)
}
// Decode & parse rules
rawRules, err := getMapSlice(rawData, "rule")
if err != nil {
return sg, fmt.Errorf("unable to retrieve rules: %w", err)
}
rules, err := parseRules(p.RuleRegistry, rawRules)
if err != nil {
return sg, fmt.Errorf("unable to parse rules: %w", err)
}
gen = StringGenerator{
Length: gen.Length,
Rules: rules,
}
err = gen.validateConfig()
if err != nil {
return sg, err
}
return gen, nil
}
func parseRules(registry Registry, rawRules []map[string]interface{}) (rules []Rule, err error) {
for _, rawRule := range rawRules {
info, err := getRuleInfo(rawRule)
if err != nil {
return nil, fmt.Errorf("unable to get rule info: %w", err)
}
rule, err := registry.parseRule(info.ruleType, info.data)
if err != nil {
return nil, fmt.Errorf("unable to parse rule %s: %w", info.ruleType, err)
}
rules = append(rules, rule)
}
return rules, nil
}
// getMapSlice from the provided map. This will retrieve and type-assert a []map[string]interface{} from the map
// This will not error if the key does not exist
// This will return an error if the value at the provided key is not of type []map[string]interface{}
func getMapSlice(m map[string]interface{}, key string) (mapSlice []map[string]interface{}, err error) {
rawSlice, exists := m[key]
if !exists {
return nil, nil
}
mapSlice = []map[string]interface{}{}
err = mapstructure.Decode(rawSlice, &mapSlice)
if err != nil {
return nil, err
}
return mapSlice, nil
}
type ruleInfo struct {
ruleType string
data map[string]interface{}
}
// getRuleInfo splits the provided HCL-decoded rule into its rule type along with the data associated with it
func getRuleInfo(rule map[string]interface{}) (data ruleInfo, err error) {
// There should only be one key, but it's a dynamic key yay!
for key := range rule {
slice, err := getMapSlice(rule, key)
if err != nil {
return data, fmt.Errorf("unable to get rule data: %w", err)
}
if len(slice) == 0 {
return data, fmt.Errorf("rule info cannot be empty")
}
data = ruleInfo{
ruleType: key,
data: slice[0],
}
return data, nil
}
return data, fmt.Errorf("rule is empty")
}
// stringToRunesFunc converts a string to a []rune for use in the mapstructure library
func stringToRunesFunc(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) {
if from != reflect.String || to != reflect.Slice {
return data, nil
}
raw := data.(string)
if !utf8.ValidString(raw) {
return nil, fmt.Errorf("invalid UTF8 string")
}
return []rune(raw), nil
}