Skip to content
This repository has been archived by the owner on Mar 22, 2019. It is now read-only.

Added method Resolve and ResolveByName #21

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
language: go

go:
- 1.6
- "1.10"

before_install:
- go get -v github.com/golang/lint/golint
- go get -v golang.org/x/lint/golint
- go get -v golang.org/x/tools/cmd/cover

install:
Expand All @@ -16,4 +16,4 @@ script:
- go vet inject.go
- $HOME/gopath/bin/golint inject.go
- go test -cpu=2 -race -v ./...
- go test -cpu=2 -covermode=atomic ./...
- go test -cpu=2 -covermode=atomic ./...
2 changes: 1 addition & 1 deletion common_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package inject_test
package goject_test

import "testing"

Expand Down
20 changes: 10 additions & 10 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package inject_test
package goject_test

import (
"fmt"
"net/http"
"os"

"github.com/facebookgo/inject"
"github.com/imaramos/goject"
)

// Our Awesome Application renders a message using two APIs in our fake
Expand All @@ -31,7 +31,7 @@ func (a *HomePlanetRenderApp) Render(id uint64) string {
type NameAPI struct {
// Here and below in PlanetAPI we add the tag to an interface value.
// This value cannot automatically be created (by definition) and
// hence must be explicitly provided to the graph.
// hence must be explicitly provided to the Container.

HTTPTransport http.RoundTripper `inject:""`
}
Expand All @@ -52,11 +52,11 @@ func (p *PlanetAPI) Planet(id uint64) string {
}

func Example() {
// Typically an application will have exactly one object graph, and
// Typically an application will have exactly one object Container, and
// you will create it and use it within a main function:
var g inject.Graph
var g goject.Container

// We provide our graph two "seed" objects, one our empty
// We provide our Container two "seed" objects, one our empty
// HomePlanetRenderApp instance which we're hoping to get filled out,
// and second our DefaultTransport to satisfy our HTTPTransport
// dependency. We have to provide the DefaultTransport because the
Expand All @@ -66,8 +66,8 @@ func Example() {
// the dependency since it implements the interface:
var a HomePlanetRenderApp
err := g.Provide(
&inject.Object{Value: &a},
&inject.Object{Value: http.DefaultTransport},
&goject.Object{Value: &a},
&goject.Object{Value: http.DefaultTransport},
)
if err != nil {
fmt.Fprintln(os.Stderr, err)
Expand All @@ -83,9 +83,9 @@ func Example() {
}

// There is a shorthand API for the simple case which combines the
// three calls above is available as inject.Populate:
// three calls above is available as goject.Populate:
//
// inject.Populate(&a, http.DefaultTransport)
// goject.Populate(&a, http.DefaultTransport)
//
// The above API shows the underlying API which also allows the use of
// named instances for more complex scenarios.
Expand Down
95 changes: 78 additions & 17 deletions inject.go → goject.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Package inject provides a reflect based injector. A large application built
// package goject provides a reflect based injector. A large application built
// with dependency injection in mind will typically involve the boring work of
// setting up the object graph. This library attempts to take care of this
// setting up the object Container. This library attempts to take care of this
// boring work by creating and connecting the various objects. Its use involves
// you seeding the object graph with some (possibly incomplete) objects, where
// you seeding the object Container with some (possibly incomplete) objects, where
// the underlying types have been tagged for injection. Given this, the
// library will populate the objects creating new ones as necessary. It uses
// singletons by default, supports optional private instances as well as named
Expand All @@ -23,10 +23,11 @@
// of the associated type. The second triggers creation of a private instance
// for the associated type. Finally the last form is asking for a named
// dependency called "dev logger".
package inject
package goject

import (
"bytes"
"errors"
"fmt"
"math/rand"
"reflect"
Expand All @@ -35,15 +36,15 @@ import (
)

// Logger allows for simple logging as inject traverses and populates the
// object graph.
// object Container.
type Logger interface {
Debugf(format string, v ...interface{})
}

// Populate is a short-hand for populating a graph with the given incomplete
// Populate is a short-hand for populating a Container with the given incomplete
// object values.
func Populate(values ...interface{}) error {
var g Graph
var g Container
for _, v := range values {
if err := g.Provide(&Object{Value: v}); err != nil {
return err
Expand All @@ -52,7 +53,7 @@ func Populate(values ...interface{}) error {
return g.Populate()
}

// An Object in the Graph.
// An Object in the Container.
type Object struct {
Value interface{}
Name string // Optional
Expand Down Expand Up @@ -82,17 +83,17 @@ func (o *Object) addDep(field string, dep *Object) {
o.Fields[field] = dep
}

// The Graph of Objects.
type Graph struct {
// The Container of Objects.
type Container struct {
Logger Logger // Optional, will trigger debug logging.
unnamed []*Object
unnamedType map[reflect.Type]bool
named map[string]*Object
}

// Provide objects to the Graph. The Object documentation describes
// Provide objects to the Container. The Object documentation describes
// the impact of various fields.
func (g *Graph) Provide(objects ...*Object) error {
func (g *Container) Provide(objects ...*Object) error {
for _, o := range objects {
o.reflectType = reflect.TypeOf(o.Value)
o.reflectValue = reflect.ValueOf(o.Value)
Expand Down Expand Up @@ -153,7 +154,7 @@ func (g *Graph) Provide(objects ...*Object) error {
}

// Populate the incomplete Objects.
func (g *Graph) Populate() error {
func (g *Container) Populate() error {
for _, o := range g.named {
if o.Complete {
continue
Expand All @@ -165,7 +166,7 @@ func (g *Graph) Populate() error {
}

// We append and modify our slice as we go along, so we don't use a standard
// range loop, and do a single pass thru each object in our graph.
// range loop, and do a single pass thru each object in our Container.
i := 0
for {
if i == len(g.unnamed) {
Expand Down Expand Up @@ -209,7 +210,41 @@ func (g *Graph) Populate() error {
return nil
}

func (g *Graph) populateExplicit(o *Object) error {
// Resolve sets the value of dst to an assignable instance
func (g *Container) Resolve(dst interface{}) error {
return g.resolve(dst, g.unnamed...)
}

// ResolveByName sets the value of dst to an assignable instance with the provided name
func (g *Container) ResolveByName(dst interface{}, name string) error {
object, ok := g.named[name]

if !ok {
return fmt.Errorf("No provided object with the name: %s", name)
}

return g.resolve(dst, object)
}

func (g *Container) resolve(dst interface{}, objects ...*Object) error {
dstPtrValue := reflect.ValueOf(dst)

if !isPointer(dstPtrValue) {
return errors.New("dst its not a pointer")
}

for _, object := range objects {
objectValue := reflect.ValueOf(object.Value)

if copyValueIfAssignable(objectValue, dstPtrValue) {
return nil
}
}

return errors.New("No provided object is assignable to dst")
}

func (g *Container) populateExplicit(o *Object) error {
// Ignore named value types.
if o.Name != "" && !isStructPtr(o.reflectType) {
return nil
Expand Down Expand Up @@ -411,7 +446,7 @@ StructLoop:
return nil
}

func (g *Graph) populateUnnamedInterface(o *Object) error {
func (g *Container) populateUnnamedInterface(o *Object) error {
// Ignore named value types.
if o.Name != "" && !isStructPtr(o.reflectType) {
return nil
Expand Down Expand Up @@ -510,7 +545,7 @@ func (g *Graph) populateUnnamedInterface(o *Object) error {

// Objects returns all known objects, named as well as unnamed. The returned
// elements are not in a stable order.
func (g *Graph) Objects() []*Object {
func (g *Container) Objects() []*Object {
objects := make([]*Object, 0, len(g.unnamed)+len(g.named))
for _, o := range g.unnamed {
if !o.embedded {
Expand Down Expand Up @@ -566,6 +601,10 @@ func isStructPtr(t reflect.Type) bool {
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}

func isPointer(value reflect.Value) bool {
return value.Type().Kind() == reflect.Ptr
}

func isNilOrZero(v reflect.Value, t reflect.Type) bool {
switch v.Kind() {
default:
Expand All @@ -574,3 +613,25 @@ func isNilOrZero(v reflect.Value, t reflect.Type) bool {
return v.IsNil()
}
}

func copyValueIfAssignable(src, destPtr reflect.Value) bool {
dstValue := destPtr.Elem()

srcType := src.Type()

switch dstValue.Kind() {
case reflect.Interface:
if srcType.AssignableTo(dstValue.Type()) {
dstValue.Set(src)
return true
}

case reflect.Struct:
if srcType.AssignableTo(destPtr.Type()) {
dstValue.Set(src.Elem())
return true
}
}

return false
}
Loading