pax_global_header00006660000000000000000000000064151723541640014522gustar00rootroot0000000000000052 comment=2c5581ecc47d98d9ea4dd83c333526ddab8b4e7a golang-github-tiendc-go-deepcopy-1.7.2/000077500000000000000000000000001517235416400177355ustar00rootroot00000000000000golang-github-tiendc-go-deepcopy-1.7.2/.codecov.yml000066400000000000000000000013121517235416400221550ustar00rootroot00000000000000coverage: range: 80..100 round: up precision: 2 status: project: # measuring the overall project coverage default: # context, you can create multiple ones with custom titles enabled: yes # must be yes|true to enable this status target: 85% # specify the target coverage for each commit status # option: "auto" (must increase from parent commit or pull request base) # option: "X%" a static target percentage to hit if_not_found: success # if parent is not found report status as success, error, or failure if_ci_failed: error # if ci fails report status as success, error, or failure golang-github-tiendc-go-deepcopy-1.7.2/.github/000077500000000000000000000000001517235416400212755ustar00rootroot00000000000000golang-github-tiendc-go-deepcopy-1.7.2/.github/workflows/000077500000000000000000000000001517235416400233325ustar00rootroot00000000000000golang-github-tiendc-go-deepcopy-1.7.2/.github/workflows/go.yml000066400000000000000000000020051517235416400244570ustar00rootroot00000000000000name: Tests on: push: branches: ['*'] tags: ['v*'] pull_request: branches: ['*'] jobs: build: runs-on: ubuntu-latest strategy: matrix: go: ["1.18.x", "1.23.x", "1.25.x"] include: - go: 1.18.x latest: true steps: - name: Setup Go uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - name: Checkout code uses: actions/checkout@v5 - name: Load cached dependencies uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Lint uses: golangci/golangci-lint-action@v8 with: version: v2.4.0 args: --timeout=3m -v - name: Test run: make cover - name: Upload coverage to codecov.io uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} golang-github-tiendc-go-deepcopy-1.7.2/.gitignore000066400000000000000000000002351517235416400217250ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test *.test *.out # Dependency vendor/ # Goland, vscode, OS .idea .vscode .DS_Store golang-github-tiendc-go-deepcopy-1.7.2/.golangci.yaml000066400000000000000000000027231517235416400224660ustar00rootroot00000000000000version: "2" linters: enable: - bodyclose - contextcheck - copyloopvar - dogsled - err113 - errname - errorlint - exhaustive - forbidigo - forcetypeassert - funlen - gocognit - goconst - gocritic - gocyclo - gosec - lll - misspell - mnd - nakedret - nestif - nilerr - rowserrcheck - staticcheck - unconvert - unparam - whitespace settings: funlen: lines: 120 statements: 80 gocognit: min-complexity: 30 gocyclo: min-complexity: 30 lll: line-length: 120 misspell: locale: US exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - contextcheck - err113 - forcetypeassert - funlen - gocognit - gocyclo - gosec - mnd - staticcheck - unused - wrapcheck path: _test\.go paths: - third_party$ - builtin$ - examples$ formatters: enable: - gci - gofmt - goimports settings: gci: sections: - standard - default - prefix(github.com/tiendc/go-deepcopy) goimports: local-prefixes: - github.com/golangci/golangci-lint exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ golang-github-tiendc-go-deepcopy-1.7.2/LICENSE000066400000000000000000000020471517235416400207450ustar00rootroot00000000000000MIT License Copyright (c) 2023 tiendc 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. golang-github-tiendc-go-deepcopy-1.7.2/Makefile000066400000000000000000000007311517235416400213760ustar00rootroot00000000000000all: lint test prepare: @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.4.0 build: @go build -v ./... test: @go test -cover -v ./... cover: @go test -race -coverprofile=coverage.txt -coverpkg=./... ./... @go tool cover -html=coverage.txt -o coverage.html lint: golangci-lint --timeout=5m0s run -v ./... bench: go test -benchmem -count 100 -bench . mod: go mod tidy && go mod vendor golang-github-tiendc-go-deepcopy-1.7.2/README.md000066400000000000000000000276661517235416400212350ustar00rootroot00000000000000[![Go Version][gover-img]][gover] [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![GoReport][rpt-img]][rpt] # Fast deep-copy library for Go ## Functionalities - True deep copy - Very fast (see [benchmarks](#benchmarks) section) - Ability to copy almost all Go types (number, string, bool, function, slice, map, struct) - Ability to copy data between convertible types (for example: copy from `int` to `float`) - Ability to copy between `pointers` and `values` (for example: copy from `*int` to `int`) - Ability to copy values via copying methods of destination types - Ability to copy inherited fields from embedded structs - Ability to set a destination struct field as `nil` if it is `zero` - Ability to copy unexported struct fields - Ability to copy with extra configuration settings ## Installation ```shell go get github.com/tiendc/go-deepcopy ``` ## Usage - [First example](#first-example) - [Copy between struct fields with different names](#copy-between-struct-fields-with-different-names) - [Skip copying struct fields](#skip-copying-struct-fields) - [Require copying for struct fields](#require-copying-for-struct-fields) - [Copy struct fields via struct methods](#copy-struct-fields-via-struct-methods) - [Copy inherited fields from embedded structs](#copy-inherited-fields-from-embedded-structs) - [Set destination struct fields as `nil` on `zero`](#set-destination-struct-fields-as-nil-on-zero) - [PostCopy event method for structs](#postcopy-event-method-for-structs) - [Copy between structs and maps](#copy-between-structs-and-maps) - [Configure extra copying behaviors](#configure-extra-copying-behaviors) ### First example [Playground](https://go.dev/play/p/CrP_rZlkNzm) ```go type SS struct { B bool } type S struct { I int U uint St string V SS } type DD struct { B bool } type D struct { I int U uint X string V DD } src := []S{{I: 1, U: 2, St: "3", V: SS{B: true}}, {I: 11, U: 22, St: "33", V: SS{B: false}}} var dst []D _ = deepcopy.Copy(&dst, &src) // NOTE: it is recommended that you always pass address of `src` to the function // when copy structs having unexported fields such as `time.Time`. for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {I:1 U:2 X: V:{B:true}} // {I:11 U:22 X: V:{B:false}} ``` ### Copy between struct fields with different names [Playground](https://go.dev/play/p/WchsGRns0O-) ```go type S struct { X int `copy:"Key"` // 'Key' is used to match the fields U uint St string } type D struct { Y int `copy:"Key"` U uint } src := []S{{X: 1, U: 2, St: "3"}, {X: 11, U: 22, St: "33"}} var dst []D _ = deepcopy.Copy(&dst, &src) for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {Y:1 U:2} // {Y:11 U:22} ``` ### Skip copying struct fields - By default, matching fields will be copied. If you don't want to copy a field, use tag value `-`. [Playground](https://go.dev/play/p/8KPe1Susjp1) ```go // S and D both have `I` field, but we don't want to copy it // Tag `-` can be used in both struct definitions or just in one type S struct { I int U uint St string } type D struct { I int `copy:"-"` U uint } src := []S{{I: 1, U: 2, St: "3"}, {I: 11, U: 22, St: "33"}} var dst []D _ = deepcopy.Copy(&dst, &src) for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {I:0 U:2} // {I:0 U:22} ``` ### Require copying for struct fields [Playground](https://go.dev/play/p/yDlLsv1wBnf) ```go type S struct { U uint St string } type D struct { I int `copy:",required"` U uint } src := []S{{U: 2, St: "3"}, {U: 22, St: "33"}} var dst []D err := deepcopy.Copy(&dst, &src) if err != nil { fmt.Println("error:", err) } // Output: // error: ErrFieldRequireCopying: struct field 'main.D[I]' requires copying ``` ### Copy struct fields via struct methods - **Note**: If a copying method is defined within a struct, it will have higher priority than matching fields. [Playground 1](https://go.dev/play/p/rCawGa5AZh3) / [Playground 2](https://go.dev/play/p/vDOhHXyUoyD) ```go type S struct { X int U uint St string } type D struct { x string U uint } // Copy method should be in form of `Copy` (or key) and return `error` type func (d *D) CopyX(i int) error { d.x = fmt.Sprintf("%d", i) return nil } ``` ```go src := []S{{X: 1, U: 2, St: "3"}, {X: 11, U: 22, St: "33"}} var dst []D _ = deepcopy.Copy(&dst, &src) for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {x:1 U:2} // {x:11 U:22} ``` ### Copy inherited fields from embedded structs - This is default behaviour from v1, for lower versions, you can use custom copying function to achieve the same result. [Playground 1](https://go.dev/play/p/Zjj12AMRYXt) / [Playground 2](https://go.dev/play/p/cJGLqpPVHXI) ```go type SBase struct { St string } // Source struct has an embedded one type S struct { SBase I int } // but destination struct doesn't type D struct { I int St string } src := []S{{I: 1, SBase: SBase{"abc"}}, {I: 11, SBase: SBase{"xyz"}}} var dst []D _ = deepcopy.Copy(&dst, &src) for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {I:1 St:abc} // {I:11 St:xyz} ``` ### Set destination struct fields as `nil` on `zero` - This is a new feature from v1.5.0. This applies to destination fields of type `pointer`, `interface`, `slice`, and `map`. When their values are zero after copying, they will be set as `nil`. This is very convenient when you don't want to send something like a date of `0001-01-01` to client, you want to send `null` instead. [Playground 1](https://go.dev/play/p/GO6VExVOLei) / [Playground 2](https://go.dev/play/p/u0zMHx9UWjA) / [Playground 3](https://go.dev/play/p/ZpA8DkQ9-7f) ```go // Source struct has a time.Time field type S struct { I int Time time.Time } // Destination field must be a nullable value such as `*time.Time` or `interface{}` type D struct { I int Time *time.Time `copy:",nilonzero"` // make sure to use this tag } src := []S{{I: 1, Time: time.Time{}}, {I: 11, Time: time.Now()}} var dst []D _ = deepcopy.Copy(&dst, &src) for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {I:1 Time:} (source is a zero time value, destination becomes `nil`) // {I:11 Time:2025-02-08 12:31:11...} (source is not zero, so be the destination) ``` ### `PostCopy` event method for structs - This is a new feature from v1.5.0. If a destination struct has PostCopy() method, it will be called after copying. [Playground](https://go.dev/play/p/fGhGZumaRUD) ```go type S struct { I int St string } type D struct { I int St string } // PostCopy must be defined on struct pointer, not value func (d *D) PostCopy(src any) error { d.I *= 2 d.St += d.St return nil } src := []S{{I: 1, St: "a"}, {I: 11, St: "aa"}} var dst []D _ = deepcopy.Copy(&dst, &src) for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {I:2 St:aa} // {I:22 St:aaaa} ``` ### Copy between structs and maps [Playground](https://go.dev/play/p/eS8RWB8dKmL) ```go type D struct { I int `copy:"i"` U uint `copy:"u"` } src := map[string]any{"i": 1, "u": 2, "s": "abc"} var dst D err := deepcopy.Copy(&dst, &src) if err != nil { fmt.Println("error:", err) } fmt.Printf("Result struct: %+v\n", dst) src2 := D{I: 11, U: 22} dst2 := map[string]any{} err = deepcopy.Copy(&dst2, &src2) if err != nil { fmt.Println("error:", err) } fmt.Printf("Result map: %+v\n", dst2) // Output: // Result struct: {I:1 U:2} // Result map: map[i:11 u:22] ``` ### Configure extra copying behaviors - Not allow to copy between `ptr` type and `value` (default is `allow`) [Playground](https://go.dev/play/p/ZYzGaCNwp2i) ```go type S struct { I int U uint } type D struct { I *int U uint } src := []S{{I: 1, U: 2}, {I: 11, U: 22}} var dst []D err := deepcopy.Copy(&dst, &src, deepcopy.CopyBetweenPtrAndValue(false)) fmt.Println("error:", err) // Output: // error: ErrTypeNonCopyable: int -> *int ``` - Ignore ErrTypeNonCopyable, the process will not return that kind of error, but some copyings won't be performed. [Playground 1](https://go.dev/play/p/YPz49D_oiTY) / [Playground 2](https://go.dev/play/p/DNrBJUP-rrM) ```go type S struct { I []int U uint } type D struct { I int U uint } src := []S{{I: []int{1, 2, 3}, U: 2}, {I: []int{1, 2, 3}, U: 22}} var dst []D // The copy will succeed with ignoring copy of field `I` _ = deepcopy.Copy(&dst, &src, deepcopy.IgnoreNonCopyableTypes(true)) for _, d := range dst { fmt.Printf("%+v\n", d) } // Output: // {I:0 U:2} // {I:0 U:22} ``` ## Benchmarks ### Go-DeepCopy vs ManualCopy vs Other Libs This benchmark is done on go-deepcopy v1.6.0 using Go 1.24.2 **Copy between 2 different struct types** [Benchmark code](https://gist.github.com/tiendc/0a739fd880b9aac5373de95458d54808) ``` Go-DeepCopy Go-DeepCopy-10 1734240 682.3 ns/op 344 B/op 4 allocs/op ManualCopy ManualCopy-10 32983267 35.59 ns/op 80 B/op 1 allocs/op JinzhuCopier JinzhuCopier-10 146428 8138 ns/op 912 B/op 40 allocs/op ulule/deepcopier ulule/deepcopier-10 47137 25717 ns/op 50752 B/op 550 allocs/op mohae/deepcopy mohae/deepcopy-10 597488 1828 ns/op 1208 B/op 42 allocs/op barkimedes/deepcopy barkimedes/deepcopy-10 528232 2206 ns/op 1464 B/op 15 allocs/op mitchellh/copystructure mitchellh/copystructure-10 123484 9545 ns/op 7592 B/op 191 allocs/op ``` **Copy between the same struct type** [Benchmark code](https://gist.github.com/tiendc/502725af830d454382234e8dca22dbdf) ``` Go-DeepCopy Go-DeepCopy-10 2693502 438.0 ns/op 248 B/op 4 allocs/op ManualCopy ManualCopy-10 36577604 32.17 ns/op 80 B/op 1 allocs/op JinzhuCopier JinzhuCopier-10 187950 6468 ns/op 824 B/op 39 allocs/op ulule/deepcopier ulule/deepcopier-10 50193 23170 ns/op 44768 B/op 486 allocs/op mohae/deepcopy mohae/deepcopy-10 1000000 1083 ns/op 640 B/op 26 allocs/op barkimedes/deepcopy barkimedes/deepcopy-10 886911 1345 ns/op 888 B/op 13 allocs/op mitchellh/copystructure mitchellh/copystructure-10 212216 5559 ns/op 4552 B/op 116 allocs/op ``` ## Contributing - You are welcome to make pull requests for new functions and bug fixes. ## License - [MIT License](LICENSE) [doc-img]: https://pkg.go.dev/badge/github.com/tiendc/go-deepcopy [doc]: https://pkg.go.dev/github.com/tiendc/go-deepcopy [gover-img]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue [gover]: https://img.shields.io/badge/Go-%3E%3D%201.18-blue [ci-img]: https://github.com/tiendc/go-deepcopy/actions/workflows/go.yml/badge.svg [ci]: https://github.com/tiendc/go-deepcopy/actions/workflows/go.yml [cov-img]: https://codecov.io/gh/tiendc/go-deepcopy/branch/main/graph/badge.svg [cov]: https://codecov.io/gh/tiendc/go-deepcopy [rpt-img]: https://goreportcard.com/badge/github.com/tiendc/go-deepcopy [rpt]: https://goreportcard.com/report/github.com/tiendc/go-deepcopy golang-github-tiendc-go-deepcopy-1.7.2/base_copier.go000066400000000000000000000066311517235416400225450ustar00rootroot00000000000000package deepcopy import ( "fmt" "reflect" ) // copier base interface defines Copy function type copier interface { Copy(dst, src reflect.Value) error } // nopCopier no-op copier type nopCopier struct { } // Copy implementation of Copy function for no-op copier func (c *nopCopier) Copy(dst, src reflect.Value) error { return nil } var defaultNopCopier = &nopCopier{} // value2PtrCopier data structure of copier that copies from a value to a pointer type value2PtrCopier struct { ctx *Context copier copier } // Copy implementation of Copy function for value-to-pointer copier func (c *value2PtrCopier) Copy(dst, src reflect.Value) error { if dst.IsNil() { dst.Set(reflect.New(dst.Type().Elem())) } dst = dst.Elem() return c.copier.Copy(dst, src) } func (c *value2PtrCopier) init(dstType, srcType reflect.Type) (err error) { c.copier, err = buildCopier(c.ctx, dstType.Elem(), srcType) return } // ptr2ValueCopier data structure of copier that copies from a pointer to a value type ptr2ValueCopier struct { ctx *Context copier copier } // Copy implementation of Copy function for pointer-to-value copier func (c *ptr2ValueCopier) Copy(dst, src reflect.Value) error { src = src.Elem() if !src.IsValid() { dst.Set(reflect.Zero(dst.Type())) // NOTE: Go1.18 has no SetZero return nil } return c.copier.Copy(dst, src) } func (c *ptr2ValueCopier) init(dstType, srcType reflect.Type) (err error) { c.copier, err = buildCopier(c.ctx, dstType, srcType.Elem()) return } // ptr2PtrCopier data structure of copier that copies from a pointer to a pointer type ptr2PtrCopier struct { ctx *Context copier copier } // Copy implementation of Copy function for pointer-to-pointer copier func (c *ptr2PtrCopier) Copy(dst, src reflect.Value) error { src = src.Elem() if !src.IsValid() { dst.Set(reflect.Zero(dst.Type())) // NOTE: Go1.18 has no SetZero return nil } if dst.IsNil() { dst.Set(reflect.New(dst.Type().Elem())) } dst = dst.Elem() return c.copier.Copy(dst, src) } func (c *ptr2PtrCopier) init(dstType, srcType reflect.Type) (err error) { c.copier, err = buildCopier(c.ctx, dstType.Elem(), srcType.Elem()) return } // directCopier copier that does copying by assigning `src` value to `dst` directly type directCopier struct { } func (c *directCopier) Copy(dst, src reflect.Value) error { dst.Set(src) return nil } var defaultDirectCopier = &directCopier{} // convCopier copier that does copying with converting `src` value to `dst` type type convCopier struct { } func (c *convCopier) Copy(dst, src reflect.Value) error { dst.Set(src.Convert(dst.Type())) return nil } var defaultConvCopier = &convCopier{} // inlineCopier copier that does copying on the fly. // This copier is usually used to avoid circular reference. type inlineCopier struct { ctx *Context dstType reflect.Type srcType reflect.Type } func (c *inlineCopier) Copy(dst, src reflect.Value) error { cp, err := buildCopier(c.ctx, c.dstType, c.srcType) if err != nil { return err } return cp.Copy(dst, src) } // methodCopier copier that calls a copying method type methodCopier struct { dstMethod int } func (c *methodCopier) Copy(dst, src reflect.Value) (err error) { dst = dst.Addr().Method(c.dstMethod) errVal := dst.Call([]reflect.Value{src})[0] if errVal.IsNil() { return nil } err, ok := errVal.Interface().(error) if !ok { return fmt.Errorf("%w: copying method returned non-error value", ErrTypeInvalid) } return err } golang-github-tiendc-go-deepcopy-1.7.2/base_copier_test.go000066400000000000000000000104741517235416400236040ustar00rootroot00000000000000package deepcopy import ( "testing" "github.com/stretchr/testify/assert" ) func Test_Copy_ptr2ptr(t *testing.T) { t.Run("#1: int-ptr -> int-ptr", func(t *testing.T) { var s *int = ptrOf(111) var d *int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, *d) assert.True(t, d != s) }) t.Run("#2: int-ptr -> int-ptr (dst has set)", func(t *testing.T) { var s *int = ptrOf(111) var d *int = ptrOf(222) err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, *d) assert.True(t, d != s) }) t.Run("#3: nil int-ptr -> int-ptr", func(t *testing.T) { var s *int var d *int err := Copy(&d, s) assert.Nil(t, err) assert.Nil(t, d) }) t.Run("#4: nil int-ptr -> int-ptr (dst has set)", func(t *testing.T) { var s *int var d *int = ptrOf(111) err := Copy(&d, s) assert.Nil(t, err) assert.Nil(t, d) }) } func Test_Copy_ptr2ptr_error(t *testing.T) { t.Run("#1: int-ptr -> struct-ptr (error)", func(t *testing.T) { type DD struct { I int } var s *int = ptrOf(111) var d *DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) } func Test_Copy_ptr2value(t *testing.T) { t.Run("#1: int-ptr -> int", func(t *testing.T) { var s *int = ptrOf(111) var d int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, d) }) t.Run("#2: int-ptr -> int (dst has set)", func(t *testing.T) { var s *int = ptrOf(111) var d int = 222 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, d) }) t.Run("#3: nil int-ptr -> int", func(t *testing.T) { var s *int var d int = 111 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 0, d) }) } func Test_Copy_ptr2value_error(t *testing.T) { t.Run("#1: int-ptr -> struct (error)", func(t *testing.T) { type DD struct { I int } var s *int = ptrOf(111) var d DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#2: ptr -> value not allow", func(t *testing.T) { var s *int = ptrOf(111) var d int err := Copy(&d, s, func(ctx *Context) { ctx.CopyBetweenPtrAndValue = false }) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) } func Test_Copy_value2ptr(t *testing.T) { t.Run("#1: int -> int-ptr", func(t *testing.T) { var s int = 111 var d *int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, *d) }) t.Run("#2: int -> int-ptr (dst has set)", func(t *testing.T) { var s int = 111 var d *int = ptrOf(222) err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, *d) }) } func Test_Copy_value2ptr_error(t *testing.T) { t.Run("#1: int -> struct-ptr (error)", func(t *testing.T) { type DD struct { I int } var s int = 111 var d *DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#2: value -> ptr not allow", func(t *testing.T) { var s int = 111 var d *int err := Copy(&d, s, func(ctx *Context) { ctx.CopyBetweenPtrAndValue = false }) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) } func Test_Copy_basicTypes(t *testing.T) { t.Run("#1: int -> int", func(t *testing.T) { var s int = 111 var d int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, d) }) t.Run("#2: int -> IntT", func(t *testing.T) { var s int = 111 var d IntT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, IntT(111), d) }) t.Run("#3: int -> float64", func(t *testing.T) { var s int = 111 var d float64 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, float64(111), d) }) t.Run("#4: float64 -> int", func(t *testing.T) { var s float64 = 111.111 var d int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 111, d) }) t.Run("#5: string -> StrT", func(t *testing.T) { var s string = "abc" var d StrT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, StrT("abc"), d) }) t.Run("#6: func -> func", func(t *testing.T) { var s = func(i int) int { return i * 2 } var d func(int) int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, 2, d(1)) }) } func Test_Copy_basicTypes_error(t *testing.T) { t.Run("#1: nil dst", func(t *testing.T) { var s int = 111 var d *int err := Copy(d, s) assert.ErrorIs(t, err, ErrValueInvalid) }) t.Run("#2: func -> func (unmatched)", func(t *testing.T) { var s = func(i int) int { return i * 2 } var d func(int) float32 err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) } golang-github-tiendc-go-deepcopy-1.7.2/build_copier.go000066400000000000000000000153761517235416400227400ustar00rootroot00000000000000package deepcopy import ( "fmt" "reflect" "strings" "sync" ) // cacheKey key data structure of cached copiers type cacheKey struct { dstType reflect.Type srcType reflect.Type flags uint8 } var ( // copierCacheMap global cache for any parsed type copierCacheMap = make(map[cacheKey]copier, 10) //nolint:mnd // mu read/write cache lock mu sync.RWMutex // simpleKindMask mask for checking basic kinds such as int, string, ... simpleKindMask = func() uint32 { n := uint32(0) n |= 1 << reflect.Bool n |= 1 << reflect.String n |= 1 << reflect.Int n |= 1 << reflect.Int8 n |= 1 << reflect.Int16 n |= 1 << reflect.Int32 n |= 1 << reflect.Int64 n |= 1 << reflect.Uint n |= 1 << reflect.Uint8 n |= 1 << reflect.Uint16 n |= 1 << reflect.Uint32 n |= 1 << reflect.Uint64 n |= 1 << reflect.Float32 n |= 1 << reflect.Float64 n |= 1 << reflect.Complex64 n |= 1 << reflect.Complex128 n |= 1 << reflect.Uintptr n |= 1 << reflect.Func return n }() ) const ( // flagCopyBetweenPtrAndValue indicates copying will be performed between `pointers` and `values` flagCopyBetweenPtrAndValue = 1 // flagCopyViaCopyingMethod indicates copying will be performed via copying methods of destination types flagCopyViaCopyingMethod = 2 // flagIgnoreNonCopyableTypes indicates copying will skip copying non-copyable types without raising errors flagIgnoreNonCopyableTypes = 3 ) // prepare prepares context for copiers func (ctx *Context) prepare() { if ctx.UseGlobalCache { ctx.copierCacheMap = copierCacheMap ctx.mu = &mu } else { ctx.copierCacheMap = make(map[cacheKey]copier, 5) //nolint:mnd ctx.mu = &sync.RWMutex{} } // Recalculate the flags ctx.flags = 0 if ctx.CopyBetweenPtrAndValue { ctx.flags |= 1 << flagCopyBetweenPtrAndValue } if ctx.CopyViaCopyingMethod { ctx.flags |= 1 << flagCopyViaCopyingMethod } if ctx.IgnoreNonCopyableTypes { ctx.flags |= 1 << flagIgnoreNonCopyableTypes } } // createCacheKey creates and returns key for caching a copier func (ctx *Context) createCacheKey(dstType, srcType reflect.Type) *cacheKey { return &cacheKey{ dstType: dstType, srcType: srcType, flags: ctx.flags, } } // defaultContext creates a default context func defaultContext() *Context { return &Context{ CopyBetweenPtrAndValue: true, CopyViaCopyingMethod: true, UseGlobalCache: true, } } // buildCopier build copier for handling copy from `srcType` to `dstType` // //nolint:gocognit,gocyclo,funlen func buildCopier(ctx *Context, dstType, srcType reflect.Type) (copier copier, err error) { // Finds cached copier, returns it if found cacheKey := ctx.createCacheKey(dstType, srcType) ctx.mu.RLock() cachedCopier, cachedCopierFound := ctx.copierCacheMap[*cacheKey] ctx.mu.RUnlock() if cachedCopier != nil { return cachedCopier, nil } dstKind, srcKind := dstType.Kind(), srcType.Kind() // Trivial case if simpleKindMask&(1< 0 { if dstType == srcType { copier = defaultDirectCopier goto OnComplete } if srcType.ConvertibleTo(dstType) { copier = defaultConvCopier goto OnComplete } } if dstKind == reflect.Interface { cp := &toIfaceCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } if srcKind == reflect.Interface { cp := &fromIfaceCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } //nolint:nestif if srcKind == reflect.Pointer { if dstKind == reflect.Pointer { // ptr -> ptr cp := &ptr2PtrCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } else { // ptr -> value if !ctx.CopyBetweenPtrAndValue { goto OnNonCopyable } cp := &ptr2ValueCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } } else { if dstKind == reflect.Pointer { // value -> ptr if !ctx.CopyBetweenPtrAndValue { goto OnNonCopyable } cp := &value2PtrCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } } // Both are not Pointers if srcKind == reflect.Slice || srcKind == reflect.Array { if dstKind != reflect.Slice && dstKind != reflect.Array { goto OnNonCopyable } cp := &sliceCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } //nolint:nestif if srcKind == reflect.Struct { if dstKind == reflect.Struct { // Build a special copier for Go standard types such as time.Time, unique.Handle copier = buildCopierForStandardStructs(dstType, srcType) if copier != nil { goto OnComplete } // At this point, cached copier should not exist in the cache map. // If it exists, seems like a circular reference occurs, use an inline copier. if cachedCopierFound { return &inlineCopier{ctx: ctx, dstType: dstType, srcType: srcType}, nil } // Circular reference can happen via struct field reference. // Put a `nil` copier to the cache to mark that the copier building for the struct types is in-progress. setCachedCopier(ctx, cacheKey, nil) cp := &structCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) if err != nil { deleteCachedCopier(ctx, cacheKey) } goto OnComplete } if dstKind == reflect.Map { cp := &structToMapCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } goto OnNonCopyable } if srcKind == reflect.Map { if dstKind == reflect.Map { cp := &mapCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } if dstKind == reflect.Struct { cp := &mapToStructCopier{ctx: ctx} copier, err = cp, cp.init(dstType, srcType) goto OnComplete } goto OnNonCopyable } OnComplete: if err == nil { if copier != nil { setCachedCopier(ctx, cacheKey, copier) return copier, err } } else { return nil, err } OnNonCopyable: if ctx.IgnoreNonCopyableTypes { return defaultNopCopier, nil } return nil, fmt.Errorf("%w: %v -> %v", ErrTypeNonCopyable, srcType, dstType) } func buildCopierForStandardStructs(dstType, srcType reflect.Type) copier { switch srcType.PkgPath() { // When copy time.Time -> time.Time or derived type case "time": if srcType.Name() == "Time" { if dstType == srcType { return defaultDirectCopier } if dstType.ConvertibleTo(srcType) { return defaultConvCopier } } // When copy unique.Handle[T] -> unique.Handle[T] or derived type case "unique": if strings.HasPrefix(srcType.Name(), "Handle[") { if dstType == srcType { return defaultDirectCopier } if dstType.ConvertibleTo(srcType) { return defaultConvCopier } } } return nil } func setCachedCopier(ctx *Context, cacheKey *cacheKey, cp copier) { ctx.mu.Lock() ctx.copierCacheMap[*cacheKey] = cp ctx.mu.Unlock() } func deleteCachedCopier(ctx *Context, cacheKey *cacheKey) { ctx.mu.Lock() delete(ctx.copierCacheMap, *cacheKey) ctx.mu.Unlock() } golang-github-tiendc-go-deepcopy-1.7.2/build_copier_test.go000066400000000000000000000012261517235416400237640ustar00rootroot00000000000000package deepcopy import ( "testing" "github.com/stretchr/testify/assert" ) func Test_Ctx_prepare(t *testing.T) { ctx := defaultContext() CopyBetweenPtrAndValue(false)(ctx) CopyBetweenStructFieldAndMethod(false)(ctx) IgnoreNonCopyableTypes(false)(ctx) ctx.prepare() assert.Equal(t, uint8(0), ctx.flags) UseGlobalCache(false)(ctx) ctx.prepare() assert.True(t, &copierCacheMap != &ctx.copierCacheMap) assert.True(t, &mu != ctx.mu) UseGlobalCache(true)(ctx) ctx.prepare() ctx.copierCacheMap[cacheKey{flags: 1}] = nil copierCacheMap[cacheKey{flags: 2}] = nil assert.Equal(t, copierCacheMap, ctx.copierCacheMap) assert.True(t, &mu == ctx.mu) } golang-github-tiendc-go-deepcopy-1.7.2/data_test.go000066400000000000000000000043601517235416400222370ustar00rootroot00000000000000package deepcopy import ( "errors" "reflect" ) type errorCopier struct { } func (c *errorCopier) Copy(dst, src reflect.Value) error { return errTest } type StrT string type IntT int func ptrOf[T any](v T) *T { return &v } var ( errTest = errors.New("err test") ) type srcStruct2 struct { S string I int U uint32 F float64 B bool Method int } type dstStruct2 struct { S string I int U uint32 F float64 B bool MethodVal int `copy:"Method"` } func (d *dstStruct2) CopyMethod(v int) error { d.MethodVal = v * 2 return nil } func (d *dstStruct2) EqualSrcStruct2(s *srcStruct2) bool { return d.S == s.S && d.I == s.I && d.U == s.U && d.F == s.F && d.B == s.B && d.MethodVal == s.Method*2 } type srcStruct1 struct { S string I int U uint32 F float64 B bool II []int UU []uint SS []string V srcStruct2 VV []srcStruct2 M1 map[int]string M2 *map[int8]int8 M3 map[int]int M4 map[[3]int]*srcStruct2 NilP2V *int P2V *int V2P int S2A []string MatchedX string `copy:"Matched"` UnmatchedX string `copy:"UnmatchedX"` } type dstStruct1 struct { S StrT I int U uint32 F float64 B *bool II []int UU []uint SS []*StrT V dstStruct2 VV []dstStruct2 M1 map[int]string M2 map[int8]int8 M3 *map[int]IntT M4 map[[3]int]*dstStruct2 NilP2V int P2V int V2P *int S2A [3]string MatchedY string `copy:"Matched"` UnmatchedY string `copy:"UnmatchedY"` } var ( srcStructA = srcStruct2{ S: "string", I: 10, U: 100, F: 1.234, B: true, Method: 1234, } srcStructB = srcStruct2{ S: "string", I: 10, U: 100, F: 1.234, B: true, Method: 4321, } p2v = 1234 //nolint srcStruct = srcStruct1{ S: "string", I: 10, U: 10, F: 1.234, B: true, II: []int{1, 2, 3}, UU: nil, SS: []string{"string1", "string2", "", "string4", "string5"}, V: srcStructA, VV: []srcStruct2{srcStructA, srcStructB, srcStructA, srcStructB, srcStructA}, M1: map[int]string{1: "11", 2: "22", 3: "33"}, M2: nil, M3: map[int]int{7: 77, 8: 88, 9: 99}, M4: map[[3]int]*srcStruct2{ [3]int{1, 1, 1}: &srcStructA, [3]int{2, 2, 2}: &srcStructB, }, NilP2V: nil, P2V: &p2v, V2P: 123, S2A: []string{"1", "2", "3", "4"}, MatchedX: "MatchedX", UnmatchedX: "UnmatchedX", } ) golang-github-tiendc-go-deepcopy-1.7.2/deepcopy.go000066400000000000000000000065631517235416400221060ustar00rootroot00000000000000package deepcopy import ( "fmt" "reflect" "strings" "sync" ) const ( DefaultTagName = "copy" ) var ( // defaultTagName default tag name for the program to parse input struct tags // to build copier configuration. defaultTagName = DefaultTagName ) // Context copier context type Context struct { // CopyBetweenPtrAndValue allow or not copying between pointers and values (default is `true`) CopyBetweenPtrAndValue bool // CopyViaCopyingMethod allow or not copying via destination type copying methods (default is `true`) CopyViaCopyingMethod bool // IgnoreNonCopyableTypes ignore non-copyable types (default is `false`) IgnoreNonCopyableTypes bool // UseGlobalCache if false not use global cache (default is `true`) UseGlobalCache bool // copierCacheMap cache to speed up parsing types copierCacheMap map[cacheKey]copier mu *sync.RWMutex flags uint8 } // Option configuration option function provided as extra arguments of copying function type Option func(ctx *Context) // CopyBetweenPtrAndValue config function for setting flag `CopyBetweenPtrAndValue` func CopyBetweenPtrAndValue(flag bool) Option { return func(ctx *Context) { ctx.CopyBetweenPtrAndValue = flag } } // CopyBetweenStructFieldAndMethod config function for setting flag `CopyViaCopyingMethod` // Deprecated: use CopyViaCopyingMethod instead func CopyBetweenStructFieldAndMethod(flag bool) Option { return func(ctx *Context) { ctx.CopyViaCopyingMethod = flag } } // CopyViaCopyingMethod config function for setting flag `CopyViaCopyingMethod` func CopyViaCopyingMethod(flag bool) Option { return func(ctx *Context) { ctx.CopyViaCopyingMethod = flag } } // IgnoreNonCopyableTypes config function for setting flag `IgnoreNonCopyableTypes` func IgnoreNonCopyableTypes(flag bool) Option { return func(ctx *Context) { ctx.IgnoreNonCopyableTypes = flag } } // UseGlobalCache config function for setting flag `UseGlobalCache` func UseGlobalCache(flag bool) Option { return func(ctx *Context) { ctx.UseGlobalCache = flag } } // Copy performs deep copy from `src` to `dst`. // // `dst` must be a pointer to the output var, `src` can be either value or pointer. // In case you want to copy unexported struct fields within `src`, `src` must be a pointer. func Copy(dst, src any, options ...Option) (err error) { if src == nil || dst == nil { return fmt.Errorf("%w: source and destination must be non-nil", ErrValueInvalid) } dstVal, srcVal := reflect.ValueOf(dst), reflect.ValueOf(src) dstType, srcType := dstVal.Type(), srcVal.Type() if dstType.Kind() != reflect.Pointer { return fmt.Errorf("%w: destination must be pointer", ErrTypeInvalid) } dstVal, dstType = dstVal.Elem(), dstType.Elem() if !dstVal.IsValid() { return fmt.Errorf("%w: destination must be non-nil", ErrValueInvalid) } ctx := defaultContext() for _, opt := range options { opt(ctx) } ctx.prepare() cp, err := buildCopier(ctx, dstType, srcType) if err != nil { return err } return cp.Copy(dstVal, srcVal) } // ClearCache clears global cache of previously used copiers func ClearCache() { mu.Lock() copierCacheMap = map[cacheKey]copier{} mu.Unlock() } // SetDefaultTagName overwrites the default tag name. // This function should only be called once at program startup. func SetDefaultTagName(tag string) { tagName := strings.TrimSpace(tag) if tagName != "" && tagName == tag { defaultTagName = tagName } } golang-github-tiendc-go-deepcopy-1.7.2/deepcopy_test.go000066400000000000000000000047051517235416400231410ustar00rootroot00000000000000package deepcopy import ( "testing" "github.com/stretchr/testify/assert" ) func Test_Copy(t *testing.T) { src := &srcStruct var dst dstStruct1 err := Copy(&dst, src) assert.Nil(t, err) // Common fields assert.Equal(t, StrT(src.S), dst.S) assert.Equal(t, src.I, dst.I) assert.Equal(t, src.U, dst.U) assert.Equal(t, src.F, dst.F) assert.Equal(t, src.B, *dst.B) assert.True(t, dst.V.EqualSrcStruct2(&src.V)) // Slice/Array fields assert.Equal(t, src.II, dst.II) assert.Equal(t, src.UU, dst.UU) for i := range dst.SS { assert.Equal(t, StrT(src.SS[i]), *dst.SS[i]) } for i := range dst.VV { assert.True(t, dst.VV[i].EqualSrcStruct2(&src.VV[i])) } assert.Equal(t, 0, dst.NilP2V) assert.Equal(t, *src.P2V, dst.P2V) assert.Equal(t, src.V2P, *dst.V2P) // Map fields assert.Equal(t, src.M1, dst.M1) assert.True(t, src.M2 == nil && dst.M2 == nil) assert.Equal(t, len(src.M3), len(*dst.M3)) for k, v := range *dst.M3 { assert.Equal(t, IntT(src.M3[k]), v) } assert.Equal(t, len(src.M4), len(dst.M4)) for k, v := range dst.M4 { assert.True(t, v.EqualSrcStruct2(src.M4[k])) } // Slice to Array assert.Equal(t, 3, len(dst.S2A)) for i := range dst.S2A { assert.Equal(t, src.S2A[i], dst.S2A[i]) } // Copy key matching assert.Equal(t, src.MatchedX, dst.MatchedY) assert.NotEqual(t, src.UnmatchedX, dst.UnmatchedY) } func Test_ClearCache(t *testing.T) { ClearCache() assert.Equal(t, 0, len(copierCacheMap)) } func Test_ConfigOption(t *testing.T) { ctx := defaultContext() CopyBetweenPtrAndValue(false)(ctx) assert.Equal(t, false, ctx.CopyBetweenPtrAndValue) CopyBetweenPtrAndValue(true)(ctx) assert.Equal(t, true, ctx.CopyBetweenPtrAndValue) CopyViaCopyingMethod(false)(ctx) assert.Equal(t, false, ctx.CopyViaCopyingMethod) CopyViaCopyingMethod(true)(ctx) assert.Equal(t, true, ctx.CopyViaCopyingMethod) IgnoreNonCopyableTypes(false)(ctx) assert.Equal(t, false, ctx.IgnoreNonCopyableTypes) IgnoreNonCopyableTypes(true)(ctx) assert.Equal(t, true, ctx.IgnoreNonCopyableTypes) UseGlobalCache(false)(ctx) assert.Equal(t, false, ctx.UseGlobalCache) UseGlobalCache(true)(ctx) assert.Equal(t, true, ctx.UseGlobalCache) } func Test_SetDefaultTagName(t *testing.T) { assert.Equal(t, DefaultTagName, defaultTagName) // Invalid one SetDefaultTagName(" abc") assert.Equal(t, DefaultTagName, defaultTagName) // Valid one SetDefaultTagName("abc") assert.Equal(t, "abc", defaultTagName) // Restore the tag SetDefaultTagName(DefaultTagName) } golang-github-tiendc-go-deepcopy-1.7.2/errors.go000066400000000000000000000017551517235416400216100ustar00rootroot00000000000000package deepcopy import ( "errors" ) // Errors may be returned from Copy function var ( // ErrTypeInvalid returned when type of input var does not meet the requirement ErrTypeInvalid = errors.New("ErrTypeInvalid") // ErrTypeNonCopyable returned when the function can not perform copying between types ErrTypeNonCopyable = errors.New("ErrTypeNonCopyable") // ErrValueInvalid returned when input value does not meet the requirement ErrValueInvalid = errors.New("ErrValueInvalid") // ErrValueUnaddressable returned when value is `unaddressable` which is required // in some situations such as when accessing an unexported struct field. ErrValueUnaddressable = errors.New("ErrValueUnaddressable") // ErrFieldRequireCopying returned when a field is required to be copied // but no copying is done for it. ErrFieldRequireCopying = errors.New("ErrFieldRequireCopying") // ErrMethodInvalid returned when copying method of a struct is not valid ErrMethodInvalid = errors.New("ErrMethodInvalid") ) golang-github-tiendc-go-deepcopy-1.7.2/go.mod000066400000000000000000000003561517235416400210470ustar00rootroot00000000000000module github.com/tiendc/go-deepcopy go 1.18 require github.com/stretchr/testify v1.11.1 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-tiendc-go-deepcopy-1.7.2/go.sum000066400000000000000000000015631517235416400210750ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-tiendc-go-deepcopy-1.7.2/iface_copier.go000066400000000000000000000026501517235416400226770ustar00rootroot00000000000000package deepcopy import ( "reflect" ) // fromIfaceCopier data structure of copier that copies from an interface type fromIfaceCopier struct { ctx *Context } func (c *fromIfaceCopier) init(dst, src reflect.Type) error { return nil } // Copy implementation of Copy function for from-iface copier func (c *fromIfaceCopier) Copy(dst, src reflect.Value) error { for src.Kind() == reflect.Interface { src = src.Elem() if !src.IsValid() { dst.Set(reflect.Zero(dst.Type())) // NOTE: Go1.18 has no SetZero return nil } } cp, err := buildCopier(c.ctx, dst.Type(), src.Type()) if err != nil { return err } return cp.Copy(dst, src) } // toIfaceCopier data structure of copier that copies to an interface type toIfaceCopier struct { ctx *Context } func (c *toIfaceCopier) init(dst, src reflect.Type) error { return nil } // Copy implementation of Copy function for to-iface copier func (c *toIfaceCopier) Copy(dst, src reflect.Value) error { for src.Kind() == reflect.Interface { src = src.Elem() if !src.IsValid() { dst.Set(reflect.Zero(dst.Type())) // NOTE: Go1.18 has no SetZero return nil } } // As `dst` is interface, we clone the `src` and assign back to the `dst` srcType := src.Type() cloneSrc := reflect.New(srcType).Elem() cp, err := buildCopier(c.ctx, srcType, srcType) if err != nil { return err } if err = cp.Copy(cloneSrc, src); err != nil { return err } dst.Set(cloneSrc) return nil } golang-github-tiendc-go-deepcopy-1.7.2/iface_copier_test.go000066400000000000000000000062451517235416400237420ustar00rootroot00000000000000package deepcopy import ( "testing" "github.com/stretchr/testify/assert" ) func Test_Copy_iface(t *testing.T) { t.Run("#1: iface carries nil", func(t *testing.T) { var v []int var s any = v var d any err := Copy(&d, s) assert.Nil(t, err) assert.Nil(t, d) }) t.Run("#2: iface of str -> iface", func(t *testing.T) { var s any = "100" var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, "100", d) }) t.Run("#3: iface of str -> str", func(t *testing.T) { var s any = "abc" var d string err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, "abc", d) }) t.Run("#4: iface of struct -> iface", func(t *testing.T) { type SS struct { I int U uint } var s any = SS{ I: 1, U: 2, } var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, SS{I: 1, U: 2}, d.(SS)) }) t.Run("#5: iface of struct ptr -> iface", func(t *testing.T) { type SS struct { I int U uint } var s any = &SS{ I: 1, U: 2, } var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, &SS{I: 1, U: 2}, d.(*SS)) }) t.Run("#6: iface of slice -> iface", func(t *testing.T) { var s any = []int{1, 2, 3} var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []int{1, 2, 3}, d.([]int)) }) t.Run("#7: iface of slice ptr -> iface", func(t *testing.T) { var s any = &[]int{1, 2, 3} var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, &[]int{1, 2, 3}, d.(*[]int)) }) t.Run("#8: iface of array -> iface", func(t *testing.T) { var s any = [3]int{1, 2, 3} var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, [3]int{1, 2, 3}, d.([3]int)) }) t.Run("#9: slice of iface -> iface", func(t *testing.T) { var s []any = []any{1, 2, 3} var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []any{1, 2, 3}, d.([]any)) }) t.Run("#10: array of iface -> iface", func(t *testing.T) { var s [3]any = [3]any{1, 2, 3} var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, [3]any{1, 2, 3}, d.([3]any)) }) t.Run("#11: iface of struct -> struct", func(t *testing.T) { type SS struct { I int U uint } type DD struct { I int U uint } var s any = SS{ I: 1, U: 2, } var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: 2}, d) }) t.Run("#12: iface of iface", func(t *testing.T) { var s2 any var s any = &s2 var d any err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, &s2, d) }) t.Run("#13: iface of iface", func(t *testing.T) { var s2 any = "abc" var s any = &s2 var d string err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, "abc", d) }) } func Test_Copy_iface_error(t *testing.T) { t.Run("#1: nil interface", func(t *testing.T) { var s any var d any err := Copy(&d, s) assert.ErrorIs(t, err, ErrValueInvalid) }) t.Run("#2: iface of str -> int", func(t *testing.T) { var s any = "100" var d int err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#3: iface of chan -> iface", func(t *testing.T) { ch := make(chan int, 10) var s any = ch var d any err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) } golang-github-tiendc-go-deepcopy-1.7.2/map_copier.go000066400000000000000000000047411517235416400224100ustar00rootroot00000000000000package deepcopy import ( "reflect" ) // mapCopier data structure of copier that copies from a `map` type mapCopier struct { ctx *Context keyCopier *mapItemCopier valueCopier *mapItemCopier } // Copy implementation of Copy function for map copier func (c *mapCopier) Copy(dst, src reflect.Value) (err error) { if src.IsNil() { dst.Set(reflect.Zero(dst.Type())) // NOTE: Go1.18 has no SetZero return nil } if dst.IsNil() { dst.Set(reflect.MakeMapWithSize(dst.Type(), src.Len())) } iter := src.MapRange() for iter.Next() { k := iter.Key() v := iter.Value() if c.keyCopier != nil { if k, err = c.keyCopier.Copy(k); err != nil { return err } } if c.valueCopier != nil { if v, err = c.valueCopier.Copy(v); err != nil { return err } } dst.SetMapIndex(k, v) } return nil } func (c *mapCopier) init(dstType, srcType reflect.Type) error { srcKeyType, srcValType := srcType.Key(), srcType.Elem() dstKeyType, dstValType := dstType.Key(), dstType.Elem() buildKeyCopier, buildValCopier := true, true // OPTIMIZATION: buildCopier() can handle this nicely if simpleKindMask&(1< 0 { if srcKeyType == dstKeyType { // Just keep c.keyCopier = nil buildKeyCopier = false } else if srcKeyType.ConvertibleTo(dstKeyType) { c.keyCopier = &mapItemCopier{dstType: dstKeyType, copier: defaultConvCopier} buildKeyCopier = false } } // OPTIMIZATION: buildCopier() can handle this nicely if simpleKindMask&(1< 0 { if srcValType == dstValType { // Just keep c.valueCopier = nil buildValCopier = false } else if srcValType.ConvertibleTo(dstValType) { c.valueCopier = &mapItemCopier{dstType: dstValType, copier: defaultConvCopier} buildValCopier = false } } if buildKeyCopier { cp, err := buildCopier(c.ctx, dstKeyType, srcKeyType) if err != nil { return err } c.keyCopier = &mapItemCopier{dstType: dstKeyType, copier: cp} } if buildValCopier { cp, err := buildCopier(c.ctx, dstValType, srcValType) if err != nil { return err } c.valueCopier = &mapItemCopier{dstType: dstValType, copier: cp} } return nil } // mapItemCopier data structure of copier that copies from a map's key or value type mapItemCopier struct { dstType reflect.Type copier copier } // Copy implementation of Copy function for map item copier func (c *mapItemCopier) Copy(src reflect.Value) (reflect.Value, error) { dst := reflect.New(c.dstType).Elem() err := c.copier.Copy(dst, src) return dst, err } golang-github-tiendc-go-deepcopy-1.7.2/map_copier_test.go000066400000000000000000000117161517235416400234470ustar00rootroot00000000000000package deepcopy import ( "reflect" "testing" "github.com/stretchr/testify/assert" ) func Test_Copy_map(t *testing.T) { t.Run("#1: nil map", func(t *testing.T) { var s map[int]int var d map[int]int err := Copy(&d, s) assert.Nil(t, err) assert.Nil(t, d) }) t.Run("#2: map of (int,int) -> map of (int,int)", func(t *testing.T) { var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d map[int]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]int{1: 11, 2: 22, 3: 33}, d) }) t.Run("#3: map of (int,int) -> map of (int,IntT)", func(t *testing.T) { var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d map[int]IntT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]IntT{1: 11, 2: 22, 3: 33}, d) }) t.Run("#4: map of (int,int) -> map of (int,float32)", func(t *testing.T) { var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d map[int]float32 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]float32{1: 11, 2: 22, 3: 33}, d) }) t.Run("#5: map of (int,float32) -> map of (int,int)", func(t *testing.T) { var s map[int]float32 = map[int]float32{1: 11.11, 2: 22.22, 3: 33.22} var d map[int]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]int{1: 11, 2: 22, 3: 33}, d) }) t.Run("#6: map of (int,struct) -> map of (int,struct)", func(t *testing.T) { type SS struct { I int U uint } type DD struct { I int U uint } var s map[int]SS = map[int]SS{1: {1, 11}, 2: {2, 22}, 3: {3, 33}} var d map[int]DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]DD{1: {1, 11}, 2: {2, 22}, 3: {3, 33}}, d) }) t.Run("#7: map of (int,*struct) -> map of (int,*struct)", func(t *testing.T) { type SS struct { I int U uint } type DD struct { I int U uint } var s map[int]*SS = map[int]*SS{1: {1, 11}, 2: {2, 22}, 3: {3, 33}} var d map[int]*DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]*DD{1: {1, 11}, 2: {2, 22}, 3: {3, 33}}, d) }) t.Run("#8: map of (int,slice) -> map of (int,array)", func(t *testing.T) { var s map[int][]int = map[int][]int{1: {1, 11}, 2: {2, 22}, 3: {3, 33}} var d map[int][3]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int][3]int{1: {1, 11, 0}, 2: {2, 22, 0}, 3: {3, 33, 0}}, d) }) t.Run("#9: map of (IntT,int) -> map of (int,IntT)", func(t *testing.T) { var s map[IntT]int = map[IntT]int{1: 11, 2: 22, 3: 33} var d map[int]IntT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]IntT{1: 11, 2: 22, 3: 33}, d) }) t.Run("#10: map of (int,iface) -> map of (int,int)", func(t *testing.T) { var s map[int]any = map[int]any{1: 11, 2: 22, 3: 33} var d map[int]IntT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[int]IntT{1: 11, 2: 22, 3: 33}, d) }) t.Run("#11: map of (int,int) -> map-derived-type of (int,int)", func(t *testing.T) { type MapT map[int]int var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d MapT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, MapT{1: 11, 2: 22, 3: 33}, d) }) t.Run("#12: map of (int,int) -> map-derived-type of (int,float32)", func(t *testing.T) { type MapT map[int]float32 var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d MapT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, MapT{1: 11, 2: 22, 3: 33}, d) }) } func Test_Copy_map_error(t *testing.T) { t.Run("#1: map of (int,iface-of-int) -> map of (int,bool)", func(t *testing.T) { var s map[int]any = map[int]any{1: 11, 2: 22, 3: 33} var d map[int]bool err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#2: map of (int,int) -> map of (int,string)", func(t *testing.T) { var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d map[int][]int err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#3: map of (int,int) -> map of ([3]int,int)", func(t *testing.T) { var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d map[[3]int]int err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#4: map -> slice (error)", func(t *testing.T) { var s map[int]int = map[int]int{1: 11, 2: 22, 3: 33} var d []int err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#5: key copier returns error", func(t *testing.T) { var s map[int]int = map[int]int{1: 1, 2: 2} var d map[int]int cp := &mapCopier{ ctx: defaultContext(), keyCopier: &mapItemCopier{copier: &errorCopier{}, dstType: reflect.TypeOf(0)}, } err := cp.Copy(reflect.ValueOf(&d).Elem(), reflect.ValueOf(s)) assert.ErrorIs(t, err, errTest) }) t.Run("#6: value copier returns error", func(t *testing.T) { var s map[int]int = map[int]int{1: 1, 2: 2} var d map[int]int cp := &mapCopier{ ctx: defaultContext(), valueCopier: &mapItemCopier{copier: &errorCopier{}, dstType: reflect.TypeOf(0)}, } err := cp.Copy(reflect.ValueOf(&d).Elem(), reflect.ValueOf(s)) assert.ErrorIs(t, err, errTest) }) } golang-github-tiendc-go-deepcopy-1.7.2/map_to_struct_copier.go000066400000000000000000000162051517235416400245140ustar00rootroot00000000000000package deepcopy import ( "fmt" "reflect" "strings" "unsafe" ) // mapToStructCopier data structure of copier that copies a map to a struct type mapToStructCopier struct { ctx *Context mapDstCopyingMethod map[string]*reflect.Method mapDstStructFields map[string]*simpleFieldDetail dstStructRequiredFields int postCopyMethod *int } type simpleFieldDetail struct { fieldType reflect.Type fieldUnexported bool key string required bool nilOnZero bool index []int } // Copy implementation of Copy function for map to struct copier // //nolint:gocognit,gocyclo func (c *mapToStructCopier) Copy(dstStruct, srcMap reflect.Value) error { if !srcMap.IsValid() || srcMap.IsNil() { return nil } dstStructType := dstStruct.Type() // Marks all fields of the dst struct which require copying var mapCopiedKeys map[string]struct{} if c.dstStructRequiredFields > 0 { mapCopiedKeys = make(map[string]struct{}, c.dstStructRequiredFields) } // Copies map entries to struct fields iter := srcMap.MapRange() for iter.Next() { key := iter.Key() srcVal := iter.Value() srcValType := srcVal.Type() keyStr := key.String() // Copying methods have higher priority, so if a method defined in the destination, use it if c.mapDstCopyingMethod != nil { methodName := "Copy" + strings.ToUpper(keyStr[:1]) + keyStr[1:] dstCpMethod, exists := c.mapDstCopyingMethod[methodName] if exists && !dstCpMethod.Type.In(1).AssignableTo(srcValType) { return fmt.Errorf("%w: struct method '%v.%s' does not accept argument type '%v' from '%v[%s]'", ErrMethodInvalid, dstStructType, dstCpMethod.Name, srcValType, srcMap.Type(), keyStr) } if exists { err := (&methodCopier{dstMethod: dstCpMethod.Index}).Copy(dstStruct, srcVal) if err != nil { return err } continue } } // Find field details from `dst` having the key dfDetail := c.mapDstStructFields[keyStr] if dfDetail == nil { continue } entryCopier, err := c.buildCopier(dstStructType, srcValType, dfDetail) if err != nil { return err } err = entryCopier.Copy(dstStruct, srcVal) if err != nil { return err } // Marks the field as copied if dfDetail.required { mapCopiedKeys[dfDetail.key] = struct{}{} } } // Checks if any dst field requires copying if c.dstStructRequiredFields > 0 { for _, v := range c.mapDstStructFields { if !v.required { continue } if _, exists := mapCopiedKeys[v.key]; !exists { return fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, dstStructType, v.key) } } } // Executes post-copy function of the destination struct if c.postCopyMethod != nil { method := dstStruct.Addr().Method(*c.postCopyMethod) errVal := method.Call([]reflect.Value{srcMap})[0] if errVal.IsNil() { return nil } err, ok := errVal.Interface().(error) if !ok { // Should never get in here return fmt.Errorf("%w: PostCopy method returns non-error value", ErrTypeInvalid) } return err } return nil } func (c *mapToStructCopier) init(dstType, srcType reflect.Type) (err error) { mapKeyType, mapValType := srcType.Key(), srcType.Elem() if mapKeyType.Kind() != reflect.String { if c.ctx.IgnoreNonCopyableTypes { return nil } return fmt.Errorf("%w: copying from 'map[%v]%v' to struct type '%v' requires map key type to be 'string'", ErrTypeNonCopyable, mapKeyType, mapValType, dstType) } var postCopyMethod *reflect.Method c.mapDstCopyingMethod, postCopyMethod = typeParseMethods(c.ctx, dstType) if postCopyMethod != nil { c.postCopyMethod = &postCopyMethod.Index } dstDirectFields, mapDstDirectFields, dstInheritedFields, mapDstInheritedFields := structParseAllFields(dstType) c.mapDstStructFields = make(map[string]*simpleFieldDetail, len(dstDirectFields)+len(dstInheritedFields)) for _, key := range append(dstDirectFields, dstInheritedFields...) { dfDetail := mapDstDirectFields[key] if dfDetail == nil || dfDetail.field.Anonymous { dfDetail = mapDstInheritedFields[key] } if dfDetail == nil || dfDetail.ignored || dfDetail.done || dfDetail.field.Anonymous { continue } c.mapDstStructFields[dfDetail.key] = &simpleFieldDetail{ key: dfDetail.key, fieldType: dfDetail.field.Type, fieldUnexported: !dfDetail.field.IsExported(), required: dfDetail.required, nilOnZero: dfDetail.nilOnZero, index: dfDetail.index, } if dfDetail.required { c.dstStructRequiredFields++ } } return nil } func (c *mapToStructCopier) buildCopier(dstStructType, srcValType reflect.Type, dstFieldDetail *simpleFieldDetail) (copier, error) { // OPTIMIZATION: buildCopier() can handle this nicely if simpleKindMask&(1< 0 { if srcValType == dstFieldDetail.fieldType { // NOTE: pass nil to unset custom copier and trigger direct copying. // We can pass `&directCopier{}` for the same result (but it's a bit slower). return c.createValue2FieldCopier(dstFieldDetail, nil), nil } if srcValType.ConvertibleTo(dstFieldDetail.fieldType) { return c.createValue2FieldCopier(dstFieldDetail, defaultConvCopier), nil } } cp, err := buildCopier(c.ctx, dstFieldDetail.fieldType, srcValType) if err != nil { // NOTE: If the copy is not required and the field is unexported, ignore the error if !dstFieldDetail.required && dstFieldDetail.fieldUnexported { return defaultNopCopier, nil } return nil, err } if c.ctx.IgnoreNonCopyableTypes && dstFieldDetail.required { _, isNopCopier := cp.(*nopCopier) if isNopCopier { return nil, fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, dstStructType, dstFieldDetail.key) } } return c.createValue2FieldCopier(dstFieldDetail, cp), nil } func (c *mapToStructCopier) createValue2FieldCopier(df *simpleFieldDetail, cp copier) copier { return &value2StructFieldCopier{ copier: cp, dstFieldIndex: df.index, dstFieldUnexported: df.fieldUnexported, dstFieldSetNilOnZero: df.nilOnZero, required: df.required || !df.fieldUnexported, } } // value2StructFieldCopier data structure of copier that copies from a value to a struct field type value2StructFieldCopier struct { copier copier dstFieldIndex []int dstFieldUnexported bool dstFieldSetNilOnZero bool required bool } func (c *value2StructFieldCopier) Copy(dst, src reflect.Value) (err error) { if len(c.dstFieldIndex) == 1 { dst = dst.Field(c.dstFieldIndex[0]) } else { // Get dst field with making sure it's settable dst = structFieldGetWithInit(dst, c.dstFieldIndex) } if c.dstFieldUnexported { // NOTE: dst is always addressable as Copy() requires `dst` to be pointer dst = reflect.NewAt(dst.Type(), unsafe.Pointer(dst.UnsafeAddr())).Elem() //nolint:gosec } // Use custom copier if set if c.copier != nil { if err = c.copier.Copy(dst, src); err != nil { if c.required { return err } return nil } } else { // Otherwise, just perform simple direct copying dst.Set(src) } // When instructed to set `dst` as `nil` on zero if c.dstFieldSetNilOnZero { nillableValueSetNilOnZero(dst) } return nil } golang-github-tiendc-go-deepcopy-1.7.2/map_to_struct_copier_test.go000066400000000000000000000242711517235416400255550ustar00rootroot00000000000000package deepcopy import ( "testing" "unsafe" "github.com/stretchr/testify/assert" ) func Test_Copy_mapToStruct(t *testing.T) { t.Run("#1: simple case", func(t *testing.T) { type DD struct { I int U uint } s := map[string]int{"I": 1, "U": 2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: 2}, d) }) t.Run("#2: with copy key", func(t *testing.T) { type DD struct { I int `copy:"i"` U uint } s := map[string]int{"i": 1, "U": 2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: 2}, d) }) t.Run("#3: with custom map key/value type", func(t *testing.T) { type DD struct { I int `copy:"i"` U uint } type MapKey string type MapValue int8 s := map[MapKey]MapValue{"i": 1, "U": 2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: 2}, d) }) t.Run("#4: with lossy conversion (int -> int8)", func(t *testing.T) { type DD struct { I int8 `copy:"i"` U uint } s := map[string]int{"i": 128, "U": 222} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: -128, U: 222}, d) }) t.Run("#5: with int -> float conversion", func(t *testing.T) { type DD struct { I float32 `copy:"i"` U uint } s := map[string]int{"i": 1, "U": 2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: 2}, d) }) t.Run("#6: with ptr -> value conversion", func(t *testing.T) { type DD struct { I int `copy:"i"` S string } s := map[string]any{"i": ptrOf(1), "S": ptrOf("abc")} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, S: "abc"}, d) }) t.Run("#7: with value -> ptr conversion", func(t *testing.T) { type DD struct { I *int `copy:"i"` S *string } s := map[string]any{"i": 1, "S": "abc"} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: ptrOf(1), S: ptrOf("abc")}, d) }) t.Run("#8: with struct field has type 'any'", func(t *testing.T) { type DD struct { I any `copy:"i"` S any } s := map[string]any{"i": 1, "S": ptrOf("abc")} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, S: ptrOf("abc")}, d) }) t.Run("#9: with map value has type slice", func(t *testing.T) { type DD struct { I []int `copy:"i"` S []string } s := map[string]any{"i": []int{1, 2}, "S": []string{"aa", "bb"}} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: []int{1, 2}, S: []string{"aa", "bb"}}, d) }) t.Run("#10: with struct field is ignored", func(t *testing.T) { type DD struct { I int `copy:"-"` S string } s := map[string]any{"i": ptrOf(1), "S": ptrOf("abc")} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 0, S: "abc"}, d) }) t.Run("#11: cyclic reference", func(t *testing.T) { type DD struct { I int Ref *DD } s := map[string]any{"Ref": &DD{I: 1}} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{Ref: &DD{I: 1}}, d) }) t.Run("#12: map key type is not string, but ignore error NonCopyable", func(t *testing.T) { type DD struct { I int } s := map[int]string{1: "a"} var d DD err := Copy(&d, &s, IgnoreNonCopyableTypes(true)) assert.Nil(t, err) assert.Equal(t, DD{}, d) }) t.Run("#13: type is non-copyable and struct field is unexported, but not required (ignored)", func(t *testing.T) { type DD struct { i int } s := map[string]string{"i": "1"} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{}, d) }) t.Run("#14: deep embedded struct field", func(t *testing.T) { type DD3 struct { I int `copy:"i"` } type DD2 struct { DD3 } type DD struct { DD2 } s := map[string]any{"i": 1} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{DD2: DD2{DD3: DD3{I: 1}}}, d) }) t.Run("#15: deep embedded struct field, but use ptr", func(t *testing.T) { type DD3 struct { I int `copy:"i"` } type DD2 struct { *DD3 } type DD struct { DD2 } s := map[string]any{"i": 1} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{DD2: DD2{DD3: &DD3{I: 1}}}, d) }) t.Run("#16: src map is nil", func(t *testing.T) { type DD struct { I int } var s map[string]int d := DD{I: 1} err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1}, d) }) t.Run("#17: set destination field nil when it's zero", func(t *testing.T) { type DD struct { I *int `copy:",nilonzero"` } s := map[string]int{"I": 0} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: nil}, d) }) t.Run("#18: map-in-map", func(t *testing.T) { type DD2 struct { I int } type DD struct { DD2 DD2 } s := map[string]map[string]any{"DD2": {"I": 1}} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{DD2: DD2{I: 1}}, d) }) } func Test_Copy_mapToStruct_error(t *testing.T) { t.Run("#1: with struct fields have different types", func(t *testing.T) { type DD struct { I int U uint } s := map[string]any{"I": 1, "U": "a"} var d DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#2: with non-copyable type", func(t *testing.T) { type DD struct { P uint } s := map[string]any{"P": (unsafe.Pointer)(ptrOf(111))} var d DD err := Copy(&d, &s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#3: map key type is not string", func(t *testing.T) { type DD struct { I int } s := map[int]any{1: 1, 2: "a"} var d DD err := Copy(&d, &s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#4: field requires copying", func(t *testing.T) { type DD struct { I int `copy:",required"` } s := map[string]any{"A": 123} var d DD err := Copy(&d, &s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#5: non-copyable, but field requires copying", func(t *testing.T) { type DD struct { P unsafe.Pointer `copy:",required"` } s := map[string]unsafe.Pointer{"P": nil} var d DD err := Copy(&d, &s, IgnoreNonCopyableTypes(true)) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) } func Test_Copy_mapToStruct_unexported(t *testing.T) { t.Run("#1: struct field unexported", func(t *testing.T) { type DD struct { I int u uint } s := map[string]any{"I": 1, "u": 2} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, u: 2}, d) }) t.Run("#2: struct field unexported, but required", func(t *testing.T) { type DD struct { I int u uint `copy:"u,required"` } s := map[string]any{"I": 1, "u": 2} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, u: 2}, d) }) } func Test_Copy_mapToStruct_unexported_error(t *testing.T) { t.Run("#1: struct field unexported, but required", func(t *testing.T) { type DD struct { I int u uint `copy:"u,required"` } s := map[string]any{"I": 1} var d DD err := Copy(&d, &s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) } type testD3 struct { x1 int x2 int U uint } func (d *testD3) CopyI1(i1 int) error { d.x1 = i1 * 2 return nil } func (d *testD3) CopyI2(i2 int) { // incorrect method prototype (no return error) d.x2 = i2 * 2 } type testD4 struct { x4 int `copy:",required"` U uint } func (d *testD4) CopyI4(i4 int, v string) error { // incorrect method prototype (2 input args) d.x4 = i4 * 2 return nil } type testD5 struct { x5 int `copy:",required"` U uint } func (d *testD5) CopyI5(i5 int) string { // incorrect method prototype (not return error type) return "" } type testD6 struct { x6 int `copy:",required"` U uint } func (d *testD6) CopyI6(i6 int) error { // incorrect method prototype (unmatched input type) return errTest } func Test_Copy_mapToStruct_method(t *testing.T) { t.Run("#1: map entry -> dst method", func(t *testing.T) { s := map[string]int{"I1": 1, "U": 2} var d testD3 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD3{x1: 2, U: 2}, d) }) t.Run("#2: incorrect method prototype (CopyI2())", func(t *testing.T) { s := map[string]int{"I2": 1, "U": 2} var d testD3 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD3{U: 2}, d) }) t.Run("#3: not allow copying via method", func(t *testing.T) { s := map[string]int{"I1": 1, "U": 2} var d testD3 err := Copy(&d, s, CopyBetweenStructFieldAndMethod(false)) assert.Nil(t, err) assert.Equal(t, testD3{U: 2}, d) }) } func Test_Copy_mapToStruct_method_error(t *testing.T) { t.Run("#1: unmatched method arg type (CopyI1())", func(t *testing.T) { s := map[string]string{"I1": "a"} var d testD3 err := Copy(&d, s) assert.ErrorIs(t, err, ErrMethodInvalid) }) t.Run("#2: incorrect method prototype (CopyI4())", func(t *testing.T) { s := map[string]int{"I4": 1, "U": 2} var d testD4 err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#3: incorrect method prototype (CopyI5())", func(t *testing.T) { s := map[string]int{"I5": 1, "U": 2} var d testD5 err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#4: copying method returns error (CopyI6())", func(t *testing.T) { s := map[string]int{"I6": 1, "U": 2} var d testD6 err := Copy(&d, s) assert.ErrorIs(t, err, errTest) }) } type testD7 struct { I int U uint } func (d *testD7) PostCopy(src any) error { srcMap, _ := src.(map[string]int) if srcMap["I"] == 100 { return errTest } d.I *= 2 d.U *= 2 return nil } type testD8 struct { I int U uint } func (d *testD8) PostCopy(src any) int { // has incorrect method prototype d.I *= 2 d.U *= 2 return 0 } func Test_Copy_mapToStruct_with_post_copy_event(t *testing.T) { t.Run("#1: success without error", func(t *testing.T) { s := map[string]int{"I": 1, "U": 2} var d testD7 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD7{I: 2, U: 4}, d) }) t.Run("#2: PostCopy returns error", func(t *testing.T) { s := map[string]int{"I": 100, "U": 2} // When map["I"] == 100, PostCopy returns error var d testD7 err := Copy(&d, s) assert.ErrorIs(t, err, errTest) }) t.Run("#3: PostCopy not satisfied", func(t *testing.T) { s := map[string]int{"I": 1, "U": 2} var d testD8 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD8{I: 1, U: 2}, d) }) } golang-github-tiendc-go-deepcopy-1.7.2/slice_copier.go000066400000000000000000000024021517235416400227220ustar00rootroot00000000000000package deepcopy import ( "reflect" ) // sliceCopier data structure of copier that copies from a `slice` type sliceCopier struct { ctx *Context itemCopier copier } // Copy implementation of Copy function for slice copier func (c *sliceCopier) Copy(dst, src reflect.Value) error { srcLen := src.Len() if dst.Kind() == reflect.Slice { // Slice/Array -> Slice // `src` is nil slice, set `dst` nil if src.Kind() == reflect.Slice && src.IsNil() { dst.Set(reflect.Zero(dst.Type())) // NOTE: Go1.18 has no SetZero return nil } newSlice := reflect.MakeSlice(dst.Type(), srcLen, srcLen) for i := 0; i < srcLen; i++ { if err := c.itemCopier.Copy(newSlice.Index(i), src.Index(i)); err != nil { return err } } dst.Set(newSlice) return nil } // Slice/Array -> Array dstLen := dst.Len() if dstLen < srcLen { srcLen = dstLen } i := 0 for ; i < srcLen; i++ { if err := c.itemCopier.Copy(dst.Index(i), src.Index(i)); err != nil { return err } } for ; i < dstLen; i++ { item := dst.Index(i) item.Set(reflect.Zero(item.Type())) // NOTE: Go1.18 has no SetZero } return nil } func (c *sliceCopier) init(dstType, srcType reflect.Type) (err error) { c.itemCopier, err = buildCopier(c.ctx, dstType.Elem(), srcType.Elem()) return } golang-github-tiendc-go-deepcopy-1.7.2/slice_copier_test.go000066400000000000000000000113541517235416400237670ustar00rootroot00000000000000package deepcopy import ( "reflect" "testing" "github.com/stretchr/testify/assert" ) func Test_Copy_slice(t *testing.T) { t.Run("#1: nil slice", func(t *testing.T) { var d []int err := Copy(&d, ([]int)(nil)) assert.Nil(t, err) assert.Nil(t, d) }) t.Run("#2: slice of int -> slice of int", func(t *testing.T) { var s []int = []int{1, 2, 3} var d []int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []int{1, 2, 3}, d) }) t.Run("#3: slice of int -> slice of int (dst has filled)", func(t *testing.T) { var s []int = []int{1, 2, 3} var d []int = []int{1, 1, 1, 2, 2, 2} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []int{1, 2, 3}, d) }) t.Run("#4: empty slice of str -> slice of str (dst has filled)", func(t *testing.T) { var s []string = []string{} var d []string = []string{"1", "2", "3"} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []string{}, d) }) t.Run("#5: slice of str -> slice of str-ptr", func(t *testing.T) { var s []string = []string{"1", "2", "3"} var d []*string err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []*string{ptrOf("1"), ptrOf("2"), ptrOf("3")}, d) }) t.Run("#6: slice of str -> slice of str-derived-type", func(t *testing.T) { var s []string = []string{"1", "2", "3"} var d []StrT err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []StrT{"1", "2", "3"}, d) }) } func Test_Copy_slice_error(t *testing.T) { t.Run("#1: dst is not pointer", func(t *testing.T) { var s []int = []int{1, 2, 3} var d []int err := Copy(d, s) assert.ErrorIs(t, err, ErrTypeInvalid) }) t.Run("#2: slice -> map (error)", func(t *testing.T) { var s []string = []string{"1", "2", "3"} var d map[int]string err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#3: slice of int -> slice of struct", func(t *testing.T) { type DD struct { I int } var s []int = []int{1, 2, 3} var d []DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#4: slice item copier returns error", func(t *testing.T) { var s []int = []int{1, 2, 3} var d []int cp := &sliceCopier{ctx: defaultContext(), itemCopier: &errorCopier{}} err := cp.Copy(reflect.ValueOf(&d).Elem(), reflect.ValueOf(s)) assert.ErrorIs(t, err, errTest) }) t.Run("#5: array item copier returns error", func(t *testing.T) { var s [3]int = [3]int{1, 2, 3} var d [3]int cp := &sliceCopier{ctx: defaultContext(), itemCopier: &errorCopier{}} err := cp.Copy(reflect.ValueOf(&d).Elem(), reflect.ValueOf(s)) assert.ErrorIs(t, err, errTest) }) } func Test_Copy_array(t *testing.T) { t.Run("#1: array of int -> array of int", func(t *testing.T) { var s [3]int = [3]int{1, 2, 3} var d [3]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, [3]int{1, 2, 3}, d) }) t.Run("#2: array of int -> array of int (dst has filled)", func(t *testing.T) { var s [3]int = [3]int{1, 2, 3} var d [6]int = [6]int{1, 1, 1, 2, 2, 2} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, [6]int{1, 2, 3, 0, 0, 0}, d) }) t.Run("#3: array of str -> slice of str", func(t *testing.T) { var s [3]string = [3]string{"1", "2", "3"} var d []string err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []string{"1", "2", "3"}, d) }) t.Run("#4: array of str -> slice of str (dst has filled less)", func(t *testing.T) { var s [3]string = [3]string{"1", "2", "3"} var d []string = []string{"1", "2"} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []string{"1", "2", "3"}, d) }) t.Run("#5: array of str -> slice of str (dst has filled more)", func(t *testing.T) { var s [3]string = [3]string{"1", "2", "3"} var d []string = []string{"1", "2", "3", "4", "5"} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, []string{"1", "2", "3"}, d) }) t.Run("#6: slice of str -> array of str (dst has filled less)", func(t *testing.T) { var s []string = []string{"1", "2", "3"} var d [2]string = [2]string{"x", "x"} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, [2]string{"1", "2"}, d) }) t.Run("#7: slice of str -> array of str (dst has filled more)", func(t *testing.T) { var s []string = []string{"1", "2", "3"} var d [5]string = [5]string{"1", "2", "3", "4", "5"} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, [5]string{"1", "2", "3", "", ""}, d) }) } func Test_Copy_array_error(t *testing.T) { t.Run("#1: array -> map (error)", func(t *testing.T) { var s [3]string = [3]string{"1", "2", "3"} var d map[int]string err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#2: array of int -> slice of struct", func(t *testing.T) { type DD struct { I int } var s [3]int = [3]int{1, 2, 3} var d []DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) } golang-github-tiendc-go-deepcopy-1.7.2/struct_copier.go000066400000000000000000000223301517235416400231510ustar00rootroot00000000000000package deepcopy import ( "fmt" "reflect" "strings" "unsafe" ) // structCopier data structure of copier that copies from a `struct` type structCopier struct { ctx *Context fieldCopiers []copier postCopyMethod *int } // Copy implementation of Copy function for struct copier func (c *structCopier) Copy(dst, src reflect.Value) error { for _, cp := range c.fieldCopiers { if err := cp.Copy(dst, src); err != nil { return err } } // Executes post-copy function of the destination struct if c.postCopyMethod != nil { dst = dst.Addr().Method(*c.postCopyMethod) errVal := dst.Call([]reflect.Value{src})[0] if errVal.IsNil() { return nil } err, ok := errVal.Interface().(error) if !ok { // Should never get in here return fmt.Errorf("%w: PostCopy method returns non-error value", ErrTypeInvalid) } return err } return nil } //nolint:gocognit,gocyclo func (c *structCopier) init(dstType, srcType reflect.Type) (err error) { dstCopyingMethods, postCopyMethod := typeParseMethods(c.ctx, dstType) if postCopyMethod != nil { c.postCopyMethod = &postCopyMethod.Index } dstDirectFields, mapDstDirectFields, dstInheritedFields, mapDstInheritedFields := structParseAllFields(dstType) srcDirectFields, mapSrcDirectFields, srcInheritedFields, mapSrcInheritedFields := structParseAllFields(srcType) c.fieldCopiers = make([]copier, 0, len(dstDirectFields)+len(dstInheritedFields)) for _, key := range append(srcDirectFields, srcInheritedFields...) { // Find field details from `src` having the key sfDetail := mapSrcDirectFields[key] if sfDetail == nil { sfDetail = mapSrcInheritedFields[key] } if sfDetail == nil || sfDetail.ignored || sfDetail.done { continue } // Copying methods have higher priority, so if a method defined in the dst struct, use it if dstCopyingMethods != nil { methodName := "Copy" + strings.ToUpper(key[:1]) + key[1:] dstCpMethod, exists := dstCopyingMethods[methodName] if exists && !dstCpMethod.Type.In(1).AssignableTo(sfDetail.field.Type) { return fmt.Errorf("%w: struct method '%v.%s' does not accept argument type '%v' from '%v[%s]'", ErrMethodInvalid, dstType, dstCpMethod.Name, sfDetail.field.Type, srcType, sfDetail.field.Name) } if exists { c.fieldCopiers = append(c.fieldCopiers, c.createField2MethodCopier(dstCpMethod, sfDetail)) sfDetail.markDone() continue } } // Find field details from `dst` having the key dfDetail := mapDstDirectFields[key] if dfDetail == nil { dfDetail = mapDstInheritedFields[key] } if dfDetail == nil || dfDetail.ignored || dfDetail.done { // Found no corresponding dest field to copy to, raise an error in case this is required if sfDetail.required { return fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, srcType, sfDetail.field.Name) } continue } copier, err := c.buildCopier(dstType, srcType, dfDetail, sfDetail) if err != nil { return err } c.fieldCopiers = append(c.fieldCopiers, copier) dfDetail.markDone() sfDetail.markDone() } // Remaining dst fields can't be copied for _, dfDetail := range mapDstDirectFields { if !dfDetail.done && dfDetail.required { return fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, dstType, dfDetail.field.Name) } } for _, dfDetail := range mapDstInheritedFields { if !dfDetail.done && dfDetail.required { return fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, dstType, dfDetail.field.Name) } } return nil } func (c *structCopier) buildCopier( dstStructType, srcStructType reflect.Type, dstFieldDetail, srcFieldDetail *fieldDetail, ) (copier, error) { df, sf := dstFieldDetail.field, srcFieldDetail.field // OPTIMIZATION: buildCopier() can handle this nicely if simpleKindMask&(1< 0 { if sf.Type == df.Type { // NOTE: pass nil to unset custom copier and trigger direct copying. // We can pass `&directCopier{}` for the same result (but it's a bit slower). return c.createField2FieldCopier(dstFieldDetail, srcFieldDetail, nil), nil } if sf.Type.ConvertibleTo(df.Type) { return c.createField2FieldCopier(dstFieldDetail, srcFieldDetail, defaultConvCopier), nil } } cp, err := buildCopier(c.ctx, df.Type, sf.Type) if err != nil { // NOTE: If the copy is not required and the field is unexported, ignore the error if !dstFieldDetail.required && !srcFieldDetail.required && !df.IsExported() { return defaultNopCopier, nil } return nil, err } if c.ctx.IgnoreNonCopyableTypes && (srcFieldDetail.required || dstFieldDetail.required) { _, isNopCopier := cp.(*nopCopier) if isNopCopier && dstFieldDetail.required { return nil, fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, dstStructType, dstFieldDetail.field.Name) } if isNopCopier && srcFieldDetail.required { return nil, fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, srcStructType, srcFieldDetail.field.Name) } } return c.createField2FieldCopier(dstFieldDetail, srcFieldDetail, cp), nil } func (c *structCopier) createField2MethodCopier(dM *reflect.Method, sfDetail *fieldDetail) copier { return &structField2MethodCopier{ dstMethod: dM.Index, srcFieldIndex: sfDetail.index, srcFieldUnexported: !sfDetail.field.IsExported(), required: sfDetail.required || sfDetail.field.IsExported(), } } func (c *structCopier) createField2FieldCopier(df, sf *fieldDetail, cp copier) copier { return &structField2FieldCopier{ copier: cp, dstFieldIndex: df.index, dstFieldUnexported: !df.field.IsExported(), dstFieldSetNilOnZero: df.nilOnZero, srcFieldIndex: sf.index, srcFieldUnexported: !sf.field.IsExported(), required: sf.required || df.required || df.field.IsExported(), } } // structField2FieldCopier data structure of copier that copies from // a src field to a dst field directly type structField2FieldCopier struct { copier copier dstFieldIndex []int dstFieldUnexported bool dstFieldSetNilOnZero bool srcFieldIndex []int srcFieldUnexported bool required bool } // Copy implementation of Copy function for struct field copier direct. // NOTE: `dst` and `src` are struct values. func (c *structField2FieldCopier) Copy(dst, src reflect.Value) (err error) { if len(c.srcFieldIndex) == 1 { src = src.Field(c.srcFieldIndex[0]) } else { // NOTE: When a struct pointer is embedded (e.g. type StructX struct { *BaseStruct }), // this retrieval can fail if the embedded struct pointer is nil. Just skip copying when fails. src, err = src.FieldByIndexErr(c.srcFieldIndex) if err != nil { // There's no src field to copy from, reset the dst field to zero structFieldSetZero(dst, c.dstFieldIndex) return nil //nolint:nilerr } } if c.srcFieldUnexported { if !src.CanAddr() { if c.required { return fmt.Errorf("%w: accessing unexported source field requires it to be addressable", ErrValueUnaddressable) } return nil } src = reflect.NewAt(src.Type(), unsafe.Pointer(src.UnsafeAddr())).Elem() //nolint:gosec } if len(c.dstFieldIndex) == 1 { dst = dst.Field(c.dstFieldIndex[0]) } else { // Get dst field with making sure it's settable dst = structFieldGetWithInit(dst, c.dstFieldIndex) } if c.dstFieldUnexported { // NOTE: dst is always addressable as Copy() requires `dst` to be pointer dst = reflect.NewAt(dst.Type(), unsafe.Pointer(dst.UnsafeAddr())).Elem() //nolint:gosec } // Use custom copier if set if c.copier != nil { if err = c.copier.Copy(dst, src); err != nil { if c.required { return err } return nil } } else { // Otherwise, just perform simple direct copying dst.Set(src) } // When instructed to set `dst` as `nil` on zero if c.dstFieldSetNilOnZero { nillableValueSetNilOnZero(dst) } return nil } // structField2MethodCopier data structure of copier that copies between `fields` and `methods` type structField2MethodCopier struct { dstMethod int srcFieldIndex []int srcFieldUnexported bool required bool } // Copy implementation of Copy function for struct field copier between `fields` and `methods`. // NOTE: `dst` and `src` are struct values. func (c *structField2MethodCopier) Copy(dst, src reflect.Value) (err error) { if len(c.srcFieldIndex) == 1 { src = src.Field(c.srcFieldIndex[0]) } else { // NOTE: When a struct pointer is embedded (e.g. type StructX struct { *BaseStruct }), // this retrieval can fail if the embedded struct pointer is nil. Just skip copying when fails. src, err = src.FieldByIndexErr(c.srcFieldIndex) if err != nil { return nil //nolint:nilerr } } if c.srcFieldUnexported { if !src.CanAddr() { if c.required { return fmt.Errorf("%w: accessing unexported source field requires it to be addressable", ErrValueUnaddressable) } return nil } src = reflect.NewAt(src.Type(), unsafe.Pointer(src.UnsafeAddr())).Elem() //nolint:gosec } dst = dst.Addr().Method(c.dstMethod) errVal := dst.Call([]reflect.Value{src})[0] if errVal.IsNil() { return nil } err, ok := errVal.Interface().(error) if !ok { return fmt.Errorf("%w: struct method returned non-error value", ErrTypeInvalid) } return err } golang-github-tiendc-go-deepcopy-1.7.2/struct_copier_go1.23_test.go000066400000000000000000000025231517235416400252030ustar00rootroot00000000000000//go:build go1.23 package deepcopy import ( "testing" "unique" "github.com/stretchr/testify/assert" ) func Test_Copy_struct_on_standard_types_go1_23(t *testing.T) { t.Run("#1: Copy unique.Handle[string]", func(t *testing.T) { s := unique.Make("hello") var d unique.Handle[string] err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, d, s) assert.Equal(t, d.Value(), s.Value()) // unique.Handle[T] as struct fields type A struct { I int } type S struct { H unique.Handle[A] } type D struct { H unique.Handle[A] } s2 := S{H: unique.Make(A{I: 111})} var d2 D err = Copy(&d2, s2) assert.Nil(t, err) assert.Equal(t, d2.H, s2.H) assert.Equal(t, d2.H.Value(), s2.H.Value()) }) t.Run("#2: Copy unique.Handle[string] to derived type", func(t *testing.T) { type T unique.Handle[string] s := unique.Make("hello") var d T err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, unique.Handle[string](d), s) assert.Equal(t, unique.Handle[string](d).Value(), s.Value()) // unique.Handle[T] as struct fields type S struct { H unique.Handle[string] } type D struct { H T } s2 := S{H: unique.Make("hello")} var d2 D err = Copy(&d2, s2) assert.Nil(t, err) assert.Equal(t, unique.Handle[string](d2.H), s2.H) assert.Equal(t, unique.Handle[string](d2.H).Value(), s2.H.Value()) }) } golang-github-tiendc-go-deepcopy-1.7.2/struct_copier_test.go000066400000000000000000000635421517235416400242220ustar00rootroot00000000000000package deepcopy import ( "testing" "time" "unsafe" "github.com/stretchr/testify/assert" ) func Test_Copy_struct(t *testing.T) { t.Run("#1: copy fields directly", func(t *testing.T) { type SS struct { I int U uint } type DD struct { I int U uint } var s SS = SS{I: 1, U: 2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: 2}, d) }) t.Run("#2: copy fields with conversion", func(t *testing.T) { type SS struct { I int F float32 } type DD struct { I IntT F uint } var s SS = SS{I: 1, F: 2.2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, F: 2}, d) }) t.Run("#3: copy fields with lossy conversion (int -> int8)", func(t *testing.T) { type SS struct { I int F float32 X string } type DD struct { I int8 F uint Y bool } var s SS = SS{I: 128, F: 2.2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: -128, F: 2}, d) }) t.Run("#4: copy fields with conversion (ptr -> value)", func(t *testing.T) { type SS struct { I *int F float32 } type DD struct { I int F uint } var s SS = SS{I: ptrOf(1), F: 2.2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, F: 2}, d) }) t.Run("#5: copy fields with conversion (value -> ptr)", func(t *testing.T) { type SS struct { I int F float32 } type DD struct { I *int8 F uint } var s SS = SS{I: 128, F: 2.2} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: ptrOf(int8(-128)), F: 2}, d) }) t.Run("#6: copy fields with conversion (slice -> array)", func(t *testing.T) { type SS struct { I []int F float32 } type DD struct { I [5]int8 F uint } var s SS = SS{I: []int{126, 127, 128}, F: 2.2} var d DD = DD{I: [5]int8{1, 2, 3, 4, 5}} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: [5]int8{126, 127, -128, 0, 0}, F: 2}, d) }) t.Run("#7: struct-in-struct", func(t *testing.T) { type SS2 struct { I int X bool } type SS struct { I int SS SS2 } type DD2 struct { I int Y string } type DD struct { I int DD *DD2 `copy:"SS"` } var s SS = SS{I: 1, SS: SS2{I: 11, X: true}} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, DD: &DD2{I: 11}}, d) }) t.Run("#8: with src field is ignored", func(t *testing.T) { type SS struct { I int `copy:"-"` F float32 } type DD struct { I int F uint } var s SS = SS{I: 1, F: 2.2} var d DD = DD{I: 100} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 100, F: 2}, d) }) t.Run("#9: with dst field is ignored", func(t *testing.T) { type SS struct { I int F float32 } type DD struct { I int `copy:"-"` F uint } var s SS = SS{I: 1, F: 2.2} var d DD = DD{I: 100} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 100, F: 2}, d) }) t.Run("#10: structs have fields of type function", func(t *testing.T) { type SS struct { I int Fn func(int) string `copy:"fn"` } type DD struct { I int Fn func(int) string `copy:"fn"` } fn := func(int) string { return "abc" } var s SS = SS{I: 1, Fn: fn} var d DD = DD{I: 100} err := Copy(&d, s) assert.Nil(t, err) assert.NotNil(t, d.Fn) assert.Equal(t, "abc", d.Fn(1)) }) t.Run("#11: cycle reference", func(t *testing.T) { type SS struct { I int Ref *SS } type DD struct { I int Ref *DD } var s SS = SS{I: 1, Ref: &SS{I: 2, Ref: &SS{I: 3}}} var d DD = DD{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, Ref: &DD{I: 2, Ref: &DD{I: 3}}}, d) }) t.Run("#12: copy values of same struct", func(t *testing.T) { type SS2 struct { I int } type SS struct { I int ss2 *SS2 } var s SS = SS{I: 1, ss2: &SS2{I: 2}} var d SS err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, s, d) // Changes the src, they must become different s.ss2.I++ assert.NotEqual(t, s, d) }) t.Run("#13: copy from derived struct", func(t *testing.T) { type SS2 struct { I int } type SS struct { I int ss2 *SS2 } type DD SS // Copy un-addressable field ss2, but not required var s SS = SS{I: 1, ss2: &SS2{I: 2}} var d DD err := Copy(&d, s) assert.Nil(t, err) assert.NotEqual(t, s, d) assert.Equal(t, DD{I: 1, ss2: nil}, d) // Copy addressable field ss2, but required s = SS{I: 1, ss2: &SS2{I: 2}} d = DD{} err = Copy(&d, &s) // use &s to make src field addressable assert.Nil(t, err) assert.NotEqual(t, s, d) assert.Equal(t, DD{I: 1, ss2: &SS2{I: 2}}, d) }) t.Run("#14: non-copyable type, but non-required", func(t *testing.T) { type SS2 struct { P unsafe.Pointer // unsafe.Pointer is not copyable for now } type SS struct { I int ss2 *SS2 } var s SS = SS{I: 1, ss2: &SS2{P: nil}} var d SS err := Copy(&d, &s) assert.Nil(t, err) assert.NotEqual(t, s, d) }) t.Run("#15: non-copyable type, but field unexported", func(t *testing.T) { type SS struct { I int v any } var s SS = SS{I: 1, v: unsafe.Pointer(ptrOf(1))} var d SS err := Copy(&d, &s) assert.Nil(t, err) assert.NotEqual(t, s, d) }) } func Test_Copy_struct_error(t *testing.T) { t.Run("#1: struct -> slice (error)", func(t *testing.T) { type SS struct { I int } var s SS = SS{111} var d []int err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#2: src required but no equivalent dst field", func(t *testing.T) { type SS struct { I int `copy:",required"` U uint } type DD struct { U uint } var s SS = SS{I: 1, U: 2} var d DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#3: dst required but no equivalent src field", func(t *testing.T) { type SS struct { I int } type DD struct { U uint `copy:",required"` } var s SS = SS{I: 1} var d DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#4: struct -> slice (ignore error)", func(t *testing.T) { type SS struct { I int } var s SS = SS{111} var d []int err := Copy(&d, s, IgnoreNonCopyableTypes(true)) assert.Nil(t, err) }) t.Run("#5: src ignore non-copyable but set 'required'", func(t *testing.T) { type SS struct { I int `copy:",required"` U uint } type DD struct { I []string U uint } var s SS = SS{I: 1, U: 2} var d DD err := Copy(&d, s, IgnoreNonCopyableTypes(true)) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#6: dst ignore non-copyable but set 'required'", func(t *testing.T) { type SS struct { I int U uint } type DD struct { I []string `copy:",required"` U uint } var s SS = SS{I: 1, U: 2} var d DD err := Copy(&d, s, IgnoreNonCopyableTypes(true)) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#7: non-copyable between src and dst fields", func(t *testing.T) { type SS struct { I []float32 U uint } type DD struct { I []string U uint } var s SS = SS{I: []float32{1}, U: 2} var d DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) } func Test_Copy_struct_unexported(t *testing.T) { t.Run("#1: unexported -> unexported", func(t *testing.T) { type SS struct { I int u uint } type DD struct { I int u uint `copy:",required"` } var s SS = SS{I: 1, u: 2} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, u: 2}, d) }) t.Run("#2: unexported -> exported", func(t *testing.T) { type SS struct { I int u uint } type DD struct { I int U uint `copy:"u,required"` } var s SS = SS{I: 1, u: 2} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: 2}, d) }) t.Run("#3: exported -> unexported", func(t *testing.T) { type SS struct { I int U uint } type DD struct { I int u uint `copy:"U,required"` } var s SS = SS{I: 1, U: 2} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, u: 2}, d) }) t.Run("#4: exported -> unexported with conversion", func(t *testing.T) { type SS struct { I int U uint } type DD struct { i IntT `copy:"I,required"` U uint } var s SS = SS{I: 1, U: 2} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{i: 1, U: 2}, d) }) t.Run("#5: unexported -> unexported with conversion", func(t *testing.T) { type SS struct { i int `copy:"I,required"` U uint } type DD struct { i *int `copy:"I,required"` U uint } var s SS = SS{i: 1, U: 2} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{i: ptrOf(1), U: 2}, d) }) } func Test_Copy_struct_unexported_error(t *testing.T) { t.Run("#1: src is unaddressable", func(t *testing.T) { type SS struct { I int u uint } type DD struct { I int u uint `copy:",required"` } var s SS = SS{I: 1, u: 2} var d DD err := Copy(&d, s) assert.ErrorIs(t, err, ErrValueUnaddressable) }) } type testD1 struct { x1 int x2 int x3 int x4 uint U uint } func (d *testD1) CopyI1(i1 int) error { d.x1 = i1 * 2 return nil } func (d *testD1) CopyI2(i2 int) { // incorrect method prototype (no return error) d.x2 = i2 * 2 } func (d *testD1) CopyI3(i3 int, v string) error { // incorrect method prototype (2 input args) d.x3 = i3 * 2 return nil } func (d *testD1) CopyI4(i4 uint) error { // incorrect method prototype (unmatched input type) d.x4 = i4 * 2 return nil } func (d *testD1) CopyI5(i5 int) string { // incorrect method prototype (not return error type) return "" } func (d *testD1) CopyI6(i6 int) error { // incorrect method prototype (unmatched input type) return errTest } func (d *testD1) NotCopy(i6 int) error { // not a copying method return errTest } func Test_Copy_struct_method(t *testing.T) { t.Run("#1: field -> dst method", func(t *testing.T) { type SS struct { I1 int U uint } var s SS = SS{I1: 1, U: 2} var d testD1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD1{x1: 2, U: 2}, d) }) t.Run("#2: unexported field -> dst method", func(t *testing.T) { type SS struct { i1 int `copy:"I1,required"` U uint } var s SS = SS{i1: 1, U: 2} var d testD1 err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, testD1{x1: 2, U: 2}, d) }) t.Run("#3: incorrect method prototype (CopyI2())", func(t *testing.T) { type SS struct { I2 int U uint } var s SS = SS{I2: 1, U: 2} var d testD1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD1{U: 2}, d) }) t.Run("#4: not allow copying from field -> method", func(t *testing.T) { type SS struct { I1 int U uint } var s SS = SS{I1: 1, U: 2} var d testD1 err := Copy(&d, s, CopyBetweenStructFieldAndMethod(false)) assert.Nil(t, err) assert.Equal(t, testD1{U: 2}, d) }) t.Run("#5: copy from src embedded field", func(t *testing.T) { type SBase struct { I1 int } type SS struct { SBase U uint } var s SS = SS{U: 2, SBase: SBase{I1: 123}} var d testD1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD1{U: 2, x1: 246}, d) }) t.Run("#6: copy from src embedded field, but field value can't be retrieved due to nil ptr", func(t *testing.T) { type SBase struct { I1 int } type SS struct { *SBase U uint } var s SS = SS{U: 2} var d testD1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD1{U: 2}, d) }) } func Test_Copy_struct_method_error(t *testing.T) { t.Run("#1: unexported field -> dst method", func(t *testing.T) { type SS struct { i1 int `copy:"I1,required"` U uint } var s SS = SS{i1: 1, U: 2} var d testD1 err := Copy(&d, s) assert.ErrorIs(t, err, ErrValueUnaddressable) }) t.Run("#2: incorrect method prototype (CopyI2())", func(t *testing.T) { type SS struct { I2 int `copy:",required"` U uint } var s SS = SS{I2: 1, U: 2} var d testD1 err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#3: incorrect method prototype (CopyI3())", func(t *testing.T) { type SS struct { I3 int `copy:",required"` U uint } var s SS = SS{I3: 1, U: 2} var d testD1 err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#4: incorrect method prototype (CopyI4())", func(t *testing.T) { type SS struct { I4 int `copy:",required"` U uint } var s SS = SS{I4: 1, U: 2} var d testD1 err := Copy(&d, s) assert.ErrorIs(t, err, ErrMethodInvalid) }) t.Run("#5: incorrect method prototype (CopyI5())", func(t *testing.T) { type SS struct { I5 int `copy:",required"` U uint } var s SS = SS{I5: 1, U: 2} var d testD1 err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#6: copy method return error (CopyI6())", func(t *testing.T) { type SS struct { I6 int `copy:",required"` U uint } var s SS = SS{I6: 1, U: 2} var d testD1 err := Copy(&d, s) assert.ErrorIs(t, err, errTest) }) } func Test_Copy_struct_with_embedded_struct(t *testing.T) { type SBase1 struct { I int } type SBase2 struct { SBase1 S string } type DBase1 struct { I int } type DBase2 struct { DBase1 S string } t.Run("#1: both src and dst have equivalent embedded fields", func(t *testing.T) { type SS struct { SBase2 U uint `copy:",required"` } type DD struct { DBase2 U uint `copy:",required"` } s := SS{U: 100, SBase2: SBase2{S: "abc", SBase1: SBase1{I: 11}}} d := DD{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, DBase2: DBase2{S: "abc", DBase1: DBase1{I: 11}}}, d) // With some tags type SS2 struct { SBase2 `copy:"base,required"` U uint `copy:",required"` } type DD2 struct { DBase2 `copy:"base,required"` U uint `copy:",required"` } s2 := SS2{U: 100, SBase2: SBase2{S: "abc", SBase1: SBase1{I: 11}}} d2 := DD2{} err = Copy(&d2, s2) assert.Nil(t, err) assert.Equal(t, DD2{U: 100, DBase2: DBase2{S: "abc", DBase1: DBase1{I: 11}}}, d2) }) t.Run("#2: both src and dst have same embedded struct", func(t *testing.T) { type SS struct { SBase2 U uint `copy:",required"` } type DD struct { SBase2 U uint `copy:",required"` } s := SS{U: 100, SBase2: SBase2{S: "abc", SBase1: SBase1{I: 11}}} d := DD{U: 123, SBase2: SBase2{S: "xyz", SBase1: SBase1{I: 111}}} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, SBase2: SBase2{S: "abc", SBase1: SBase1{I: 11}}}, d) }) t.Run("#3: both src and dst have equivalent embedded fields, but src embeds ptr of struct", func(t *testing.T) { type SS struct { *SBase2 U uint `copy:",required"` } type DD struct { DBase2 U uint `copy:",required"` } // Ptr has value set s := SS{U: 100, SBase2: &SBase2{S: "abc", SBase1: SBase1{I: 11}}} d := DD{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, DBase2: DBase2{S: "abc", DBase1: DBase1{I: 11}}}, d) // Ptr is nil s = SS{U: 100} d = DD{} err = Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{U: 100}, d) }) t.Run("#4: both src and dst have equivalent embedded fields, but dst embeds ptr of struct", func(t *testing.T) { type SS struct { SBase2 U uint } type DD struct { *DBase2 U uint } s := SS{U: 100, SBase2: SBase2{S: "abc", SBase1: SBase1{I: 11}}} d := DD{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, DBase2: &DBase2{S: "abc", DBase1: DBase1{I: 11}}}, d) }) t.Run("#5: src has embedded struct, dst doesn't (flattening the copy)", func(t *testing.T) { type SS struct { SBase2 U uint } type DD struct { I int `copy:",required"` S string `copy:",required"` U uint `copy:",required"` } s := SS{U: 100, SBase2: SBase2{S: "abc", SBase1: SBase1{I: 11}}} d := DD{S: "xyz"} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, S: "abc", I: 11}, d) // With ignoring a field type DD2 struct { I int `copy:",required"` S string `copy:"-"` U uint `copy:",required"` } d2 := DD2{} err = Copy(&d2, s) assert.Nil(t, err) assert.Equal(t, DD2{U: 100, I: 11}, d2) }) t.Run("#6: src has embedded struct ptr, dst doesn't (flattening the copy)", func(t *testing.T) { type SS struct { *SBase2 U uint } type DD struct { I int `copy:",required"` S string `copy:",required"` U uint `copy:",required"` } // Ptr has a value set s := SS{U: 100, SBase2: &SBase2{S: "abc", SBase1: SBase1{I: 11}}} d := DD{S: "xyz"} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, S: "abc", I: 11}, d) // Ptr is nil s = SS{U: 100} d = DD{S: "xyz"} err = Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100}, d) // With ignoring a field type DD2 struct { I int `copy:",required"` S string `copy:"-"` U uint `copy:",required"` } s = SS{U: 100, SBase2: &SBase2{S: "abc", SBase1: SBase1{I: 11}}} d2 := DD2{S: "xyz"} err = Copy(&d2, s) assert.Nil(t, err) assert.Equal(t, DD2{U: 100, S: "xyz", I: 11}, d2) }) t.Run("#7: dst has embedded struct, src doesn't (flattening the copy)", func(t *testing.T) { type SS struct { I int `copy:",required"` S string `copy:",required"` U uint `copy:",required"` } type DD struct { DBase2 U uint } s := SS{U: 100, S: "abc", I: 11} d := DD{U: 123, DBase2: DBase2{S: "xyz"}} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, DBase2: DBase2{S: "abc", DBase1: DBase1{I: 11}}}, d) }) t.Run("#8: dst has embedded struct ptr, src doesn't (flattening the copy)", func(t *testing.T) { type SS struct { I int `copy:",required"` S string `copy:",required"` U uint `copy:",required"` } type DD struct { *DBase2 U uint } // Ptr has value set s := SS{U: 100, S: "abc", I: 11} d := DD{U: 123, DBase2: &DBase2{S: "xyz"}} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, DBase2: &DBase2{S: "abc", DBase1: DBase1{I: 11}}}, d) // Ptr is nil initially s = SS{U: 100, S: "abc", I: 11} d = DD{U: 123} err = Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{U: 100, DBase2: &DBase2{S: "abc", DBase1: DBase1{I: 11}}}, d) }) t.Run("#9: embedded struct with ignoring a field", func(t *testing.T) { type SS2 struct { S string `copy:"-"` } type SS struct { I int SS2 } type DD struct { I int S string } var s SS = SS{I: 1, SS2: SS2{S: "abc"}} var d DD err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1}, d) }) } func Test_Copy_struct_with_embedded_struct_error(t *testing.T) { t.Run("#1: src inherited field requires copying", func(t *testing.T) { type SBase struct { I int `copy:",required"` } type SS struct { SBase U uint } type DD struct { U uint } s := SS{U: 100, SBase: SBase{I: 11}} d := DD{} err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) t.Run("#2: dst inherited field requires copying", func(t *testing.T) { type SS struct { U uint } type DBase struct { I int `copy:",required"` } type DD struct { DBase U uint } s := SS{U: 100} d := DD{} err := Copy(&d, s) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) } func Test_Copy_struct_with_set_nil_on_zero(t *testing.T) { t.Run("#1: primitive type", func(t *testing.T) { type SS struct { I int U uint } type DD struct { I int U *uint `copy:",nilonzero"` } // Source field is not zero s := SS{I: 1, U: 11} d := DD{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: ptrOf(uint(11))}, d) // Source field is zero s = SS{I: 1, U: 0} d = DD{} err = Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, U: nil}, d) }) t.Run("#2: string type", func(t *testing.T) { type SS struct { I int S string } type DD struct { I int S *string `copy:",nilonzero"` } // Source field is not zero s := SS{I: 1, S: "x"} d := DD{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, S: ptrOf("x")}, d) // Source field is zero s = SS{I: 1, S: ""} d = DD{} err = Copy(&d, s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, S: nil}, d) }) t.Run("#3: time.Time type", func(t *testing.T) { type SS struct { I int Time time.Time } type DD struct { I int Time *time.Time `copy:",nilonzero"` } // Source field is not zero dt := time.Now() s := SS{I: 1, Time: dt} d := DD{} err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, Time: ptrOf(dt)}, d) // Source field is zero s = SS{I: 1, Time: time.Time{}} d = DD{} err = Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, Time: nil}, d) }) t.Run("#3b: time.Time type with deeper level", func(t *testing.T) { type SS struct { I int Time time.Time } type DD struct { I int Time ***time.Time `copy:",nilonzero"` } // Source field is not zero dt := time.Now() s := SS{I: 1, Time: dt} d := DD{} err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, Time: ptrOf(ptrOf(ptrOf(dt)))}, d) // Source field is zero s = SS{I: 1, Time: time.Time{}} d = DD{} err = Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, Time: nil}, d) }) t.Run("#4: custom struct type", func(t *testing.T) { type StructType struct { I int S *string } type SS struct { I int ST StructType } type DD struct { I int ST *StructType `copy:",nilonzero"` } s := SS{I: 1, ST: StructType{}} d := DD{} err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, ST: nil}, d) }) t.Run("#5: dst field is interface type", func(t *testing.T) { type SS struct { I int Time time.Time } type DD struct { I int Time any `copy:",nilonzero"` } // Source field is not zero dt := time.Now() s := SS{I: 1, Time: dt} d := DD{} err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, Time: dt}, d) // Source field is zero s = SS{I: 1, Time: time.Time{}} d = DD{} err = Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, Time: nil}, d) }) t.Run("#6: dst field is map type", func(t *testing.T) { type SS struct { I int M map[int]string } type DD struct { I int M map[int]string `copy:",nilonzero"` } // Source field is not zero s := SS{I: 1, M: map[int]string{1: "a", 2: "b"}} d := DD{} err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, M: map[int]string{1: "a", 2: "b"}}, d) // Source field is zero s = SS{I: 1, M: map[int]string{}} d = DD{} err = Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, M: nil}, d) }) t.Run("#7: dst field is slice type", func(t *testing.T) { type SS struct { I int S []any } type DD struct { I int S []any `copy:",nilonzero"` } // Source field is not zero s := SS{I: 1, S: []any{1, "a"}} d := DD{} err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, S: []any{1, "a"}}, d) // Source field is zero s = SS{I: 1, S: make([]any, 0, 10)} d = DD{} err = Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, DD{I: 1, S: nil}, d) }) } type testS2 struct { I int S string } type testD2 struct { I int S string } func (d *testD2) PostCopy(src any) error { testS2, _ := src.(testS2) if testS2.I == 100 { return errTest } d.I *= 2 d.S += d.S return nil } type testD22 struct { I int S string } func (d *testD22) PostCopy(src any) any { d.I *= 2 d.S += d.S return nil } type testD23 struct { I int S string } func (d *testD23) PostCopy(src string) error { d.I *= 2 d.S += d.S return nil } type testD24 struct { I int S string } func (d *testD24) PostCopy(src string) (any, error) { d.I *= 2 d.S += d.S return nil, nil } type testD25 struct { I int S string } // PostCopy is defined within struct value, not pointer func (d testD25) PostCopy(src any) error { d.I *= 2 d.S += d.S return nil } func Test_Copy_struct_with_post_copy_event(t *testing.T) { t.Run("#1: success without error", func(t *testing.T) { s := testS2{I: 1, S: "a"} d := testD2{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD2{I: 2, S: "aa"}, d) }) t.Run("#2: PostCopy returns error", func(t *testing.T) { s := testS2{I: 100, S: "a"} // When testS2.I == 100, PostCopy returns error d := testD2{} err := Copy(&d, s) assert.NotNil(t, err) assert.ErrorIs(t, err, errTest) }) t.Run("#3: PostCopy not satisfied", func(t *testing.T) { s := testS2{I: 1, S: "a"} d := testD22{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD22{I: 1, S: "a"}, d) }) t.Run("#4: PostCopy not satisfied", func(t *testing.T) { s := testS2{I: 1, S: "a"} d := testD23{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD23{I: 1, S: "a"}, d) }) t.Run("#5: PostCopy not satisfied", func(t *testing.T) { s := testS2{I: 1, S: "a"} d := testD24{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD24{I: 1, S: "a"}, d) }) t.Run("#6: PostCopy is defined on struct value, not pointer", func(t *testing.T) { s := testS2{I: 1, S: "a"} d := testD25{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testD25{I: 1, S: "a"}, d) // won't work }) } func Test_Copy_struct_on_standard_types(t *testing.T) { t.Run("#1: Copy time.Time to time.Time", func(t *testing.T) { s := time.Now() var d time.Time err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, d, s) // time.Time as struct fields type S struct { T time.Time } type D struct { T time.Time } s2 := S{T: time.Now()} var d2 D err = Copy(&d2, s2) assert.Nil(t, err) assert.Equal(t, d2.T, s2.T) }) t.Run("#2: Copy time.Time to derived type", func(t *testing.T) { type T time.Time s := time.Now() var d T err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, time.Time(d), s) // time.Time as struct fields type S struct { T time.Time } type D struct { T T } s2 := S{T: time.Now()} var d2 D err = Copy(&d2, s2) assert.Nil(t, err) assert.Equal(t, time.Time(d2.T), s2.T) }) // NOTE: unique.Handle[T] is available from Go1.23, it is tested in the relevant test file } golang-github-tiendc-go-deepcopy-1.7.2/struct_tag.go000066400000000000000000000024021517235416400224410ustar00rootroot00000000000000package deepcopy import ( "reflect" "strings" ) // fieldDetail stores field copying detail parsed from a struct field type fieldDetail struct { field *reflect.StructField key string ignored bool required bool nilOnZero bool done bool index []int nestedFields []*fieldDetail } // markDone sets the `done` flag of a field detail and all of its nested fields recursively func (detail *fieldDetail) markDone() { detail.done = true for _, f := range detail.nestedFields { f.markDone() } } // parseTag parses struct tag for getting copying detail and configuration func parseTag(detail *fieldDetail) { tagValue, ok := detail.field.Tag.Lookup(defaultTagName) detail.key = detail.field.Name if !ok { return } tags := strings.Split(tagValue, ",") switch { case tags[0] == "-": detail.ignored = true case tags[0] != "": detail.key = tags[0] } for _, tagOpt := range tags[1:] { switch tagOpt { case "required": if !detail.ignored { detail.required = true } case "nilonzero": k := detail.field.Type.Kind() // Set nil on zero only applies to types which can set `nil` if k == reflect.Pointer || k == reflect.Interface || k == reflect.Slice || k == reflect.Map { detail.nilOnZero = true } } } } golang-github-tiendc-go-deepcopy-1.7.2/struct_tag_test.go000066400000000000000000000021511517235416400235010ustar00rootroot00000000000000package deepcopy import ( "reflect" "testing" "github.com/stretchr/testify/assert" ) func Test_parseTag(t *testing.T) { type Item struct { Col1 bool Col2 int `copy:"col2,required"` Col3 string `copy:"-"` Col4 string `copy:""` Col5 string `copy:",unsupported"` } structType := reflect.TypeOf(Item{}) col1, _ := structType.FieldByName("Col1") detail1 := &fieldDetail{field: &col1} parseTag(detail1) assert.True(t, detail1.key == "Col1" && !detail1.ignored) col2, _ := structType.FieldByName("Col2") detail2 := &fieldDetail{field: &col2} parseTag(detail2) assert.True(t, detail2.key == "col2" && detail2.required) col3, _ := structType.FieldByName("Col3") detail3 := &fieldDetail{field: &col3} parseTag(detail3) assert.True(t, detail3.key == "Col3" && detail3.ignored) col4, _ := structType.FieldByName("Col4") detail4 := &fieldDetail{field: &col4} parseTag(detail4) assert.True(t, detail4.key == "Col4" && !detail4.required) col5, _ := structType.FieldByName("Col5") detail5 := &fieldDetail{field: &col5} parseTag(detail5) assert.True(t, detail5.key == "Col5" && !detail5.required) } golang-github-tiendc-go-deepcopy-1.7.2/struct_to_map_copier.go000066400000000000000000000151771517235416400245230ustar00rootroot00000000000000package deepcopy import ( "fmt" "reflect" "strings" "unsafe" ) // structToMapCopier data structure of copier that copies a `struct` to a map type structToMapCopier struct { ctx *Context fieldCopiers []copier postCopyMethod *int } // Copy implementation of Copy function for struct to map copier func (c *structToMapCopier) Copy(dst, src reflect.Value) error { // Inits destination map if dst.IsNil() { dst.Set(reflect.MakeMapWithSize(dst.Type(), len(c.fieldCopiers))) } // Copies struct fields to map for _, cp := range c.fieldCopiers { if err := cp.Copy(dst, src); err != nil { return err } } // Executes post-copy function of the destination map if c.postCopyMethod != nil { dst = dst.Addr().Method(*c.postCopyMethod) errVal := dst.Call([]reflect.Value{src})[0] if errVal.IsNil() { return nil } err, ok := errVal.Interface().(error) if !ok { // Should never get in here return fmt.Errorf("%w: PostCopy method returns non-error value", ErrTypeInvalid) } return err } return nil } func (c *structToMapCopier) init(dstType, srcType reflect.Type) (err error) { mapKeyType, mapValType := dstType.Key(), dstType.Elem() mapKeyNeedConvert := false switch { case strType.AssignableTo(mapKeyType): case strType.ConvertibleTo(mapKeyType): mapKeyNeedConvert = true default: if c.ctx.IgnoreNonCopyableTypes { return nil } return fmt.Errorf("%w: copying from struct type '%v' to 'map[%v]%v' requires map key type to be 'string'", ErrTypeNonCopyable, srcType, mapKeyType, mapValType) } dstCopyingMethods, postCopyMethod := typeParseMethods(c.ctx, dstType) if postCopyMethod != nil { c.postCopyMethod = &postCopyMethod.Index } srcDirectFields, mapSrcDirectFields, srcInheritedFields, mapSrcInheritedFields := structParseAllFields(srcType) c.fieldCopiers = make([]copier, 0, len(srcDirectFields)+len(srcInheritedFields)) for _, key := range append(srcDirectFields, srcInheritedFields...) { // Find field details from `src` having the key sfDetail := mapSrcDirectFields[key] if sfDetail == nil { sfDetail = mapSrcInheritedFields[key] } if sfDetail == nil || sfDetail.ignored || sfDetail.done || sfDetail.field.Anonymous { continue } // Copying methods have higher priority, so if a method defined in the dst struct, use it if dstCopyingMethods != nil { methodName := "Copy" + strings.ToUpper(key[:1]) + key[1:] dstCpMethod, exists := dstCopyingMethods[methodName] if exists && !dstCpMethod.Type.In(1).AssignableTo(sfDetail.field.Type) { return fmt.Errorf("%w: struct method '%v.%s' does not accept argument type '%v' from '%v[%s]'", ErrMethodInvalid, dstType, dstCpMethod.Name, sfDetail.field.Type, srcType, sfDetail.field.Name) } if exists { c.fieldCopiers = append(c.fieldCopiers, c.createField2MethodCopier(dstCpMethod, sfDetail)) sfDetail.markDone() continue } } copier, err := c.buildCopier(mapKeyType, mapValType, srcType, sfDetail, mapKeyNeedConvert) if err != nil { return err } c.fieldCopiers = append(c.fieldCopiers, copier) sfDetail.markDone() } return nil } func (c *structToMapCopier) buildCopier(mapKeyType, mapValueType, srcStructType reflect.Type, srcFieldDetail *fieldDetail, mapKeyNeedConvert bool) (copier, error) { sf := srcFieldDetail.field mapKey := reflect.ValueOf(srcFieldDetail.key) if mapKeyNeedConvert { mapKey = mapKey.Convert(mapKeyType) } // OPTIMIZATION: buildCopier() can handle this nicely if simpleKindMask&(1< 0 { if sf.Type == mapValueType { // NOTE: pass nil to unset custom copier and trigger direct copying. // We can pass `&directCopier{}` for the same result (but it's a bit slower). return c.createField2MapEntryCopier(srcFieldDetail, mapKey, nil), nil } if sf.Type.ConvertibleTo(mapValueType) { return c.createField2MapEntryCopier(srcFieldDetail, mapKey, &mapItemCopier{dstType: mapValueType, copier: defaultConvCopier}), nil } } cp, err := buildCopier(c.ctx, mapValueType, sf.Type) if err != nil { // NOTE: If the copy is not required and the field is unexported, ignore the error if !srcFieldDetail.required && !sf.IsExported() { return defaultNopCopier, nil } return nil, err } if c.ctx.IgnoreNonCopyableTypes && srcFieldDetail.required { _, isNopCopier := cp.(*nopCopier) if isNopCopier { return nil, fmt.Errorf("%w: struct field '%v[%s]' requires copying", ErrFieldRequireCopying, srcStructType, srcFieldDetail.field.Name) } } return c.createField2MapEntryCopier(srcFieldDetail, mapKey, &mapItemCopier{dstType: mapValueType, copier: cp}), nil } func (c *structToMapCopier) createField2MethodCopier(dM *reflect.Method, sfDetail *fieldDetail) copier { return &structField2MethodCopier{ dstMethod: dM.Index, srcFieldIndex: sfDetail.index, srcFieldUnexported: !sfDetail.field.IsExported(), required: sfDetail.required || sfDetail.field.IsExported(), } } func (c *structToMapCopier) createField2MapEntryCopier(sf *fieldDetail, key reflect.Value, valueCopier *mapItemCopier) copier { return &structField2MapEntryCopier{ key: key, valueCopier: valueCopier, srcFieldIndex: sf.index, srcFieldUnexported: !sf.field.IsExported(), required: sf.required || sf.field.IsExported(), } } // structField2MapEntryCopier data structure of copier that copies from // a src field to the destination map type structField2MapEntryCopier struct { key reflect.Value valueCopier *mapItemCopier srcFieldIndex []int srcFieldUnexported bool required bool } // Copy implementation of Copy function for struct field copier direct. // NOTE: `dst` and `src` are struct values. func (c *structField2MapEntryCopier) Copy(dst, src reflect.Value) (err error) { if len(c.srcFieldIndex) == 1 { src = src.Field(c.srcFieldIndex[0]) } else { // NOTE: When a struct pointer is embedded (e.g. type StructX struct { *BaseStruct }), // this retrieval can fail if the embedded struct pointer is nil. Just skip copying when fails. src, err = src.FieldByIndexErr(c.srcFieldIndex) if err != nil { // There's no src field to copy from, reset the dst field to zero return nil //nolint:nilerr } } if c.srcFieldUnexported { if !src.CanAddr() { if c.required { return fmt.Errorf("%w: accessing unexported source field requires it to be addressable", ErrValueUnaddressable) } return nil } src = reflect.NewAt(src.Type(), unsafe.Pointer(src.UnsafeAddr())).Elem() //nolint:gosec } if c.valueCopier != nil { if src, err = c.valueCopier.Copy(src); err != nil { if c.required { return err } return nil } } dst.SetMapIndex(c.key, src) return nil } golang-github-tiendc-go-deepcopy-1.7.2/struct_to_map_copier_test.go000066400000000000000000000270731517235416400255600ustar00rootroot00000000000000package deepcopy import ( "testing" "unsafe" "github.com/stretchr/testify/assert" ) func Test_Copy_structToMap(t *testing.T) { t.Run("#1: simple case", func(t *testing.T) { type SS struct { I int U uint } var s SS = SS{I: 1, U: 2} var d map[string]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]int{"I": 1, "U": 2}, d) var dd map[string]any err = Copy(&dd, s) assert.Nil(t, err) assert.Equal(t, map[string]any{"I": int(1), "U": uint(2)}, dd) }) t.Run("#2: with copy key", func(t *testing.T) { type SS struct { I int `copy:"i"` U uint } var s SS = SS{I: 1, U: 2} var d map[string]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]int{"i": 1, "U": 2}, d) }) t.Run("#3: with custom map key/value type", func(t *testing.T) { type SS struct { I int `copy:"i"` U uint } type MapKey string type MapValue int8 var s SS = SS{I: 1, U: 2} var d map[MapKey]MapValue err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[MapKey]MapValue{"i": 1, "U": 2}, d) }) t.Run("#4: with lossy conversion (int -> int8)", func(t *testing.T) { type SS struct { I int `copy:"i"` U uint } type MapKey string type MapValue int8 var s SS = SS{I: 1, U: 128} var d map[MapKey]MapValue err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[MapKey]MapValue{"i": 1, "U": -128}, d) }) t.Run("#5: with int -> float conversion", func(t *testing.T) { type SS struct { I int `copy:"i"` U uint } var s SS = SS{I: 1, U: 2} var d map[string]float32 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]float32{"i": 1, "U": 2}, d) }) t.Run("#6: with ptr -> value conversion", func(t *testing.T) { type SS struct { I *int `copy:"i"` U uint } var s SS = SS{I: ptrOf(1), U: 2} var d map[string]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]int{"i": 1, "U": 2}, d) s = SS{I: nil, U: 2} d = map[string]int{} err = Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]int{"i": 0, "U": 2}, d) }) t.Run("#7: with value -> ptr conversion", func(t *testing.T) { type SS struct { I int `copy:"i"` U uint } var s SS = SS{I: 1, U: 2} var d map[string]*int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]*int{"i": ptrOf(1), "U": ptrOf(2)}, d) }) t.Run("#8: with struct field has type 'any'", func(t *testing.T) { type SS struct { I int `copy:"i"` U any } var s SS = SS{I: 1, U: 2} var d map[string]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]int{"i": 1, "U": 2}, d) }) t.Run("#9: with map value has type slice", func(t *testing.T) { type SS struct { I []int `copy:"i"` U []uint } var s SS = SS{I: []int{1, 2}, U: []uint{11, 22}} var d map[string][]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string][]int{"i": {1, 2}, "U": {11, 22}}, d) }) t.Run("#10: with struct field is ignored", func(t *testing.T) { type SS struct { I []int `copy:"-"` U uint } var s SS = SS{I: []int{1, 2}, U: 22} var d map[string]int err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]int{"U": 22}, d) }) t.Run("#11: cyclic reference", func(t *testing.T) { type SS struct { Ref *SS } var d map[string]*SS var s SS = SS{Ref: &SS{Ref: &SS{}}} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, map[string]*SS{"Ref": {Ref: &SS{}}}, d) }) t.Run("#12: map key type is not string, but ignore error NonCopyable", func(t *testing.T) { type SS struct { I int } var s SS = SS{I: 1} var d map[int]int err := Copy(&d, &s, IgnoreNonCopyableTypes(true)) assert.Nil(t, err) assert.Equal(t, map[int]int{}, d) }) t.Run("#13: type is non-copyable and struct field is unexported, but not required (ignored)", func(t *testing.T) { type SS struct { i float32 } var s SS = SS{i: 1} var d map[string]string err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, map[string]string{}, d) }) t.Run("#14: deep embedded struct field", func(t *testing.T) { type SS3 struct { I int `copy:"i"` } type SS2 struct { SS3 } type SS struct { SS2 } var s SS = SS{SS2: SS2{SS3: SS3{I: 1}}} var d map[string]int err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, map[string]int{"i": 1}, d) }) t.Run("#15: deep embedded struct field, but nil ptr", func(t *testing.T) { type SS3 struct { I int `copy:"i"` } type SS2 struct { *SS3 } type SS struct { SS2 } var s SS = SS{SS2: SS2{SS3: nil}} var d map[string]int err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, map[string]int{}, d) }) } func Test_Copy_structToMap_error(t *testing.T) { t.Run("#1: with struct fields have different types", func(t *testing.T) { type SS struct { I int S string } var s SS = SS{I: 1, S: "abc"} var d map[string]int err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#2: with struct fields have different types", func(t *testing.T) { type SS struct { I int S any } var s SS = SS{I: 1, S: "abc"} var d map[string]int err := Copy(&d, s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#3: with non-copyable type", func(t *testing.T) { type SS struct { P unsafe.Pointer // unsafe.Pointer is not copyable for now } var s SS = SS{P: nil} var d map[string]int err := Copy(&d, &s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#4: map key type is not string", func(t *testing.T) { type SS struct { I int } var s SS = SS{I: 1} var d map[int]int err := Copy(&d, &s) assert.ErrorIs(t, err, ErrTypeNonCopyable) }) t.Run("#5: non-copyable, but field requires copying", func(t *testing.T) { type SS struct { P unsafe.Pointer `copy:",required"` } s := SS{P: nil} var d map[string]unsafe.Pointer err := Copy(&d, &s, IgnoreNonCopyableTypes(true)) assert.ErrorIs(t, err, ErrFieldRequireCopying) }) } func Test_Copy_structToMap_unexported(t *testing.T) { t.Run("#1: struct field unexported, but required", func(t *testing.T) { type SS struct { I int u uint `copy:"u,required"` } var s SS = SS{I: 1, u: 2} var d map[string]int err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, map[string]int{"I": 1, "u": 2}, d) }) t.Run("#2: struct field unexported, but non-required", func(t *testing.T) { type SS struct { I int u uint `copy:"u"` } var s SS = SS{I: 1, u: 2} var d map[string]int err := Copy(&d, s) // NOTE: pass src as value to cause Unaddressable error assert.Nil(t, err) assert.Equal(t, map[string]int{"I": 1}, d) }) t.Run("#3: struct field unexported", func(t *testing.T) { type SS struct { I int u uint } var s SS = SS{I: 1, u: 2} var d map[string]int err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, map[string]int{"I": 1, "u": 2}, d) }) } func Test_Copy_structToMap_unexported_error(t *testing.T) { t.Run("#1: unaddressable field causes failure of copying unexported field", func(t *testing.T) { type SS struct { i int `copy:"i,required"` S any } var s SS = SS{i: 1, S: 2} var d map[string]int err := Copy(&d, s) assert.ErrorIs(t, err, ErrValueUnaddressable) }) } type testDstMap1 map[string]int func (d testDstMap1) CopyI1(i1 int) error { d["x1"] = i1 * 2 return nil } func (d testDstMap1) CopyI2(i2 int) { // incorrect method prototype (no return error) d["x2"] = i2 * 2 } func (d testDstMap1) CopyI3(i3 int, v string) error { // incorrect method prototype (2 input args) d["x3"] = i3 * 2 return nil } func (d testDstMap1) CopyI4(i4 uint) error { // incorrect method prototype (unmatched input type) d["x4"] = int(i4 * 2) //nolint:gosec return nil } func (d testDstMap1) CopyI5(i5 int) string { // incorrect method prototype (not return error type) return "" } func (d testDstMap1) CopyI6(i6 int) error { // incorrect method prototype (unmatched input type) return errTest } func (d testDstMap1) NotCopy(i6 int) error { // not a copying method return errTest } func Test_Copy_structToMap_method(t *testing.T) { t.Run("#1: field -> dst method", func(t *testing.T) { type SS struct { I1 int U uint } var s SS = SS{I1: 1, U: 2} var d testDstMap1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testDstMap1{"x1": 2, "U": 2}, d) }) t.Run("#2: unexported field -> dst method", func(t *testing.T) { type SS struct { i1 int `copy:"I1,required"` U uint } var s SS = SS{i1: 1, U: 2} var d testDstMap1 err := Copy(&d, &s) assert.Nil(t, err) assert.Equal(t, testDstMap1{"x1": 2, "U": 2}, d) }) t.Run("#3: incorrect method prototype (CopyI2())", func(t *testing.T) { type SS struct { I2 int U uint } var s SS = SS{I2: 1, U: 2} var d testDstMap1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testDstMap1{"I2": 1, "U": 2}, d) // CopyI2 is not called }) t.Run("#4: not allow copying from field -> method", func(t *testing.T) { type SS struct { I1 int U uint } var s SS = SS{I1: 1, U: 2} var d testDstMap1 err := Copy(&d, s, CopyBetweenStructFieldAndMethod(false)) assert.Nil(t, err) assert.Equal(t, testDstMap1{"I1": 1, "U": 2}, d) }) t.Run("#5: copy from src embedded field", func(t *testing.T) { type SBase struct { I1 int } type SS struct { SBase U uint } var s SS = SS{U: 2, SBase: SBase{I1: 123}} var d testDstMap1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testDstMap1{"U": 2, "x1": 246}, d) }) t.Run("#6: copy from src embedded field, but field value can't be retrieved due to nil ptr", func(t *testing.T) { type SBase struct { I1 int } type SS struct { *SBase U uint } var s SS = SS{U: 2} var d testDstMap1 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testDstMap1{"U": 2}, d) }) } func Test_Copy_structToMap_method_error(t *testing.T) { t.Run("#1: unexported field -> dst method", func(t *testing.T) { type SS struct { i1 int `copy:"I1,required"` U uint } var s SS = SS{i1: 1, U: 2} var d testDstMap1 err := Copy(&d, s) assert.ErrorIs(t, err, ErrValueUnaddressable) }) t.Run("#2: incorrect method prototype (CopyI4())", func(t *testing.T) { type SS struct { I4 int `copy:",required"` U uint } var s SS = SS{I4: 1, U: 2} var d testDstMap1 err := Copy(&d, s) assert.ErrorIs(t, err, ErrMethodInvalid) }) t.Run("#3: copy method return error (CopyI6())", func(t *testing.T) { type SS struct { I6 int `copy:",required"` U uint } var s SS = SS{I6: 1, U: 2} var d testDstMap1 err := Copy(&d, s) assert.ErrorIs(t, err, errTest) }) } type testSrc2 struct { I int U uint } type testDstMap2 map[string]int func (d testDstMap2) PostCopy(src any) error { testSrc2, _ := src.(testSrc2) if testSrc2.I == 100 { return errTest } d["I"] *= 2 d["U"] *= 2 return nil } type testDstMap3 struct { I int U uint } func (d testDstMap3) PostCopy(src any) any { d.I *= 2 d.U *= 2 return nil } func Test_Copy_structToMap_with_post_copy_event(t *testing.T) { t.Run("#1: success without error", func(t *testing.T) { s := testSrc2{I: 1, U: 2} var d testDstMap2 err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testDstMap2{"I": 2, "U": 4}, d) }) t.Run("#2: PostCopy returns error", func(t *testing.T) { s := testSrc2{I: 100, U: 2} // When testSrc2.I == 100, PostCopy returns error d := testDstMap2{} err := Copy(&d, s) assert.NotNil(t, err) assert.ErrorIs(t, err, errTest) }) t.Run("#3: dstStruct.PostCopy not satisfied", func(t *testing.T) { s := testSrc2{I: 1, U: 2} d := testDstMap3{} err := Copy(&d, s) assert.Nil(t, err) assert.Equal(t, testDstMap3{I: 1, U: 2}, d) }) } golang-github-tiendc-go-deepcopy-1.7.2/util.go000066400000000000000000000113231517235416400212410ustar00rootroot00000000000000package deepcopy import ( "reflect" "strings" ) var ( errType = reflect.TypeOf((*error)(nil)).Elem() ifaceType = reflect.TypeOf((*any)(nil)).Elem() strType = reflect.TypeOf((*string)(nil)).Elem() ) const ( typeMethodPostCopy = "PostCopy" ) // typeParseMethods collects all copying methods from the given type func typeParseMethods(ctx *Context, typ reflect.Type) ( copyingMethods map[string]*reflect.Method, postCopyMethod *reflect.Method) { ptrType := reflect.PointerTo(typ) numMethods := ptrType.NumMethod() for i := 0; i < numMethods; i++ { method := ptrType.Method(i) switch { // Field copying method name must be something like `Copy` case ctx.CopyViaCopyingMethod && strings.HasPrefix(method.Name, "Copy"): if method.Type.NumIn() != 2 || method.Type.NumOut() != 1 { continue } if method.Type.Out(0) != errType { continue } if copyingMethods == nil { copyingMethods = make(map[string]*reflect.Method) } copyingMethods[method.Name] = &method // The method is for `post-copy` event case method.Name == typeMethodPostCopy: if method.Type.NumIn() != 2 || method.Type.NumOut() != 1 { continue } if method.Type.In(1) != ifaceType { continue } if method.Type.Out(0) != errType { continue } postCopyMethod = &method } } return copyingMethods, postCopyMethod } // structParseAllFields parses all fields of a struct including direct fields and fields inherited from embedded structs func structParseAllFields(typ reflect.Type) ( directFieldKeys []string, mapDirectFields map[string]*fieldDetail, inheritedFieldKeys []string, mapInheritedFields map[string]*fieldDetail, ) { numFields := typ.NumField() directFieldKeys = make([]string, 0, numFields) mapDirectFields = make(map[string]*fieldDetail, numFields) inheritedFieldKeys = make([]string, 0, numFields) mapInheritedFields = make(map[string]*fieldDetail, numFields) for i := 0; i < numFields; i++ { sf := typ.Field(i) fDetail := &fieldDetail{field: &sf, index: []int{i}} parseTag(fDetail) if fDetail.ignored { continue } directFieldKeys = append(directFieldKeys, fDetail.key) mapDirectFields[fDetail.key] = fDetail // Parse embedded struct to get its fields if sf.Anonymous { for key, detail := range structParseAllNestedFields(sf.Type, fDetail.index) { inheritedFieldKeys = append(inheritedFieldKeys, key) mapInheritedFields[key] = detail fDetail.nestedFields = append(fDetail.nestedFields, detail) } } } return directFieldKeys, mapDirectFields, inheritedFieldKeys, mapInheritedFields } // structParseAllNestedFields parses all fields with initial index of starting field func structParseAllNestedFields(typ reflect.Type, index []int) map[string]*fieldDetail { if typ.Kind() == reflect.Pointer { typ = typ.Elem() } if typ.Kind() != reflect.Struct { return nil } numFields := typ.NumField() result := make(map[string]*fieldDetail, numFields) for i := 0; i < numFields; i++ { sf := typ.Field(i) fDetail := &fieldDetail{field: &sf, index: append(index, i)} parseTag(fDetail) if fDetail.ignored { continue } result[fDetail.key] = fDetail // Parse embedded struct recursively to get its fields if sf.Anonymous { for key, detail := range structParseAllNestedFields(sf.Type, fDetail.index) { result[key] = detail fDetail.nestedFields = append(fDetail.nestedFields, detail) } } } return result } // structFieldGetWithInit gets deep nested field with init value for pointer ones func structFieldGetWithInit(field reflect.Value, index []int) reflect.Value { for _, idx := range index { if field.Kind() == reflect.Pointer { if field.IsNil() { field.Set(reflect.New(field.Type().Elem())) } field = field.Elem() } field = field.Field(idx) } return field } // structFieldSetZero sets zero to a deep nested field func structFieldSetZero(field reflect.Value, index []int) { field, err := field.FieldByIndexErr(index) if err == nil && field.IsValid() { field.Set(reflect.Zero(field.Type())) // NOTE: Go1.18 has no SetZero } } // nillableValueSetNilOnZero sets value as `nil` when its inner value is zero. // Only applies to `Pointer`, `Interface`, `Slice` and `Map` types. func nillableValueSetNilOnZero(val reflect.Value) { innerVal := val for { switch innerVal.Kind() { //nolint:exhaustive case reflect.Pointer, reflect.Interface: innerVal = innerVal.Elem() if !innerVal.IsValid() || innerVal.IsZero() { val.Set(reflect.Zero(val.Type())) // NOTE: Go1.18 has no SetZero return } case reflect.Slice, reflect.Map: if innerVal.Len() == 0 { val.Set(reflect.Zero(val.Type())) // NOTE: Go1.18 has no SetZero } return // always return as we can't go deeper with a slice or map default: return } } }