package pointerstructure import ( "fmt" "reflect" "strings" ) // Get reads the value out of the total value v. // // For struct values a `pointer:""` tag on the struct's // fields may be used to override that field's name for lookup purposes. // Alternatively the tag name used can be overridden in the `Config`. func (p *Pointer) Get(v interface{}) (interface{}, error) { // fast-path the empty address case to avoid reflect.ValueOf below if len(p.Parts) == 0 { return v, nil } // Map for lookup of getter to call for type funcMap := map[reflect.Kind]func(string, reflect.Value) (reflect.Value, error){ reflect.Array: p.getSlice, reflect.Map: p.getMap, reflect.Slice: p.getSlice, reflect.Struct: p.getStruct, } currentVal := reflect.ValueOf(v) for i, part := range p.Parts { for currentVal.Kind() == reflect.Interface { currentVal = currentVal.Elem() } for currentVal.Kind() == reflect.Ptr { currentVal = reflect.Indirect(currentVal) } f, ok := funcMap[currentVal.Kind()] if !ok { return nil, fmt.Errorf( "%s: at part %d, %w: %s", p, i, ErrInvalidKind, currentVal.Kind()) } var err error currentVal, err = f(part, currentVal) if err != nil { return nil, fmt.Errorf("%s at part %d: %w", p, i, err) } if p.Config.ValueTransformationHook != nil { currentVal = p.Config.ValueTransformationHook(currentVal) if currentVal == reflect.ValueOf(nil) { return nil, fmt.Errorf("%s at part %d: ValueTransformationHook returned the value of a nil interface", p, i) } } } return currentVal.Interface(), nil } func (p *Pointer) getMap(part string, m reflect.Value) (reflect.Value, error) { var zeroValue reflect.Value // Coerce the string part to the correct key type key, err := coerce(reflect.ValueOf(part), m.Type().Key()) if err != nil { return zeroValue, err } // Verify that the key exists found := false for _, k := range m.MapKeys() { if k.Interface() == key.Interface() { found = true break } } if !found { return zeroValue, fmt.Errorf("%w %#v", ErrNotFound, key.Interface()) } // Get the key return m.MapIndex(key), nil } func (p *Pointer) getSlice(part string, v reflect.Value) (reflect.Value, error) { var zeroValue reflect.Value // Coerce the key to an int idxVal, err := coerce(reflect.ValueOf(part), reflect.TypeOf(42)) if err != nil { return zeroValue, err } idx := int(idxVal.Int()) // Verify we're within bounds if idx < 0 || idx >= v.Len() { return zeroValue, fmt.Errorf( "index %d is %w (length = %d)", idx, ErrOutOfRange, v.Len()) } // Get the key return v.Index(idx), nil } func (p *Pointer) getStruct(part string, m reflect.Value) (reflect.Value, error) { var foundField reflect.Value var found bool var ignored bool typ := m.Type() tagName := p.Config.TagName if tagName == "" { tagName = "pointer" } for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) if field.PkgPath != "" { // this is an unexported field so ignore it continue } fieldTag := field.Tag.Get(tagName) if fieldTag != "" { if idx := strings.Index(fieldTag, ","); idx != -1 { fieldTag = fieldTag[0:idx] } if strings.Contains(fieldTag, "|") { // should this panic instead? return foundField, fmt.Errorf("pointer struct tag cannot contain the '|' character") } if fieldTag == "-" { // we should ignore this field but cannot immediately return because its possible another // field has a tag that would allow it to assume this ones name. if field.Name == part { found = true ignored = true } continue } else if fieldTag == part { // we can go ahead and return now as the tag is enough to // indicate that this is the correct field return m.Field(i), nil } } else if field.Name == part { foundField = m.Field(i) found = true } } if !found { return reflect.Value{}, fmt.Errorf("couldn't find struct field with name %q", part) } if ignored { return reflect.Value{}, fmt.Errorf("struct field %q is ignored and cannot be used", part) } return foundField, nil }