github.com/dhaiducek/policy-generator-plugin@v1.99.99/internal/typohelper.go (about) 1 package internal 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "regexp" 8 "strings" 9 10 "github.com/pmezard/go-difflib/difflib" 11 ) 12 13 // addFieldNotFoundHelp adds help text to errors that occur when the input config 14 // has a field that is not present in the Config struct (eg a typo), to assist 15 // the user in debugging the problem. If the input error was not from a missing 16 // field, then it is returned unchanged. 17 func addFieldNotFoundHelp(err error) error { 18 re := regexp.MustCompile(`field (\S*) not found in type (\S*)`) 19 20 repl := func(line string) string { 21 match := re.FindStringSubmatch(line) 22 23 fieldType := reflect.TypeOf(Plugin{}) 24 fieldTag := "PolicyGenerator" 25 26 if match[2] != fieldType.String() { 27 // Search the right type, if it's not the top-level object. 28 fieldType, fieldTag = findNestedType(fieldType, match[2], "") 29 } 30 31 msg := fmt.Sprintf("field %v found but not defined", match[1]) 32 33 if fieldTag != "" { 34 msg += fmt.Sprintf(" in type %v", fieldTag) 35 } 36 37 suggestion := autocorrectField(match[1], fieldType) 38 if suggestion != "" { 39 msg += fmt.Sprintf(" - did you mean '%v'?", suggestion) 40 } 41 42 return msg 43 } 44 45 helpMsg := re.ReplaceAllStringFunc(err.Error(), repl) 46 47 if helpMsg == err.Error() { 48 // Error was unchanged, return the original to preserve the type 49 return err 50 } 51 52 return errors.New(helpMsg) 53 } 54 55 // autocorrectField returns the field in the containingType which most closely 56 // matches the input badField. It will return an empty string if no match can 57 // be found with sufficient confidence. 58 func autocorrectField(badField string, containingType reflect.Type) string { 59 if containingType == nil || containingType.Kind() != reflect.Struct { 60 return "" 61 } 62 63 matcher := difflib.NewMatcher(strings.Split(badField, ""), []string{""}) 64 bestRatio := 0.85 // require 85% or better match 65 bestMatch := "" 66 67 // iterate over all fields in the struct that have a yaml tag. 68 for _, field := range reflect.VisibleFields(containingType) { 69 yamlTag := strings.SplitN(field.Tag.Get("yaml"), ",", 2)[0] 70 if yamlTag == "" { 71 continue 72 } 73 74 matcher.SetSeq2(strings.Split(yamlTag, "")) 75 76 ratio := matcher.Ratio() 77 if ratio > bestRatio { 78 bestRatio = ratio 79 bestMatch = yamlTag 80 } 81 } 82 83 return bestMatch 84 } 85 86 // findNestedType searches through the given type and nested types (recursively) 87 // for a type matching the wanted string. It will return the matching type if 88 // found, or nil if not found. It will also return the yaml tag of the field 89 // where the type was found. 90 func findNestedType(baseType reflect.Type, want string, tag string) (reflect.Type, string) { 91 if baseType.String() == want { 92 return baseType, tag 93 } 94 95 if baseType.Kind() == reflect.Array || baseType.Kind() == reflect.Slice || baseType.Kind() == reflect.Map { 96 return findNestedType(baseType.Elem(), want, tag) 97 } 98 99 if baseType.Kind() != reflect.Struct { 100 return nil, "" 101 } 102 103 // iterate over all fields in the struct that have a yaml tag. 104 for _, field := range reflect.VisibleFields(baseType) { 105 yamlTag := strings.SplitN(field.Tag.Get("yaml"), ",", 2)[0] 106 107 if field.Type.String() == want { 108 return field.Type, yamlTag 109 } 110 111 deeperType, deeperTag := findNestedType(field.Type, want, yamlTag) 112 if deeperType != nil { 113 return deeperType, deeperTag 114 } 115 } 116 117 return nil, "" 118 }