pax_global_header00006660000000000000000000000064141402577410014517gustar00rootroot0000000000000052 comment=9f08cfd22e7202284113f321eb6b618d81426d9e pointerstructure-1.2.1/000077500000000000000000000000001414025774100151615ustar00rootroot00000000000000pointerstructure-1.2.1/.github/000077500000000000000000000000001414025774100165215ustar00rootroot00000000000000pointerstructure-1.2.1/.github/workflows/000077500000000000000000000000001414025774100205565ustar00rootroot00000000000000pointerstructure-1.2.1/.github/workflows/test.yml000066400000000000000000000006041414025774100222600ustar00rootroot00000000000000on: [push, pull_request] name: Test jobs: test: strategy: matrix: go-version: [1.15.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Install Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Test run: go test ./... pointerstructure-1.2.1/LICENSE000066400000000000000000000020631414025774100161670ustar00rootroot00000000000000MIT License Copyright (c) 2019 Mitchell Hashimoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pointerstructure-1.2.1/README.md000066400000000000000000000040161414025774100164410ustar00rootroot00000000000000# pointerstructure [![GoDoc](https://godoc.org/github.com/mitchellh/pointerstructure?status.svg)](https://godoc.org/github.com/mitchellh/pointerstructure) pointerstructure is a Go library for identifying a specific value within any Go structure using a string syntax. pointerstructure is based on [JSON Pointer (RFC 6901)](https://tools.ietf.org/html/rfc6901), but reimplemented for Go. The goal of pointerstructure is to provide a single, well-known format for addressing a specific value. This can be useful for user provided input on structures, diffs of structures, etc. ## Features * Get the value for an address * Set the value for an address within an existing structure * Delete the value at an address * Sorting a list of addresses ## Installation Standard `go get`: ``` $ go get github.com/mitchellh/pointerstructure ``` ## Usage & Example For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/pointerstructure). A quick code example is shown below: ```go complex := map[string]interface{}{ "alice": 42, "bob": []interface{}{ map[string]interface{}{ "name": "Bob", }, }, } value, err := pointerstructure.Get(complex, "/bob/0/name") if err != nil { panic(err) } fmt.Printf("%s", value) // Output: // Bob ``` Continuing the example above, you can also set values: ```go value, err = pointerstructure.Set(complex, "/bob/0/name", "Alice") if err != nil { panic(err) } value, err = pointerstructure.Get(complex, "/bob/0/name") if err != nil { panic(err) } fmt.Printf("%s", value) // Output: // Alice ``` The library also supports `Get` operations on structs including using the `pointer` struct tag to override struct field names: ```go input := struct { Values map[string]interface{} `pointer:"embedded"` }{ Values: map[string]interface{}{ "alice": 42, "bob": []interface{}{ map[string]interface{}{ "name": "Bob", }, }, }, } value, err := Get(input, "/embedded/bob/0/name") if err != nil { panic(err) } fmt.Printf("%s", value) // Output: // Bob ``` pointerstructure-1.2.1/delete.go000066400000000000000000000056371414025774100167650ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" ) // Delete deletes the value specified by the pointer p in structure s. // // When deleting a slice index, all other elements will be shifted to // the left. This is specified in RFC6902 (JSON Patch) and not RFC6901 since // RFC6901 doesn't specify operations on pointers. If you don't want to // shift elements, you should use Set to set the slice index to the zero value. // // The structures s must have non-zero values set up to this pointer. // For example, if deleting "/bob/0/name", then "/bob/0" must be set already. // // The returned value is potentially a new value if this pointer represents // the root document. Otherwise, the returned value will always be s. func (p *Pointer) Delete(s interface{}) (interface{}, error) { // if we represent the root doc, we've deleted everything if len(p.Parts) == 0 { return nil, nil } // Save the original since this is going to be our return value originalS := s // Get the parent value var err error s, err = p.Parent().Get(s) if err != nil { return nil, err } // Map for lookup of getter to call for type funcMap := map[reflect.Kind]deleteFunc{ reflect.Array: p.deleteSlice, reflect.Map: p.deleteMap, reflect.Slice: p.deleteSlice, } val := reflect.ValueOf(s) for val.Kind() == reflect.Interface { val = val.Elem() } for val.Kind() == reflect.Ptr { val = reflect.Indirect(val) } f, ok := funcMap[val.Kind()] if !ok { return nil, fmt.Errorf("delete %s: %w: %s", p, ErrInvalidKind, val.Kind()) } result, err := f(originalS, val) if err != nil { return nil, fmt.Errorf("delete %s: %s", p, err) } return result, nil } type deleteFunc func(interface{}, reflect.Value) (interface{}, error) func (p *Pointer) deleteMap(root interface{}, m reflect.Value) (interface{}, error) { part := p.Parts[len(p.Parts)-1] key, err := coerce(reflect.ValueOf(part), m.Type().Key()) if err != nil { return root, err } // Delete the key var elem reflect.Value m.SetMapIndex(key, elem) return root, nil } func (p *Pointer) deleteSlice(root interface{}, s reflect.Value) (interface{}, error) { // Coerce the key to an int part := p.Parts[len(p.Parts)-1] idxVal, err := coerce(reflect.ValueOf(part), reflect.TypeOf(42)) if err != nil { return root, err } idx := int(idxVal.Int()) // Verify we're within bounds if idx < 0 || idx >= s.Len() { return root, fmt.Errorf( "index %d is %w (length = %d)", idx, ErrOutOfRange, s.Len()) } // Mimicing the following with reflection to do this: // // copy(a[i:], a[i+1:]) // a[len(a)-1] = nil // or the zero value of T // a = a[:len(a)-1] // copy(a[i:], a[i+1:]) reflect.Copy(s.Slice(idx, s.Len()), s.Slice(idx+1, s.Len())) // a[len(a)-1] = nil // or the zero value of T s.Index(s.Len() - 1).Set(reflect.Zero(s.Type().Elem())) // a = a[:len(a)-1] s = s.Slice(0, s.Len()-1) // set the slice back on the parent return p.Parent().Set(root, s.Interface()) } pointerstructure-1.2.1/delete_test.go000066400000000000000000000024711414025774100200150ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" "testing" ) func TestPointerDelete(t *testing.T) { type testStringType string type testIntType int cases := []struct { Name string Parts []string Doc interface{} Output interface{} Err bool }{ { "empty", []string{}, 42, nil, false, }, { "nil", nil, 42, nil, false, }, { "map key", []string{"foo"}, map[string]interface{}{"foo": "bar"}, map[string]interface{}{}, false, }, { "nested map key", []string{"foo", "bar"}, map[string]interface{}{ "foo": map[string]interface{}{ "bar": 42, }, }, map[string]interface{}{ "foo": map[string]interface{}{}, }, false, }, { "slice index", []string{"0"}, []interface{}{42}, []interface{}{}, false, }, { "slice index non-zero", []string{"1"}, []interface{}{42, 84, 168}, []interface{}{42, 168}, false, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { p := &Pointer{Parts: tc.Parts} actual, err := p.Delete(tc.Doc) if (err != nil) != tc.Err { t.Fatalf("err: %s", err) } if err != nil { return } if !reflect.DeepEqual(actual, tc.Output) { t.Fatalf("bad: %#v != %#v", actual, tc.Output) } }) } } pointerstructure-1.2.1/errors.go000066400000000000000000000012741414025774100170300ustar00rootroot00000000000000package pointerstructure import "errors" var ( // ErrNotFound is returned if a key in a query can't be found ErrNotFound = errors.New("couldn't find key") // ErrParse is returned if the query cannot be parsed ErrParse = errors.New("first char must be '/'") // ErrOutOfRange is returned if a query is referencing a slice // or array and the requested index is not in the range [0,len(item)) ErrOutOfRange = errors.New("out of range") // ErrInvalidKind is returned if the item is not a map, slice, // array, or struct ErrInvalidKind = errors.New("invalid value kind") // ErrConvert is returned if an item is not of a requested type ErrConvert = errors.New("couldn't convert value") ) pointerstructure-1.2.1/errors_test.go000066400000000000000000000030041414025774100200600ustar00rootroot00000000000000package pointerstructure import ( "errors" "testing" ) var structure interface{} = map[string]interface{}{ "foo": map[string]interface{}{ "bar": map[string]interface{}{ "baz": []int{3, 0, 1, 5}, }, "quxx": "test", }, } const ( unparsable = "foo/bar" notFound = "/foo/baz/bar" outOfRange = "/foo/bar/baz/5" cantConvert = "/foo/bar/baz/-" invalidKind = "/foo/quxx/test" ) func TestErrParse(t *testing.T) { _, err := Parse(unparsable) if !errors.Is(err, ErrParse) { t.Fatalf("expected ErrParse in the error chain, but it was not") } } func TestErrNotFound(t *testing.T) { _, err := Get(structure, notFound) if !errors.Is(err, ErrNotFound) { t.Fatalf("expected ErrNotFound in the error chain, but it was not") } } func TestErrNotFound_structKey(t *testing.T) { _, err := Get(struct { Nope string }{}, notFound) if !errors.Is(err, ErrNotFound) { t.Fatalf("expected ErrNotFound in the error chain, but it was not") } } func TestErrOutOfRange(t *testing.T) { _, err := Get(structure, outOfRange) if !errors.Is(err, ErrOutOfRange) { t.Fatalf("expected ErrOutOfRange in the error chain, but it was not") } } func TestErrConvert(t *testing.T) { _, err := Set(structure, cantConvert, "test") if !errors.Is(err, ErrConvert) { t.Fatalf("expected ErrConvert in the error chain, but it was not") } } func TestErrInvalidKind(t *testing.T) { _, err := Get(structure, invalidKind) if !errors.Is(err, ErrInvalidKind) { t.Fatalf("expected ErrInvalidKind in the error chain, but it was not") } } pointerstructure-1.2.1/get.go000066400000000000000000000100301414025774100162610ustar00rootroot00000000000000package 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("%w: struct field with name %q", ErrNotFound, part) } if ignored { return reflect.Value{}, fmt.Errorf("struct field %q is ignored and cannot be used", part) } return foundField, nil } pointerstructure-1.2.1/get_test.go000066400000000000000000000143331414025774100173320ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" "testing" ) func TestPointerGet_Hook(t *testing.T) { type embedded struct { S interface{} V *embedded } type embedded2 struct { S2 interface{} } hookForInterface := func(v reflect.Value) reflect.Value { if !v.CanInterface() { return v } if e, ok := v.Interface().(embedded); ok { return reflect.ValueOf(e.S) } return v } hookForPtr := func(v reflect.Value) reflect.Value { if !v.CanInterface() { return v } if e, ok := v.Interface().(embedded); ok { return reflect.ValueOf(e.V) } return v } cases := []struct { Name string Parts []string Hook ValueTransformationHookFn Input interface{} Output interface{} Err bool }{ { Name: "hook returns value of typed nil", Parts: []string{"Key"}, Hook: hookForPtr, Input: &struct { Key embedded }{Key: embedded{}}, Output: (*embedded)(nil), Err: false, }, { Name: "hook returns value of nil interface", Parts: []string{"Key"}, Hook: hookForInterface, Input: &struct { Key embedded }{Key: embedded{}}, Output: nil, Err: true, }, { Name: "top level don't replace", Parts: []string{}, Hook: hookForInterface, Input: embedded{S: "foo"}, Output: embedded{S: "foo"}, Err: false, }, { Name: "1 deep replace", Parts: []string{"Key"}, Hook: hookForInterface, Input: &struct { Key embedded }{Key: embedded{S: "foo"}}, Output: "foo", Err: false, }, { Name: "2 deep replace", Parts: []string{"Key", "S2"}, Hook: hookForInterface, Input: &struct { Key embedded2 }{Key: embedded2{S2: embedded{S: "foo"}}}, Output: "foo", Err: false, }, { Name: "two levels not last", Parts: []string{"Key"}, Hook: hookForInterface, Input: &struct { Key embedded }{Key: embedded{S: embedded2{S2: "foo"}}}, Output: embedded2{S2: "foo"}, Err: false, }, { Name: "dont call hook twice per part", Parts: []string{"Key"}, Hook: hookForInterface, Input: &struct { Key embedded }{Key: embedded{S: embedded{S: "foo"}}}, Output: embedded{S: "foo"}, Err: false, }, { Name: "through map", Parts: []string{"Key"}, Hook: hookForInterface, Input: &map[string]interface{}{ "Key": embedded{S: "foo"}, }, Output: "foo", Err: false, }, { Name: "slice key", Parts: []string{"0"}, Hook: hookForInterface, Input: []interface{}{embedded{S: "foo"}}, Output: "foo", Err: false, }, { Name: "array key", Parts: []string{"0"}, Hook: hookForInterface, Input: [1]interface{}{embedded{S: "foo"}}, Output: "foo", Err: false, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { p := &Pointer{ Parts: tc.Parts, Config: Config{ ValueTransformationHook: tc.Hook, }, } actual, err := p.Get(tc.Input) if (err != nil) != tc.Err { t.Fatalf("err: %s", err) } if err != nil { return } if !reflect.DeepEqual(actual, tc.Output) { t.Fatalf("bad: %#v", actual) } }) } } func TestPointerGet(t *testing.T) { type testStringType string type testIntType int cases := []struct { Name string Parts []string TagName string Input interface{} Output interface{} Err bool }{ { "empty", []string{}, "", 42, 42, false, }, { "nil", nil, "", 42, 42, false, }, { "map key", []string{"foo"}, "", map[string]interface{}{"foo": "bar"}, "bar", false, }, { "map key type change", []string{"foo"}, "", map[testStringType]interface{}{"foo": "bar"}, "bar", false, }, { "map key type change non-string", []string{"42"}, "", map[testIntType]interface{}{42: "bar"}, "bar", false, }, { "map key missing", []string{"foo"}, "", map[string]interface{}{"bar": "baz"}, nil, true, }, { "map key number", []string{"42"}, "", map[int]interface{}{42: "baz"}, "baz", false, }, { "map recursive", []string{"foo", "42"}, "", map[string]interface{}{ "foo": map[int]interface{}{ 42: "baz", }, }, "baz", false, }, { "slice key", []string{"3"}, "", []interface{}{"a", "b", "c", "d", "e"}, "d", false, }, { "slice key non-existent", []string{"7"}, "", []interface{}{"a", "b", "c", "d", "e"}, nil, true, }, { "slice key below zero", []string{"-1"}, "", []interface{}{"a", "b", "c", "d", "e"}, nil, true, }, { "array key", []string{"3"}, "", &[5]interface{}{"a", "b", "c", "d", "e"}, "d", false, }, { "struct key", []string{"Key"}, "", &struct{ Key string }{Key: "foo"}, "foo", false, }, { "struct tag", []string{"synthetic-name"}, "", &struct { Key string `pointer:"synthetic-name"` }{Key: "foo"}, "foo", false, }, { "struct tag alt name", []string{"synthetic-name"}, "altptr", &struct { Key string `altptr:"synthetic-name"` Other string `pointer:"synthetic-name"` }{Key: "foo", Other: "bar"}, "foo", false, }, { "struct tag ignore", []string{"Key"}, "altptr", &struct { Key string `altptr:"-"` }{Key: "foo"}, "", true, }, { "struct tag ignore and override", []string{"X"}, "", &struct { X string `pointer:"-"` Y string `pointer:"X"` }{X: "foo", Y: "bar"}, "bar", false, }, { "struct tag ignore after comma", []string{"synthetic"}, "pointer", &struct { Key string `pointer:"synthetic,name"` }{Key: "foo"}, "foo", false, }, { "struct tag invalid", []string{"synthetic|name"}, "", &struct { Key string `pointer:"synthetic|name"` }{Key: "foo"}, "", true, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { p := &Pointer{Parts: tc.Parts, Config: Config{TagName: tc.TagName}} actual, err := p.Get(tc.Input) if (err != nil) != tc.Err { t.Fatalf("err: %s", err) } if err != nil { return } if !reflect.DeepEqual(actual, tc.Output) { t.Fatalf("bad: %#v", actual) } }) } } pointerstructure-1.2.1/go.mod000066400000000000000000000001501414025774100162630ustar00rootroot00000000000000module github.com/mitchellh/pointerstructure go 1.13 require github.com/mitchellh/mapstructure v1.4.1 pointerstructure-1.2.1/go.sum000066400000000000000000000002711414025774100163140ustar00rootroot00000000000000github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= pointerstructure-1.2.1/parse.go000066400000000000000000000024111414025774100166200ustar00rootroot00000000000000package pointerstructure import ( "fmt" "strings" ) // Parse parses a pointer from the input string. The input string // is expected to follow the format specified by RFC 6901: '/'-separated // parts. Each part can contain escape codes to contain '/' or '~'. func Parse(input string) (*Pointer, error) { // Special case the empty case if input == "" { return &Pointer{}, nil } // We expect the first character to be "/" if input[0] != '/' { return nil, fmt.Errorf( "parse Go pointer %q: %w", input, ErrParse) } // Trim out the first slash so we don't have to +1 every index input = input[1:] // Parse out all the parts var parts []string lastSlash := -1 for i, r := range input { if r == '/' { parts = append(parts, input[lastSlash+1:i]) lastSlash = i } } // Add last part parts = append(parts, input[lastSlash+1:]) // Process each part for string replacement for i, p := range parts { // Replace ~1 followed by ~0 as specified by the RFC parts[i] = strings.Replace( strings.Replace(p, "~1", "/", -1), "~0", "~", -1) } return &Pointer{Parts: parts}, nil } // MustParse is like Parse but panics if the input cannot be parsed. func MustParse(input string) *Pointer { p, err := Parse(input) if err != nil { panic(err) } return p } pointerstructure-1.2.1/parse_test.go000066400000000000000000000020571414025774100176650ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" "testing" ) func TestParse(t *testing.T) { cases := []struct { Name string Input string Expected []string Err bool }{ { "empty", "", nil, false, }, { "relative", "foo", nil, true, }, { "basic", "/foo/bar", []string{"foo", "bar"}, false, }, { "three parts", "/foo/bar/baz", []string{"foo", "bar", "baz"}, false, }, { "escaped /", "/foo/a~1b", []string{"foo", "a/b"}, false, }, { "escaped ~", "/foo/a~0b", []string{"foo", "a~b"}, false, }, { "escaped ~1", "/foo/a~01b", []string{"foo", "a~1b"}, false, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { p, err := Parse(tc.Input) if (err != nil) != tc.Err { t.Fatalf("err: %s", err) } if err != nil { return } if p == nil { t.Fatal("nil pointer") } if !reflect.DeepEqual(p.Parts, tc.Expected) { t.Fatalf("bad: %#v", p.Parts) } }) } } pointerstructure-1.2.1/pointer.go000066400000000000000000000106061414025774100171730ustar00rootroot00000000000000// Package pointerstructure provides functions for identifying a specific // value within any Go structure using a string syntax. // // The syntax used is based on JSON Pointer (RFC 6901). package pointerstructure import ( "fmt" "reflect" "strings" "github.com/mitchellh/mapstructure" ) // ValueTransformationHookFn transforms a Go data structure into another. // This is useful for situations where you want the JSON Pointer to not be an // exact match to the structure of the Go struct or map, for example when // working with protocol buffers' well-known types. type ValueTransformationHookFn func(reflect.Value) reflect.Value type Config struct { // The tag name that pointerstructure reads for field names. This // defaults to "pointer" TagName string // ValueTransformationHook is called on each reference token within the // provided JSON Pointer when Get is used. The returned value from this // hook is then used for matching for all following parts of the JSON // Pointer. If this returns a nil interface Get will return an error. ValueTransformationHook ValueTransformationHookFn } // Pointer represents a pointer to a specific value. You can construct // a pointer manually or use Parse. type Pointer struct { // Parts are the pointer parts. No escape codes are processed here. // The values are expected to be exact. If you have escape codes, use // the Parse functions. Parts []string // Config is the configuration controlling how items are looked up // in structures. Config Config } // Get reads the value at the given pointer. // // This is a shorthand for calling Parse on the pointer and then calling Get // on that result. An error will be returned if the value cannot be found or // there is an error with the format of pointer. func Get(value interface{}, pointer string) (interface{}, error) { p, err := Parse(pointer) if err != nil { return nil, err } return p.Get(value) } // Set sets the value at the given pointer. // // This is a shorthand for calling Parse on the pointer and then calling Set // on that result. An error will be returned if the value cannot be found or // there is an error with the format of pointer. // // Set returns the complete document, which might change if the pointer value // points to the root (""). func Set(doc interface{}, pointer string, value interface{}) (interface{}, error) { p, err := Parse(pointer) if err != nil { return nil, err } return p.Set(doc, value) } // String returns the string value that can be sent back to Parse to get // the same Pointer result. func (p *Pointer) String() string { if len(p.Parts) == 0 { return "" } // Copy the parts so we can convert back the escapes result := make([]string, len(p.Parts)) copy(result, p.Parts) for i, p := range p.Parts { result[i] = strings.Replace( strings.Replace(p, "~", "~0", -1), "/", "~1", -1) } return "/" + strings.Join(result, "/") } // Parent returns a pointer to the parent element of this pointer. // // If Pointer represents the root (empty parts), a pointer representing // the root is returned. Therefore, to check for the root, IsRoot() should be // called. func (p *Pointer) Parent() *Pointer { // If this is root, then we just return a new root pointer. We allocate // a new one though so this can still be modified. if p.IsRoot() { return &Pointer{} } parts := make([]string, len(p.Parts)-1) copy(parts, p.Parts[:len(p.Parts)-1]) return &Pointer{ Parts: parts, Config: p.Config, } } // IsRoot returns true if this pointer represents the root document. func (p *Pointer) IsRoot() bool { return len(p.Parts) == 0 } // coerce is a helper to coerce a value to a specific type if it must // and if its possible. If it isn't possible, an error is returned. func coerce(value reflect.Value, to reflect.Type) (reflect.Value, error) { // If the value is already assignable to the type, then let it go if value.Type().AssignableTo(to) { return value, nil } // If a direct conversion is possible, do that if value.Type().ConvertibleTo(to) { return value.Convert(to), nil } // Create a new value to hold our result result := reflect.New(to) // Decode if err := mapstructure.WeakDecode(value.Interface(), result.Interface()); err != nil { return result, fmt.Errorf( "%w %#v to type %s", ErrConvert, value.Interface(), to.String()) } // We need to indirect the value since reflect.New always creates a pointer return reflect.Indirect(result), nil } pointerstructure-1.2.1/pointer_examples_test.go000066400000000000000000000012651414025774100221310ustar00rootroot00000000000000package pointerstructure import ( "fmt" ) func ExampleGet() { complex := map[string]interface{}{ "alice": 42, "bob": []interface{}{ map[string]interface{}{ "name": "Bob", }, }, } value, err := Get(complex, "/bob/0/name") if err != nil { panic(err) } fmt.Printf("%s", value) // Output: // Bob } func ExampleSet() { complex := map[string]interface{}{ "alice": 42, "bob": []interface{}{ map[string]interface{}{ "name": "Bob", }, }, } value, err := Set(complex, "/bob/0/name", "Alice") if err != nil { panic(err) } value, err = Get(complex, "/bob/0/name") if err != nil { panic(err) } fmt.Printf("%s", value) // Output: // Alice } pointerstructure-1.2.1/pointer_test.go000066400000000000000000000017131414025774100202310ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" "testing" ) func TestPointerParent(t *testing.T) { cases := []struct { Name string Input []string Expected []string }{ { "basic", []string{"foo", "bar"}, []string{"foo"}, }, { "single element", []string{"foo"}, []string{}, }, { "root", nil, nil, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { p := &Pointer{Parts: tc.Input} result := p.Parent() actual := result.Parts if !reflect.DeepEqual(actual, tc.Expected) { t.Fatalf("bad: %#v", actual) } }) } } func TestPointerString(t *testing.T) { cases := []string{ "/foo", "/foo/bar", "/foo/bar~0", "/foo/bar~1", "/foo/bar~01/baz", } for i, tc := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { p := MustParse(tc) actual := p.String() if actual != tc { t.Fatalf("bad: %#v", actual) } }) } } pointerstructure-1.2.1/set.go000066400000000000000000000061241414025774100163060ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" ) // Set writes a value v to the pointer p in structure s. // // The structures s must have non-zero values set up to this pointer. // For example, if setting "/bob/0/name", then "/bob/0" must be set already. // // The returned value is potentially a new value if this pointer represents // the root document. Otherwise, the returned value will always be s. func (p *Pointer) Set(s, v interface{}) (interface{}, error) { // if we represent the root doc, return that if len(p.Parts) == 0 { return v, nil } // Save the original since this is going to be our return value originalS := s // Get the parent value var err error s, err = p.Parent().Get(s) if err != nil { return nil, err } // Map for lookup of getter to call for type funcMap := map[reflect.Kind]setFunc{ reflect.Array: p.setSlice, reflect.Map: p.setMap, reflect.Slice: p.setSlice, } val := reflect.ValueOf(s) for val.Kind() == reflect.Interface { val = val.Elem() } for val.Kind() == reflect.Ptr { val = reflect.Indirect(val) } f, ok := funcMap[val.Kind()] if !ok { return nil, fmt.Errorf("set %s: %w: %s", p, ErrInvalidKind, val.Kind()) } result, err := f(originalS, val, reflect.ValueOf(v)) if err != nil { return nil, fmt.Errorf("set %s: %w", p, err) } return result, nil } type setFunc func(interface{}, reflect.Value, reflect.Value) (interface{}, error) func (p *Pointer) setMap(root interface{}, m, value reflect.Value) (interface{}, error) { part := p.Parts[len(p.Parts)-1] key, err := coerce(reflect.ValueOf(part), m.Type().Key()) if err != nil { return root, err } elem, err := coerce(value, m.Type().Elem()) if err != nil { return root, err } // Set the key m.SetMapIndex(key, elem) return root, nil } func (p *Pointer) setSlice(root interface{}, s, value reflect.Value) (interface{}, error) { // Coerce the value, we'll need that no matter what value, err := coerce(value, s.Type().Elem()) if err != nil { return root, err } // If the part is the special "-", that means to append it (RFC6901 4.) part := p.Parts[len(p.Parts)-1] if part == "-" { return p.setSliceAppend(root, s, value) } // Coerce the key to an int idxVal, err := coerce(reflect.ValueOf(part), reflect.TypeOf(42)) if err != nil { return root, err } idx := int(idxVal.Int()) // Verify we're within bounds if idx < 0 || idx >= s.Len() { return root, fmt.Errorf( "index %d is %w (length = %d)", idx, ErrOutOfRange, s.Len()) } // Set the key s.Index(idx).Set(value) return root, nil } func (p *Pointer) setSliceAppend(root interface{}, s, value reflect.Value) (interface{}, error) { // Coerce the value, we'll need that no matter what. This should // be a no-op since we expect it to be done already, but there is // a fast-path check for that in coerce so do it anyways. value, err := coerce(value, s.Type().Elem()) if err != nil { return root, err } // We can assume "s" is the parent of pointer value. We need to actually // write s back because Append can return a new slice. return p.Parent().Set(root, reflect.Append(s, value).Interface()) } pointerstructure-1.2.1/set_test.go000066400000000000000000000034661414025774100173530ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" "testing" ) func TestPointerSet(t *testing.T) { type testStringType string type testIntType int cases := []struct { Name string Parts []string Doc interface{} Value interface{} Output interface{} Err bool }{ { "empty", []string{}, 42, []string{}, []string{}, false, }, { "nil", nil, 42, 84, 84, false, }, { "map key", []string{"foo"}, map[string]interface{}{"foo": "bar"}, "baz", map[string]interface{}{"foo": "baz"}, false, }, { "nested map key", []string{"foo", "bar"}, map[string]interface{}{ "foo": map[string]interface{}{ "bar": 42, }, }, "baz", map[string]interface{}{ "foo": map[string]interface{}{ "bar": "baz", }, }, false, }, { "map value type coercse", []string{"foo"}, map[string]int{"foo": 12}, "42", map[string]int{"foo": 42}, false, }, { "slice index", []string{"0"}, []interface{}{42}, "baz", []interface{}{"baz"}, false, }, { "slice index non-zero", []string{"1"}, []interface{}{42, 84, 168}, "baz", []interface{}{42, "baz", 168}, false, }, { "slice index append", []string{"-"}, []interface{}{42}, "baz", []interface{}{42, "baz"}, false, }, { "slice index value coerce", []string{"0"}, []int{42}, "84", []int{84}, false, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { p := &Pointer{Parts: tc.Parts} actual, err := p.Set(tc.Doc, tc.Value) if (err != nil) != tc.Err { t.Fatalf("err: %s", err) } if err != nil { return } if !reflect.DeepEqual(actual, tc.Output) { t.Fatalf("bad: %#v != %#v", actual, tc.Output) } }) } } pointerstructure-1.2.1/sort.go000066400000000000000000000021471414025774100165030ustar00rootroot00000000000000package pointerstructure import ( "sort" ) // Sort does an in-place sort of the pointers so that they are in order // of least specific to most specific alphabetized. For example: // "/foo", "/foo/0", "/qux" // // This ordering is ideal for applying the changes in a way that ensures // that parents are set first. func Sort(p []*Pointer) { sort.Sort(PointerSlice(p)) } // PointerSlice is a slice of pointers that adheres to sort.Interface type PointerSlice []*Pointer func (p PointerSlice) Len() int { return len(p) } func (p PointerSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p PointerSlice) Less(i, j int) bool { // Equal number of parts, do a string compare per part for idx, ival := range p[i].Parts { // If we're passed the length of p[j] parts, then we're done if idx >= len(p[j].Parts) { break } // Compare the values if they're not equal jval := p[j].Parts[idx] if ival != jval { return ival < jval } } // Equal prefix, take the shorter if len(p[i].Parts) != len(p[j].Parts) { return len(p[i].Parts) < len(p[j].Parts) } // Equal, it doesn't matter return false } pointerstructure-1.2.1/sort_test.go000066400000000000000000000015251414025774100175410ustar00rootroot00000000000000package pointerstructure import ( "fmt" "reflect" "sort" "testing" ) func TestSort_interface(t *testing.T) { var _ sort.Interface = new(PointerSlice) } func TestSort(t *testing.T) { cases := []struct { Input []string Output []string }{ { []string{"/foo", ""}, []string{"", "/foo"}, }, { []string{"/foo", "", "/foo/0"}, []string{"", "/foo", "/foo/0"}, }, { []string{"/foo", "", "/bar/0"}, []string{"", "/bar/0", "/foo"}, }, } for i, tc := range cases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { var ps []*Pointer for _, raw := range tc.Input { ps = append(ps, MustParse(raw)) } Sort(ps) result := make([]string, len(ps)) for i, p := range ps { result[i] = p.String() } if !reflect.DeepEqual(result, tc.Output) { t.Fatalf("bad: %#v", result) } }) } }