| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| 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 |
| } |