Skip to content

Commit

Permalink
Merge f3b523d into 68fb397
Browse files Browse the repository at this point in the history
  • Loading branch information
anuptalwalkar committed May 25, 2017
2 parents 68fb397 + f3b523d commit 80754e7
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 45 deletions.
72 changes: 50 additions & 22 deletions container_test.go
Expand Up @@ -102,7 +102,7 @@ func TestResolve(t *testing.T) {
var p1 *Parent1
return c.Resolve(&p1)
},
"type *dig.Parent1 is not registered",
"",
},
}

Expand Down Expand Up @@ -319,8 +319,6 @@ func TestConstructorErrors(t *testing.T) {
var p1 *FlakyParent
err := c.Resolve(&p1)
if tt.wantErr != "" {
var registeredError *error
require.Error(t, c.Resolve(&registeredError), "type *error is not registered")
require.EqualError(t, err, tt.wantErr)
} else {
require.NoError(t, err)
Expand Down Expand Up @@ -394,7 +392,7 @@ func TestInvokeReturnedError(t *testing.T) {
return errors.New("oops")
})
var registeredError *error
require.Error(t, c.Resolve(&registeredError), "type *error is not registered")
require.NoError(t, c.Resolve(&registeredError), "unexpected error resolving unknown type")
require.Contains(t, err.Error(), "error invoking the function func() error: oops")

err = c.Invoke(func() (*Child1, error) {
Expand All @@ -403,22 +401,6 @@ func TestInvokeReturnedError(t *testing.T) {
assert.NoError(t, err)
}

func TestInvokeFailureUnresolvedDependencies(t *testing.T) {
t.Parallel()
c := New()

err := c.Provide(
NewParent1,
)
assert.NoError(t, err)

err = c.Invoke(func(p1 *Parent1) {})
require.Contains(t, err.Error(), "unable to resolve *dig.Parent1")

err = c.Invoke(func(p12 *Parent12) {})
require.Contains(t, err.Error(), "dependency of type *dig.Parent12 is not registered")
}

func TestProvide(t *testing.T) {
t.Parallel()
c := New()
Expand Down Expand Up @@ -514,7 +496,10 @@ func TestEmptyAfterReset(t *testing.T) {
var first *Grandchild1
require.NoError(t, c.Resolve(&first), "No error expected during first Resolve")
c.Reset()
require.Contains(t, c.Resolve(&first).Error(), "not registered")

first = nil
assert.NoError(t, c.Resolve(&first), "unexpected error resolving unknown type")
assert.Nil(t, first, "expected zero value after resolving unknown type")
}

func TestPanicConstructor(t *testing.T) {
Expand Down Expand Up @@ -551,10 +536,53 @@ func TestMultiObjectRegisterResolve(t *testing.T) {
require.NoError(t, c.Resolve(&third), "No error expected during first Resolve")

var errRegistered *error
require.Error(t, c.Resolve(&errRegistered), "type *error shouldn't be registered")
require.NoError(t, c.Resolve(&errRegistered), "unexpected error resolving unknown type")
require.Nil(t, errRegistered)

require.NotNil(t, first, "Child1 must have been registered")
require.NotNil(t, second, "Child2 must have been registered")
require.NotNil(t, third, "Child3 must have been registered")
}

func TestZeroValueOnInvoke(t *testing.T) {
t.Parallel()

type missing struct{}
type present struct{}

c := New()
c.Provide(func() *present {
return &present{}
})

var called int
assert.NoError(t, c.Invoke(func(m *missing) {
assert.Nil(t, m, "expected zero value for missing deps")
called++
}), "unexpected failure invoking with missing deps")

assert.NoError(t, c.Invoke(func(p *present, m *missing) {
assert.Nil(t, m, "expected zero value for missing dep")
assert.NotNil(t, p, "expected non-zero value for present dep")
called++
}), "unexpected failure invoking with a mix of present and missing deps")

assert.Equal(t, 2, called, "didn't run invokes")
}

func TestZeroValueOnProvide(t *testing.T) {
t.Parallel()

type missing struct{}
type provided struct{}

c := New()
assert.NoError(t, c.Provide(func(m *missing) (*provided, error) {
assert.Nil(t, m, "expected zero value for missing dep")
return &provided{}, nil
}), "unexpected failure providing with missing deps")

var p *provided
assert.NoError(t, c.Resolve(&p), "unexpected error resolving provided type")
assert.NotNil(t, p, "expected constructor to provide non-nil instance")
}
16 changes: 2 additions & 14 deletions internal/graph/graph.go
Expand Up @@ -57,7 +57,7 @@ func (g *Graph) Read(objType reflect.Type) (reflect.Value, error) {
// check if the type is a registered objNode
n, ok := g.nodes[objType]
if !ok {
return reflect.Zero(objType), fmt.Errorf("type %v is not registered", objType)
return reflect.Zero(objType), nil
}
v, err := n.value(g, objType)
if err != nil {
Expand Down Expand Up @@ -178,18 +178,6 @@ func (g *Graph) recursiveDetectCycles(n graphNode, l []string) error {
return nil
}

func (g *Graph) validateGraph(ct reflect.Type) (reflect.Value, error) {
for _, node := range g.nodes {
for _, dep := range node.dependencies() {
// check that the dependency is a registered objNode
if _, ok := g.nodes[dep]; !ok {
return reflect.Zero(ct), fmt.Errorf("%v dependency of type %v is not registered", ct, dep)
}
}
}
return reflect.Zero(ct), nil
}

// ConstructorArguments returns arguments in the provided constructor
func (g *Graph) ConstructorArguments(ctype reflect.Type) ([]reflect.Value, error) {
// find dependencies from the graph and place them in the args
Expand All @@ -204,7 +192,7 @@ func (g *Graph) ConstructorArguments(ctype reflect.Type) ([]reflect.Value, error
}
args[idx] = v
} else {
return nil, fmt.Errorf("%v dependency of type %v is not registered", ctype, arg)
args[idx] = reflect.Zero(arg)
}
}
return args, nil
Expand Down
16 changes: 15 additions & 1 deletion internal/graph/graph_test.go
Expand Up @@ -94,6 +94,20 @@ func TestResolvedArguments(t *testing.T) {
assert.Equal(t, reflect.TypeOf(values[1].Interface()).String(), "*graph.Parent12")
}

func Test_UnprovidedConstructorArguments(t *testing.T) {
t.Parallel()
g := NewGraph()

values, err := g.ConstructorArguments(reflect.TypeOf(constructor))
require.NoError(t, err)

assert.Equal(t, reflect.TypeOf(values[0].Interface()).String(), "*graph.Parent1")
assert.Equal(t, reflect.TypeOf(values[1].Interface()).String(), "*graph.Parent12")

assert.Equal(t, values[0], reflect.Zero(reflect.TypeOf(values[0].Interface())))
assert.Equal(t, values[1], reflect.Zero(reflect.TypeOf(values[1].Interface())))
}

func TestGraphString(t *testing.T) {
g := NewGraph()
p1 := &Parent1{
Expand Down Expand Up @@ -152,6 +166,6 @@ func TestMultiObjectRegisterResolve(t *testing.T) {

var errRegistered *error
v, err = g.Read(reflect.TypeOf(errRegistered))
require.Error(t, err, "type *error shouldn't be registered")
require.NoError(t, err, "shouldn't get errors resolving unknown types")
require.Nil(t, v.Interface())
}
8 changes: 0 additions & 8 deletions internal/graph/node.go
Expand Up @@ -97,14 +97,6 @@ func (n *funcNode) value(g *Graph, objType reflect.Type) (reflect.Value, error)
}

ct := reflect.TypeOf(n.constructor)

// check that all the dependencies have nodes present in the graph
// doesn't mean everything will go smoothly during resolve, but it
// drastically increases the chances that we're not missing something
if v, err := g.validateGraph(ct); err != nil {
return v, err
}

args, err := g.ConstructorArguments(ct)
if err != nil {
return reflect.Zero(objType), err
Expand Down
30 changes: 30 additions & 0 deletions vallie/vallie.go
@@ -0,0 +1,30 @@
// Package vallie stands in for a stand-alone validation library
// purely for discussion purposes.
//
// Assuming this is a desired approach, more fidelity will be added
// and vallie would be extracted into it's own repo
package vallie

import (
"reflect"

"github.com/pkg/errors"
)

var errZeroVal = errors.New("Object is a zero value")

// Validate accepts objects to be validated from constructor and returns an error
// if any of the provided object is a zero value object
//
// TODO: if Validate returns an error, how can we hook into the dig resolution
// cycle to produce a meaningful error message, i.e.
// "failed to resolve Type3, because non-optional Type2 is not registered"
func Validate(values ...interface{}) error {
for _, val := range values {
v := reflect.ValueOf(val)
if v == reflect.Zero(v.Type()) {
return errors.Wrapf(errZeroVal, "%v", v.Type())
}
}
return nil
}
35 changes: 35 additions & 0 deletions vallie/vallie_test.go
@@ -0,0 +1,35 @@
package vallie

import (
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

func TestValidation(t *testing.T) {
type nothing struct{}

tests := []struct {
desc string
input []interface{}
valid bool
}{
{"empty input", []interface{}{}, true},
{"nil pointer", []interface{}{(*nothing)(nil)}, false},
{"zero value", []interface{}{nothing{}}, false},
{"non-zero value", []interface{}{5}, true},
{"mixed input", []interface{}{5, http.DefaultServeMux}, true},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
assert.Equal(
t,
tt.valid,
Validate(tt.input...) == nil,
"unexpected validation result for %v", tt.input,
)
})
}
}

0 comments on commit 80754e7

Please sign in to comment.