├── .gitignore ├── .travis.yml ├── LICENSE ├── api.go ├── api_test.go ├── datasource.go ├── datasource_reader.go ├── datasource_reader_test.go ├── datasource_test.go ├── datasource_writer.go ├── datasource_writer_test.go ├── documentation.go ├── example ├── main.go └── main_test.go ├── graph.go ├── graph_assert.go ├── graph_assert_test.go ├── graph_connect.go ├── graph_connect_test.go ├── graph_datasource.go ├── graph_datasource_test.go ├── graph_inject.go ├── graph_inject_test.go ├── graph_node.go ├── graph_node_dependency.go ├── graph_node_dependency_test.go ├── graph_node_test.go ├── graph_provide.go ├── graph_provide_test.go ├── graph_test.go ├── readme.markdown ├── reflect_identifier.go ├── reflect_identifier_test.go ├── reflect_type.go ├── reflect_type_test.go ├── reflect_zero.go ├── reflect_zero_test.go ├── structpath.go └── structpath_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4 5 | - 1.5 6 | 7 | before_install: 8 | - go get github.com/axw/gocov/gocov 9 | - go get github.com/mattn/goveralls 10 | - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 11 | 12 | script: 13 | - $HOME/gopath/bin/goveralls -service=travis-ci 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Paul M Fox 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 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | // +build !noglobals 2 | 3 | package inj 4 | 5 | ////////////////////////////////////////////// 6 | // Interface definitions 7 | ////////////////////////////////////////////// 8 | 9 | // A Grapher is anything that can represent an application graph 10 | type Grapher interface { 11 | Provide(inputs ...interface{}) error 12 | Inject(fn interface{}, args ...interface{}) 13 | Assert() (valid bool, errors []string) 14 | AddDatasource(...interface{}) error 15 | } 16 | 17 | ////////////////////////////////////////////// 18 | // The one true global variable 19 | ////////////////////////////////////////////// 20 | 21 | // A default grapher to use in the public API 22 | var globalGraph Grapher = NewGraph() 23 | 24 | // Fetch the current grapher instance (in other words, get the global graph) 25 | func GetGrapher() Grapher { 26 | return globalGraph 27 | } 28 | 29 | // Set a specific grapher instance, which will replace the global graph. 30 | func SetGrapher(g Grapher) { 31 | globalGraph = g 32 | } 33 | 34 | ////////////////////////////////////////////// 35 | // Public API 36 | ////////////////////////////////////////////// 37 | 38 | // Insert zero or more objected into the graph, and then attempt to wire up any unmet 39 | // dependencies in the graph. 40 | // 41 | // As explained in the main documentation (https://godoc.org/github.com/yourheropaul/inj), 42 | // a graph consists of what is essentially a map of types to values. If the same type is 43 | // provided twice with different values, the *last* value will be stored in the graph. 44 | func Provide(inputs ...interface{}) error { 45 | return globalGraph.Provide(inputs...) 46 | } 47 | 48 | // Given a function, call it with arguments assigned 49 | // from the graph. Additional arguments can be provided 50 | // for the sake of utility. 51 | // 52 | // Inject() will panic if the provided argument isn't a function, 53 | // or if the provided function accepts variadic arguments (because 54 | // that's not currently supported in the scope of inj). 55 | func Inject(fn interface{}, args ...interface{}) { 56 | globalGraph.Inject(fn, args...) 57 | } 58 | 59 | // Make sure that all provided dependencies have their 60 | // requirements met, and return a list of errors if they 61 | // haven't. A graph is never really finalised, so Provide() and 62 | // Assert() can be called any number of times. 63 | func Assert() (valid bool, errors []string) { 64 | return globalGraph.Assert() 65 | } 66 | 67 | // Add any number of Datasources, DatasourceReaders or DatasourceWriters 68 | // to the graph. Returns an error if any of the supplied arguments aren't 69 | // one of the accepted types. 70 | // 71 | // Once added, the datasources will be active immediately, and the graph 72 | // will automatically re-Provide itself, so that any depdendencies that 73 | // can only be met by an external datasource will be wired up automatically. 74 | // 75 | func AddDatasource(ds ...interface{}) error { 76 | return globalGraph.AddDatasource(ds...) 77 | } 78 | -------------------------------------------------------------------------------- /api_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | const ( 9 | HELLO_SAYER_MESSAGE = "Hello!" 10 | GOODBYE_SAYER_MESSAGE = "Bye!" 11 | DEFAULT_STRING = "this is a string" 12 | ) 13 | 14 | /////////////////////////////////////////////////// 15 | // Types for the unit and feature tests 16 | /////////////////////////////////////////////////// 17 | 18 | type InterfaceOne interface { 19 | SayHello() string 20 | } 21 | 22 | type InterfaceTwo interface { 23 | SayGoodbye() string 24 | } 25 | 26 | type FuncType func(string) string 27 | type ChanType chan interface{} 28 | 29 | /////////////////////////////////////////////////// 30 | // Sample concrete type which requires two interfaces, 31 | // the func type, the channel type and a string 32 | /////////////////////////////////////////////////// 33 | 34 | type ConcreteType struct { 35 | Hello InterfaceOne `inj:""` 36 | Goodbye InterfaceTwo `inj:""` 37 | Stringer FuncType `inj:""` 38 | Channel ChanType `inj:""` 39 | String string `inj:""` 40 | 41 | // This is nested 42 | Nested NestedType 43 | 44 | // These are not included in the injection 45 | Something string `in:` 46 | SomethingElse int 47 | } 48 | 49 | // A nested type that contains dependencies 50 | type NestedType struct { 51 | Hello InterfaceOne `inj:""` 52 | Goodbye InterfaceTwo `inj:""` 53 | } 54 | 55 | func (c ConcreteType) expectedDeps() []graphNodeDependency { 56 | 57 | d := []graphNodeDependency{ 58 | graphNodeDependency{ 59 | Path: ".Hello", 60 | Type: reflect.TypeOf(c.Hello), 61 | }, 62 | graphNodeDependency{ 63 | Path: ".Goodbye", 64 | Type: reflect.TypeOf(c.Goodbye), 65 | }, 66 | graphNodeDependency{ 67 | Path: ".Stringer", 68 | Type: reflect.TypeOf(c.Stringer), 69 | }, 70 | graphNodeDependency{ 71 | Path: ".Channel", 72 | Type: reflect.TypeOf(c.Channel), 73 | }, 74 | graphNodeDependency{ 75 | Path: ".String", 76 | Type: reflect.TypeOf(c.String), 77 | }, 78 | graphNodeDependency{ 79 | Path: ".Nested.Hello", 80 | Type: reflect.TypeOf(c.Nested.Hello), 81 | }, 82 | graphNodeDependency{ 83 | Path: ".Nested.Goodbye", 84 | Type: reflect.TypeOf(c.Nested.Goodbye), 85 | }, 86 | } 87 | 88 | return d 89 | } 90 | 91 | // Data types for anonymous field testing 92 | type Embeddable struct { 93 | X int 94 | } 95 | 96 | type HasEmbeddable struct { 97 | Embeddable `inj:""` 98 | } 99 | 100 | func (c HasEmbeddable) expectedDeps() []graphNodeDependency { 101 | 102 | return []graphNodeDependency{ 103 | graphNodeDependency{ 104 | Path: ".Embeddable", 105 | Type: reflect.TypeOf(c.Embeddable), 106 | }, 107 | } 108 | } 109 | 110 | // Channel instance 111 | var ichannel = make(ChanType) 112 | 113 | /////////////////////////////////////////////////// 114 | // Implementation of a hello-sayer 115 | /////////////////////////////////////////////////// 116 | 117 | type helloSayer struct{} 118 | 119 | func (g *helloSayer) SayHello() string { return HELLO_SAYER_MESSAGE } 120 | 121 | /////////////////////////////////////////////////// 122 | // Implementation of a goodbye-sayer 123 | /////////////////////////////////////////////////// 124 | 125 | type goodbyeSayer struct{} 126 | 127 | func (g *goodbyeSayer) SayGoodbye() string { return GOODBYE_SAYER_MESSAGE } 128 | 129 | /////////////////////////////////////////////////// 130 | // Implementation of a FuncType 131 | /////////////////////////////////////////////////// 132 | 133 | func funcInstance(s string) string { 134 | return s 135 | } 136 | 137 | ////////////////////////////////////////// 138 | // Assertion for concrete type 139 | ////////////////////////////////////////// 140 | 141 | // Once the dependencies have been injected, all the dependent 142 | // members should be non-nil and functional. 143 | func assertConcreteValue(c ConcreteType, t *testing.T) { 144 | 145 | if c.Hello == nil { 146 | t.Errorf("c.Hello is nil") 147 | } 148 | 149 | if c.Goodbye == nil { 150 | t.Errorf("c.Goodbye is nil") 151 | } 152 | 153 | if c.Stringer == nil { 154 | t.Errorf("c.Stringer is nil") 155 | } 156 | 157 | if c.Channel == nil { 158 | t.Errorf("c.Channel is nil") 159 | } 160 | 161 | if c.String == "" { 162 | t.Errorf("c.String is nil") 163 | } 164 | 165 | if c.Nested.Hello == nil { 166 | t.Errorf("c.Hello is nil") 167 | } 168 | 169 | if c.Nested.Goodbye == nil { 170 | t.Errorf("c.Goodbye is nil") 171 | } 172 | 173 | if g, e := c.Hello.SayHello(), HELLO_SAYER_MESSAGE; g != e { 174 | t.Errorf("i2.SayHello(): got %s, expected %s", g, e) 175 | } 176 | 177 | if g, e := c.Goodbye.SayGoodbye(), GOODBYE_SAYER_MESSAGE; g != e { 178 | t.Errorf("i2.SayHello(): got %s, expected %s", g, e) 179 | } 180 | 181 | // test the function 182 | if g, e := c.Stringer(DEFAULT_STRING), DEFAULT_STRING; g != e { 183 | t.Errorf("Test Stringer: got %s, expected %s", g, e) 184 | } 185 | } 186 | 187 | ////////////////////////////////////////// 188 | // Self-referential, ouroboros types 189 | ////////////////////////////////////////// 190 | 191 | type Valuer interface { 192 | Value() int 193 | } 194 | 195 | // Self-referential valuer #1 196 | type Ouroboros1 struct { 197 | A Valuer `inj:""` 198 | B Valuer `inj:""` 199 | V int 200 | } 201 | 202 | func (o Ouroboros1) Value() int { return o.V } 203 | 204 | // Self-referential valuer #2 205 | type Ouroboros2 struct { 206 | A Valuer `inj:""` 207 | B Valuer `inj:""` 208 | V int 209 | } 210 | 211 | func (o Ouroboros2) Value() int { return o.V } 212 | 213 | // Self-referential valuer #3 214 | type Ouroboros3 struct { 215 | V int 216 | } 217 | 218 | func (o Ouroboros3) Value() int { return o.V } 219 | 220 | // Self-referential valuer #4 221 | type Ouroboros4 struct { 222 | Ouroboros3 `inj:""` 223 | V int 224 | } 225 | 226 | func (o Ouroboros4) Value() int { return o.V } 227 | -------------------------------------------------------------------------------- /datasource.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | // A Datasource is any external interface that provides an interface given a 4 | // string key. It's split into two fundamental components: the DatasourceReader 5 | // and the DatasourceWriter. For the purposes of automatic configuration via 6 | // dependency injection, a DatasourceReader is all that's required. 7 | // 8 | // Datasource paths are supplied by values in structfield tags (which are empty 9 | // for normal inj operation). Here's an example of a struct that will try to 10 | // fulfil its dependencies from a datasource: 11 | // 12 | // type MyStruct stuct { 13 | // SomeIntVal int `inj:"some.path.in.a.datasource"` 14 | // } 15 | // 16 | // When trying to connect the dependency on an instance of the struct above, inj will 17 | // poll any available DatasourceReaders in the graph by calling their Read() function 18 | // with the string argument "some.path.in.a.datasource". If the function doesn't return 19 | // an error, then the resultant value will be used for the dependency injection. 20 | // 21 | // A struct tag may contain multiple datasource paths, separated by commas. The paths 22 | // will be polled in order of their appearance in the code, and the value from the first 23 | // DatasourceReader that doesn't return an error on its Read() function will be used to 24 | // meet the dependency. 25 | // 26 | // DatasourceWriters function in a similar way: when a value is set on an instance of a 27 | // struct via inj's dependency injection, any associated DatasourceWriters' Write() functions 28 | // are called for each datasource path. 29 | // 30 | type Datasource interface { 31 | DatasourceReader 32 | DatasourceWriter 33 | } 34 | -------------------------------------------------------------------------------- /datasource_reader.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | // A datasource reader provides an object from a datasource, identified by a 4 | // given string key. Refer to the documentation for the Datasource interface for more information. 5 | type DatasourceReader interface { 6 | Read(string) (interface{}, error) 7 | } 8 | -------------------------------------------------------------------------------- /datasource_reader_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | /////////////////////////////////////////////////////////////// 9 | // A mock DatasourceReader implementation 10 | /////////////////////////////////////////////////////////////// 11 | 12 | type MockDatasourceReader struct { 13 | stack map[string]interface{} 14 | } 15 | 16 | func NewMockDatasourceReader(data ...map[string]interface{}) *MockDatasourceReader { 17 | 18 | d := &MockDatasourceReader{} 19 | 20 | d.stack = make(map[string]interface{}) 21 | 22 | for _, datum := range data { 23 | for k, v := range datum { 24 | d.stack[k] = v 25 | } 26 | } 27 | 28 | return d 29 | } 30 | 31 | func (d *MockDatasourceReader) Read(key string) (interface{}, error) { 32 | 33 | if value, exists := d.stack[key]; exists { 34 | return value, nil 35 | } 36 | 37 | return nil, fmt.Errorf("No stack entry for '%s'", key) 38 | } 39 | 40 | /////////////////////////////////////////////////////////////// 41 | // Unit tests for graph implementation 42 | /////////////////////////////////////////////////////////////// 43 | 44 | func Test_TheDatasourceReaderWritesToTheDepdendency(t *testing.T) { 45 | 46 | dep := dataSourceDep{} 47 | ds := newMockDataSourceWithValues(t) 48 | g := NewGraph() 49 | 50 | g.AddDatasource(ds) 51 | g.Provide(&dep) 52 | 53 | assertNoGraphErrors(t, g.(*graph)) 54 | 55 | if g, e := dep.StringValue, DEFAULT_STRING; g != e { 56 | t.Errorf("Expected string '%s', got '%s'", e, g) 57 | } 58 | 59 | if dep.FuncValue == nil { 60 | t.Errorf("Didn't get expected function instance") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /datasource_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | /////////////////////////////////////////////////////////////// 9 | // A mock DatasourceReader and DatasourceWriter implementation 10 | /////////////////////////////////////////////////////////////// 11 | 12 | type MockDatasource struct { 13 | stack map[string]interface{} 14 | } 15 | 16 | func NewMockDatasource() Datasource { 17 | 18 | d := &MockDatasource{} 19 | 20 | d.stack = make(map[string]interface{}) 21 | 22 | return d 23 | } 24 | 25 | func (d *MockDatasource) Read(key string) (interface{}, error) { 26 | 27 | if value, exists := d.stack[key]; exists { 28 | return value, nil 29 | } 30 | 31 | return nil, fmt.Errorf("No stack entry for '%s'", key) 32 | } 33 | 34 | func (d *MockDatasource) Write(key string, value interface{}) error { 35 | 36 | d.stack[key] = value 37 | 38 | return nil 39 | } 40 | 41 | /////////////////////////////////////////////////////////////// 42 | // A mock specific dependency implementation for testing 43 | /////////////////////////////////////////////////////////////// 44 | 45 | type dataSourceDep struct { 46 | StringValue string `inj:"datasource.string"` 47 | FuncValue FuncType `inj:"datasource.func"` 48 | IntValue int `inj:"datasource.int"` 49 | } 50 | 51 | func newMockDataSourceWithValues(t *testing.T) Datasource { 52 | 53 | d := NewMockDatasource() 54 | 55 | if e := d.Write("datasource.string", DEFAULT_STRING); e != nil { 56 | t.Fatalf("newMockDataSourceWithValues: Datasource.Write: %s", e) 57 | } 58 | 59 | if e := d.Write("datasource.func", funcInstance); e != nil { 60 | t.Fatalf("newMockDataSourceWithValues: Datasource.Write: %s", e) 61 | } 62 | 63 | // Try and integer value expressed as a float 64 | if e := d.Write("datasource.int", 16.01); e != nil { 65 | t.Fatalf("newMockDataSourceWithValues: Datasource.Write: %s", e) 66 | } 67 | 68 | return d 69 | } 70 | -------------------------------------------------------------------------------- /datasource_writer.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | // A datasource reader sends an object to a datasource, identified by a 4 | // given string key. Refer to the documentation for the Datasource interface for more information. 5 | type DatasourceWriter interface { 6 | Write(string, interface{}) error 7 | } 8 | -------------------------------------------------------------------------------- /datasource_writer_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "testing" 4 | 5 | /////////////////////////////////////////////////////////////// 6 | // A mock DatasourceWriter implementation 7 | /////////////////////////////////////////////////////////////// 8 | 9 | type MockDatasourceWriter struct { 10 | stack map[string]interface{} 11 | } 12 | 13 | func NewMockDatasourceWriter(data ...map[string]interface{}) *MockDatasourceWriter { 14 | 15 | d := &MockDatasourceWriter{} 16 | 17 | d.stack = make(map[string]interface{}) 18 | 19 | for _, datum := range data { 20 | for k, v := range datum { 21 | d.stack[k] = v 22 | } 23 | } 24 | 25 | return d 26 | } 27 | 28 | func (d *MockDatasourceWriter) Write(key string, value interface{}) error { 29 | 30 | d.stack[key] = value 31 | 32 | return nil 33 | } 34 | 35 | func (d *MockDatasourceWriter) Assert(t *testing.T, key string, value interface{}) { 36 | 37 | v, exists := d.stack[key] 38 | 39 | if !exists { 40 | t.Fatalf("MockDatasourceWriter.Assert: Key '%s' doesn't exist", key) 41 | } 42 | 43 | if v != value { 44 | t.Fatalf("MockDatasourceWriter.Assert: key %s doesn't match (%v, %v)", key, v, value) 45 | } 46 | } 47 | 48 | func (d *MockDatasourceWriter) AssertMap(t *testing.T, data map[string]interface{}) { 49 | 50 | for k, v := range data { 51 | d.Assert(t, k, v) 52 | } 53 | } 54 | 55 | /////////////////////////////////////////////////////////////// 56 | // Unit tests for graph implementation 57 | /////////////////////////////////////////////////////////////// 58 | 59 | type datasourceWriterDep struct { 60 | IntVal int `inj:"datasource.writer.int"` 61 | StringVal string `inj:"datasource.writer.string"` 62 | } 63 | 64 | func Test_DatasourceReaderWriterLoopInGraph(t *testing.T) { 65 | 66 | expected_values := map[string]interface{}{ 67 | "datasource.writer.int": 10, 68 | "datasource.writer.string": DEFAULT_STRING, 69 | } 70 | 71 | reader := NewMockDatasourceReader(expected_values) 72 | writer := NewMockDatasourceWriter() 73 | dep := datasourceWriterDep{} 74 | g := NewGraph() 75 | 76 | g.AddDatasource(reader, writer) 77 | g.Provide(&dep) 78 | 79 | assertNoGraphErrors(t, g.(*graph)) 80 | 81 | writer.AssertMap(t, expected_values) 82 | } 83 | 84 | func Test_DatasourceWriterWritesWithoutAReader(t *testing.T) { 85 | 86 | expected_values := map[string]interface{}{ 87 | "datasource.writer.int": 10, 88 | "datasource.writer.string": DEFAULT_STRING, 89 | } 90 | 91 | writer := NewMockDatasourceWriter() 92 | dep := datasourceWriterDep{} 93 | g := NewGraph() 94 | 95 | g.AddDatasource(writer) 96 | g.Provide( 97 | expected_values["datasource.writer.int"], 98 | expected_values["datasource.writer.string"], 99 | &dep, 100 | ) 101 | 102 | assertNoGraphErrors(t, g.(*graph)) 103 | 104 | writer.AssertMap(t, expected_values) 105 | } 106 | -------------------------------------------------------------------------------- /documentation.go: -------------------------------------------------------------------------------- 1 | // Package inj provides reflection-based dependency injection for structs and functions. Some parts of it will be familiar to 2 | // anyone who's ever used https://github.com/facebookgo/inject; others bear a passing similarity to depdendency injection in 3 | // Angular.js. It's designed for medium to large applications, but it works just fine for small apps too. It's especially 4 | // useful if your project is is BDD/TDD-orientated. 5 | // 6 | // The essential premise of the package is that of object graphs. An object graph is just an index of objects organised by 7 | // their explicitly nominated relationships. Depending on the application, a graph can be very simple - struct A depends on 8 | // struct B - or extremely complicated. Package inj aims to simplify the process of creating and maintaining a graph of any 9 | // complexity using out-of-the-box features of the Go language, and to povide easy access to the objects in the graph. 10 | // 11 | // A simple and unrealistically trivial example is this: 12 | // 13 | // package main 14 | // 15 | // import ( 16 | // "fmt" 17 | // "github.com/yourheropaul/inj" 18 | // ) 19 | // 20 | // type ServerConfig struct { 21 | // Port int `inj:""` 22 | // Host string `inj:""` 23 | // } 24 | // 25 | // func main() { 26 | // config := ServerConfig{} 27 | // inj.Provide(&config, 6060, "localhost") 28 | // 29 | // // The struct fields have now been set by the graph, and 30 | // // this will print "localhost:6060" 31 | // fmt.Printf("%s:%d",config.Host,config.Port) 32 | // } 33 | // 34 | // To understand what's happening there, you have to perform a bit of counter-intuitive reasoning. Inj has a global graph 35 | // (which is optional, and can be disabled with the noglobals build tag) that is accessed through the three main API functions – 36 | // inj.Provide(), inj.Assert() and inj.Inject(). The first and most fundamental of those functions - inj.Provide() - inserts anything 37 | // passed to it into the graph, and then tries to wire up any dependency requirements. 38 | // 39 | // Before we get on to what dependency requirements are, it's important to know how a graph works. It is, in its simplest form, a map 40 | // of types to values. In the example above, the graph - after inj.Provide() is called - will have three entries: 41 | // 42 | // [ServerConfig] => (a pointer to the config variable) 43 | // [int] => 6060 44 | // [string] => "localhost" 45 | // 46 | // There can only be one entry for each type in a graph, but since Go allows new types to be created arbitrarly, that's not very much of 47 | // a problem for inj. For example, if I wanted a second string-like type to be stored in the graph, I could simply type one: 48 | // 49 | // package main 50 | // 51 | // import ( 52 | // "github.com/yourheropaul/inj" 53 | // ) 54 | // 55 | // type MyString string 56 | // 57 | // func main() { 58 | // var basicString string = "this is a 'normal' string" 59 | // var myString MyString = "this is a typed string" 60 | // inj.Provide(basicString,myString) 61 | // } 62 | // 63 | // In that example, the graph would now contain two separate, stringer entities: 64 | // 65 | // [string] => "this is a 'normal' string" 66 | // [MyString] => "this is a typed string" 67 | // 68 | // Back to depdendency requirements. The ServerConfig struct above has two, indicated by the inj:"" struct field tags (advanced usage of the 69 | // package makes use of values in the tags, but we can ignore that for now). At the end of the inj.Provide() call, the graph is wired up – 70 | // which essentially means finding values for all of the dependency requirements by type. The Port field of the ServerConfig struct requires 71 | // and int, and the graph has one, so it's assigned; the Host field requires as string, and that can be assigned from the graph too. 72 | // 73 | // Obviously these examples are trivial in the extreme, and you'd probably never use the inj package in that way. The easiest way to understand 74 | // the package for real-world applications is to refer to the example application: https://github.com/yourheropaul/inj/tree/master/example. 75 | // 76 | // For more general information, see the Wikipedia article for a technical breakdown of dependency injection: 77 | // https://en.wikipedia.org/wiki/Dependency_injection. 78 | // 79 | package inj 80 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package inj/example is a demonstration the inj package. It takes the form of a very simple web application 3 | that is modelled by a struct. Depdendencies for the application are defined on struct fields using tags, 4 | and provided in the main function. On startup, the application will provide and check its dependencies, 5 | then start a simple HTTP server with two endpoints. 6 | 7 | The functions for the endpoints use a rudimentary HTTP middleware wrapped so they can make use of inj.Inject, 8 | and thus have non-standard argument requirements. That will make sense when you see it in action. 9 | 10 | Hopefully the code is reasonably self-explanatory. To experience the full utility of inj, compare main.go to 11 | main_test.go. 12 | */ 13 | package main 14 | 15 | import ( 16 | "fmt" 17 | "net/http" 18 | "os" 19 | 20 | "github.com/yourheropaul/inj" 21 | ) 22 | 23 | /* 24 | This is the struct that describes the application. The struct has one function (`run()`, which you'll see below) 25 | and three fields that represent things it will need for the demo. None of the fields will be populates explicitly: 26 | rather, they will be provided using dependency injection. 27 | 28 | As you can probably see, depdendency requirements are indicated using an `inj:""` struct tag. Fields with this tag 29 | will be assigned, if possible, during the provision call; fields without the tag will be ignored. 30 | 31 | There are currently no values to pass in the struct tag. Future versions may make more use of them, but for the 32 | moment all tags must pass strings in double quotes to satisfy Go's reflect.StructTag parsing. 33 | 34 | It should be noted that any field can be associatd with the tag. The field doesn't have to be a special type, and 35 | there are no limits (aside from the constraints of the language itself) as to what they can be. 36 | 37 | `Application`'s functions (there are three) are at the end of this file. 38 | */ 39 | type Application struct { 40 | Config Configurer `inj:""` 41 | Log Logger `inj:""` 42 | Exit ExitChan `inj:""` 43 | } 44 | 45 | ///////////////////////////////////////////// 46 | // Application types 47 | ///////////////////////////////////////////// 48 | 49 | /* 50 | For the purposes of this app, the `Configurer` type is very simple: it's an interface that stipulates exactly one 51 | function. The return value of the the implementing object will used as the port number in the HTTP server. 52 | */ 53 | type Configurer interface { 54 | Port() int 55 | } 56 | 57 | /* A logger is a simple function that happens to conform to `fmt.Printf`'s signature. */ 58 | type Logger func(string, ...interface{}) (int, error) 59 | 60 | /* This is a basic implementation of the Configurer interface. */ 61 | type Config struct{} 62 | 63 | func (c *Config) Port() int { return 8080 } 64 | 65 | /* The final struct type, ExitChan, is just a channel that accepts blank interfaces */ 66 | type ExitChan chan interface{} 67 | 68 | ///////////////////////////////////////////// 69 | // Other types 70 | ///////////////////////////////////////////// 71 | 72 | /* 73 | One of the application behaviours is write to an HTTP client. For code readability, this is a type that can do 74 | that. 75 | */ 76 | type Responder func(w http.ResponseWriter, r *http.Request) 77 | 78 | /* ... and here's a trivial implementation of that type. */ 79 | func WriteResponse(w http.ResponseWriter, r *http.Request) { 80 | fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) 81 | } 82 | 83 | ///////////////////////////////////////////// 84 | // Entrypoint 85 | ///////////////////////////////////////////// 86 | 87 | func main() { 88 | 89 | app := Application{} 90 | 91 | /* 92 | This is the first of three `inj` API calls. (The others are `inj.Assert()` and `inj.Inject`, more on those 93 | later). `inj.Provide()` is a variadic function that takes any number of `interface{}`s and inserts then into 94 | a list of globally-available objects call the application graph. 95 | 96 | After each call to `inj.Provide()`, the graph is 'connected', meaning that any structs with depdendencies (that 97 | is, structs containing fields with `inj:""` tags) have their dependent fields assigned to nodes on the graph. 98 | This how how we'll assign values to the three fields of the `Application` struct. 99 | 100 | You can call `inj.Provide()` as many times as you like, which means that it's possible to build a cascade of 101 | dependencies from imported packages using the `init()` function. Be careful with that, because `inj.Provide()` 102 | is where all the heavy lifting is done, and it's not fast enough to execute for each request or operation. 103 | Generally it should be called at startup, like we're doing here. 104 | */ 105 | inj.Provide( 106 | 107 | // A pointer to our `Application` is necessary to fulfil its depdendencies. (It must be a pointer so the values 108 | // can be assigned). 109 | &app, 110 | 111 | // Objects are stored in the graph, even if they're stored anywhere else. Here we're creating a channel. 112 | make(ExitChan), 113 | 114 | // The same with the `Config` struct; just create a new instance for the graph and move on. 115 | &Config{}, 116 | 117 | // As noted above, `Logger` happens to match the signature of `fmt.Printf`, so we use that. 118 | fmt.Printf, 119 | 120 | // This is that 'Hello, I love %s' writer. `Application` doesn't use it, but other graph-dependent functions do. 121 | WriteResponse, 122 | ) 123 | 124 | /* 125 | Once you've made all your calls to `inj.Provide()`, it's time to make sure that all the dependencies were met. In 126 | `inj`, that's fairly simple: use the `inj.Assert()` function. It returns a boolean and a slice of strings. If the 127 | boolean is false, then the slice will be populated with all the errors that occured during struct injection. 128 | */ 129 | if valid, messages := inj.Assert(); !valid { 130 | fmt.Println(messages) 131 | os.Exit(1) 132 | } 133 | 134 | /* 135 | If we get to this point, we know that `app` has had all its dependencies met, so we can call `app.run()`. 136 | */ 137 | app.run() 138 | } 139 | 140 | ///////////////////////////////////////////// 141 | // A coda: middleware and `inj.Inject()` 142 | ///////////////////////////////////////////// 143 | 144 | /* 145 | This application uses `http.HandleFunc` to, well, handle HTTP requests using a function. That's because I needed to find a 146 | vaguely realistic way of demonstrating the third and last API function, `inj.Inject()`. As it turns out, it's quite a nice 147 | use of the `inj` API, but I realise you might not use this in your own applications. 148 | 149 | `inj.Inject()` is a variadic function that requires a function (any kind of function) as its first argument, and then zero 150 | or more other objects as it variadic component. Under the hood, it examines the arguments of the function, tries to find 151 | values for each one from the graph (or, if they're not available in the graph, the variadic arguments to inj.Inject()` 152 | itself) and then calls the function. 153 | 154 | This can be a little bit confusing to think about to begin with, and HTTP middleware (which is typically a function that 155 | returns a function) doesn't necessarily clear things up. It might be easier to visualise it like this: 156 | 157 | inj.Provide("This string value is in the graph") 158 | inj.Inject( 159 | func(s string, i int) { 160 | fmt.Sprintf("String value: %s, int value: %d", s, i) 161 | }, 162 | 10 // This is an int value 163 | ) 164 | 165 | The code above inserts a string value into the graph and then `inj.Inject()` to call a function. The function is called 166 | immediately, and prints some text to stdout: "String value: This string value is in the graph, int value: 10". 167 | 168 | The values come from the graph, and the variadic arguments to `inj.Inject()`. They're specified by the arguments to function 169 | itself. When `inj.Inject()` is called, it examines the each argument in turn and checks if it has a value for it. Because 170 | there's a string in the graph, the string argument is injectable; because there's an integer in the call to `inj.Inject()` 171 | the integer agument is injectable. The order of the arguments doesn't matter. If an argument is not available, then a runtime 172 | panic will be thrown. 173 | 174 | It's not just for callback functions. Closures in Go mean that you can chuck in a call to `inj.Inject()` anywhere, and pull 175 | objects from the graph. Imagine you have an instance of a database accessor in your graph: 176 | 177 | inj.Provide(databaseStruct{}) 178 | 179 | Now say you have some business logic function that needs access to the database accessor, but it has no way to directly find it. 180 | You can use a simple nested closure with `inj.Inject()` to assemble all the values you need: 181 | 182 | func (b *SomeBusinessLogicController) DoSomethingImportant(ctx *business.Context, p *ptrToThing) { 183 | inj.Inject(func(db DatatabaseInterface) { 184 | // Now `ctx`, `p` AND `db` are all in scope, and you didn't have to change the arguments to your function 185 | }) 186 | } 187 | 188 | I don't want to brag, but that's pretty nifty, right? 189 | 190 | Getting back to the demo, the `middleware()` function returns another function that makes use of `inj.Inject()`. 191 | Everything in the graph will be available to the the `fn` function, as well as the `http.ResponseWriter` and `*http.Request` 192 | arguments that are passed by the http package. You'll see implementations for the handle functions at the end of this file, 193 | and you'll notice that they can specify any arguments they like, not just the standard ones you'd expect. This is the really 194 | nifty part of the `inj` package: callback functions can decide which variables they want dynamically. 195 | */ 196 | 197 | func middleware(fn interface{}) func(http.ResponseWriter, *http.Request) { 198 | 199 | return func(w http.ResponseWriter, r *http.Request) { 200 | inj.Inject(fn, w, r) 201 | } 202 | } 203 | 204 | ///////////////////////////////////////////// 205 | // Application functions 206 | ///////////////////////////////////////////// 207 | 208 | /* This is the function that's called at the end of `main()` */ 209 | func (a Application) run() { 210 | 211 | // The handler functions use the middleware as outlined above. 212 | http.HandleFunc("/", middleware(a.handler)) 213 | http.HandleFunc("/shutdown", middleware(a.shutdown)) 214 | 215 | // The `Config` type (which implements `Configurer`) will have been automatically injected as part of the initial 216 | // `inj.Provide()` call in `main()`. 217 | port := a.Config.Port() 218 | 219 | // The `Logger` will also have been automatically injected. 220 | a.Log("Running server on port %d...\n", port) 221 | 222 | // This has nothing to do with `inj`; it's a standard library call 223 | go http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 224 | 225 | // Remember the `ExitChan` type that was created and provided in `main()`? This is what it's for. 226 | <-a.Exit 227 | 228 | a.Log("Received data on exit channel.\n") 229 | } 230 | 231 | /* 232 | This is the "/" handler, which is wrapped using `middleware()`, as outlined above. Its purpose in the application is to 233 | route requests through the `WriteResponse` ("Hello! I love %s") function defined near the top of the file. That function 234 | is a `Responder` type, and it happens to be in the graph because it was part of the `inj.Provide()` call in `main()`. 235 | 236 | This callback is wrapped by `inj.Inject()`, so it can specify whatever arguments it wants. Here, we're requesting the 237 | `w http.ResponseWriter` and `r *http.Request` variables that the `http` package sends, and also a `Responder` from the 238 | graph. 239 | */ 240 | func (a Application) handler(w http.ResponseWriter, r *http.Request, responder Responder) { 241 | responder(w, r) 242 | } 243 | 244 | /* 245 | Here's another handler, registed on "/shutdown". It calls for the `w http.ResponseWriter` (but not the `r *http.Request`), 246 | and also an `ExitChan`. 247 | 248 | (Yes, I know the ExitChan is already in the `a` variable. This is a demo application. It doesn't have to make complete, 249 | real-world sense. The function illustrates that you can omit normally-required functions and add others, and that's all 250 | it needs to do). 251 | */ 252 | func (a Application) shutdown(w http.ResponseWriter, e ExitChan) { 253 | w.Write([]byte("Shutting down!")) 254 | a.Log("Shutting down...\n") 255 | e <- 0 256 | } 257 | 258 | /* 259 | That's pretty much it! To recap: 260 | 261 | - There are three API functions in the `inj` package: `inj.Provide()`, `inj.Assert()` and `inj.Inject()`. 262 | - Use `inj.Provide()` any number of times to initially wire up your graph 263 | - Then use `inj.Assert()` to make sure all the dependencies were met. 264 | - If you want to use dynamic function values, use `inj.Inject()`, which can also accept local arguments. 265 | 266 | Do check out main_test.go, which demonstrates how to use `inj` for testing. 267 | */ 268 | -------------------------------------------------------------------------------- /example/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | The test component of the inj/example application demonstrates the use of mock objects by 5 | constructing a special graph of test objects that conforms to the application requirements, 6 | and then runs the application. 7 | 8 | Run this package's tests in verbose mode (`go test -test.v`) to get the full experience. 9 | */ 10 | 11 | import ( 12 | "fmt" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "net/http/httptest" 17 | "testing" 18 | 19 | "github.com/yourheropaul/inj" 20 | ) 21 | 22 | const ( 23 | MOCK_HTTP_PORT = 8080 24 | ) 25 | 26 | // This a mock implementation of the `Configurer` interface. The only real difference is that 27 | // it reads its port from a const, which is also used later in the test suite. 28 | type MockConfig struct{} 29 | 30 | func (c *MockConfig) Port() int { return MOCK_HTTP_PORT } 31 | 32 | // This fills in for the logging function. The test graph includes a pointer to a testing.T 33 | // object, which is used by the mock logger. (These messages will only be visible if go test 34 | // is running in verbose mode. See http://golang.org/pkg/testing/#T.Logf) 35 | func MockLogger(s string, a ...interface{}) (int, error) { 36 | 37 | inj.Inject(func(t *testing.T) { 38 | t.Logf(s, a...) 39 | }) 40 | 41 | return 0, nil 42 | } 43 | 44 | // We're also going to need a mock `Responder` (the function that the root HTTP hander 45 | // invokes in the application). The application always responds with "Hi there, I love X", 46 | // but the test can be simpler: it can just echo out whatever was added to the URL. 47 | func MockWriteResponse(w http.ResponseWriter, r *http.Request) { 48 | fmt.Fprintf(w, "%s", r.URL.Path[1:]) 49 | } 50 | 51 | /* 52 | The first test assembles the components in an isolated graph so that we don't 53 | contaminate the global graph during the scope of the test. All it really does 54 | is `Provide()`s the required objects and asserts the intergrity of the graph. 55 | */ 56 | func Test_GraphInitialisation(t *testing.T) { 57 | 58 | g := inj.NewGraph() 59 | 60 | g.Provide( 61 | 62 | // An instance of the application object. There's no need to keep a 63 | // pointer to it, since we won't be using it 64 | &Application{}, 65 | 66 | // The exit channel is the same as the non-test version 67 | make(ExitChan), 68 | 69 | // Our mock config will fill the `Configurer` dependency 70 | &MockConfig{}, 71 | 72 | // Use the mock `Logger` 73 | MockLogger, 74 | 75 | // The mock `Responder` 76 | MockWriteResponse, 77 | 78 | // Finally, add a pointer to a `testing.T`. It's not used in this test, 79 | // but it's worth including for completeness. 80 | t, 81 | ) 82 | 83 | // Make sure everything was loaded properly 84 | if valid, messages := g.Assert(); !valid { 85 | t.Fatalf("g.Assert() failed: %s", messages) 86 | } 87 | } 88 | 89 | /* 90 | The second test ensures that our mock `Responder` does its job. This is accomplished 91 | uing the httptest package from the standard library. 92 | */ 93 | func Test_MockResponder(t *testing.T) { 94 | 95 | // Wrap the mock function in the middleware, just like the app does 96 | handler := middleware(MockWriteResponse) 97 | 98 | // A bunch of strings with which to test the system. In a real application, these 99 | // would likely be randomly-generated. 100 | tests := []string{ 101 | "foo", 102 | "basket", 103 | "ramakin", 104 | "logical fallacy", 105 | "existential terror", 106 | "€*¢", 107 | } 108 | 109 | // Test each string using httptest.Recorder. Again, there are many better ways to 110 | // do this; what follows is an aside for the example text suite. 111 | for _, input := range tests { 112 | req, err := http.NewRequest("GET", "http://localhost/"+input, nil) 113 | 114 | if err != nil { 115 | t.Fatalf("http.NewRequest failed: %s", err) 116 | } 117 | 118 | w := httptest.NewRecorder() 119 | handler(w, req) 120 | 121 | t.Logf("Testing %s...", input) 122 | 123 | if g, e := w.Body.String(), input; g != e { 124 | t.Fatalf("Got %s, expected %s") 125 | } 126 | } 127 | } 128 | 129 | /* 130 | This is the main test: add all the dependencies to the global graph and run the 131 | application. We can use the http package from the standard library to make sure 132 | the responses from the server are correct, and shut down the server properly when 133 | we're done. 134 | */ 135 | func Test_Application(t *testing.T) { 136 | 137 | app := Application{} 138 | exit := make(ExitChan) 139 | 140 | // Set up the graph, much the same was as in main.go – and exactly as in the test 141 | // above, only this time globally 142 | inj.Provide( 143 | &app, // This time it is a pointer 144 | exit, // `ExitChan`, this time shared 145 | &MockConfig{}, // Use the mock config 146 | MockLogger, // `Logger`, as above 147 | MockWriteResponse, // Our specialised response writer 148 | t, // Pointer to `testing.T`, which is now going to be used 149 | ) 150 | 151 | // Make sure everything was loaded properly again 152 | if valid, messages := inj.Assert(); !valid { 153 | t.Fatalf("inj.Assert() failed: %s", messages) 154 | } 155 | 156 | // Run the application in its own goroutine. It should stay running until it's explicitly 157 | // shut down 158 | go app.run() 159 | 160 | // Here we make some requests to the application, and check the responses. Again, there are 161 | // better ways to choose test strings 162 | tests := []string{ 163 | "fulfilment", 164 | "doubt", 165 | "futility", 166 | "inetivatable narrative hijacking", 167 | } 168 | 169 | for _, input := range tests { 170 | 171 | res, err := http.Get(fmt.Sprintf("http://localhost:%d/%s", MOCK_HTTP_PORT, input)) 172 | 173 | if err != nil { 174 | t.Fatalf("http.Get(): %s", err) 175 | } 176 | 177 | body, err := ioutil.ReadAll(res.Body) 178 | res.Body.Close() 179 | 180 | if err != nil { 181 | log.Fatal(err) 182 | t.Fatalf("ioutil.ReadAll %s", err) 183 | } 184 | 185 | t.Logf("Testing %s...", input) 186 | 187 | if g, e := res.StatusCode, 200; g != e { 188 | t.Errorf("Expected status code %d, got %d", g, e) 189 | } 190 | 191 | if g, e := string(body), input; g != e { 192 | t.Fatalf("Got %s, expected %s") 193 | } 194 | } 195 | 196 | // Kill the app 197 | exit <- struct{}{} 198 | } 199 | 200 | /* 201 | That's pretty much it for the example application test suite. To recap, the suite: 202 | 203 | - defines a mock `Configurer`, `Logger` and `Responder` 204 | - tests an isolated graph with all dependencies 205 | - explicitly tests the mock `Responder` 206 | - runs the application (with all the mock dependencies in the global graph) 207 | - checks that the real HTTP responses from the application are as expected. 208 | 209 | Once again, this is an example. One would expect any real world application to be far more 210 | complex, and to use a more sophisticated structure. In any case, the `inj` procedure is 211 | always pretty much the same: your application pulls its dependencies from the graph, either 212 | directly (using `inj.Provide`) or through a functional callback (ie. `inj.Inject`) – or both. 213 | In the production code, the dependencies are assembled somewhere near the `main()` function; in 214 | the tests, they can be provided specially, thus allowing discreet segments of the code to be 215 | tested in the most realistic way possible. 216 | */ 217 | -------------------------------------------------------------------------------- /graph.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "reflect" 4 | 5 | // A Graph object represents an flat tree of application 6 | // dependencies, a count of currently unmet dependencies, 7 | // and a list of encountered errors. 8 | type graph struct { 9 | nodes nodeMap 10 | unmetDependency int 11 | errors []string 12 | indexes []reflect.Type 13 | datasourceReaders []DatasourceReader 14 | datasourceWriters []DatasourceWriter 15 | } 16 | 17 | // Create a new instance of a graph with allocated memory 18 | func NewGraph(providers ...interface{}) Grapher { 19 | 20 | g := &graph{} 21 | 22 | g.nodes = make(nodeMap) 23 | g.errors = make([]string, 0) 24 | g.datasourceReaders = make([]DatasourceReader, 0) 25 | g.datasourceWriters = make([]DatasourceWriter, 0) 26 | 27 | g.Provide(providers...) 28 | 29 | return g 30 | } 31 | 32 | // Add a node by reflection type 33 | func (g *graph) add(typ reflect.Type) (n *graphNode) { 34 | 35 | n = newGraphNode() 36 | g.nodes[typ] = n 37 | 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /graph_assert.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | // Make sure that all provided dependencies have their 4 | // requirements met, and return a list of errors if they 5 | // haven't. A graph is never really finalised, so Provide() and 6 | // Assert() can be called any number of times. 7 | func (g *graph) Assert() (valid bool, errors []string) { 8 | 9 | valid = true 10 | 11 | if g.unmetDependency > 0 || len(g.errors) > 0 { 12 | valid = false 13 | } 14 | 15 | return valid, g.errors 16 | } 17 | -------------------------------------------------------------------------------- /graph_assert_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | type AssertionTester struct { 9 | S string `inj:""` 10 | I int `inj:""` 11 | } 12 | 13 | func Test_AssertionHappyPath(t *testing.T) { 14 | 15 | g := NewGraph() 16 | 17 | g.Provide(&AssertionTester{}, "hello", 1) 18 | 19 | if v, m := g.Assert(); !v { 20 | fmt.Println(m) 21 | t.Error("Assert() failed") 22 | } 23 | } 24 | 25 | func Test_AssertionComplexHappyPath(t *testing.T) { 26 | 27 | g, c := NewGraph(), ConcreteType{} 28 | 29 | // Register providers (can include non-providers, which will then be wired up) 30 | if err := g.Provide( 31 | &c, 32 | &helloSayer{}, 33 | &goodbyeSayer{}, 34 | funcInstance, 35 | ichannel, 36 | DEFAULT_STRING, 37 | ); err != nil { 38 | t.Fatalf("Graph.Provide: %s", err) 39 | } 40 | 41 | if v, m := g.Assert(); !v { 42 | fmt.Println(m) 43 | t.Error("Assert() failed") 44 | } 45 | } 46 | 47 | func Test_AssertionSadPath(t *testing.T) { 48 | 49 | g := NewGraph() 50 | 51 | // Only provide the concrete type 52 | g.Provide(&AssertionTester{}) 53 | 54 | v, m := g.Assert() 55 | 56 | if v { 57 | t.Error("Assert() didn't fail") 58 | } 59 | 60 | if g, e := len(m), 2; g != e { 61 | t.Errorf("Expected %d errors, got %d", e, g) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /graph_connect.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Usually called after Provide() to assign the values 9 | // of all requested dependencies. 10 | func (g *graph) connect() { 11 | 12 | // Reset error counts 13 | g.unmetDependency = 0 14 | g.errors = make([]string, 0) 15 | 16 | // loop through all nodes 17 | for _, node := range g.nodes { 18 | 19 | // assign dependencies to the object 20 | for _, dep := range node.Dependencies { 21 | if e := g.assignValueToNode(node.Value, dep); e != nil { 22 | g.unmetDependency++ 23 | g.errors = append(g.errors, e.Error()) 24 | } 25 | } 26 | } 27 | } 28 | 29 | func (g *graph) assignValueToNode(o reflect.Value, dep graphNodeDependency) error { 30 | 31 | parents := []reflect.Value{} 32 | v, err := g.findFieldValue(o, dep.Path, &parents) 33 | vtype := v.Type() 34 | 35 | if err != nil { 36 | return err 37 | } 38 | 39 | // Sanity check 40 | if !v.CanSet() { 41 | return fmt.Errorf("%s%s can't be set", o, dep.Path) 42 | } 43 | 44 | // If there are any datasource paths supplied... 45 | for _, path := range dep.DatasourcePaths { 46 | 47 | // ...check to see if a datasource reader has the value 48 | for _, d := range g.datasourceReaders { 49 | 50 | if dsvalue, err := d.Read(path); err == nil { 51 | 52 | typ := reflect.TypeOf(dsvalue) 53 | 54 | value := reflect.ValueOf(dsvalue) 55 | 56 | if typ != vtype && typ.ConvertibleTo(vtype) { 57 | value = value.Convert(vtype) 58 | } 59 | 60 | if value.Type().AssignableTo(vtype) { 61 | 62 | // The value can be set by reflection 63 | v.Set(value) 64 | 65 | // Any datasourcewriters need to be updated 66 | for _, w := range g.datasourceWriters { 67 | w.Write(path, v.Interface()) 68 | } 69 | 70 | return nil 71 | } 72 | } 73 | } 74 | } 75 | 76 | // Run through the graph and see if anything is settable 77 | for typ, node := range g.nodes { 78 | 79 | valid := true 80 | 81 | // Don't assign anything to itself or its children 82 | for _, parent := range parents { 83 | 84 | if parent.Interface() == node.Value.Interface() { 85 | valid = false 86 | break 87 | } 88 | } 89 | 90 | if !valid { 91 | continue 92 | } 93 | 94 | if typ.AssignableTo(v.Type()) { 95 | 96 | // The value can be set by reflection 97 | v.Set(node.Value) 98 | 99 | // Any datasourcewriters need to be updated 100 | for _, path := range dep.DatasourcePaths { 101 | for _, w := range g.datasourceWriters { 102 | w.Write(path, v.Interface()) 103 | } 104 | } 105 | 106 | return nil 107 | } 108 | } 109 | 110 | return fmt.Errorf("Couldn't find suitable dependency for %s", dep.Type) 111 | } 112 | 113 | // Required a struct type 114 | func (g *graph) findFieldValue(parent reflect.Value, path structPath, linneage *[]reflect.Value) (reflect.Value, error) { 115 | 116 | *linneage = append(*linneage, parent) 117 | 118 | // Dereference incoming values 119 | if parent.Kind() == reflect.Ptr { 120 | parent = parent.Elem() 121 | } 122 | 123 | // Only accept structs 124 | if parent.Kind() != reflect.Struct { 125 | return parent, fmt.Errorf("Type is %s, not struct", parent.Kind().String()) 126 | } 127 | 128 | // Take the first entry from the path 129 | stub, path := path.Shift() 130 | 131 | // Try to get the field 132 | f := parent.FieldByName(stub) 133 | 134 | if !f.IsValid() { 135 | return f, fmt.Errorf("Can't find field %s in %s", stub, parent) 136 | } 137 | 138 | // If that's the end of the path, return the value 139 | if path.Empty() { 140 | return f, nil 141 | } 142 | 143 | // Otherwise recurse 144 | return g.findFieldValue(f, path, linneage) 145 | } 146 | -------------------------------------------------------------------------------- /graph_connect_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | ////////////////////////////////////////////// 10 | // Types 11 | ////////////////////////////////////////////// 12 | 13 | type validConnectTester struct { 14 | Child1 *connectTesterChild1 `inj:""` 15 | Child2 *connectTesterChild2 `inj:""` 16 | } 17 | 18 | type invalidConnectTester struct { 19 | Child1 *connectTesterChild1 `inj:""` 20 | child2 *connectTesterChild2 `inj:""` 21 | } 22 | 23 | type connectTesterChild1 struct { 24 | Value string 25 | } 26 | 27 | type connectTesterChild2 struct { 28 | Value string 29 | } 30 | 31 | func newChildren() (*connectTesterChild1, *connectTesterChild2) { 32 | return &connectTesterChild1{"1"}, &connectTesterChild2{"2"} 33 | } 34 | 35 | ////////////////////////////////////////////// 36 | // Unit tests 37 | ////////////////////////////////////////////// 38 | 39 | // Connected objects should be in the graph 40 | func Test_ConnectHappyPath(t *testing.T) { 41 | 42 | g, p := newGraph(), validConnectTester{} 43 | c1, c2 := newChildren() 44 | 45 | g.Provide(&p, c1, c2) 46 | 47 | // Provide calls connect, but call it again 48 | // explicitly 49 | g.connect() 50 | 51 | // Basic tests against injection failure 52 | if p.Child1 == nil { 53 | t.Errorf("Child1 is nil") 54 | } 55 | 56 | if p.Child2 == nil { 57 | t.Errorf("Child2 is nil") 58 | } 59 | 60 | // The main test is of the graph itself 61 | c1_found, c2_found := false, false 62 | 63 | for _, n := range g.nodes { 64 | 65 | if n.Object == c1 { 66 | c1_found = true 67 | } 68 | 69 | if n.Object == c2 { 70 | c2_found = true 71 | } 72 | } 73 | 74 | if !c1_found { 75 | t.Errorf("Didn't find c1") 76 | } 77 | 78 | if !c2_found { 79 | t.Errorf("Didn't find c2") 80 | } 81 | } 82 | 83 | // Unmet dependencies should be counted, and their 84 | // errors stored 85 | func Test_ConnectDepCount(t *testing.T) { 86 | 87 | g, p := newGraph(), validConnectTester{} 88 | 89 | g.Provide(&p) 90 | 91 | g.connect() 92 | 93 | if g, e := g.unmetDependency, 2; g != e { 94 | t.Errorf("Got %d unmet deps, expected %d", g, e) 95 | } 96 | 97 | if g, e := len(g.errors), 2; g != e { 98 | t.Errorf("Got %d unmet dep errors, expected %d", g, e) 99 | } 100 | } 101 | 102 | // Values should actually be assigned 103 | func Test_ConnectAssignmentHappyPath(t *testing.T) { 104 | 105 | g, p := newGraph(), &validConnectTester{} 106 | c1, c2 := newChildren() 107 | v := reflect.ValueOf(p) 108 | 109 | gnds := []graphNodeDependency{ 110 | graphNodeDependency{ 111 | Path: structPath(".Child1"), 112 | Type: reflect.TypeOf(c1), 113 | }, 114 | graphNodeDependency{ 115 | Path: structPath(".Child2"), 116 | Type: reflect.TypeOf(c2), 117 | }, 118 | } 119 | 120 | g.Provide(c1, c2) 121 | 122 | for _, gnd := range gnds { 123 | if err := g.assignValueToNode(v, gnd); err != nil { 124 | t.Errorf("assignValueToNode: %s", err.Error()) 125 | } 126 | } 127 | 128 | // Basic tests against injection failure 129 | if p.Child1 == nil { 130 | t.Errorf("Child1 is nil") 131 | } 132 | 133 | if p.Child2 == nil { 134 | t.Errorf("Child2 is nil") 135 | } 136 | } 137 | 138 | // Nodes should update when the values do 139 | func Test_ConnectAssignmentNeutralPath(t *testing.T) { 140 | 141 | g, p := newGraph(), &validConnectTester{} 142 | c1, c2 := newChildren() 143 | c3, c4 := newChildren() 144 | v := reflect.ValueOf(p) 145 | 146 | // Manually assign the deps 147 | p.Child1 = c1 148 | p.Child2 = c2 149 | 150 | gnds := []graphNodeDependency{ 151 | graphNodeDependency{ 152 | Path: structPath(".Child1"), 153 | Type: reflect.TypeOf(c1), 154 | }, 155 | graphNodeDependency{ 156 | Path: structPath(".Child2"), 157 | Type: reflect.TypeOf(c2), 158 | }, 159 | } 160 | 161 | // Assign everything 162 | g.Provide(c3, c4) 163 | 164 | // Run through and re-assign (shouldn't error) 165 | for _, gnd := range gnds { 166 | if err := g.assignValueToNode(v, gnd); err != nil { 167 | t.Errorf("assignValueToNode: %s", err.Error()) 168 | } 169 | } 170 | 171 | if p.Child1 == c1 { 172 | t.Errorf("Child1 is the original c1") 173 | } 174 | 175 | if p.Child2 == c2 { 176 | t.Errorf("Child1 is the original c1") 177 | } 178 | } 179 | 180 | // Internal variabled should cause the assignment to fail 181 | func Test_ConnectSadPath1(t *testing.T) { 182 | 183 | g, p := newGraph(), &invalidConnectTester{} 184 | c1, c2 := newChildren() 185 | v := reflect.ValueOf(p) 186 | 187 | gnds := []graphNodeDependency{ 188 | graphNodeDependency{ 189 | Path: structPath(".child2"), 190 | Type: reflect.TypeOf(c2), 191 | }, 192 | } 193 | 194 | g.Provide(c1, c2) 195 | 196 | // Run through and assign (should error) 197 | for _, gnd := range gnds { 198 | if err := g.assignValueToNode(v, gnd); err == nil { 199 | t.Errorf("assignValueToNode: didn't error") 200 | } 201 | } 202 | } 203 | 204 | // Unmet dependencies should cause an error 205 | func Test_ConnectSadPath2(t *testing.T) { 206 | 207 | g, p := newGraph(), &validConnectTester{} 208 | c1, c2 := newChildren() 209 | v := reflect.ValueOf(p) 210 | 211 | gnds := []graphNodeDependency{ 212 | graphNodeDependency{ 213 | Path: structPath(".Child1"), 214 | Type: reflect.TypeOf(c1), 215 | }, 216 | graphNodeDependency{ 217 | Path: structPath(".Child2"), 218 | Type: reflect.TypeOf(c2), 219 | }, 220 | } 221 | 222 | // Run through and assign (should error) 223 | for _, gnd := range gnds { 224 | if err := g.assignValueToNode(v, gnd); err == nil { 225 | t.Errorf("assignValueToNode: didn't error") 226 | } 227 | } 228 | } 229 | 230 | // Should find a reflect value for a path 231 | func Test_ConnectFindFieldValue(t *testing.T) { 232 | 233 | g, p := newGraph(), &validConnectTester{} 234 | v := reflect.ValueOf(p) 235 | 236 | var descs = []struct { 237 | fieldName string 238 | path structPath 239 | }{ 240 | { 241 | fieldName: "*inj.connectTesterChild1", 242 | path: structPath(".Child1"), 243 | }, 244 | { 245 | fieldName: "*inj.connectTesterChild2", 246 | path: structPath(".Child2"), 247 | }, 248 | } 249 | 250 | for _, d := range descs { 251 | rv, e := g.findFieldValue(v, d.path, &[]reflect.Value{}) 252 | 253 | if e != nil { 254 | t.Errorf("findFieldValue: %s", e.Error()) 255 | } 256 | 257 | if g, e := rv.Type().String(), d.fieldName; g != e { 258 | t.Errorf("fieldname: got %s, expected %s", g, e) 259 | } 260 | } 261 | } 262 | 263 | // Error should be returned if input reflect value isn't 264 | // a struct 265 | func Test_ConnectF(t *testing.T) { 266 | 267 | g := newGraph() 268 | 269 | _, e := g.findFieldValue(reflect.ValueOf("123"), ".Child1", &[]reflect.Value{}) 270 | 271 | if e == nil { 272 | fmt.Errorf("Didn't error when type wasn't struct") 273 | } 274 | } 275 | 276 | // Incorrect paths should cause an error to be returned 277 | func Test_ConnectG(t *testing.T) { 278 | 279 | g, p := newGraph(), &validConnectTester{} 280 | 281 | _, e := g.findFieldValue(reflect.ValueOf(p), ".This.Doesnt.Exist", &[]reflect.Value{}) 282 | 283 | if e == nil { 284 | fmt.Errorf("Didn't error when path was wrong") 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /graph_datasource.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "fmt" 4 | 5 | // Add any number of Datasources, DatasourceReaders or DatasourceWriters 6 | // to the graph. Returns an error if any of the supplied arguments aren't 7 | // one of the accepted types. 8 | // 9 | // Once added, the datasources will be active immediately, and the graph 10 | // will automatically re-Provide itself, so that any depdendencies that 11 | // can only be met by an external datasource will be wired up automatically. 12 | // 13 | func (g *graph) AddDatasource(ds ...interface{}) error { 14 | 15 | for i, d := range ds { 16 | found := false 17 | 18 | if v, ok := d.(DatasourceReader); ok { 19 | found = true 20 | g.datasourceReaders = append(g.datasourceReaders, v) 21 | } 22 | 23 | if v, ok := d.(DatasourceWriter); ok { 24 | found = true 25 | g.datasourceWriters = append(g.datasourceWriters, v) 26 | } 27 | 28 | if !found { 29 | return fmt.Errorf("Supplied argument %d isn't a DatasourceReader or a DatasourceWriter", i) 30 | } 31 | } 32 | 33 | g.Provide() 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /graph_datasource_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "testing" 4 | 5 | /////////////////////////////////////////////////////////////// 6 | // Basic datasource/graph integration tests 7 | /////////////////////////////////////////////////////////////// 8 | 9 | func Test_ADatasourceCanBeAddedToAGraph(t *testing.T) { 10 | 11 | g := newGraph() 12 | d := NewMockDatasource() 13 | 14 | if e := g.AddDatasource(d); e != nil { 15 | t.Fatalf("g.AddDatasource: %s", e) 16 | } 17 | 18 | if g, e := len(g.datasourceReaders), 1; g != e { 19 | t.Errorf("Expected %d datasource readers, got %d", e, g) 20 | } 21 | 22 | if g, e := len(g.datasourceWriters), 1; g != e { 23 | t.Errorf("Expected %d datasource writers, got %d", e, g) 24 | } 25 | } 26 | 27 | func Test_SomeNonDatasourceTypeCantBeAddedToGraph(t *testing.T) { 28 | 29 | g := newGraph() 30 | d := struct{}{} 31 | 32 | if e := g.AddDatasource(d); e == nil { 33 | t.Fatalf("Expected nill error, got '%s'", e) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /graph_inject.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Given a function, call it with arguments from the graph. 9 | // Throws a runtime error in the form of a panic on failure. 10 | func (g *graph) Inject(fn interface{}, args ...interface{}) { 11 | 12 | // Reflect the input 13 | f := reflect.ValueOf(fn) 14 | 15 | // It's slightly faster to store the type rather than constantly 16 | // retrieving it. 17 | ftype := f.Type() 18 | 19 | // We can only accept functions 20 | if ftype.Kind() != reflect.Func { 21 | panic("[inj.Inject] Passed argument is not a function") 22 | } 23 | 24 | // Variadic functions aren't currently supported 25 | if ftype.IsVariadic() { 26 | panic("[inj.Inject] Passed function is variadic") 27 | } 28 | 29 | // Assemble extra arg types list 30 | xargs := make([]reflect.Type, len(args)) 31 | 32 | for i := 0; i < len(args); i++ { 33 | xargs[i] = reflect.TypeOf(args[i]) 34 | } 35 | 36 | // Number of required incoming arguments 37 | argc := ftype.NumIn() 38 | 39 | // Assemble a list of function arguments 40 | argv := make([]reflect.Value, argc) 41 | 42 | for i := 0; i < argc; i++ { 43 | 44 | func() { 45 | // Get an incoming arg reflection type 46 | in := ftype.In(i) 47 | 48 | // Look in the additional args list for the requirement 49 | for j := 0; j < len(xargs); j++ { 50 | if xargs[j].AssignableTo(in) { 51 | argv[i] = reflect.ValueOf(args[j]) 52 | return 53 | } 54 | } 55 | 56 | // Find an entry in the graph 57 | for j := 0; j < len(g.indexes); j++ { 58 | if g.indexes[j].AssignableTo(in) { 59 | argv[i] = g.nodes[g.indexes[j]].Value 60 | return 61 | } 62 | } 63 | 64 | // If it's STILL not found, panic 65 | panic(fmt.Sprintf("[inj.Inject] Can't find value for arg %d [%s]", i, in)) 66 | }() 67 | } 68 | 69 | // Make the function call, with the args which should now be complete. 70 | f.Call(argv) 71 | } 72 | -------------------------------------------------------------------------------- /graph_inject_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "testing" 4 | 5 | ////////////////////////////////////////// 6 | // Standard injection testers 7 | ////////////////////////////////////////// 8 | 9 | type StandardPasserType struct{} 10 | 11 | func (s StandardPasserType) Fn(i1 InterfaceOne, i2 InterfaceTwo, t *testing.T) { 12 | assertPasserInterfaceValues(i1, i2, t) 13 | } 14 | 15 | func StandardPasserFn(i1 InterfaceOne, i2 InterfaceTwo, t *testing.T) { 16 | assertPasserInterfaceValues(i1, i2, t) 17 | } 18 | 19 | // Used for benchmark tests 20 | func benchmarker(i1 InterfaceOne, i2 InterfaceTwo) { 21 | } 22 | 23 | ////////////////////////////////////////// 24 | // Assertions for passer types 25 | ////////////////////////////////////////// 26 | 27 | func assertPasserInterfaceValues(i1 InterfaceOne, i2 InterfaceTwo, t *testing.T) { 28 | 29 | if i1 == nil { 30 | t.Errorf("i1 is nil") 31 | } 32 | 33 | if g, e := i1.SayHello(), HELLO_SAYER_MESSAGE; g != e { 34 | t.Errorf("i2.SayHello(): got %s, expected %s", g, e) 35 | } 36 | 37 | if i2 == nil { 38 | t.Errorf("i1 is nil") 39 | } 40 | 41 | if g, e := i2.SayGoodbye(), GOODBYE_SAYER_MESSAGE; g != e { 42 | t.Errorf("i2.SayHello(): got %s, expected %s", g, e) 43 | } 44 | } 45 | 46 | ////////////////////////////////////////// 47 | // Unit tests 48 | ////////////////////////////////////////// 49 | 50 | // A basic test of the entire injection feature 51 | func Test_GraphSimpleInjectionHappyPath(t *testing.T) { 52 | 53 | defer func() { 54 | if r := recover(); r != nil { 55 | t.Fatalf("%s", r) 56 | } 57 | }() 58 | 59 | g := NewGraph() 60 | 61 | // This is tested elsewhere 62 | if err := g.Provide( 63 | &helloSayer{}, 64 | &goodbyeSayer{}, 65 | t, 66 | ); err != nil { 67 | t.Fatalf("Graph.Provide: %s", err) 68 | } 69 | 70 | // Pass an anonymous function 71 | g.Inject(func(i1 InterfaceOne, i2 InterfaceTwo) { 72 | assertPasserInterfaceValues(i1, i2, t) 73 | }) 74 | 75 | // Pass a first class function 76 | g.Inject(StandardPasserFn) 77 | 78 | // Pass a member of a struct 79 | spt := StandardPasserType{} 80 | g.Inject(spt.Fn) 81 | } 82 | 83 | // Graph.Inject should panic if a non-function is passed 84 | func Test_GraphSimpleInjectionSadPath1(t *testing.T) { 85 | 86 | defer func() { 87 | if recover() != nil { 88 | // The test has succeeded 89 | } 90 | }() 91 | 92 | g := NewGraph() 93 | g.Inject("not a func") 94 | 95 | t.Error("Inject failed to panic with non-func type") 96 | } 97 | 98 | // Should panic on missing dependencies 99 | func Test_GraphSimpleInjectionSadPath2(t *testing.T) { 100 | 101 | defer func() { 102 | if recover() != nil { 103 | // The test has succeeded 104 | } 105 | }() 106 | 107 | g := NewGraph() 108 | g.Inject(func(s string) {}) 109 | 110 | t.Error("Inject failed to panic with no dependency provided") 111 | } 112 | 113 | // Should panic if a variadic function is passed 114 | func Test_GraphSimpleInjectionSadPath3(t *testing.T) { 115 | 116 | defer func() { 117 | if recover() != nil { 118 | // The test has succeeded 119 | } 120 | }() 121 | 122 | g := NewGraph() 123 | g.Inject(func(s ...string) {}) 124 | 125 | t.Error("Inject failed to panic with a variadic function argument") 126 | } 127 | 128 | // Complex injection is essentially passing additional variables 129 | func Test_GraphComplexInjectionHappyPath1(t *testing.T) { 130 | 131 | defer func() { 132 | if r := recover(); r != nil { 133 | t.Fatalf("%s", r) 134 | } 135 | }() 136 | 137 | // Don't provide anything for this graph 138 | g := NewGraph() 139 | 140 | // Pass an anonymous function 141 | g.Inject(func(i1 InterfaceOne, i2 InterfaceTwo) { 142 | assertPasserInterfaceValues(i1, i2, t) 143 | }, &helloSayer{}, &goodbyeSayer{}, t) 144 | 145 | // Pass a first class function 146 | g.Inject(StandardPasserFn, &helloSayer{}, &goodbyeSayer{}, t) 147 | 148 | // Pass a member of a struct 149 | spt := StandardPasserType{} 150 | g.Inject(spt.Fn, &helloSayer{}, &goodbyeSayer{}, t) 151 | } 152 | 153 | // Dependencies should come from the graph before the xargs 154 | func Test_GraphComplexInjectionHappyPath2(t *testing.T) { 155 | 156 | defer func() { 157 | if r := recover(); r != nil { 158 | t.Fatalf("%s", r) 159 | } 160 | }() 161 | 162 | // Don't provide anything for this graph 163 | g := NewGraph("string one") 164 | 165 | // Pass an anonymous function 166 | g.Inject(func(s string) { 167 | if s != "string two" { 168 | t.Fatalf("Expected 'string one', got %s", s) 169 | } 170 | }, "string two") 171 | } 172 | 173 | ////////////////////////////////////////// 174 | // Benchmark tests 175 | ////////////////////////////////////////// 176 | 177 | // Figure out the normal rate 178 | func BenchmarkOrdinaryCall(b *testing.B) { 179 | h, g := &helloSayer{}, &goodbyeSayer{} 180 | 181 | for n := 0; n < b.N; n++ { 182 | benchmarker(h, g) 183 | } 184 | } 185 | 186 | // Test a fully provided graph 187 | func BenchmarkProvided1(b *testing.B) { 188 | 189 | g := NewGraph(&helloSayer{}, &goodbyeSayer{}) 190 | 191 | for n := 0; n < b.N; n++ { 192 | g.Inject(benchmarker) 193 | } 194 | } 195 | 196 | // Test a dynamically provided graph 197 | func BenchmarkProvided2(b *testing.B) { 198 | 199 | g := NewGraph() 200 | 201 | for n := 0; n < b.N; n++ { 202 | g.Inject(benchmarker, &helloSayer{}, &goodbyeSayer{}) 203 | } 204 | } 205 | 206 | // Test a dynamically provided graph 207 | func BenchmarkProvided3(b *testing.B) { 208 | 209 | g := NewGraph(&helloSayer{}) 210 | 211 | for n := 0; n < b.N; n++ { 212 | g.Inject(benchmarker, &goodbyeSayer{}) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /graph_node.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "reflect" 4 | 5 | type graphNode struct { 6 | Name string 7 | Object interface{} 8 | Type reflect.Type 9 | Value reflect.Value 10 | Dependencies []graphNodeDependency 11 | } 12 | 13 | type nodeMap map[reflect.Type]*graphNode 14 | 15 | func newGraphNode() (n *graphNode) { 16 | 17 | n = &graphNode{} 18 | 19 | n.Dependencies = make([]graphNodeDependency, 0) 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /graph_node_dependency.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | type graphNodeDependency struct { 9 | DatasourcePaths []string 10 | Path structPath 11 | Type reflect.Type 12 | } 13 | 14 | func findDependencies(t reflect.Type, deps *[]graphNodeDependency, path *structPath) error { 15 | 16 | for i := 0; i < t.NumField(); i++ { 17 | 18 | f := t.Field(i) 19 | 20 | // Ignore unpexported fields, regardless of tags 21 | if f.PkgPath != "" { 22 | continue 23 | } 24 | 25 | // Get all tags 26 | tag := f.Tag 27 | 28 | // Generate a struct path branch 29 | branch := path.Branch(f.Name) 30 | 31 | // Ignore tags that don't have injection deps 32 | if !strings.Contains(string(tag), "inj:") { 33 | 34 | if f.Type.Kind() == reflect.Struct { 35 | 36 | // Recurse 37 | findDependencies(f.Type, deps, &branch) 38 | } 39 | 40 | continue 41 | } 42 | 43 | // Assemble everything we know about the dependency 44 | dep := parseStructTag(tag) 45 | 46 | // Add the path in the struct 47 | dep.Path = branch 48 | 49 | // We also know the type 50 | dep.Type = f.Type 51 | 52 | // Add the dependency 53 | *deps = append(*deps, dep) 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func parseStructTag(t reflect.StructTag) (d graphNodeDependency) { 60 | 61 | parts := strings.Split(t.Get("inj"), ",") 62 | 63 | if len(parts) > 0 && len(parts[0]) > 0 { 64 | d.DatasourcePaths = parts 65 | } 66 | 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /graph_node_dependency_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func compareGraphNodeDeps(d1 graphNodeDependency, d2 graphNodeDependency, t *testing.T) { 9 | 10 | if d1.Path != d2.Path { 11 | t.Errorf("compareGraphNodeDeps: paths don't match (%s,%s)", d1.Path, d2.Path) 12 | } 13 | 14 | if d1.Type != nil && d1.Type != d2.Type { 15 | t.Errorf("compareGraphNodeDeps: types don't match (%s,%s)", d1.Type, d2.Type) 16 | } 17 | } 18 | 19 | // findDependencies should populate a known number of deps 20 | func Test_FindDependencies(t *testing.T) { 21 | 22 | c := ConcreteType{} 23 | d := make([]graphNodeDependency, 0) 24 | s := emptyStructPath() 25 | 26 | if e := findDependencies(reflect.TypeOf(c), &d, &s); e != nil { 27 | t.Errorf("Unexpected error: %s", e) 28 | } 29 | 30 | eds := c.expectedDeps() 31 | 32 | if g, e := len(d), len(eds); g != e { 33 | t.Errorf("Expected %d deps in c, got %d", e, g) 34 | } 35 | 36 | for i, ed := range eds { 37 | compareGraphNodeDeps(ed, d[i], t) 38 | } 39 | } 40 | 41 | // findDependencies should populate a known number of deps 42 | func Test_FindDependenciesInEmbeddedStructs(t *testing.T) { 43 | 44 | c := HasEmbeddable{} 45 | d := make([]graphNodeDependency, 0) 46 | s := emptyStructPath() 47 | 48 | if e := findDependencies(reflect.TypeOf(c), &d, &s); e != nil { 49 | t.Errorf("Unexpected error: %s", e) 50 | } 51 | 52 | eds := c.expectedDeps() 53 | 54 | if g, e := len(d), len(eds); g != e { 55 | t.Errorf("Expected %d deps in c, got %d", e, g) 56 | } 57 | 58 | for i, ed := range eds { 59 | compareGraphNodeDeps(ed, d[i], t) 60 | } 61 | } 62 | 63 | // parseStructTag should return expected struct tags 64 | func Test_ParseStructTag(t *testing.T) { 65 | 66 | inps := []struct { 67 | tag reflect.StructTag 68 | expectedValues []string 69 | }{ 70 | { 71 | tag: "inj:\"\" someothertag:\"\"", 72 | }, 73 | { 74 | tag: "inj:\"some.datasource.path\"", 75 | expectedValues: []string{"some.datasource.path"}, 76 | }, 77 | { 78 | tag: "inj:\"one,two\"", 79 | expectedValues: []string{"one", "two"}, 80 | }, 81 | } 82 | 83 | for _, inp := range inps { 84 | d := parseStructTag(inp.tag) 85 | 86 | if !reflect.DeepEqual(inp.expectedValues, d.DatasourcePaths) { 87 | t.Errorf("inp.expectedValues != d.DatasourcePaths") 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /graph_node_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "testing" 4 | 5 | // New graph nodes should be different objects 6 | func Test_GraphNodeInitialisation1(t *testing.T) { 7 | 8 | gn1, gn2 := newGraphNode(), newGraphNode() 9 | 10 | if gn1 == gn2 { 11 | t.Errorf("gn1 == gn2") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /graph_provide.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "reflect" 4 | 5 | // Insert zero or more objected into the graph, and then attempt to wire up any unmet 6 | // dependencies in the graph. 7 | // 8 | // As explained in the main documentation (https://godoc.org/github.com/yourheropaul/inj), 9 | // a graph consists of what is essentially a map of types to values. If the same type is 10 | // provided twice with different values, the *last* value will be stored in the graph. 11 | func (g *graph) Provide(inputs ...interface{}) error { 12 | 13 | for _, input := range inputs { 14 | 15 | // Get reflection types 16 | mtype, stype := getReflectionTypes(input) 17 | 18 | // Assign a node in the graph 19 | n := g.add(mtype) 20 | 21 | // Populate the new node 22 | n.Object = input 23 | n.Type = mtype 24 | n.Value = reflect.ValueOf(input) 25 | n.Name = identifier(stype) 26 | 27 | // For structs, find dependencies 28 | if stype.Kind() == reflect.Struct { 29 | var basePath = emptyStructPath() 30 | findDependencies(stype, &n.Dependencies, &basePath) 31 | } 32 | } 33 | 34 | /////////////////////////////////////////////// 35 | // Plug everything together 36 | /////////////////////////////////////////////// 37 | 38 | g.connect() 39 | 40 | /////////////////////////////////////////////// 41 | // Store a list of types for speed later on 42 | /////////////////////////////////////////////// 43 | 44 | g.indexes = make([]reflect.Type, 0) 45 | 46 | for typ, _ := range g.nodes { 47 | g.indexes = append(g.indexes, typ) 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /graph_provide_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "testing" 4 | 5 | ////////////////////////////////////////// 6 | // Unit tests 7 | ////////////////////////////////////////// 8 | 9 | // Objects passed to a graph should appear in the graph and be populated 10 | func Test_ProvideHappyPath1(t *testing.T) { 11 | 12 | g, c := newGraph(), ConcreteType{} 13 | 14 | // Register providers (can include non-providers, which will then be wired up) 15 | if err := g.Provide( 16 | &c, 17 | &helloSayer{}, 18 | &goodbyeSayer{}, 19 | funcInstance, 20 | ichannel, 21 | DEFAULT_STRING, 22 | ); err != nil { 23 | t.Fatalf("Graph.Provide: %s", err) 24 | } 25 | 26 | // There should be exactly six nodes in the graph now 27 | if g, e := len(g.nodes), 6; g != e { 28 | t.Errorf("Got %d nodes, expected %d", g, e) 29 | } 30 | 31 | // Check the whole type 32 | assertConcreteValue(c, t) 33 | 34 | // Check index count 35 | if g, e := len(g.indexes), 6; g != e { 36 | t.Errorf("Expected %d indexes, got %d", g, e) 37 | } 38 | } 39 | 40 | // Multiple calls to Provide should have the same effect as a single call 41 | func Test_ProvideHappyPath2(t *testing.T) { 42 | 43 | g, c := newGraph(), ConcreteType{} 44 | 45 | vs := []interface{}{ 46 | &c, 47 | &helloSayer{}, 48 | &goodbyeSayer{}, 49 | funcInstance, 50 | ichannel, 51 | DEFAULT_STRING, 52 | } 53 | 54 | // Register providers individually 55 | for _, v := range vs { 56 | if err := g.Provide(v); err != nil { 57 | t.Fatalf("Graph.Provide: %s", err) 58 | } 59 | } 60 | 61 | // There should be exactly six nodes in the graph now 62 | if g, e := len(g.nodes), 6; g != e { 63 | t.Errorf("Got %d nodes, expected %d", g, e) 64 | } 65 | 66 | // Check the whole type 67 | assertConcreteValue(c, t) 68 | 69 | // Check index count 70 | if g, e := len(g.indexes), 6; g != e { 71 | t.Errorf("Expected %d indexes, got %d", g, e) 72 | } 73 | } 74 | 75 | // New dependency provisions shouldn't overwrite previously set ones 76 | func Test_ProvideOverride1(t *testing.T) { 77 | 78 | g, c := newGraph(), ConcreteType{} 79 | 80 | if err := g.Provide( 81 | &c, 82 | DEFAULT_STRING, 83 | ); err != nil { 84 | t.Fatalf("Graph.Provide: %s", err) 85 | } 86 | 87 | // Check index count 88 | if g, e := len(g.indexes), 2; g != e { 89 | t.Errorf("[1] Expected %d indexes, got %d", g, e) 90 | } 91 | 92 | // The graph now includes DEFAULT_STRING as its 93 | // only met dependency (missing dependencies covered 94 | // by graph_assert_test.go). Adding another string 95 | // to the graph shouldn't alter the value of the 96 | // concrete type. 97 | 98 | if err := g.Provide( 99 | &c, 100 | "some other string", 101 | ); err != nil { 102 | t.Fatalf("Graph.Provide: %s", err) 103 | } 104 | 105 | if g, e := c.String, DEFAULT_STRING; g == e { 106 | t.Errorf("Got %s, expected %s", g, e) 107 | } 108 | 109 | // Check index count 110 | if g, e := len(g.indexes), 2; g != e { 111 | t.Errorf("[1] Expected %d indexes, got %d", g, e) 112 | } 113 | } 114 | 115 | // Embedded structs should be parsed like any other 116 | func Test_EmbeddedStructProvision(t *testing.T) { 117 | 118 | g := newGraph() 119 | 120 | e, he := Embeddable{X: 10}, &HasEmbeddable{} 121 | 122 | if err := g.Provide(e, he); err != nil { 123 | t.Fatalf("g.Provide: %s", err.Error()) 124 | } 125 | 126 | if v, errs := g.Assert(); !v { 127 | t.Error(errs) 128 | } 129 | 130 | if g, e := e.X, he.X; g != e { 131 | t.Errorf("Values don't match: got %d, expected %d", g, e) 132 | } 133 | } 134 | 135 | // Self-referential dependencies shouldn't be assigned 136 | func Test_SelfReferencingDoesntHappen(t *testing.T) { 137 | 138 | g := newGraph() 139 | 140 | o := Ouroboros1{} 141 | 142 | g.Provide(&o) 143 | 144 | valid, errs := g.Assert() 145 | 146 | if valid { 147 | t.Fatalf("g.Assert() is valid when it shouldn't be") 148 | } 149 | 150 | // There are two deps that should have missed 151 | if g, e := len(errs), 2; g != e { 152 | t.Fatalf("Expected %d errors, got %d (%v)", e, g, errs) 153 | } 154 | } 155 | 156 | // Self-referential dependencies shouldn't impede proper injection 157 | func Test_SelfReferencingShouldntCircumentInjection(t *testing.T) { 158 | 159 | g := newGraph() 160 | 161 | o1 := Ouroboros1{V: 1} 162 | o2 := Ouroboros2{V: 2} 163 | 164 | g.Provide(&o1, &o2) 165 | 166 | valid, errs := g.Assert() 167 | 168 | if !valid { 169 | t.Fatalf("g.Assert() is not valid when it should be (%v)", errs) 170 | } 171 | 172 | // The values should now be 'crossed' 173 | if o1.A.Value() != o1.B.Value() { 174 | t.Errorf("o1.A != o1.B") 175 | } 176 | 177 | if o1.A.Value() != o2.Value() { 178 | t.Errorf("o1.B and B aren't equal to o2") 179 | } 180 | 181 | if o2.A.Value() != o2.B.Value() { 182 | t.Errorf("o2.A != o2.B") 183 | } 184 | 185 | if o2.A.Value() != o1.Value() { 186 | t.Errorf("o2.B and B aren't equal to o1") 187 | } 188 | } 189 | 190 | // Self-referential prevention must extend to embedding 191 | func Test_EmbeddedSelfReferencingDoesntHappen(t *testing.T) { 192 | 193 | g := newGraph() 194 | 195 | o := Ouroboros4{} 196 | 197 | g.Provide(&o) 198 | 199 | valid, errs := g.Assert() 200 | 201 | if valid { 202 | t.Fatalf("g.Assert() is valid when it shouldn't be") 203 | } 204 | 205 | // There is one dep that should have missed 206 | if g, e := len(errs), 1; g != e { 207 | t.Fatalf("Expected %d error, got %d (%v)", e, g, errs) 208 | } 209 | } 210 | 211 | // Self-referential dependencies shouldn't impede proper injection 212 | func Test_EmbeddedSelfReferencingShouldntCircumentInjection(t *testing.T) { 213 | 214 | g := newGraph() 215 | 216 | o1 := Ouroboros3{V: 1} 217 | o2 := Ouroboros4{V: 2} 218 | 219 | g.Provide(o1, &o2) 220 | 221 | valid, errs := g.Assert() 222 | 223 | if !valid { 224 | t.Fatalf("g.Assert() is not valid when it should be (%v)", errs) 225 | } 226 | 227 | // The value should now be assigned 228 | if o2.Ouroboros3.Value() != o1.Value() { 229 | t.Errorf("o2.A != o2.B") 230 | } 231 | } 232 | 233 | ////////////////////////////////////////// 234 | // Benchmark tests 235 | ////////////////////////////////////////// 236 | 237 | func BenchmarkComplexProvide(b *testing.B) { 238 | 239 | for n := 0; n < b.N; n++ { 240 | NewGraph( 241 | &ConcreteType{}, 242 | &helloSayer{}, 243 | &goodbyeSayer{}, 244 | funcInstance, 245 | ichannel, 246 | DEFAULT_STRING, 247 | ) 248 | } 249 | } 250 | 251 | func BenchmarkManyProvisions(b *testing.B) { 252 | 253 | for n := 0; n < b.N; n++ { 254 | g := NewGraph() 255 | g.Provide(&ConcreteType{}) 256 | g.Provide(&helloSayer{}) 257 | g.Provide(&goodbyeSayer{}) 258 | g.Provide(funcInstance) 259 | g.Provide(ichannel) 260 | g.Provide(DEFAULT_STRING) 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /graph_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func newGraph() *graph { 9 | return NewGraph().(*graph) 10 | } 11 | 12 | func assertNoGraphErrors(t *testing.T, g *graph) { 13 | 14 | if len(g.errors) != 0 { 15 | t.Fatalf("Graph was initialised with errors > 0") 16 | } 17 | 18 | if g.unmetDependency != 0 { 19 | t.Fatalf("Graph was initialised with UnmetDependencies > 0") 20 | } 21 | } 22 | 23 | // New graphs should be different objects 24 | func Test_GraphInitialisation1(t *testing.T) { 25 | 26 | g1, g2 := NewGraph(), NewGraph() 27 | 28 | if g1 == g2 { 29 | t.Errorf("g1 == g2") 30 | } 31 | } 32 | 33 | // Initial graph state should be zero values 34 | func Test_GraphInitialisation2(t *testing.T) { 35 | 36 | g := newGraph() 37 | 38 | if len(g.errors) != 0 { 39 | t.Errorf("Graph was initialised with errors > 0") 40 | } 41 | 42 | if g.unmetDependency != 0 { 43 | t.Errorf("Graph was initialised with UnmetDependencies > 0") 44 | } 45 | } 46 | 47 | // Added nodes should be in the graph object 48 | func Test_GraphNodeAddition(t *testing.T) { 49 | 50 | g := newGraph() 51 | typ := reflect.TypeOf(0) 52 | 53 | if len(g.nodes) != 0 { 54 | t.Errorf("Graph was initialised with nodes > 0") 55 | } 56 | 57 | n := g.add(typ) 58 | 59 | if n == nil { 60 | t.Errorf("New graph node is nil") 61 | } 62 | 63 | if len(g.nodes) != 1 { 64 | t.Errorf("New graph node count != 1") 65 | } 66 | 67 | for typ2, node := range g.nodes { 68 | 69 | if typ2 == typ && node != n { 70 | t.Errorf("Expected typ to equate to node") 71 | } 72 | 73 | if node == n && typ2 != typ { 74 | t.Errorf("Expected node to equate to typ") 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/yourheropaul/inj.svg?branch=master)](https://travis-ci.org/yourheropaul/inj) [![Coverage Status](https://coveralls.io/repos/yourheropaul/inj/badge.svg?branch=master&service=github)](https://coveralls.io/github/yourheropaul/inj?branch=master) [![GoDoc](https://godoc.org/github.com/yourheropaul/inj?status.svg)](https://godoc.org/github.com/yourheropaul/inj) [![Example](https://img.shields.io/badge/code-example-2a988f.svg)](https://github.com/yourheropaul/inj/tree/master/example) [![YHP](https://img.shields.io/badge/author-YHP-f08332.svg)](http://yhp.io) [![Slashie](https://img.shields.io/badge/ridiculously-goodlooking-736caf.svg)](http://yhp.io) 2 | ====== 3 | 4 | Are you troubled by dependency configuration issues in the middle of the night? Do you experience feelings of dread at the code boundaries of your application or during functional testing? Have you or your team ever used a global variable, exported package-level object or a build constraint hack? If the answer is *yes*, then don't wait another minute. Pick up your terminal and `go get github.com/yourheropaul/inj` today. 5 | 6 | Inject yourself before you wreck yourself. 7 | 8 | ### What *is* this thing? 9 | 10 | `inj` provides reflection-based dependency injection for Go structs and functions. Some parts of it will be familiar to anyone who's ever used [facebookgo/inject](https://github.com/facebookgo/inject); others bear a passing similarity to [dependency injection in Angular.js](https://docs.angularjs.org/guide/di). It's designed for medium to large applications, but it works just fine for small apps too. It's especially useful if your project is is BDD/TDD-orientated. 11 | 12 | ### Dependency injection is boring and hard to maintain. I want something that works out of the box. 13 | 14 | `inj` may well be for you. A good, idiomatic Go package is a simple, clearly laid out, well-tested Go package – and `inj` is, or aspires to be, all of those things. It works well and it works intuitively. There's only one, optional configuration option (whether or not to use a global variable inside the package) and there's very little to think about once you're set up. 15 | 16 | ### How do I use it? 17 | 18 | A simple and unrealistically trivial example is this: 19 | 20 | ``` 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | 26 | "github.com/yourheropaul/inj" 27 | ) 28 | 29 | type ServerConfig struct { 30 | Port int `inj:""` 31 | Host string `inj:""` 32 | } 33 | 34 | func main() { 35 | config := ServerConfig{} 36 | inj.Provide(&config, 6060, "localhost") 37 | 38 | // The struct fields have now been set by the graph, and 39 | // this will print "localhost:6060" 40 | fmt.Printf("%s:%d", config.Host, config.Port) 41 | } 42 | ``` 43 | There's a full explanation for this basic example in the [Godoc](https://godoc.org/github.com/yourheropaul/inj). 44 | Obviously this example is trivial in the extreme, and you'd probably never use the the package in that way. The easiest way to understand 45 | `inj` for real-world applications is to refer to the [example application](https://github.com/yourheropaul/inj/tree/master/example) in this repository. The API is small, and everything in the core API is demonstrated there. 46 | ### Dependency injection is great and everything, but I really want to be able to pull data directly from external services, not just the object graph. 47 | 48 | You mean you want to read from a JSON or TOML config file, and inject the values into Go objects directly? Maybe you'd like to pull values from a DynamoDB instance and insert them into Go struct instances with almost zero code overhead? 49 | 50 | That's what's `inj` is designed for! And what's more, intrepid programmer [Adrian Duke](http://adeduke.com/) has already done the leg work for you in his fantastic [configr](https://github.com/adrianduke/configr) package – see his readme for brief instructions. 51 | 52 | ### I want absolutely, positively no globals in my application. None. Can I do that with this package? 53 | 54 | Of course, and it couldn't be easier! Just compile your application with the tag `noglobals`, and the package-level API functions (including the one package-level variable they use) won't be included. You can create a new graph for your application by calling `inj.NewGraph()`, which has the same functional interface as the package API. 55 | 56 | ### This whole thing sounds too useful to be true 57 | 58 | I appreciate your skepticism, so let's gather some data. There are two things you need to be aware of when using `inj`. 59 | 60 | The first is that the application graph is one dimensional and indexed by type. That means you can't call `inj.Provide(someIntValue,someOtherIntValue)` and expect both integers to be in the graph – the second will override the first. Other dependency injection approaches allow for named and private depdendencies, but that has been sacrified here so that `inj.Inject()` could exist in a consistent way. When there's a choice, `inj` errs on the side of simplicity, consistency and idiomatic implementation over complexity and magic. 61 | 62 | The second consideration is execution speed. Obviously, calling `inj.Inject(fn)` is slower than calling `fn()` directly. In Go 1.4, with a medium-sized graph, it takes about 350 times longer to execute the call; in Go 1.5 rc1, it's about 240 times. If those numbers seem high, it's because they are. The impact on an application is measurable, but for most purposes negligible. 63 | 64 | If the average execution time of a pure Go function is around 4 nanoseconds (as it is in my tests) then the execution time of `inj.Inject()` will be somewhere between 900 and 1,400 nanoseconds. Or in more useful terms, 0.0014 milliseconds (which is 1.4e-6 seconds). If your application is built for speed, then you will need to be judicious in your use of `inj.Inect()`. Even if speed isn't a concern, it's generally not a good idea to nest injection calls, or put them in loops. 65 | 66 | Finally, `inj.Provide()` is fairly slow, but it's designed to executed at runtime only. There are benchmark tests in the package if you want to see how it performs on your system. 67 | 68 | ### But how do I use it? 69 | 70 | Seriously? I just explained that a minute ago. Maybe look at the [example application](https://github.com/yourheropaul/inj/tree/master/example) or the [Godoc](https://godoc.org/github.com/yourheropaul/inj). 71 | -------------------------------------------------------------------------------- /reflect_identifier.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "reflect" 4 | 5 | // A unique indentifier for a given reflect.Type 6 | func identifier(t reflect.Type) string { 7 | return t.PkgPath() + "/" + t.String() 8 | } 9 | -------------------------------------------------------------------------------- /reflect_identifier_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | // Different instances of the same type should have the same identifier. 9 | // This is a a bit like testing the reflect package, which is unncessary, 10 | // but since it's an assumption of the inj package it's a useful sanity 11 | // check. 12 | func Test_Identifier(t *testing.T) { 13 | 14 | if reflect.TypeOf(ConcreteType{}) != reflect.TypeOf(ConcreteType{}) { 15 | t.Errorf("Different instance of the same type don't match") 16 | } 17 | 18 | if reflect.TypeOf(&ConcreteType{}) == reflect.TypeOf(ConcreteType{}) { 19 | t.Errorf("Different instance of the different types match") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /reflect_type.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "reflect" 4 | 5 | func getReflectionTypes(input interface{}) (master_type, specific_type reflect.Type) { 6 | 7 | // Grab the master type 8 | master_type = reflect.TypeOf(input) 9 | 10 | // We need the specific type 11 | if master_type.Kind() == reflect.Ptr { 12 | specific_type = master_type.Elem() 13 | } else { 14 | specific_type = master_type 15 | } 16 | 17 | return 18 | } 19 | -------------------------------------------------------------------------------- /reflect_type_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | // If the main type is a pointer, the specific type should be the 9 | // indirect value 10 | func Test_GetReflectionTypes(t *testing.T) { 11 | 12 | m, i := &ConcreteType{}, ConcreteType{} 13 | tm, ti := reflect.TypeOf(m), reflect.TypeOf(i) 14 | rm, ri := getReflectionTypes(m) 15 | 16 | if rm != tm { 17 | t.Errorf("Master type doesn't match expected type") 18 | } 19 | 20 | if ri != ti { 21 | t.Errorf("Specific type doesn't match expected type") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /reflect_zero.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "reflect" 4 | 5 | func zero(v reflect.Value) bool { 6 | switch v.Kind() { 7 | case reflect.Interface, reflect.Ptr: 8 | return v.IsNil() 9 | default: 10 | return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /reflect_zero_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | // All empty types should return zero 9 | func Test_Zero(t *testing.T) { 10 | 11 | var zeroConcrete *ConcreteType 12 | var zeroNested *NestedType 13 | 14 | inputs := []interface{}{ 15 | ConcreteType{}, 16 | zeroConcrete, 17 | NestedType{}, 18 | zeroNested, 19 | } 20 | 21 | for i, input := range inputs { 22 | if !zero(reflect.ValueOf(input)) { 23 | t.Errorf("[%d] Value not zero", i) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /structpath.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "strings" 4 | 5 | type structPath string 6 | 7 | // Create a new, empty struct path 8 | func emptyStructPath() structPath { 9 | return "" 10 | } 11 | 12 | // Create a seprate struct path with a parent name 13 | func (s structPath) Branch(parent string) structPath { 14 | return s + structPath("."+parent) 15 | } 16 | 17 | // Remove the first element from the path, and return it 18 | // and the resultant path 19 | func (s structPath) Shift() (string, structPath) { 20 | 21 | if s.Empty() { 22 | return "", emptyStructPath() 23 | } 24 | 25 | parts := strings.Split(string(s), ".") 26 | 27 | // Make a new structpath 28 | var s2 structPath 29 | 30 | if len(parts) > 2 { 31 | s2 = structPath("." + strings.Join(parts[2:], ".")) 32 | } else { 33 | s2 = structPath(strings.Join(parts[2:], ".")) 34 | } 35 | 36 | return parts[1], s2 37 | } 38 | 39 | // Returns true if the path is empty 40 | func (s structPath) Empty() bool { 41 | 42 | if string(s) == "" { 43 | return true 44 | } 45 | 46 | return false 47 | } 48 | 49 | // Implementation of the Stringer interface 50 | func (s structPath) String() string { 51 | return string(s) 52 | } 53 | -------------------------------------------------------------------------------- /structpath_test.go: -------------------------------------------------------------------------------- 1 | package inj 2 | 3 | import "testing" 4 | 5 | // smptystructPath should return an empty struct path 6 | func Test_structPathA(t *testing.T) { 7 | 8 | if string(emptyStructPath()) != "" { 9 | t.Errorf("smptystructPath() didn't return empty string") 10 | } 11 | } 12 | 13 | // Branching should work intuitively 14 | func Test_structPathB(t *testing.T) { 15 | 16 | inputs := []struct { 17 | start structPath 18 | add string 19 | end structPath 20 | }{ 21 | {"", "Node", ".Node"}, 22 | {".Node", "Node", ".Node.Node"}, 23 | {".some thing", "else", ".some thing.else"}, 24 | } 25 | 26 | for i, input := range inputs { 27 | sp := structPath(input.start) 28 | sp = sp.Branch(input.add) 29 | 30 | if g, e := sp, input.end; g != e { 31 | t.Errorf("[%d] Got %s, expected %s", i, g, e) 32 | } 33 | } 34 | } 35 | 36 | // Shifting should work intuitively 37 | func Test_structPathC(t *testing.T) { 38 | 39 | inputs := []struct { 40 | start structPath 41 | str string 42 | sp structPath 43 | }{ 44 | {"", "", ""}, 45 | {".Node", "Node", ""}, 46 | {".Node.NodeTwo", "Node", ".NodeTwo"}, 47 | {".Parent.Node.NodeTwo", "Parent", ".Node.NodeTwo"}, 48 | } 49 | 50 | for i, input := range inputs { 51 | sp := structPath(input.start) 52 | s, sp2 := sp.Shift() 53 | 54 | if g, e := s, input.str; g != e { 55 | t.Errorf("[%d] Got string %s, expected %s", i, g, e) 56 | } 57 | 58 | if g, e := sp2, input.sp; g != e { 59 | t.Errorf("[%d] Got path %s, expected %s", i, g, e) 60 | } 61 | } 62 | } 63 | 64 | // Emptying a structpath should cause the Empty() func to 65 | // return true 66 | func Test_structPathD(t *testing.T) { 67 | sp := structPath("") 68 | 69 | if !sp.Empty() { 70 | t.Errorf("structPath wasn't empty when it should be") 71 | } 72 | } 73 | --------------------------------------------------------------------------------