github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/base/fill/path_value.go (about)

     1  package fill
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  )
     9  
    10  var (
    11  	// ErrNotFound is returned when a field isn't found
    12  	ErrNotFound = fmt.Errorf("not found")
    13  )
    14  
    15  // FieldError represents an error when a value does not match the expected type
    16  type FieldError struct {
    17  	Got  string
    18  	Want string
    19  	Val  interface{}
    20  }
    21  
    22  // Error displays the text version of the FieldError
    23  func (c *FieldError) Error() string {
    24  	return fmt.Sprintf("need %s, got %s: %#v", c.Want, c.Got, c.Val)
    25  }
    26  
    27  // SetPathValue sets a value on a the output struct, accessed using the path of dot-separated fields
    28  func SetPathValue(path string, val interface{}, output interface{}) error {
    29  	target := reflect.ValueOf(output)
    30  	steps := strings.Split(path, ".")
    31  	// Collector errors
    32  	collector := NewErrorCollector()
    33  	for _, s := range steps {
    34  		collector.PushField(s)
    35  	}
    36  	// Find target place to assign to, field is a non-empty string only if target is a map.
    37  	target, field, err := findTargetAtPath(steps, target)
    38  	if err != nil {
    39  		if err == ErrNotFound {
    40  			return fmt.Errorf("at %q: %w", path, ErrNotFound)
    41  		}
    42  		collector.Add(err)
    43  		return collector.AsSingleError()
    44  	}
    45  	if field == "" {
    46  		// Convert val into the correct type, parsing ints and bools and so forth.
    47  		val, err = coerceToTargetType(val, target)
    48  		if err != nil {
    49  			collector.Add(err)
    50  			return collector.AsSingleError()
    51  		}
    52  		putValueToPlace(val, target, collector)
    53  		return collector.AsSingleError()
    54  	}
    55  	// A map with a field name to assign to.
    56  	// TODO: Only works for map[string]string, not map's with a struct for a value.
    57  	target.SetMapIndex(reflect.ValueOf(field), reflect.ValueOf(val))
    58  	return nil
    59  }
    60  
    61  // GetPathValue gets a value from the input struct, accessed using the path of dot-separated fields
    62  func GetPathValue(path string, input interface{}) (interface{}, error) {
    63  	target := reflect.ValueOf(input)
    64  	steps := strings.Split(path, ".")
    65  	// Find target place to assign to, field is a non-empty string only if target is a map.
    66  	target, field, err := findTargetAtPath(steps, target)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("at %q: %w", path, err)
    69  	}
    70  	// If all steps completed, resolve the target to a real value.
    71  	if field == "" {
    72  		return target.Interface(), nil
    73  	}
    74  	// Otherwise, one step is left, look it up using case-insensitive comparison.
    75  	if lookup := mapLookupCaseInsensitive(target, field); lookup != nil {
    76  		return lookup.Interface(), nil
    77  	}
    78  	return nil, fmt.Errorf("at %q: %w", path, ErrNotFound)
    79  }
    80  
    81  // Recursively look up the first step in place, until there are no steps left. If place is ever
    82  // a map with one step left, return that map and final step - this is done in order to enable
    83  // assignment to that map.
    84  func findTargetAtPath(steps []string, place reflect.Value) (reflect.Value, string, error) {
    85  	if len(steps) == 0 {
    86  		return place, "", nil
    87  	}
    88  	// Get current step of the path, dispatch on its type.
    89  	s := steps[0]
    90  	rest := steps[1:]
    91  	if place.Kind() == reflect.Struct {
    92  		field := getFieldCaseInsensitive(place, s)
    93  		if !field.IsValid() {
    94  			return place, "", ErrNotFound
    95  		}
    96  		return findTargetAtPath(rest, field)
    97  	} else if place.Kind() == reflect.Interface {
    98  		var inner reflect.Value
    99  		if place.IsNil() {
   100  			alloc := reflect.New(place.Type().Elem())
   101  			place.Set(alloc)
   102  			inner = alloc.Elem()
   103  		} else {
   104  			inner = place.Elem()
   105  		}
   106  		return findTargetAtPath(steps, inner)
   107  	} else if place.Kind() == reflect.Ptr {
   108  		var inner reflect.Value
   109  		if place.IsNil() {
   110  			alloc := reflect.New(place.Type().Elem())
   111  			place.Set(alloc)
   112  			inner = alloc.Elem()
   113  		} else {
   114  			inner = place.Elem()
   115  		}
   116  		return findTargetAtPath(steps, inner)
   117  	} else if place.Kind() == reflect.Map {
   118  		if place.IsNil() {
   119  			place.Set(reflect.MakeMap(place.Type()))
   120  		}
   121  		if len(steps) == 1 {
   122  			return place, s, nil
   123  		}
   124  		found := mapLookupCaseInsensitive(place, s)
   125  		if found == nil {
   126  			return place, "", ErrNotFound
   127  		}
   128  		return findTargetAtPath(rest, *found)
   129  	} else if place.Kind() == reflect.Slice {
   130  		num, err := coerceToInt(s)
   131  		if err != nil {
   132  			return place, "", err
   133  		}
   134  		if num >= place.Len() {
   135  			return place, "", fmt.Errorf("index outside of range: %d, len is %d", num, place.Len())
   136  		}
   137  		elem := place.Index(num)
   138  		return findTargetAtPath(rest, elem)
   139  	} else {
   140  		return place, "", fmt.Errorf("cannot set field of type %s", place.Kind())
   141  	}
   142  }
   143  
   144  // Look up value in the map, using case-insensitive string comparison. Return nil if not found
   145  func mapLookupCaseInsensitive(mapValue reflect.Value, k string) *reflect.Value {
   146  	// Try looking up the value without changing the string case
   147  	key := reflect.ValueOf(k)
   148  	result := mapValue.MapIndex(key)
   149  	if result.IsValid() {
   150  		return &result
   151  	}
   152  	// Try lower-casing the key and looking that up
   153  	klower := strings.ToLower(k)
   154  	key = reflect.ValueOf(klower)
   155  	result = mapValue.MapIndex(key)
   156  	if result.IsValid() {
   157  		return &result
   158  	}
   159  	// Iterate over the map keys, compare each, using case-insensitive matching
   160  	mapKeys := mapValue.MapKeys()
   161  	for _, mk := range mapKeys {
   162  		mstr := mk.String()
   163  		if strings.ToLower(mstr) == klower {
   164  			result = mapValue.MapIndex(mk)
   165  			if result.IsValid() {
   166  				return &result
   167  			}
   168  		}
   169  	}
   170  	// Not found, return nil
   171  	return nil
   172  }
   173  
   174  func coerceToTargetType(val interface{}, place reflect.Value) (interface{}, error) {
   175  	switch place.Kind() {
   176  	case reflect.Bool:
   177  		str, ok := val.(string)
   178  		if ok {
   179  			str = strings.ToLower(str)
   180  			if str == "true" {
   181  				return true, nil
   182  			} else if str == "false" {
   183  				return false, nil
   184  			}
   185  		}
   186  		b, ok := val.(bool)
   187  		if ok {
   188  			return b, nil
   189  		}
   190  		return nil, &FieldError{Want: "bool", Got: reflect.TypeOf(val).Name(), Val: val}
   191  	case reflect.Int:
   192  		num, ok := val.(int)
   193  		if ok {
   194  			return num, nil
   195  		}
   196  		str, ok := val.(string)
   197  		if ok {
   198  			parsed, err := strconv.ParseInt(str, 10, 32)
   199  			if err == nil {
   200  				return int(parsed), nil
   201  			}
   202  		}
   203  		return nil, &FieldError{Want: "int", Got: reflect.TypeOf(val).Name(), Val: val}
   204  	case reflect.Int64:
   205  		num, ok := val.(int)
   206  		if ok {
   207  			return num, nil
   208  		}
   209  		num64, ok := val.(int64)
   210  		if ok {
   211  			return num64, nil
   212  		}
   213  		float64, ok := val.(float64)
   214  		if ok {
   215  			return float64, nil
   216  		}
   217  		str, ok := val.(string)
   218  		if ok {
   219  			parsed, err := strconv.ParseInt(str, 10, 64)
   220  			if err == nil {
   221  				return int64(parsed), nil
   222  			}
   223  		}
   224  		return nil, &FieldError{Want: "int64", Got: reflect.TypeOf(val).Name(), Val: val}
   225  	case reflect.Ptr:
   226  		alloc := reflect.New(place.Type().Elem())
   227  		return coerceToTargetType(val, alloc.Elem())
   228  	case reflect.String:
   229  		str, ok := val.(string)
   230  		if ok {
   231  			return str, nil
   232  		}
   233  		return nil, &FieldError{Want: "string", Got: reflect.TypeOf(val).Name(), Val: val}
   234  	default:
   235  		return nil, fmt.Errorf("unknown kind: %s", place.Kind())
   236  	}
   237  }
   238  
   239  func coerceToInt(str string) (int, error) {
   240  	parsed, err := strconv.ParseInt(str, 10, 32)
   241  	if err == nil {
   242  		return int(parsed), nil
   243  	}
   244  	return -1, &FieldError{Want: "int", Got: reflect.TypeOf(str).Name(), Val: str}
   245  }
   246  
   247  func getFieldCaseInsensitive(place reflect.Value, name string) reflect.Value {
   248  	name = strings.ToLower(name)
   249  	return place.FieldByNameFunc(func(s string) bool { return strings.ToLower(s) == name })
   250  }