Skip to content

Commit

Permalink
Merge branch 'feature/data-sources' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul M Fox committed Jan 1, 2016
2 parents 8b3129a + 69802b0 commit ad14a22
Show file tree
Hide file tree
Showing 15 changed files with 432 additions and 32 deletions.
8 changes: 0 additions & 8 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,37 +56,30 @@ func (c ConcreteType) expectedDeps() []graphNodeDependency {

d := []graphNodeDependency{
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.Hello)),
Path: ".Hello",
Type: reflect.TypeOf(c.Hello),
},
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.Goodbye)),
Path: ".Goodbye",
Type: reflect.TypeOf(c.Goodbye),
},
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.Stringer)),
Path: ".Stringer",
Type: reflect.TypeOf(c.Stringer),
},
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.Channel)),
Path: ".Channel",
Type: reflect.TypeOf(c.Channel),
},
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.String)),
Path: ".String",
Type: reflect.TypeOf(c.String),
},
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.Nested.Hello)),
Path: ".Nested.Hello",
Type: reflect.TypeOf(c.Nested.Hello),
},
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.Nested.Goodbye)),
Path: ".Nested.Goodbye",
Type: reflect.TypeOf(c.Nested.Goodbye),
},
Expand All @@ -108,7 +101,6 @@ func (c HasEmbeddable) expectedDeps() []graphNodeDependency {

return []graphNodeDependency{
graphNodeDependency{
Name: identifier(reflect.TypeOf(&c.Embeddable)),
Path: ".Embeddable",
Type: reflect.TypeOf(c.Embeddable),
},
Expand Down
33 changes: 33 additions & 0 deletions datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package inj

// A Datasource is any external interface that provides an interface given a
// string key. It's split into two fundamental components: the DatasourceReader
// and the DatasourceWriter. For the purposes of automatic configuration via
// dependency injection, a DatasourceReader is all that's required.
//
// Datasource paths are supplied by values in structfield tags (which are empty
// for normal inj operation). Here's an example of a struct that will try to
// fulfil its dependencies from a datasource:
//
// type MyStruct stuct {
// SomeIntVal int `inj:"some.path.in.a.datasource"`
// }
//
// When trying to connect the dependency on an instance of the struct above, inj will
// poll any available DatasourceReaders in the graph by calling their Read() function
// with the string argument "some.path.in.a.datasource". If the function doesn't return
// an error, then the resultant value will be used for the dependency injection.
//
// A struct tag may contain multiple datasource paths, separated by commas. The paths
// will be polled in order of their appearance in the code, and the value from the first
// DatasourceReader that doesn't return an error on its Read() function will be used to
// meet the dependency.
//
// DatasourceWriters function in a similar way: when a value is set on an instance of a
// struct via inj's dependency injection, any associated DatasourceWriters' Write() functions
// are called for each datasource path.
//
type Datasource interface {
DatasourceReader
DatasourceWriter
}
7 changes: 7 additions & 0 deletions datasource_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package inj

// A datasource reader provides an object from a datasource, identified by a
// given string key. Refer to the documentation for the Datasource interface for more information.
type DatasourceReader interface {
Read(string) (interface{}, error)
}
62 changes: 62 additions & 0 deletions datasource_reader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package inj

import (
"fmt"
"testing"
)

///////////////////////////////////////////////////////////////
// A mock DatasourceReader implementation
///////////////////////////////////////////////////////////////

type MockDatasourceReader struct {
stack map[string]interface{}
}

func NewMockDatasourceReader(data ...map[string]interface{}) *MockDatasourceReader {

d := &MockDatasourceReader{}

d.stack = make(map[string]interface{})

for _, datum := range data {
for k, v := range datum {
d.stack[k] = v
}
}

return d
}

func (d *MockDatasourceReader) Read(key string) (interface{}, error) {

if value, exists := d.stack[key]; exists {
return value, nil
}

return nil, fmt.Errorf("No stack entry for '%s'", key)
}

///////////////////////////////////////////////////////////////
// Unit tests for graph implementation
///////////////////////////////////////////////////////////////

func Test_TheDatasourceReaderWritesToTheDepdendency(t *testing.T) {

dep := dataSourceDep{}
ds := newMockDataSourceWithValues(t)
g := NewGraph()

g.AddDatasource(ds)
g.Provide(&dep)

assertNoGraphErrors(t, g)

if g, e := dep.StringValue, DEFAULT_STRING; g != e {
t.Errorf("Expected string '%s', got '%s'", e, g)
}

if dep.FuncValue == nil {
t.Errorf("Didn't get expected function instance")
}
}
69 changes: 69 additions & 0 deletions datasource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package inj

import (
"fmt"
"testing"
)

///////////////////////////////////////////////////////////////
// A mock DatasourceReader and DatasourceWriter implementation
///////////////////////////////////////////////////////////////

type MockDatasource struct {
stack map[string]interface{}
}

func NewMockDatasource() Datasource {

d := &MockDatasource{}

d.stack = make(map[string]interface{})

return d
}

func (d *MockDatasource) Read(key string) (interface{}, error) {

if value, exists := d.stack[key]; exists {
return value, nil
}

return nil, fmt.Errorf("No stack entry for '%s'", key)
}

func (d *MockDatasource) Write(key string, value interface{}) error {

d.stack[key] = value

return nil
}

///////////////////////////////////////////////////////////////
// A mock specific dependency implementation for testing
///////////////////////////////////////////////////////////////

type dataSourceDep struct {
StringValue string `inj:"datasource.string"`
FuncValue FuncType `inj:"datasource.func"`
IntValue int `inj:"datasource.int"`
}

func newMockDataSourceWithValues(t *testing.T) Datasource {

d := NewMockDatasource()

if e := d.Write("datasource.string", DEFAULT_STRING); e != nil {
t.Fatalf("newMockDataSourceWithValues: Datasource.Write: %s", e)
}

if e := d.Write("datasource.func", funcInstance); e != nil {
t.Fatalf("newMockDataSourceWithValues: Datasource.Write: %s", e)
}

// Try and integer value expressed as a float
if e := d.Write("datasource.int", 16.01); e != nil {
t.Fatalf("newMockDataSourceWithValues: Datasource.Write: %s", e)
}

return d
}
7 changes: 7 additions & 0 deletions datasource_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package inj

// A datasource reader sends an object to a datasource, identified by a
// given string key. Refer to the documentation for the Datasource interface for more information.
type DatasourceWriter interface {
Write(string, interface{}) error
}
105 changes: 105 additions & 0 deletions datasource_writer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package inj

import "testing"

///////////////////////////////////////////////////////////////
// A mock DatasourceWriter implementation
///////////////////////////////////////////////////////////////

type MockDatasourceWriter struct {
stack map[string]interface{}
}

func NewMockDatasourceWriter(data ...map[string]interface{}) *MockDatasourceWriter {

d := &MockDatasourceWriter{}

d.stack = make(map[string]interface{})

for _, datum := range data {
for k, v := range datum {
d.stack[k] = v
}
}

return d
}

func (d *MockDatasourceWriter) Write(key string, value interface{}) error {

d.stack[key] = value

return nil
}

func (d *MockDatasourceWriter) Assert(t *testing.T, key string, value interface{}) {

v, exists := d.stack[key]

if !exists {
t.Fatalf("MockDatasourceWriter.Assert: Key '%s' doesn't exist", key)
}

if v != value {
t.Fatalf("MockDatasourceWriter.Assert: key %s doesn't match (%v, %v)", key, v, value)
}
}

func (d *MockDatasourceWriter) AssertMap(t *testing.T, data map[string]interface{}) {

for k, v := range data {
d.Assert(t, k, v)
}
}

///////////////////////////////////////////////////////////////
// Unit tests for graph implementation
///////////////////////////////////////////////////////////////

type datasourceWriterDep struct {
IntVal int `inj:"datasource.writer.int"`
StringVal string `inj:"datasource.writer.string"`
}

func Test_DatasourceReaderWriterLoopInGraph(t *testing.T) {

expected_values := map[string]interface{}{
"datasource.writer.int": 10,
"datasource.writer.string": DEFAULT_STRING,
}

reader := NewMockDatasourceReader(expected_values)
writer := NewMockDatasourceWriter()
dep := datasourceWriterDep{}
g := NewGraph()

g.AddDatasource(reader, writer)
g.Provide(&dep)

assertNoGraphErrors(t, g)

writer.AssertMap(t, expected_values)
}

func Test_DatasourceWriterWritesWithoutAReader(t *testing.T) {

expected_values := map[string]interface{}{
"datasource.writer.int": 10,
"datasource.writer.string": DEFAULT_STRING,
}

writer := NewMockDatasourceWriter()
dep := datasourceWriterDep{}
g := NewGraph()

g.AddDatasource(writer)
g.Provide(
expected_values["datasource.writer.int"],
expected_values["datasource.writer.string"],
&dep,
)

assertNoGraphErrors(t, g)

writer.AssertMap(t, expected_values)
}
4 changes: 4 additions & 0 deletions graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type Graph struct {
UnmetDependencies int
Errors []string
indexes []reflect.Type
datasourceReaders []DatasourceReader
datasourceWriters []DatasourceWriter
}

// Create a new instance of a graph with allocated memory
Expand All @@ -19,6 +21,8 @@ func NewGraph(providers ...interface{}) (g *Graph) {

g.nodes = make(nodeMap)
g.Errors = make([]string, 0)
g.datasourceReaders = make([]DatasourceReader, 0)
g.datasourceWriters = make([]DatasourceWriter, 0)

g.Provide(providers...)

Expand Down
Loading

0 comments on commit ad14a22

Please sign in to comment.