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 }