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  }