├── LICENSE ├── README.md ├── caststructure.go ├── caststructure_test.go └── go.mod /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⛔️ THIS DOESN'T WORK ⛔️ 2 | 3 | The functionality provided by this library unfortunately doesn't work due 4 | to limitations of the Go `reflect` package and runtime. I believe if these 5 | issues are resolved, the code here should work as-is and tests should begin 6 | passing. 7 | 8 | The Go issues related to this are rooted here: https://github.com/golang/go/issues/38783 9 | 10 | # caststructure [![Godoc](https://godoc.org/github.com/mitchellh/caststructure?status.svg)](https://godoc.org/github.com/mitchellh/caststructure) 11 | 12 | caststructure is a Go library that provides functions for downcasting types, 13 | composing values dynamically, and more. See the examples below for more details. 14 | 15 | ## Installation 16 | 17 | Standard `go get`: 18 | 19 | ``` 20 | $ go get github.com/mitchellh/caststructure 21 | ``` 22 | 23 | ## Usage & Example 24 | 25 | For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/caststructure). 26 | 27 | A quick code example is shown below that shows **downcasting**: 28 | 29 | ```go 30 | // Three interface types. 31 | type A interface { A() int } 32 | type B interface { B() int } 33 | type C interface { C() int } 34 | 35 | // Impl implements A, B, AND C. 36 | type Impl struct {} 37 | func (Impl) A() int { return 42 } 38 | func (Impl) B() int { return 42 } 39 | func (Impl) C() int { return 42 } 40 | 41 | // We have a value of type Impl, so it implements all interfaces. 42 | var value Impl 43 | 44 | // But we only want value to implement A and B, not C. 45 | newValue := caststructure.Must(caststructure.Down(value, (*A)(nil), (*B)(nil))) 46 | _, ok := newValue.(A) // ok == true 47 | _, ok := newValue.(B) // ok == true 48 | _, ok := newValue.(C) // ok == false 49 | ``` 50 | 51 | Here is an example that shows **composing**: 52 | 53 | ```go 54 | // Three interface types. 55 | type A interface { A() int } 56 | type B interface { B() int } 57 | type C interface { C() int } 58 | 59 | // Implementation for A and B, respectively 60 | type ImplA struct {} 61 | func (ImplA) A() int { return 42 } 62 | 63 | type ImplB struct {} 64 | func (Impl) B() int { return 42 } 65 | 66 | // We have a value that implements A and B, separately. 67 | var valueA implA 68 | var valueB implB 69 | 70 | // But we want a value that implements BOTH A and B. 71 | newValue := caststructure.Must(caststructure.Compose(valueA, (*A)(nil), valueB, (*B)(nil))) 72 | _, ok := newValue.(A) // ok == true 73 | _, ok := newValue.(B) // ok == true 74 | ``` 75 | 76 | ## But... Why? 77 | 78 | In general, what this library achieves can be done manually through 79 | explicit type declarations. For example, composing `A` and `B` can be 80 | done [explicitly like this](https://play.golang.org/p/It0NHvZt_-w). But in 81 | cases where the set of interfaces is large and the combinations dynamic, 82 | creating explicit types becomes a burden. 83 | 84 | Some Go code allows optionally implementing interfaces to change behavior. 85 | For example, code might check if `io.Closer` is implemented and call it. If 86 | it is not implemented, it isn't an error, it just isn't called. Taking this 87 | further, some Go code has numerous opt-in interfaces to change behavior. This 88 | library was born out of the need to work with such APIs. 89 | 90 | For downcasing, you might get a value that implements interfaces `A`, `B`, `C` and you 91 | want to return a value that only implements `A` and `B`. If this is the only 92 | scenario, you can manually create a [new interface type](https://play.golang.org/p/Sgn7MhsyrXt). 93 | But if the list of interfaces you want to implement is dynamic, you would 94 | have to declare all combinations of the interface. This library does this 95 | dynamically at runtime, without panics. 96 | 97 | For composing, you may have two separate values that implement two separate 98 | interfaces `A` and `B` respectively. For example, you may have an `io.Reader` 99 | and an `io.Writer` but you want an `io.ReadWriter` using the two together. 100 | This library can construct that ReadWriter for you. 101 | -------------------------------------------------------------------------------- /caststructure.go: -------------------------------------------------------------------------------- 1 | // Package caststructure can downcast a value to a subset of the interfaces 2 | // that it implements. This is useful in situations where the set of interfaces 3 | // you want a type to implement is dynamic. In cases where the set is small or 4 | // fixed, it is likely a better idea to define a new type instead. 5 | package caststructure 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | ) 11 | 12 | // Must is a helper that wraps a call to a function returning (interface{}, error) 13 | // and panics if the error is non-nil. This can be used around Interface to 14 | // force a successful return value or panic. 15 | func Must(v interface{}, err error) interface{} { 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | return v 21 | } 22 | 23 | // Down downcasts the value "from" to a type that implements the list 24 | // of interfaces "to". The "from" value must already implement these interfaces. 25 | // This is typically used to downcast a value that implements other interfaces 26 | // to one that only implements the given list. 27 | // 28 | // An error will be returned if from doesn't implement any one of the to values, 29 | // or creating the structure fails (due to overlapping interface implements, 30 | // unexported types being used, etc.). 31 | func Down(from interface{}, to ...interface{}) (interface{}, error) { 32 | fromVal := reflect.ValueOf(from) 33 | fromTyp := fromVal.Type() 34 | 35 | // Go through all our to interfaces and build up our struct field list. 36 | // We also do some validation here. 37 | fields := make([]reflect.StructField, len(to)) 38 | for i, typPtr := range to { 39 | typ := reflect.TypeOf(typPtr) 40 | if typ == nil { 41 | return nil, fmt.Errorf("to type must be a pointer, got nil value") 42 | } 43 | if typ.Kind() != reflect.Ptr { 44 | return nil, fmt.Errorf("to type must be a pointer, got %s", typ.String()) 45 | } 46 | typ = typ.Elem() 47 | 48 | // Ensure our from value maps to this 49 | if !fromTyp.AssignableTo(typ) { 50 | return nil, fmt.Errorf("from value is not assignable to destination type %s", typ.String()) 51 | } 52 | 53 | // Build the field 54 | fields[i] = reflect.StructField{ 55 | Name: "A_" + typ.Name(), // Force capitalization so its exported 56 | Type: typ, 57 | Anonymous: true, // Anonymous so it is embedded 58 | } 59 | } 60 | 61 | // Make our structure 62 | resultVal := reflect.New(reflect.StructOf(fields)).Elem() 63 | for i := 0; i < len(to); i++ { 64 | resultVal.Field(i).Set(fromVal) 65 | } 66 | 67 | return resultVal.Interface(), nil 68 | } 69 | 70 | // Compose composes multiple types into a single type. For example if you 71 | // have a value that implements A and another that implements B, this can 72 | // compose those two values into a single value that implements both A and B. 73 | // 74 | // The arguments to this must be a list of pairs in (value, type) order. 75 | // For example: 76 | // 77 | // Compose(a, (*A)(nil), b, (*B)(nil)) 78 | // 79 | func Compose(pairs ...interface{}) (interface{}, error) { 80 | if len(pairs)%2 != 0 { 81 | return nil, fmt.Errorf("Compose requires an even number of arguments since they're pairs") 82 | } 83 | 84 | fields := make([]reflect.StructField, 0, len(pairs)/2) 85 | values := make([]reflect.Value, 0, len(pairs)/2) 86 | for i := 0; i < len(pairs); i += 2 { 87 | pairVal := reflect.ValueOf(pairs[i]) 88 | pairTyp := pairs[i+1] 89 | 90 | typ := reflect.TypeOf(pairTyp) 91 | if typ == nil { 92 | return nil, fmt.Errorf("to type must be a pointer, got nil value") 93 | } 94 | if typ.Kind() != reflect.Ptr { 95 | return nil, fmt.Errorf("to type must be a pointer, got %s", typ.String()) 96 | } 97 | typ = typ.Elem() 98 | 99 | // Ensure our from value maps to this 100 | if !pairVal.Type().AssignableTo(typ) { 101 | return nil, fmt.Errorf("value is not assignable to destination type %s", typ.String()) 102 | } 103 | 104 | // Build the field 105 | fields = append(fields, reflect.StructField{ 106 | Name: "A_" + typ.Name(), // Force capitalization so its exported 107 | Type: typ, 108 | Anonymous: true, // Anonymous so it is embedded 109 | }) 110 | 111 | // Build the value 112 | values = append(values, pairVal) 113 | } 114 | 115 | // Make our structure 116 | resultVal := reflect.New(reflect.StructOf(fields)).Elem() 117 | for i := 0; i < len(fields); i++ { 118 | resultVal.Field(i).Set(values[i]) 119 | } 120 | 121 | return resultVal.Interface(), nil 122 | } 123 | -------------------------------------------------------------------------------- /caststructure_test.go: -------------------------------------------------------------------------------- 1 | package caststructure 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDown(t *testing.T) { 8 | var from impl 9 | 10 | value, err := Down(from, (*testA)(nil), (*TestB)(nil)) 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | value.(testA).A() 16 | if v, ok := value.(testA); !ok { 17 | t.Fatal("should implement A") 18 | } else if v.A() != 42 { 19 | t.Fatal("invalid value") 20 | } 21 | if v, ok := value.(TestB); !ok { 22 | t.Fatal("should implement B") 23 | } else if v.B() != 42 { 24 | t.Fatal("invalid value") 25 | } 26 | if _, ok := value.(testC); ok { 27 | t.Fatal("should not implement C") 28 | } 29 | } 30 | 31 | func TestDown_nonImpl(t *testing.T) { 32 | from := 42 33 | _, err := Down(from, (*testA)(nil)) 34 | if err == nil { 35 | t.Fatal("should error") 36 | } 37 | } 38 | 39 | func TestDown_nonPtr(t *testing.T) { 40 | var from impl 41 | _, err := Down(from, (testA)(nil)) 42 | if err == nil { 43 | t.Fatal("should error") 44 | } 45 | } 46 | 47 | func TestCompose(t *testing.T) { 48 | var a implA 49 | var b implB 50 | 51 | value, err := Compose(a, (*testA)(nil), b, (*TestB)(nil)) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | if _, ok := value.(testA); !ok { 57 | t.Fatal("should implement A") 58 | } 59 | if _, ok := value.(TestB); !ok { 60 | t.Fatal("should implement B") 61 | } 62 | if _, ok := value.(testC); ok { 63 | t.Fatal("should not implement C") 64 | } 65 | } 66 | 67 | type testA interface{ A() int } 68 | type TestB interface{ B() int } // Purposefully exported to test that case 69 | type testC interface{ C() int } 70 | 71 | type impl struct{} 72 | 73 | func (impl) A() int { return 42 } 74 | func (impl) B() int { return 42 } 75 | func (impl) C() int { return 42 } 76 | 77 | type implA struct{} 78 | 79 | func (implA) A() int { return 42 } 80 | 81 | type implB struct{} 82 | 83 | func (implB) B() int { return 42 } 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitchellh/caststructure 2 | 3 | go 1.14 4 | --------------------------------------------------------------------------------