mirror of
https://github.com/kataras/iris.git
synced 2025-12-19 02:47:04 +00:00
OK, my dream-idea is implemented. TODO: Some examples and doc.go is not updated yet, comments on the mvc/di subpackage, the tutorial/vuejs-todo-mvc is running but not finished yet (it's using browser's localstorage and it should be replaced by the http requests that are registered via iris mvc
Former-commit-id: 0ea7e01ce1d78bcb78b40f3b0f5c03ad7c9abaea
This commit is contained in:
92
mvc/di/di.go
Normal file
92
mvc/di/di.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
type (
|
||||
// Hijacker is a type which is used to catch fields or function's input argument
|
||||
// to bind a custom object based on their type.
|
||||
Hijacker func(reflect.Type) (*BindObject, bool)
|
||||
// TypeChecker checks if a specific field's or function input argument's
|
||||
// is valid to be binded.
|
||||
TypeChecker func(reflect.Type) bool
|
||||
)
|
||||
|
||||
// D is the Dependency Injection container,
|
||||
// it contains the Values that can be changed before the injectors.
|
||||
// `Struct` and the `Func` methods returns an injector for specific
|
||||
// struct instance-value or function.
|
||||
type D struct {
|
||||
Values
|
||||
|
||||
hijacker Hijacker
|
||||
goodFunc TypeChecker
|
||||
}
|
||||
|
||||
// New creates and returns a new Dependency Injection container.
|
||||
// See `Values` field and `Func` and `Struct` methods for more.
|
||||
func New() *D {
|
||||
return &D{}
|
||||
}
|
||||
|
||||
// Hijack sets a hijacker function, read the `Hijacker` type for more explaination.
|
||||
func (d *D) Hijack(fn Hijacker) *D {
|
||||
d.hijacker = fn
|
||||
return d
|
||||
}
|
||||
|
||||
// GoodFunc sets a type checker for a valid function that can be binded,
|
||||
// read the `TypeChecker` type for more explaination.
|
||||
func (d *D) GoodFunc(fn TypeChecker) *D {
|
||||
d.goodFunc = fn
|
||||
return d
|
||||
}
|
||||
|
||||
// Clone returns a new Dependency Injection container, it adopts the
|
||||
// parent's (current "D") hijacker, good func type checker and all dependencies values.
|
||||
func (d *D) Clone() *D {
|
||||
clone := New()
|
||||
clone.hijacker = d.hijacker
|
||||
clone.goodFunc = d.goodFunc
|
||||
|
||||
// copy the current dynamic bindings (func binders)
|
||||
// and static struct bindings (services) to this new child.
|
||||
if n := len(d.Values); n > 0 {
|
||||
values := make(Values, n, n)
|
||||
copy(values, d.Values)
|
||||
clone.Values = values
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// Struct is being used to return a new injector based on
|
||||
// a struct value instance, if it contains fields that the types of those
|
||||
// are matching with one or more of the `Values` then they are binded
|
||||
// with the injector's `Inject` and `InjectElem` methods.
|
||||
func (d *D) Struct(s interface{}) *StructInjector {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
v := ValueOf(s)
|
||||
|
||||
return MakeStructInjector(
|
||||
v,
|
||||
d.hijacker,
|
||||
d.goodFunc,
|
||||
d.Values...,
|
||||
)
|
||||
}
|
||||
|
||||
// Func is being used to return a new injector based on
|
||||
// a function, if it contains input arguments that the types of those
|
||||
// are matching with one or more of the `Values` then they are binded
|
||||
// to the function's input argument when called
|
||||
// with the injector's `Fill` method.
|
||||
func (d *D) Func(fn interface{}) *FuncInjector {
|
||||
return MakeFuncInjector(
|
||||
ValueOf(fn),
|
||||
d.hijacker,
|
||||
d.goodFunc,
|
||||
d.Values...,
|
||||
)
|
||||
}
|
||||
107
mvc/di/func.go
Normal file
107
mvc/di/func.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
type (
|
||||
targetFuncInput struct {
|
||||
Object *BindObject
|
||||
InputIndex int
|
||||
}
|
||||
|
||||
FuncInjector struct {
|
||||
// the original function, is being used
|
||||
// only the .Call, which is refering to the same function, always.
|
||||
fn reflect.Value
|
||||
|
||||
inputs []*targetFuncInput
|
||||
// Length is the number of the valid, final binded input arguments.
|
||||
Length int
|
||||
// Valid is True when `Length` is > 0, it's statically set-ed for
|
||||
// performance reasons.
|
||||
Valid bool //
|
||||
}
|
||||
)
|
||||
|
||||
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector {
|
||||
typ := IndirectType(fn.Type())
|
||||
s := &FuncInjector{
|
||||
fn: fn,
|
||||
}
|
||||
|
||||
if !IsFunc(typ) {
|
||||
return s
|
||||
}
|
||||
|
||||
n := typ.NumIn()
|
||||
|
||||
// function input can have many values of the same types,
|
||||
// so keep track of them in order to not set a func input to a next bind value,
|
||||
// i.e (string, string) with two different binder funcs because of the different param's name.
|
||||
consumedValues := make(map[int]bool, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
inTyp := typ.In(i)
|
||||
|
||||
if hijack != nil {
|
||||
if b, ok := hijack(inTyp); ok && b != nil {
|
||||
s.inputs = append(s.inputs, &targetFuncInput{
|
||||
InputIndex: i,
|
||||
Object: b,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for valIdx, val := range values {
|
||||
if _, shouldSkip := consumedValues[valIdx]; shouldSkip {
|
||||
continue
|
||||
}
|
||||
inTyp := typ.In(i)
|
||||
|
||||
// the binded values to the func's inputs.
|
||||
b, err := MakeBindObject(val, goodFunc)
|
||||
|
||||
if err != nil {
|
||||
return s // if error stop here.
|
||||
}
|
||||
|
||||
if b.IsAssignable(inTyp) {
|
||||
// println(inTyp.String() + " is assignable to " + val.Type().String())
|
||||
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
|
||||
// i, b.Type.String(), val.String(), val.Pointer())
|
||||
s.inputs = append(s.inputs, &targetFuncInput{
|
||||
InputIndex: i,
|
||||
Object: &b,
|
||||
})
|
||||
|
||||
consumedValues[valIdx] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// s.Length = n
|
||||
s.Length = len(s.inputs)
|
||||
s.Valid = s.Length > 0
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
|
||||
args := *in
|
||||
for _, input := range s.inputs {
|
||||
input.Object.Assign(ctx, func(v reflect.Value) {
|
||||
// fmt.Printf("assign input index: %d for value: %v\n",
|
||||
// input.InputIndex, v.String())
|
||||
args[input.InputIndex] = v
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
*in = args
|
||||
}
|
||||
|
||||
func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value {
|
||||
in := make([]reflect.Value, s.Length, s.Length)
|
||||
s.Inject(&in, ctx...)
|
||||
return s.fn.Call(in)
|
||||
}
|
||||
97
mvc/di/object.go
Normal file
97
mvc/di/object.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type BindType uint32
|
||||
|
||||
const (
|
||||
Static BindType = iota // simple assignable value, a static value.
|
||||
Dynamic // dynamic value, depends on some input arguments from the caller.
|
||||
)
|
||||
|
||||
type BindObject struct {
|
||||
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
|
||||
Value reflect.Value
|
||||
|
||||
BindType BindType
|
||||
ReturnValue func([]reflect.Value) reflect.Value
|
||||
}
|
||||
|
||||
func MakeBindObject(v reflect.Value, goodFunc TypeChecker) (b BindObject, err error) {
|
||||
if IsFunc(v) {
|
||||
b.BindType = Dynamic
|
||||
b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc)
|
||||
} else {
|
||||
b.BindType = Static
|
||||
b.Type = v.Type()
|
||||
b.Value = v
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var errBad = errors.New("bad")
|
||||
|
||||
// MakeReturnValue takes any function
|
||||
// that accept custom values and returns something,
|
||||
// it returns a binder function, which accepts a slice of reflect.Value
|
||||
// and returns a single one reflect.Value for that.
|
||||
// It's being used to resolve the input parameters on a "x" consumer faster.
|
||||
//
|
||||
// The "fn" can have the following form:
|
||||
// `func(myService) MyViewModel`.
|
||||
//
|
||||
// The return type of the "fn" should be a value instance, not a pointer, for your own protection.
|
||||
// The binder function should return only one value.
|
||||
func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Value) reflect.Value, reflect.Type, error) {
|
||||
typ := IndirectType(fn.Type())
|
||||
|
||||
// invalid if not a func.
|
||||
if typ.Kind() != reflect.Func {
|
||||
return nil, typ, errBad
|
||||
}
|
||||
|
||||
// invalid if not returns one single value.
|
||||
if typ.NumOut() != 1 {
|
||||
return nil, typ, errBad
|
||||
}
|
||||
|
||||
if goodFunc != nil {
|
||||
if !goodFunc(typ) {
|
||||
return nil, typ, errBad
|
||||
}
|
||||
}
|
||||
|
||||
outTyp := typ.Out(0)
|
||||
zeroOutVal := reflect.New(outTyp).Elem()
|
||||
|
||||
bf := func(ctxValue []reflect.Value) reflect.Value {
|
||||
results := fn.Call(ctxValue)
|
||||
if len(results) == 0 {
|
||||
return zeroOutVal
|
||||
}
|
||||
|
||||
v := results[0]
|
||||
if !v.IsValid() {
|
||||
return zeroOutVal
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
return bf, outTyp, nil
|
||||
}
|
||||
|
||||
func (b *BindObject) IsAssignable(to reflect.Type) bool {
|
||||
return equalTypes(b.Type, to)
|
||||
}
|
||||
|
||||
func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) {
|
||||
if b.BindType == Dynamic {
|
||||
toSetter(b.ReturnValue(ctx))
|
||||
return
|
||||
}
|
||||
toSetter(b.Value)
|
||||
}
|
||||
180
mvc/di/reflect.go
Normal file
180
mvc/di/reflect.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
var emptyIn = []reflect.Value{}
|
||||
|
||||
// IsZero returns true if a value is nil, remember boolean's false is zero.
|
||||
// Remember; fields to be checked should be exported otherwise it returns false.
|
||||
func IsZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
zero := true
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
zero = zero && IsZero(v.Field(i))
|
||||
}
|
||||
|
||||
if typ := v.Type(); typ != nil && v.IsValid() {
|
||||
f, ok := typ.MethodByName("IsZero")
|
||||
// if not found
|
||||
// if has input arguments (1 is for the value receiver, so > 1 for the actual input args)
|
||||
// if output argument is not boolean
|
||||
// then skip this IsZero user-defined function.
|
||||
if !ok || f.Type.NumIn() > 1 || f.Type.NumOut() != 1 && f.Type.Out(0).Kind() != reflect.Bool {
|
||||
return zero
|
||||
}
|
||||
|
||||
method := v.Method(f.Index)
|
||||
// no needed check but:
|
||||
if method.IsValid() && !method.IsNil() {
|
||||
// it shouldn't panic here.
|
||||
zero = method.Call(emptyIn)[0].Interface().(bool)
|
||||
}
|
||||
}
|
||||
|
||||
return zero
|
||||
case reflect.Func, reflect.Map, reflect.Slice:
|
||||
return v.IsNil()
|
||||
case reflect.Array:
|
||||
zero := true
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
zero = zero && IsZero(v.Index(i))
|
||||
}
|
||||
return zero
|
||||
}
|
||||
// if not any special type then use the reflect's .Zero
|
||||
// usually for fields, but remember if it's boolean and it's false
|
||||
// then it's zero, even if set-ed.
|
||||
|
||||
if !v.CanInterface() {
|
||||
// if can't interface, i.e return value from unexported field or method then return false
|
||||
return false
|
||||
}
|
||||
zero := reflect.Zero(v.Type())
|
||||
return v.Interface() == zero.Interface()
|
||||
}
|
||||
|
||||
func IndirectValue(v reflect.Value) reflect.Value {
|
||||
return reflect.Indirect(v)
|
||||
}
|
||||
|
||||
func ValueOf(o interface{}) reflect.Value {
|
||||
if v, ok := o.(reflect.Value); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return reflect.ValueOf(o)
|
||||
}
|
||||
|
||||
func IndirectType(typ reflect.Type) reflect.Type {
|
||||
switch typ.Kind() {
|
||||
case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
|
||||
return typ.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func goodVal(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return v.IsValid()
|
||||
}
|
||||
|
||||
func IsFunc(kindable interface {
|
||||
Kind() reflect.Kind
|
||||
}) bool {
|
||||
return kindable.Kind() == reflect.Func
|
||||
}
|
||||
|
||||
func equalTypes(got reflect.Type, expected reflect.Type) bool {
|
||||
if got == expected {
|
||||
return true
|
||||
}
|
||||
// if accepts an interface, check if the given "got" type does
|
||||
// implement this "expected" user handler's input argument.
|
||||
if expected.Kind() == reflect.Interface {
|
||||
// fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String())
|
||||
return got.Implements(expected)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// for controller's fields only.
|
||||
func structFieldIgnored(f reflect.StructField) bool {
|
||||
if !f.Anonymous {
|
||||
return true // if not anonymous(embedded), ignore it.
|
||||
}
|
||||
|
||||
s := f.Tag.Get("ignore")
|
||||
return s == "true" // if has an ignore tag then ignore it.
|
||||
}
|
||||
|
||||
type field struct {
|
||||
Type reflect.Type
|
||||
Index []int // the index of the field, slice if it's part of a embedded struct
|
||||
Name string // the actual name
|
||||
|
||||
// this could be empty, but in our cases it's not,
|
||||
// it's filled with the bind object (as service which means as static value)
|
||||
// and it's filled from the lookupFields' caller.
|
||||
AnyValue reflect.Value
|
||||
}
|
||||
|
||||
func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
|
||||
if elemTyp.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
|
||||
for i, n := 0, elemTyp.NumField(); i < n; i++ {
|
||||
f := elemTyp.Field(i)
|
||||
|
||||
if IndirectType(f.Type).Kind() == reflect.Struct &&
|
||||
!structFieldIgnored(f) {
|
||||
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...)
|
||||
continue
|
||||
}
|
||||
|
||||
// skip unexported fields here,
|
||||
// after the check for embedded structs, these can be binded if their
|
||||
// fields are exported.
|
||||
if f.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
index := []int{i}
|
||||
if len(parentIndex) > 0 {
|
||||
index = append(parentIndex, i)
|
||||
}
|
||||
|
||||
field := field{
|
||||
Type: f.Type,
|
||||
Name: f.Name,
|
||||
Index: index,
|
||||
}
|
||||
|
||||
fields = append(fields, field)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LookupNonZeroFieldsValues lookup for filled fields based on the "v" struct value instance.
|
||||
// It returns a slice of reflect.Value (same type as `Values`) that can be binded,
|
||||
// like the end-developer's custom values.
|
||||
func LookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) {
|
||||
elem := IndirectValue(v)
|
||||
fields := lookupFields(IndirectType(v.Type()), nil)
|
||||
for _, f := range fields {
|
||||
|
||||
if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !IsZero(fieldVal) {
|
||||
bindValues = append(bindValues, fieldVal)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
84
mvc/di/struct.go
Normal file
84
mvc/di/struct.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
type (
|
||||
targetStructField struct {
|
||||
Object *BindObject
|
||||
FieldIndex []int
|
||||
}
|
||||
|
||||
StructInjector struct {
|
||||
elemType reflect.Type
|
||||
//
|
||||
fields []*targetStructField
|
||||
Valid bool // is True when contains fields and it's a valid target struct.
|
||||
}
|
||||
)
|
||||
|
||||
func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
|
||||
s := &StructInjector{
|
||||
elemType: IndirectType(v.Type()),
|
||||
}
|
||||
|
||||
fields := lookupFields(s.elemType, nil)
|
||||
for _, f := range fields {
|
||||
|
||||
if hijack != nil {
|
||||
if b, ok := hijack(f.Type); ok && b != nil {
|
||||
s.fields = append(s.fields, &targetStructField{
|
||||
FieldIndex: f.Index,
|
||||
Object: b,
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for _, val := range values {
|
||||
// the binded values to the struct's fields.
|
||||
b, err := MakeBindObject(val, goodFunc)
|
||||
|
||||
if err != nil {
|
||||
return s // if error stop here.
|
||||
}
|
||||
|
||||
if b.IsAssignable(f.Type) {
|
||||
// fmt.Printf("bind the object to the field: %s at index: %#v and type: %s\n", f.Name, f.Index, f.Type.String())
|
||||
s.fields = append(s.fields, &targetStructField{
|
||||
FieldIndex: f.Index,
|
||||
Object: &b,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
s.Valid = len(s.fields) > 0
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) {
|
||||
if dest == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v := IndirectValue(ValueOf(dest))
|
||||
s.InjectElem(v, ctx...)
|
||||
}
|
||||
|
||||
func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) {
|
||||
for _, f := range s.fields {
|
||||
f.Object.Assign(ctx, func(v reflect.Value) {
|
||||
// fmt.Printf("%s for %s at index: %d\n", destElem.Type().String(), f.Object.Type.String(), f.FieldIndex)
|
||||
destElem.FieldByIndex(f.FieldIndex).Set(v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value {
|
||||
dest := reflect.New(s.elemType)
|
||||
s.InjectElem(dest, ctx...)
|
||||
return dest
|
||||
}
|
||||
98
mvc/di/values.go
Normal file
98
mvc/di/values.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package di
|
||||
|
||||
import "reflect"
|
||||
|
||||
type Values []reflect.Value
|
||||
|
||||
func NewValues() Values {
|
||||
return Values{}
|
||||
}
|
||||
|
||||
// Add binds values to this controller, if you want to share
|
||||
// binding values between controllers use the Engine's `Bind` function instead.
|
||||
func (bv *Values) Add(values ...interface{}) {
|
||||
for _, val := range values {
|
||||
bv.AddValue(reflect.ValueOf(val))
|
||||
}
|
||||
}
|
||||
|
||||
// AddValue same as `Add` but accepts reflect.Value
|
||||
// instead.
|
||||
func (bv *Values) AddValue(values ...reflect.Value) {
|
||||
for _, v := range values {
|
||||
if !goodVal(v) {
|
||||
continue
|
||||
}
|
||||
*bv = append(*bv, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unbinds a binding value based on the type,
|
||||
// it returns true if at least one field is not binded anymore.
|
||||
//
|
||||
// The "n" indicates the number of elements to remove, if <=0 then it's 1,
|
||||
// this is useful because you may have bind more than one value to two or more fields
|
||||
// with the same type.
|
||||
func (bv *Values) Remove(value interface{}, n int) bool {
|
||||
return bv.remove(reflect.TypeOf(value), n)
|
||||
}
|
||||
|
||||
func (bv *Values) remove(typ reflect.Type, n int) (ok bool) {
|
||||
input := *bv
|
||||
for i, in := range input {
|
||||
if equalTypes(in.Type(), typ) {
|
||||
ok = true
|
||||
input = input[:i+copy(input[i:], input[i+1:])]
|
||||
if n > 1 {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
*bv = input
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Has returns true if a binder responsible to
|
||||
// bind and return a type of "typ" is already registered to this controller.
|
||||
func (bv *Values) Has(value interface{}) bool {
|
||||
return bv.valueTypeExists(reflect.TypeOf(value))
|
||||
}
|
||||
|
||||
func (bv *Values) valueTypeExists(typ reflect.Type) bool {
|
||||
input := *bv
|
||||
for _, in := range input {
|
||||
if equalTypes(in.Type(), typ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddOnce binds a value to the controller's field with the same type,
|
||||
// if it's not binded already.
|
||||
//
|
||||
// Returns false if binded already or the value is not the proper one for binding,
|
||||
// otherwise true.
|
||||
func (bv *Values) AddOnce(value interface{}) bool {
|
||||
return bv.addIfNotExists(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
func (bv *Values) addIfNotExists(v reflect.Value) bool {
|
||||
var (
|
||||
typ = v.Type() // no element, raw things here.
|
||||
)
|
||||
|
||||
if !goodVal(v) {
|
||||
return false
|
||||
}
|
||||
|
||||
if bv.valueTypeExists(typ) {
|
||||
return false
|
||||
}
|
||||
|
||||
bv.AddValue(v)
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user