mirror of https://github.com/grafana/grafana
Enable Grafana extensions at build time. (#11752)
* extensions: import and build * bus: use predefined error * enterprise: build script for enterprise packages * poc: auto registering services and dependency injection (cherry picked from commit b5b1ef875f905473af41e49f8071cb9028edc845) * poc: backend services registry progress (cherry picked from commit 97be69725881241bfbf1e7adf0e66801d6b0af3d) * poc: minor update (cherry picked from commit 03d7a6888b81403f458b94305792e075568f0794) * ioc: introduce manuel ioc * enterprise: adds setting for enterprise * build: test and build specific ee commit * cleanup: test testing code * removes example hello servicepull/11755/head
parent
afce0feb05
commit
28f7b6dad1
@ -0,0 +1,3 @@ |
|||||||
|
package extensions |
||||||
|
|
||||||
|
import _ "github.com/pkg/errors" |
@ -0,0 +1,33 @@ |
|||||||
|
package registry |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
) |
||||||
|
|
||||||
|
var services = []Service{} |
||||||
|
|
||||||
|
func RegisterService(srv Service) { |
||||||
|
services = append(services, srv) |
||||||
|
} |
||||||
|
|
||||||
|
func GetServices() []Service { |
||||||
|
return services |
||||||
|
} |
||||||
|
|
||||||
|
type Service interface { |
||||||
|
Init() error |
||||||
|
} |
||||||
|
|
||||||
|
// Useful for alerting service
|
||||||
|
type CanBeDisabled interface { |
||||||
|
IsDisabled() bool |
||||||
|
} |
||||||
|
|
||||||
|
type BackgroundService interface { |
||||||
|
Run(ctx context.Context) error |
||||||
|
} |
||||||
|
|
||||||
|
func IsDisabled(srv Service) bool { |
||||||
|
canBeDisabled, ok := srv.(CanBeDisabled) |
||||||
|
return ok && canBeDisabled.IsDisabled() |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# |
||||||
|
# This script is executed from within the container. |
||||||
|
# |
||||||
|
|
||||||
|
echo "building enterprise version" |
||||||
|
|
||||||
|
GOPATH=/go |
||||||
|
REPO_PATH=$GOPATH/src/github.com/grafana/grafana |
||||||
|
|
||||||
|
|
||||||
|
cd /go/src/github.com/grafana/grafana |
||||||
|
echo "current dir: $(pwd)" |
||||||
|
|
||||||
|
cd .. |
||||||
|
git clone -b ee_build --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10 |
||||||
|
cd grafana-enterprise |
||||||
|
git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f |
||||||
|
./build.sh |
||||||
|
|
||||||
|
cd ../grafana |
||||||
|
|
||||||
|
function exit_if_fail { |
||||||
|
command=$@ |
||||||
|
echo "Executing '$command'" |
||||||
|
eval $command |
||||||
|
rc=$? |
||||||
|
if [ $rc -ne 0 ]; then |
||||||
|
echo "'$command' returned $rc." |
||||||
|
exit $rc |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
exit_if_fail go test ./pkg/extensions/... |
||||||
|
|
||||||
|
|
||||||
|
if [ "$CIRCLE_TAG" != "" ]; then |
||||||
|
echo "Building a release from tag $ls" |
||||||
|
go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true -includeBuildNumber=false build |
||||||
|
else |
||||||
|
echo "Building incremental build for $CIRCLE_BRANCH" |
||||||
|
go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true build |
||||||
|
fi |
||||||
|
|
||||||
|
yarn install --pure-lockfile --no-progress |
||||||
|
|
||||||
|
source /etc/profile.d/rvm.sh |
||||||
|
|
||||||
|
echo "current dir: $(pwd)" |
||||||
|
|
||||||
|
if [ "$CIRCLE_TAG" != "" ]; then |
||||||
|
echo "Packaging a release from tag $CIRCLE_TAG" |
||||||
|
go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true -includeBuildNumber=false package latest |
||||||
|
else |
||||||
|
echo "Packaging incremental build for $CIRCLE_BRANCH" |
||||||
|
go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true package latest |
||||||
|
fi |
@ -0,0 +1,576 @@ |
|||||||
|
// Package inject 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
|
||||||
|
// boring work by creating and connecting the various objects. Its use involves
|
||||||
|
// you seeding the object graph 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
|
||||||
|
// instances.
|
||||||
|
//
|
||||||
|
// It works using Go's reflection package and is inherently limited in what it
|
||||||
|
// can do as opposed to a code-gen system with respect to private fields.
|
||||||
|
//
|
||||||
|
// The usage pattern for the library involves struct tags. It requires the tag
|
||||||
|
// format used by the various standard libraries, like json, xml etc. It
|
||||||
|
// involves tags in one of the three forms below:
|
||||||
|
//
|
||||||
|
// `inject:""`
|
||||||
|
// `inject:"private"`
|
||||||
|
// `inject:"dev logger"`
|
||||||
|
//
|
||||||
|
// The first no value syntax is for the common case of a singleton dependency
|
||||||
|
// 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 |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"math/rand" |
||||||
|
"reflect" |
||||||
|
|
||||||
|
"github.com/facebookgo/structtag" |
||||||
|
) |
||||||
|
|
||||||
|
// Logger allows for simple logging as inject traverses and populates the
|
||||||
|
// object graph.
|
||||||
|
type Logger interface { |
||||||
|
Debugf(format string, v ...interface{}) |
||||||
|
} |
||||||
|
|
||||||
|
// Populate is a short-hand for populating a graph with the given incomplete
|
||||||
|
// object values.
|
||||||
|
func Populate(values ...interface{}) error { |
||||||
|
var g Graph |
||||||
|
for _, v := range values { |
||||||
|
if err := g.Provide(&Object{Value: v}); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return g.Populate() |
||||||
|
} |
||||||
|
|
||||||
|
// An Object in the Graph.
|
||||||
|
type Object struct { |
||||||
|
Value interface{} |
||||||
|
Name string // Optional
|
||||||
|
Complete bool // If true, the Value will be considered complete
|
||||||
|
Fields map[string]*Object // Populated with the field names that were injected and their corresponding *Object.
|
||||||
|
reflectType reflect.Type |
||||||
|
reflectValue reflect.Value |
||||||
|
private bool // If true, the Value will not be used and will only be populated
|
||||||
|
created bool // If true, the Object was created by us
|
||||||
|
embedded bool // If true, the Object is an embedded struct provided internally
|
||||||
|
} |
||||||
|
|
||||||
|
// String representation suitable for human consumption.
|
||||||
|
func (o *Object) String() string { |
||||||
|
var buf bytes.Buffer |
||||||
|
fmt.Fprint(&buf, o.reflectType) |
||||||
|
if o.Name != "" { |
||||||
|
fmt.Fprintf(&buf, " named %s", o.Name) |
||||||
|
} |
||||||
|
return buf.String() |
||||||
|
} |
||||||
|
|
||||||
|
func (o *Object) addDep(field string, dep *Object) { |
||||||
|
if o.Fields == nil { |
||||||
|
o.Fields = make(map[string]*Object) |
||||||
|
} |
||||||
|
o.Fields[field] = dep |
||||||
|
} |
||||||
|
|
||||||
|
// The Graph of Objects.
|
||||||
|
type Graph 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
|
||||||
|
// the impact of various fields.
|
||||||
|
func (g *Graph) Provide(objects ...*Object) error { |
||||||
|
for _, o := range objects { |
||||||
|
o.reflectType = reflect.TypeOf(o.Value) |
||||||
|
o.reflectValue = reflect.ValueOf(o.Value) |
||||||
|
|
||||||
|
if o.Fields != nil { |
||||||
|
return fmt.Errorf( |
||||||
|
"fields were specified on object %s when it was provided", |
||||||
|
o, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
if o.Name == "" { |
||||||
|
if !isStructPtr(o.reflectType) { |
||||||
|
return fmt.Errorf( |
||||||
|
"expected unnamed object value to be a pointer to a struct but got type %s "+ |
||||||
|
"with value %v", |
||||||
|
o.reflectType, |
||||||
|
o.Value, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
if !o.private { |
||||||
|
if g.unnamedType == nil { |
||||||
|
g.unnamedType = make(map[reflect.Type]bool) |
||||||
|
} |
||||||
|
|
||||||
|
if g.unnamedType[o.reflectType] { |
||||||
|
return fmt.Errorf( |
||||||
|
"provided two unnamed instances of type *%s.%s", |
||||||
|
o.reflectType.Elem().PkgPath(), o.reflectType.Elem().Name(), |
||||||
|
) |
||||||
|
} |
||||||
|
g.unnamedType[o.reflectType] = true |
||||||
|
} |
||||||
|
g.unnamed = append(g.unnamed, o) |
||||||
|
} else { |
||||||
|
if g.named == nil { |
||||||
|
g.named = make(map[string]*Object) |
||||||
|
} |
||||||
|
|
||||||
|
if g.named[o.Name] != nil { |
||||||
|
return fmt.Errorf("provided two instances named %s", o.Name) |
||||||
|
} |
||||||
|
g.named[o.Name] = o |
||||||
|
} |
||||||
|
|
||||||
|
if g.Logger != nil { |
||||||
|
if o.created { |
||||||
|
g.Logger.Debugf("created %s", o) |
||||||
|
} else if o.embedded { |
||||||
|
g.Logger.Debugf("provided embedded %s", o) |
||||||
|
} else { |
||||||
|
g.Logger.Debugf("provided %s", o) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Populate the incomplete Objects.
|
||||||
|
func (g *Graph) Populate() error { |
||||||
|
for _, o := range g.named { |
||||||
|
if o.Complete { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err := g.populateExplicit(o); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 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.
|
||||||
|
i := 0 |
||||||
|
for { |
||||||
|
if i == len(g.unnamed) { |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
o := g.unnamed[i] |
||||||
|
i++ |
||||||
|
|
||||||
|
if o.Complete { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err := g.populateExplicit(o); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// A Second pass handles injecting Interface values to ensure we have created
|
||||||
|
// all concrete types first.
|
||||||
|
for _, o := range g.unnamed { |
||||||
|
if o.Complete { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err := g.populateUnnamedInterface(o); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for _, o := range g.named { |
||||||
|
if o.Complete { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err := g.populateUnnamedInterface(o); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (g *Graph) populateExplicit(o *Object) error { |
||||||
|
// Ignore named value types.
|
||||||
|
if o.Name != "" && !isStructPtr(o.reflectType) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
StructLoop: |
||||||
|
for i := 0; i < o.reflectValue.Elem().NumField(); i++ { |
||||||
|
field := o.reflectValue.Elem().Field(i) |
||||||
|
fieldType := field.Type() |
||||||
|
fieldTag := o.reflectType.Elem().Field(i).Tag |
||||||
|
fieldName := o.reflectType.Elem().Field(i).Name |
||||||
|
tag, err := parseTag(string(fieldTag)) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf( |
||||||
|
"unexpected tag format `%s` for field %s in type %s", |
||||||
|
string(fieldTag), |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Skip fields without a tag.
|
||||||
|
if tag == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Cannot be used with unexported fields.
|
||||||
|
if !field.CanSet() { |
||||||
|
return fmt.Errorf( |
||||||
|
"inject requested on unexported field %s in type %s", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Inline tag on anything besides a struct is considered invalid.
|
||||||
|
if tag.Inline && fieldType.Kind() != reflect.Struct { |
||||||
|
return fmt.Errorf( |
||||||
|
"inline requested on non inlined field %s in type %s", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Don't overwrite existing values.
|
||||||
|
if !isNilOrZero(field, fieldType) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Named injects must have been explicitly provided.
|
||||||
|
if tag.Name != "" { |
||||||
|
existing := g.named[tag.Name] |
||||||
|
if existing == nil { |
||||||
|
return fmt.Errorf( |
||||||
|
"did not find object named %s required by field %s in type %s", |
||||||
|
tag.Name, |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
if !existing.reflectType.AssignableTo(fieldType) { |
||||||
|
return fmt.Errorf( |
||||||
|
"object named %s of type %s is not assignable to field %s (%s) in type %s", |
||||||
|
tag.Name, |
||||||
|
fieldType, |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
existing.reflectType, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
field.Set(reflect.ValueOf(existing.Value)) |
||||||
|
if g.Logger != nil { |
||||||
|
g.Logger.Debugf( |
||||||
|
"assigned %s to field %s in %s", |
||||||
|
existing, |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o, |
||||||
|
) |
||||||
|
} |
||||||
|
o.addDep(fieldName, existing) |
||||||
|
continue StructLoop |
||||||
|
} |
||||||
|
|
||||||
|
// Inline struct values indicate we want to traverse into it, but not
|
||||||
|
// inject itself. We require an explicit "inline" tag for this to work.
|
||||||
|
if fieldType.Kind() == reflect.Struct { |
||||||
|
if tag.Private { |
||||||
|
return fmt.Errorf( |
||||||
|
"cannot use private inject on inline struct on field %s in type %s", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
if !tag.Inline { |
||||||
|
return fmt.Errorf( |
||||||
|
"inline struct on field %s in type %s requires an explicit \"inline\" tag", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
err := g.Provide(&Object{ |
||||||
|
Value: field.Addr().Interface(), |
||||||
|
private: true, |
||||||
|
embedded: o.reflectType.Elem().Field(i).Anonymous, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Interface injection is handled in a second pass.
|
||||||
|
if fieldType.Kind() == reflect.Interface { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Maps are created and required to be private.
|
||||||
|
if fieldType.Kind() == reflect.Map { |
||||||
|
if !tag.Private { |
||||||
|
return fmt.Errorf( |
||||||
|
"inject on map field %s in type %s must be named or private", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
field.Set(reflect.MakeMap(fieldType)) |
||||||
|
if g.Logger != nil { |
||||||
|
g.Logger.Debugf( |
||||||
|
"made map for field %s in %s", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o, |
||||||
|
) |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Can only inject Pointers from here on.
|
||||||
|
if !isStructPtr(fieldType) { |
||||||
|
return fmt.Errorf( |
||||||
|
"found inject tag on unsupported field %s in type %s", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Unless it's a private inject, we'll look for an existing instance of the
|
||||||
|
// same type.
|
||||||
|
if !tag.Private { |
||||||
|
for _, existing := range g.unnamed { |
||||||
|
if existing.private { |
||||||
|
continue |
||||||
|
} |
||||||
|
if existing.reflectType.AssignableTo(fieldType) { |
||||||
|
field.Set(reflect.ValueOf(existing.Value)) |
||||||
|
if g.Logger != nil { |
||||||
|
g.Logger.Debugf( |
||||||
|
"assigned existing %s to field %s in %s", |
||||||
|
existing, |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o, |
||||||
|
) |
||||||
|
} |
||||||
|
o.addDep(fieldName, existing) |
||||||
|
continue StructLoop |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
newValue := reflect.New(fieldType.Elem()) |
||||||
|
newObject := &Object{ |
||||||
|
Value: newValue.Interface(), |
||||||
|
private: tag.Private, |
||||||
|
created: true, |
||||||
|
} |
||||||
|
|
||||||
|
// Add the newly ceated object to the known set of objects.
|
||||||
|
err = g.Provide(newObject) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Finally assign the newly created object to our field.
|
||||||
|
field.Set(newValue) |
||||||
|
if g.Logger != nil { |
||||||
|
g.Logger.Debugf( |
||||||
|
"assigned newly created %s to field %s in %s", |
||||||
|
newObject, |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o, |
||||||
|
) |
||||||
|
} |
||||||
|
o.addDep(fieldName, newObject) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (g *Graph) populateUnnamedInterface(o *Object) error { |
||||||
|
// Ignore named value types.
|
||||||
|
if o.Name != "" && !isStructPtr(o.reflectType) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
for i := 0; i < o.reflectValue.Elem().NumField(); i++ { |
||||||
|
field := o.reflectValue.Elem().Field(i) |
||||||
|
fieldType := field.Type() |
||||||
|
fieldTag := o.reflectType.Elem().Field(i).Tag |
||||||
|
fieldName := o.reflectType.Elem().Field(i).Name |
||||||
|
tag, err := parseTag(string(fieldTag)) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf( |
||||||
|
"unexpected tag format `%s` for field %s in type %s", |
||||||
|
string(fieldTag), |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Skip fields without a tag.
|
||||||
|
if tag == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// We only handle interface injection here. Other cases including errors
|
||||||
|
// are handled in the first pass when we inject pointers.
|
||||||
|
if fieldType.Kind() != reflect.Interface { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Interface injection can't be private because we can't instantiate new
|
||||||
|
// instances of an interface.
|
||||||
|
if tag.Private { |
||||||
|
return fmt.Errorf( |
||||||
|
"found private inject tag on interface field %s in type %s", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Don't overwrite existing values.
|
||||||
|
if !isNilOrZero(field, fieldType) { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Named injects must have already been handled in populateExplicit.
|
||||||
|
if tag.Name != "" { |
||||||
|
panic(fmt.Sprintf("unhandled named instance with name %s", tag.Name)) |
||||||
|
} |
||||||
|
|
||||||
|
// Find one, and only one assignable value for the field.
|
||||||
|
var found *Object |
||||||
|
for _, existing := range g.unnamed { |
||||||
|
if existing.private { |
||||||
|
continue |
||||||
|
} |
||||||
|
if existing.reflectType.AssignableTo(fieldType) { |
||||||
|
if found != nil { |
||||||
|
return fmt.Errorf( |
||||||
|
"found two assignable values for field %s in type %s. one type "+ |
||||||
|
"%s with value %v and another type %s with value %v", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
found.reflectType, |
||||||
|
found.Value, |
||||||
|
existing.reflectType, |
||||||
|
existing.reflectValue, |
||||||
|
) |
||||||
|
} |
||||||
|
found = existing |
||||||
|
field.Set(reflect.ValueOf(existing.Value)) |
||||||
|
if g.Logger != nil { |
||||||
|
g.Logger.Debugf( |
||||||
|
"assigned existing %s to interface field %s in %s", |
||||||
|
existing, |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o, |
||||||
|
) |
||||||
|
} |
||||||
|
o.addDep(fieldName, existing) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// If we didn't find an assignable value, we're missing something.
|
||||||
|
if found == nil { |
||||||
|
return fmt.Errorf( |
||||||
|
"found no assignable value for field %s in type %s", |
||||||
|
o.reflectType.Elem().Field(i).Name, |
||||||
|
o.reflectType, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Objects returns all known objects, named as well as unnamed. The returned
|
||||||
|
// elements are not in a stable order.
|
||||||
|
func (g *Graph) Objects() []*Object { |
||||||
|
objects := make([]*Object, 0, len(g.unnamed)+len(g.named)) |
||||||
|
for _, o := range g.unnamed { |
||||||
|
if !o.embedded { |
||||||
|
objects = append(objects, o) |
||||||
|
} |
||||||
|
} |
||||||
|
for _, o := range g.named { |
||||||
|
if !o.embedded { |
||||||
|
objects = append(objects, o) |
||||||
|
} |
||||||
|
} |
||||||
|
// randomize to prevent callers from relying on ordering
|
||||||
|
for i := 0; i < len(objects); i++ { |
||||||
|
j := rand.Intn(i + 1) |
||||||
|
objects[i], objects[j] = objects[j], objects[i] |
||||||
|
} |
||||||
|
return objects |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
injectOnly = &tag{} |
||||||
|
injectPrivate = &tag{Private: true} |
||||||
|
injectInline = &tag{Inline: true} |
||||||
|
) |
||||||
|
|
||||||
|
type tag struct { |
||||||
|
Name string |
||||||
|
Inline bool |
||||||
|
Private bool |
||||||
|
} |
||||||
|
|
||||||
|
func parseTag(t string) (*tag, error) { |
||||||
|
found, value, err := structtag.Extract("inject", t) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if !found { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
if value == "" { |
||||||
|
return injectOnly, nil |
||||||
|
} |
||||||
|
if value == "inline" { |
||||||
|
return injectInline, nil |
||||||
|
} |
||||||
|
if value == "private" { |
||||||
|
return injectPrivate, nil |
||||||
|
} |
||||||
|
return &tag{Name: value}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func isStructPtr(t reflect.Type) bool { |
||||||
|
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct |
||||||
|
} |
||||||
|
|
||||||
|
func isNilOrZero(v reflect.Value, t reflect.Type) bool { |
||||||
|
switch v.Kind() { |
||||||
|
default: |
||||||
|
return reflect.DeepEqual(v.Interface(), reflect.Zero(t).Interface()) |
||||||
|
case reflect.Interface, reflect.Ptr: |
||||||
|
return v.IsNil() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
BSD License |
||||||
|
|
||||||
|
For inject software |
||||||
|
|
||||||
|
Copyright (c) 2015, Facebook, Inc. All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, |
||||||
|
are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this |
||||||
|
list of conditions and the following disclaimer. |
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer in the documentation |
||||||
|
and/or other materials provided with the distribution. |
||||||
|
|
||||||
|
* Neither the name Facebook nor the names of its contributors may be used to |
||||||
|
endorse or promote products derived from this software without specific |
||||||
|
prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,33 @@ |
|||||||
|
Additional Grant of Patent Rights Version 2 |
||||||
|
|
||||||
|
"Software" means the inject software distributed by Facebook, Inc. |
||||||
|
|
||||||
|
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software |
||||||
|
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable |
||||||
|
(subject to the termination provision below) license under any Necessary |
||||||
|
Claims, to make, have made, use, sell, offer to sell, import, and otherwise |
||||||
|
transfer the Software. For avoidance of doubt, no license is granted under |
||||||
|
Facebook’s rights in any patent claims that are infringed by (i) modifications |
||||||
|
to the Software made by you or any third party or (ii) the Software in |
||||||
|
combination with any software or other technology. |
||||||
|
|
||||||
|
The license granted hereunder will terminate, automatically and without notice, |
||||||
|
if you (or any of your subsidiaries, corporate affiliates or agents) initiate |
||||||
|
directly or indirectly, or take a direct financial interest in, any Patent |
||||||
|
Assertion: (i) against Facebook or any of its subsidiaries or corporate |
||||||
|
affiliates, (ii) against any party if such Patent Assertion arises in whole or |
||||||
|
in part from any software, technology, product or service of Facebook or any of |
||||||
|
its subsidiaries or corporate affiliates, or (iii) against any party relating |
||||||
|
to the Software. Notwithstanding the foregoing, if Facebook or any of its |
||||||
|
subsidiaries or corporate affiliates files a lawsuit alleging patent |
||||||
|
infringement against you in the first instance, and you respond by filing a |
||||||
|
patent infringement counterclaim in that lawsuit against that party that is |
||||||
|
unrelated to the Software, the license granted hereunder will not terminate |
||||||
|
under section (i) of this paragraph due to such counterclaim. |
||||||
|
|
||||||
|
A "Necessary Claim" is a claim of a patent owned by Facebook that is |
||||||
|
necessarily infringed by the Software standing alone. |
||||||
|
|
||||||
|
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, |
||||||
|
or contributory infringement or inducement to infringe any patent, including a |
||||||
|
cross-claim or counterclaim. |
@ -0,0 +1,27 @@ |
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are |
||||||
|
met: |
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright |
||||||
|
notice, this list of conditions and the following disclaimer. |
||||||
|
* Redistributions in binary form must reproduce the above |
||||||
|
copyright notice, this list of conditions and the following disclaimer |
||||||
|
in the documentation and/or other materials provided with the |
||||||
|
distribution. |
||||||
|
* Neither the name of Google Inc. nor the names of its |
||||||
|
contributors may be used to endorse or promote products derived from |
||||||
|
this software without specific prior written permission. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,61 @@ |
|||||||
|
// Package structtag provides parsing of the defacto struct tag style.
|
||||||
|
package structtag |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
var errInvalidTag = errors.New("invalid tag") |
||||||
|
|
||||||
|
// Extract the quoted value for the given name returning it if it is found. The
|
||||||
|
// found boolean helps differentiate between the "empty and found" vs "empty
|
||||||
|
// and not found" nature of default empty strings.
|
||||||
|
func Extract(name, tag string) (found bool, value string, err error) { |
||||||
|
for tag != "" { |
||||||
|
// skip leading space
|
||||||
|
i := 0 |
||||||
|
for i < len(tag) && tag[i] == ' ' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
tag = tag[i:] |
||||||
|
if tag == "" { |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
// scan to colon.
|
||||||
|
// a space or a quote is a syntax error
|
||||||
|
i = 0 |
||||||
|
for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { |
||||||
|
return false, "", errInvalidTag |
||||||
|
} |
||||||
|
foundName := string(tag[:i]) |
||||||
|
tag = tag[i+1:] |
||||||
|
|
||||||
|
// scan quoted string to find value
|
||||||
|
i = 1 |
||||||
|
for i < len(tag) && tag[i] != '"' { |
||||||
|
if tag[i] == '\\' { |
||||||
|
i++ |
||||||
|
} |
||||||
|
i++ |
||||||
|
} |
||||||
|
if i >= len(tag) { |
||||||
|
return false, "", errInvalidTag |
||||||
|
} |
||||||
|
qvalue := string(tag[:i+1]) |
||||||
|
tag = tag[i+1:] |
||||||
|
|
||||||
|
if foundName == name { |
||||||
|
value, err := strconv.Unquote(qvalue) |
||||||
|
if err != nil { |
||||||
|
return false, "", err |
||||||
|
} |
||||||
|
return true, value, nil |
||||||
|
} |
||||||
|
} |
||||||
|
return false, "", nil |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
Copyright (c) 2015, Dave Cheney <dave@cheney.net> |
||||||
|
All rights reserved. |
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without |
||||||
|
modification, are permitted provided that the following conditions are met: |
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this |
||||||
|
list of conditions and the following disclaimer. |
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, |
||||||
|
this list of conditions and the following disclaimer in the documentation |
||||||
|
and/or other materials provided with the distribution. |
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,269 @@ |
|||||||
|
// Package errors provides simple error handling primitives.
|
||||||
|
//
|
||||||
|
// The traditional error handling idiom in Go is roughly akin to
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// which applied recursively up the call stack results in error reports
|
||||||
|
// without context or debugging information. The errors package allows
|
||||||
|
// programmers to add context to the failure path in their code in a way
|
||||||
|
// that does not destroy the original value of the error.
|
||||||
|
//
|
||||||
|
// Adding context to an error
|
||||||
|
//
|
||||||
|
// The errors.Wrap function returns a new error that adds context to the
|
||||||
|
// original error by recording a stack trace at the point Wrap is called,
|
||||||
|
// and the supplied message. For example
|
||||||
|
//
|
||||||
|
// _, err := ioutil.ReadAll(r)
|
||||||
|
// if err != nil {
|
||||||
|
// return errors.Wrap(err, "read failed")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||||
|
// functions destructure errors.Wrap into its component operations of annotating
|
||||||
|
// an error with a stack trace and an a message, respectively.
|
||||||
|
//
|
||||||
|
// Retrieving the cause of an error
|
||||||
|
//
|
||||||
|
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||||
|
// preceding error. Depending on the nature of the error it may be necessary
|
||||||
|
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||||
|
// for inspection. Any error value which implements this interface
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||||
|
// the topmost error which does not implement causer, which is assumed to be
|
||||||
|
// the original cause. For example:
|
||||||
|
//
|
||||||
|
// switch err := errors.Cause(err).(type) {
|
||||||
|
// case *MyError:
|
||||||
|
// // handle specifically
|
||||||
|
// default:
|
||||||
|
// // unknown error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// causer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// Formatted printing of errors
|
||||||
|
//
|
||||||
|
// All error values returned from this package implement fmt.Formatter and can
|
||||||
|
// be formatted by the fmt package. The following verbs are supported
|
||||||
|
//
|
||||||
|
// %s print the error. If the error has a Cause it will be
|
||||||
|
// printed recursively
|
||||||
|
// %v see %s
|
||||||
|
// %+v extended format. Each Frame of the error's StackTrace will
|
||||||
|
// be printed in detail.
|
||||||
|
//
|
||||||
|
// Retrieving the stack trace of an error or wrapper
|
||||||
|
//
|
||||||
|
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||||
|
// invoked. This information can be retrieved with the following interface.
|
||||||
|
//
|
||||||
|
// type stackTracer interface {
|
||||||
|
// StackTrace() errors.StackTrace
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Where errors.StackTrace is defined as
|
||||||
|
//
|
||||||
|
// type StackTrace []Frame
|
||||||
|
//
|
||||||
|
// The Frame type represents a call site in the stack trace. Frame supports
|
||||||
|
// the fmt.Formatter interface that can be used for printing information about
|
||||||
|
// the stack trace of this error. For example:
|
||||||
|
//
|
||||||
|
// if err, ok := err.(stackTracer); ok {
|
||||||
|
// for _, f := range err.StackTrace() {
|
||||||
|
// fmt.Printf("%+s:%d", f)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// stackTracer interface is not exported by this package, but is considered a part
|
||||||
|
// of stable public API.
|
||||||
|
//
|
||||||
|
// See the documentation for Frame.Format for more details.
|
||||||
|
package errors |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
) |
||||||
|
|
||||||
|
// New returns an error with the supplied message.
|
||||||
|
// New also records the stack trace at the point it was called.
|
||||||
|
func New(message string) error { |
||||||
|
return &fundamental{ |
||||||
|
msg: message, |
||||||
|
stack: callers(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Errorf formats according to a format specifier and returns the string
|
||||||
|
// as a value that satisfies error.
|
||||||
|
// Errorf also records the stack trace at the point it was called.
|
||||||
|
func Errorf(format string, args ...interface{}) error { |
||||||
|
return &fundamental{ |
||||||
|
msg: fmt.Sprintf(format, args...), |
||||||
|
stack: callers(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// fundamental is an error that has a message and a stack, but no caller.
|
||||||
|
type fundamental struct { |
||||||
|
msg string |
||||||
|
*stack |
||||||
|
} |
||||||
|
|
||||||
|
func (f *fundamental) Error() string { return f.msg } |
||||||
|
|
||||||
|
func (f *fundamental) Format(s fmt.State, verb rune) { |
||||||
|
switch verb { |
||||||
|
case 'v': |
||||||
|
if s.Flag('+') { |
||||||
|
io.WriteString(s, f.msg) |
||||||
|
f.stack.Format(s, verb) |
||||||
|
return |
||||||
|
} |
||||||
|
fallthrough |
||||||
|
case 's': |
||||||
|
io.WriteString(s, f.msg) |
||||||
|
case 'q': |
||||||
|
fmt.Fprintf(s, "%q", f.msg) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||||
|
// If err is nil, WithStack returns nil.
|
||||||
|
func WithStack(err error) error { |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return &withStack{ |
||||||
|
err, |
||||||
|
callers(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type withStack struct { |
||||||
|
error |
||||||
|
*stack |
||||||
|
} |
||||||
|
|
||||||
|
func (w *withStack) Cause() error { return w.error } |
||||||
|
|
||||||
|
func (w *withStack) Format(s fmt.State, verb rune) { |
||||||
|
switch verb { |
||||||
|
case 'v': |
||||||
|
if s.Flag('+') { |
||||||
|
fmt.Fprintf(s, "%+v", w.Cause()) |
||||||
|
w.stack.Format(s, verb) |
||||||
|
return |
||||||
|
} |
||||||
|
fallthrough |
||||||
|
case 's': |
||||||
|
io.WriteString(s, w.Error()) |
||||||
|
case 'q': |
||||||
|
fmt.Fprintf(s, "%q", w.Error()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Wrap returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrap is called, and the supplied message.
|
||||||
|
// If err is nil, Wrap returns nil.
|
||||||
|
func Wrap(err error, message string) error { |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
err = &withMessage{ |
||||||
|
cause: err, |
||||||
|
msg: message, |
||||||
|
} |
||||||
|
return &withStack{ |
||||||
|
err, |
||||||
|
callers(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Wrapf returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrapf is call, and the format specifier.
|
||||||
|
// If err is nil, Wrapf returns nil.
|
||||||
|
func Wrapf(err error, format string, args ...interface{}) error { |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
err = &withMessage{ |
||||||
|
cause: err, |
||||||
|
msg: fmt.Sprintf(format, args...), |
||||||
|
} |
||||||
|
return &withStack{ |
||||||
|
err, |
||||||
|
callers(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithMessage annotates err with a new message.
|
||||||
|
// If err is nil, WithMessage returns nil.
|
||||||
|
func WithMessage(err error, message string) error { |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
return &withMessage{ |
||||||
|
cause: err, |
||||||
|
msg: message, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type withMessage struct { |
||||||
|
cause error |
||||||
|
msg string |
||||||
|
} |
||||||
|
|
||||||
|
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } |
||||||
|
func (w *withMessage) Cause() error { return w.cause } |
||||||
|
|
||||||
|
func (w *withMessage) Format(s fmt.State, verb rune) { |
||||||
|
switch verb { |
||||||
|
case 'v': |
||||||
|
if s.Flag('+') { |
||||||
|
fmt.Fprintf(s, "%+v\n", w.Cause()) |
||||||
|
io.WriteString(s, w.msg) |
||||||
|
return |
||||||
|
} |
||||||
|
fallthrough |
||||||
|
case 's', 'q': |
||||||
|
io.WriteString(s, w.Error()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Cause returns the underlying cause of the error, if possible.
|
||||||
|
// An error value has a cause if it implements the following
|
||||||
|
// interface:
|
||||||
|
//
|
||||||
|
// type causer interface {
|
||||||
|
// Cause() error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// If the error does not implement Cause, the original error will
|
||||||
|
// be returned. If the error is nil, nil will be returned without further
|
||||||
|
// investigation.
|
||||||
|
func Cause(err error) error { |
||||||
|
type causer interface { |
||||||
|
Cause() error |
||||||
|
} |
||||||
|
|
||||||
|
for err != nil { |
||||||
|
cause, ok := err.(causer) |
||||||
|
if !ok { |
||||||
|
break |
||||||
|
} |
||||||
|
err = cause.Cause() |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
@ -0,0 +1,178 @@ |
|||||||
|
package errors |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"path" |
||||||
|
"runtime" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// Frame represents a program counter inside a stack frame.
|
||||||
|
type Frame uintptr |
||||||
|
|
||||||
|
// pc returns the program counter for this frame;
|
||||||
|
// multiple frames may have the same PC value.
|
||||||
|
func (f Frame) pc() uintptr { return uintptr(f) - 1 } |
||||||
|
|
||||||
|
// file returns the full path to the file that contains the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) file() string { |
||||||
|
fn := runtime.FuncForPC(f.pc()) |
||||||
|
if fn == nil { |
||||||
|
return "unknown" |
||||||
|
} |
||||||
|
file, _ := fn.FileLine(f.pc()) |
||||||
|
return file |
||||||
|
} |
||||||
|
|
||||||
|
// line returns the line number of source code of the
|
||||||
|
// function for this Frame's pc.
|
||||||
|
func (f Frame) line() int { |
||||||
|
fn := runtime.FuncForPC(f.pc()) |
||||||
|
if fn == nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
_, line := fn.FileLine(f.pc()) |
||||||
|
return line |
||||||
|
} |
||||||
|
|
||||||
|
// Format formats the frame according to the fmt.Formatter interface.
|
||||||
|
//
|
||||||
|
// %s source file
|
||||||
|
// %d source line
|
||||||
|
// %n function name
|
||||||
|
// %v equivalent to %s:%d
|
||||||
|
//
|
||||||
|
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||||
|
//
|
||||||
|
// %+s path of source file relative to the compile time GOPATH
|
||||||
|
// %+v equivalent to %+s:%d
|
||||||
|
func (f Frame) Format(s fmt.State, verb rune) { |
||||||
|
switch verb { |
||||||
|
case 's': |
||||||
|
switch { |
||||||
|
case s.Flag('+'): |
||||||
|
pc := f.pc() |
||||||
|
fn := runtime.FuncForPC(pc) |
||||||
|
if fn == nil { |
||||||
|
io.WriteString(s, "unknown") |
||||||
|
} else { |
||||||
|
file, _ := fn.FileLine(pc) |
||||||
|
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) |
||||||
|
} |
||||||
|
default: |
||||||
|
io.WriteString(s, path.Base(f.file())) |
||||||
|
} |
||||||
|
case 'd': |
||||||
|
fmt.Fprintf(s, "%d", f.line()) |
||||||
|
case 'n': |
||||||
|
name := runtime.FuncForPC(f.pc()).Name() |
||||||
|
io.WriteString(s, funcname(name)) |
||||||
|
case 'v': |
||||||
|
f.Format(s, 's') |
||||||
|
io.WriteString(s, ":") |
||||||
|
f.Format(s, 'd') |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||||
|
type StackTrace []Frame |
||||||
|
|
||||||
|
func (st StackTrace) Format(s fmt.State, verb rune) { |
||||||
|
switch verb { |
||||||
|
case 'v': |
||||||
|
switch { |
||||||
|
case s.Flag('+'): |
||||||
|
for _, f := range st { |
||||||
|
fmt.Fprintf(s, "\n%+v", f) |
||||||
|
} |
||||||
|
case s.Flag('#'): |
||||||
|
fmt.Fprintf(s, "%#v", []Frame(st)) |
||||||
|
default: |
||||||
|
fmt.Fprintf(s, "%v", []Frame(st)) |
||||||
|
} |
||||||
|
case 's': |
||||||
|
fmt.Fprintf(s, "%s", []Frame(st)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// stack represents a stack of program counters.
|
||||||
|
type stack []uintptr |
||||||
|
|
||||||
|
func (s *stack) Format(st fmt.State, verb rune) { |
||||||
|
switch verb { |
||||||
|
case 'v': |
||||||
|
switch { |
||||||
|
case st.Flag('+'): |
||||||
|
for _, pc := range *s { |
||||||
|
f := Frame(pc) |
||||||
|
fmt.Fprintf(st, "\n%+v", f) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *stack) StackTrace() StackTrace { |
||||||
|
f := make([]Frame, len(*s)) |
||||||
|
for i := 0; i < len(f); i++ { |
||||||
|
f[i] = Frame((*s)[i]) |
||||||
|
} |
||||||
|
return f |
||||||
|
} |
||||||
|
|
||||||
|
func callers() *stack { |
||||||
|
const depth = 32 |
||||||
|
var pcs [depth]uintptr |
||||||
|
n := runtime.Callers(3, pcs[:]) |
||||||
|
var st stack = pcs[0:n] |
||||||
|
return &st |
||||||
|
} |
||||||
|
|
||||||
|
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||||
|
func funcname(name string) string { |
||||||
|
i := strings.LastIndex(name, "/") |
||||||
|
name = name[i+1:] |
||||||
|
i = strings.Index(name, ".") |
||||||
|
return name[i+1:] |
||||||
|
} |
||||||
|
|
||||||
|
func trimGOPATH(name, file string) string { |
||||||
|
// Here we want to get the source file path relative to the compile time
|
||||||
|
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||||
|
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||||
|
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||||
|
// the import path, which does not include the GOPATH. Thus we can trim
|
||||||
|
// segments from the beginning of the file path until the number of path
|
||||||
|
// separators remaining is one more than the number of path separators in
|
||||||
|
// the function name. For example, given:
|
||||||
|
//
|
||||||
|
// GOPATH /home/user
|
||||||
|
// file /home/user/src/pkg/sub/file.go
|
||||||
|
// fn.Name() pkg/sub.Type.Method
|
||||||
|
//
|
||||||
|
// We want to produce:
|
||||||
|
//
|
||||||
|
// pkg/sub/file.go
|
||||||
|
//
|
||||||
|
// From this we can easily see that fn.Name() has one less path separator
|
||||||
|
// than our desired output. We count separators from the end of the file
|
||||||
|
// path until it finds two more than in the function name and then move
|
||||||
|
// one character forward to preserve the initial path segment without a
|
||||||
|
// leading separator.
|
||||||
|
const sep = "/" |
||||||
|
goal := strings.Count(name, sep) + 2 |
||||||
|
i := len(file) |
||||||
|
for n := 0; n < goal; n++ { |
||||||
|
i = strings.LastIndex(file[:i], sep) |
||||||
|
if i == -1 { |
||||||
|
// not enough separators found, set i so that the slice expression
|
||||||
|
// below leaves file unmodified
|
||||||
|
i = -len(sep) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
// get back to 0 or trim the leading separator
|
||||||
|
file = file[i+len(sep):] |
||||||
|
return file |
||||||
|
} |
Loading…
Reference in new issue