pax_global_header00006660000000000000000000000064150573176300014521gustar00rootroot0000000000000052 comment=79024bae312d3bf1083ef695024a58164427caee golang-github-lestrrat-go-blackmagic-1.0.4/000077500000000000000000000000001505731763000205665ustar00rootroot00000000000000golang-github-lestrrat-go-blackmagic-1.0.4/.github/000077500000000000000000000000001505731763000221265ustar00rootroot00000000000000golang-github-lestrrat-go-blackmagic-1.0.4/.github/dependabot.yml000066400000000000000000000004001505731763000247500ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" target-branch: "main" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" target-branch: "main"golang-github-lestrrat-go-blackmagic-1.0.4/.github/workflows/000077500000000000000000000000001505731763000241635ustar00rootroot00000000000000golang-github-lestrrat-go-blackmagic-1.0.4/.github/workflows/ci.yml000066400000000000000000000016071505731763000253050ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: go: [ '1.23', '1.24' ] name: Go ${{ matrix.go }} test steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Cache Go modules uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | ~/go/pkg/mod ~/.cache/go-build ~/.cache/bazel key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Install Go stable version uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ matrix.go }} check-latest: true - name: Test run: go test -v -race ./... golang-github-lestrrat-go-blackmagic-1.0.4/.github/workflows/lint.yml000066400000000000000000000007161505731763000256600ustar00rootroot00000000000000name: lint on: [push] jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version-file: "go.mod" - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 - name: Run go vet run: | go vet ./... golang-github-lestrrat-go-blackmagic-1.0.4/.gitignore000066400000000000000000000004151505731763000225560ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ golang-github-lestrrat-go-blackmagic-1.0.4/LICENSE000066400000000000000000000020541505731763000215740ustar00rootroot00000000000000MIT License Copyright (c) 2021 lestrrat-go 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-lestrrat-go-blackmagic-1.0.4/README.md000066400000000000000000000001041505731763000220400ustar00rootroot00000000000000# blackmagic Reflect-based black magic. YMMV, and use with caution golang-github-lestrrat-go-blackmagic-1.0.4/blackmagic.go000066400000000000000000000066761505731763000232110ustar00rootroot00000000000000package blackmagic import ( "fmt" "reflect" ) type errInvalidValue struct{} func (*errInvalidValue) Error() string { return "invalid value (probably an untyped nil)" } // InvalidValueError is a sentinel error that can be used to // indicate that a value is invalid. This can happen when the // source value is an untyped nil, and we have no further information // about the type of the value, obstructing the assignment. func InvalidValueError() error { return &errInvalidValue{} } // AssignField is a convenience function to assign a value to // an optional struct field. In Go, an optional struct field is // usually denoted by a pointer to T instead of T: // // type Object struct { // Optional *T // } // // This gets a bit cumbersome when you want to assign literals // or you do not want to worry about taking the address of a // variable. // // Object.Optional = &"foo" // doesn't compile! // // Instead you can use this function to do it in one line: // // blackmagic.AssignOptionalField(&Object.Optionl, "foo") func AssignOptionalField(dst, src interface{}) error { dstRV := reflect.ValueOf(dst) srcRV := reflect.ValueOf(src) if dstRV.Kind() != reflect.Pointer || dstRV.Elem().Kind() != reflect.Pointer { return fmt.Errorf(`dst must be a pointer to a field that is turn a pointer of src (%T)`, src) } if !dstRV.Elem().CanSet() { return fmt.Errorf(`dst (%T) is not assignable`, dstRV.Elem().Interface()) } if !reflect.PointerTo(srcRV.Type()).AssignableTo(dstRV.Elem().Type()) { return fmt.Errorf(`cannot assign src (%T) to dst (%T)`, src, dst) } ptr := reflect.New(srcRV.Type()) ptr.Elem().Set(srcRV) dstRV.Elem().Set(ptr) return nil } // AssignIfCompatible is a convenience function to safely // assign arbitrary values. dst must be a pointer to an // empty interface, or it must be a pointer to a compatible // variable type that can hold src. func AssignIfCompatible(dst, src interface{}) error { orv := reflect.ValueOf(src) // save this value for error reporting result := orv // src can be a pointer or a slice, and the code will slightly change // depending on this var srcIsPtr bool var srcIsSlice bool switch result.Kind() { case reflect.Ptr: srcIsPtr = true case reflect.Slice: srcIsSlice = true } rv := reflect.ValueOf(dst) if rv.Kind() != reflect.Ptr { return fmt.Errorf(`destination argument to AssignIfCompatible() must be a pointer: %T`, dst) } actualDst := rv for { if !actualDst.IsValid() { return fmt.Errorf(`could not find a valid destination for AssignIfCompatible() (%T)`, dst) } if actualDst.CanSet() { break } actualDst = actualDst.Elem() } switch actualDst.Kind() { case reflect.Interface: // If it's an interface, we can just assign the pointer to the interface{} default: // If it's a pointer to the struct we're looking for, we need to set // the de-referenced struct if !srcIsSlice && srcIsPtr { result = result.Elem() } } if !result.IsValid() { // At this point there's nothing we can do. return an error return fmt.Errorf(`source value is invalid (%T): %w`, src, InvalidValueError()) } if actualDst.Kind() == reflect.Ptr { actualDst.Set(result.Addr()) return nil } if !result.Type().AssignableTo(actualDst.Type()) { return fmt.Errorf(`argument to AssignIfCompatible() must be compatible with %T (was %T)`, orv.Interface(), dst) } if !actualDst.CanSet() { return fmt.Errorf(`argument to AssignIfCompatible() must be settable`) } actualDst.Set(result) return nil } golang-github-lestrrat-go-blackmagic-1.0.4/blackmagic_test.go000066400000000000000000000060101505731763000242260ustar00rootroot00000000000000package blackmagic_test import ( "errors" "fmt" "testing" "github.com/lestrrat-go/blackmagic" "github.com/stretchr/testify/require" ) func TestAssignment(t *testing.T) { const val = 42 t.Run("to interface{}", func(t *testing.T) { var dst interface{} require.NoError(t, blackmagic.AssignIfCompatible(&dst, val), `blackmagic.AssignIfCompatible should succeed`) require.Equal(t, val, dst, `dst should be equal to src`) }) t.Run("to int", func(t *testing.T) { var dst int require.NoError(t, blackmagic.AssignIfCompatible(&dst, val), `blackmagic.AssignIfCompatible should succeed`) require.Equal(t, val, dst, `dst should be equal to src`) }) t.Run("to string (should fail)", func(t *testing.T) { var dst string err := blackmagic.AssignIfCompatible(&dst, val) require.Error(t, err, `blackmagic.AssignIfCompatible should fail`) }) } func TestAssignmentEdgeCases(t *testing.T) { testcases := []struct { Name string Error bool ErrorCheck func(error) error Value interface{} Destination func() interface{} }{ { Name: `empty struct`, Error: false, Value: struct{}{}, Destination: func() interface{} { var v interface{} return &v }, }, { Name: `non pointer destination`, Error: true, Value: &struct{}{}, }, { Name: `assign empty struct to int`, Error: true, Value: &struct{}{}, Destination: func() interface{} { var v int return &v }, }, { Name: `source is nil`, Error: true, Value: nil, Destination: func() interface{} { var v interface{} return &v }, ErrorCheck: func(err error) error { if !errors.Is(err, blackmagic.InvalidValueError()) { return fmt.Errorf(`error should be InvalidValueError, but got %v`, err) } return nil }, }, } for _, tc := range testcases { tc := tc t.Run(tc.Name, func(t *testing.T) { var dst interface{} if dstFunc := tc.Destination; dstFunc != nil { dst = dstFunc() } err := blackmagic.AssignIfCompatible(dst, tc.Value) if tc.Error { require.Error(t, err, `blackmagic.AssignIfCompatible should fail`) if check := tc.ErrorCheck; check != nil { if checkErr := check(err); checkErr != nil { require.NoError(t, checkErr, `check function should succeed`) } } } else { require.NoError(t, err, `blackmagic.AssignIfCompatible should succeed`) } }) } } func TestAssignOptionalField(t *testing.T) { var f struct { Foo *string Bar *int } require.NoError(t, blackmagic.AssignOptionalField(&f.Foo, "Hello"), `blackmagic.AssignOptionalField should succeed`) require.Equal(t, *(f.Foo), "Hello") require.NoError(t, blackmagic.AssignOptionalField(&f.Bar, 1), `blackmagic.AssignOptionalField should succeed`) require.Equal(t, *(f.Bar), 1) } func TestAssignPointer(t *testing.T) { var src int var dst *int require.NoError(t, blackmagic.AssignIfCompatible(&dst, &src), `blackmagic.AssignIfCompatible should succeed`) src = 42 require.Equal(t, 42, *dst, `dst should be updated to point to the value of src`) } golang-github-lestrrat-go-blackmagic-1.0.4/go.mod000066400000000000000000000003621505731763000216750ustar00rootroot00000000000000module github.com/lestrrat-go/blackmagic go 1.23 require github.com/stretchr/testify v1.10.0 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-lestrrat-go-blackmagic-1.0.4/go.sum000066400000000000000000000015631505731763000217260ustar00rootroot00000000000000github.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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=