github.com/wfusion/gofusion@v1.1.14/config/component.go (about)

     1  package config
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"sync"
     8  
     9  	"github.com/pkg/errors"
    10  	"github.com/wfusion/gofusion/common/constant"
    11  	"github.com/wfusion/gofusion/common/utils"
    12  	"github.com/wfusion/gofusion/common/utils/clone"
    13  )
    14  
    15  // common component names
    16  const (
    17  	ComponentApp           = "App"
    18  	ComponentDebug         = "Debug"
    19  	ComponentCrypto        = "Crypto"
    20  	ComponentMetrics       = "Metrics"
    21  	ComponentLog           = "Log"
    22  	ComponentDB            = "DB"
    23  	ComponentRedis         = "Redis"
    24  	ComponentMongo         = "Mongo"
    25  	ComponentI18n          = "I18n"
    26  	ComponentLock          = "Lock"
    27  	ComponentMessageQueue  = "MQ"
    28  	ComponentHttp          = "Http"
    29  	ComponentCache         = "Cache"
    30  	ComponentCron          = "Cron"
    31  	ComponentAsync         = "Async"
    32  	ComponentGoroutinePool = "GoroutinePool"
    33  )
    34  
    35  const (
    36  	DefaultInstanceKey = "default"
    37  )
    38  
    39  var (
    40  	// componentOrder common component setup order
    41  	componentOrder = []string{
    42  		ComponentApp,
    43  		ComponentDebug,
    44  		ComponentCrypto,
    45  		ComponentLog,
    46  		ComponentMetrics,
    47  		ComponentRedis,
    48  		ComponentCache,
    49  		ComponentDB,
    50  		ComponentMongo,
    51  		ComponentI18n,
    52  		ComponentLock,
    53  		ComponentMessageQueue,
    54  		ComponentAsync,
    55  		ComponentGoroutinePool,
    56  		ComponentCron,
    57  		ComponentHttp,
    58  	}
    59  
    60  	componentLocker sync.RWMutex
    61  	components      []*componentItem
    62  )
    63  
    64  func indexComponent(name string) (idx int) {
    65  	for idx = 0; idx < len(componentOrder); idx++ {
    66  		if componentOrder[idx] == name {
    67  			return
    68  		}
    69  	}
    70  	idx = -1
    71  	return
    72  }
    73  
    74  type Component struct {
    75  	name                 string
    76  	tag                  string
    77  	constructor          reflect.Value
    78  	constructorInputType reflect.Type
    79  	isCore               bool
    80  	flagString           *string
    81  }
    82  
    83  func (c *Component) Clone() (r *Component) {
    84  	return &Component{
    85  		name:                 c.name,
    86  		tag:                  c.tag,
    87  		constructor:          c.constructor,
    88  		constructorInputType: c.constructorInputType,
    89  		isCore:               c.isCore,
    90  		flagString:           c.flagString,
    91  	}
    92  }
    93  
    94  type options struct {
    95  	tagList         []string
    96  	isCoreComponent bool
    97  	flagValue       *string
    98  }
    99  
   100  type ComponentOption func(*options)
   101  
   102  func newOptions() *options {
   103  	return &options{}
   104  }
   105  
   106  // WithTag set component struct tags
   107  func WithTag(name, val string) ComponentOption {
   108  	return func(opt *options) {
   109  		opt.tagList = append(opt.tagList, fmt.Sprintf(`%s:"%s"`, name, val))
   110  	}
   111  }
   112  
   113  // WithCore mark component as core component, they must be init first
   114  func WithCore() ComponentOption {
   115  	return func(opt *options) {
   116  		opt.isCoreComponent = true
   117  	}
   118  }
   119  
   120  func WithFlag(flagValue *string) ComponentOption {
   121  	return func(o *options) {
   122  		o.flagValue = flagValue
   123  	}
   124  }
   125  
   126  type componentItem struct {
   127  	name        string
   128  	constructor any
   129  	opt         []ComponentOption
   130  }
   131  
   132  func AddComponent(name string, constructor any, opts ...ComponentOption) {
   133  	componentLocker.Lock()
   134  	defer componentLocker.Unlock()
   135  	parseConstructor(constructor)
   136  	components = append(components, &componentItem{name, constructor, opts})
   137  }
   138  
   139  func getComponents() []*componentItem {
   140  	componentLocker.RLock()
   141  	defer componentLocker.RUnlock()
   142  	return clone.Clone(components)
   143  }
   144  
   145  func parseConstructor(fn any) (fnVal reflect.Value, input reflect.Type) {
   146  	fnVal = reflect.ValueOf(fn)
   147  	typ := reflect.TypeOf(fn)
   148  	if typ.Kind() != reflect.Func {
   149  		panic(errors.New("component constructor should be a function"))
   150  	}
   151  
   152  	// check output
   153  	if typ.NumOut() != 1 {
   154  		panic(errors.New("component constructor should return one finalizer function"))
   155  	}
   156  	retTyp := typ.Out(0)
   157  	if retTyp.Kind() != reflect.Func {
   158  		panic(errors.New("component constructor should return one finalizer function"))
   159  	}
   160  	if retTyp.NumIn() != 0 {
   161  		panic(errors.New("component constructor should return one finalizer function looks like func()"))
   162  	}
   163  
   164  	// check input
   165  	fnType := fnVal.Type()
   166  	if n := fnType.NumIn(); n != 1 && (!fnType.IsVariadic() && n != 3) {
   167  		panic(errors.New("component constructor should receive input looks like " +
   168  			"func(context.Context), func(context.Context, *serializableConf, ...utils.OptionExtender)"))
   169  	}
   170  	if fnType.In(0) != constant.ContextType {
   171  		panic(errors.New("component constructor should receive context.Context as first input " +
   172  			"looks like func(context.Context), func(context.Context, *serializableConf, ...utils.OptionExtender)"))
   173  	}
   174  
   175  	// wrapper
   176  	switch typ.NumIn() {
   177  	case 1:
   178  		input = reflect.TypeOf(int(0))
   179  		fnVal = reflect.ValueOf(func(ctx context.Context, mock int, _ ...utils.OptionExtender) func() {
   180  			out := reflect.ValueOf(fn).Call([]reflect.Value{reflect.ValueOf(ctx)})
   181  			if retfn := out[0]; retfn.IsNil() {
   182  				return nil
   183  			} else if obj := retfn.Interface(); obj == nil {
   184  				return nil
   185  			} else if fn, ok := obj.(func()); !ok {
   186  				return nil
   187  			} else {
   188  				return fn
   189  			}
   190  		})
   191  	case 3:
   192  		input = typ.In(1)
   193  		argsType := typ.In(2)
   194  		if argsType.Kind() != reflect.Slice ||
   195  			argsType.Elem() != reflect.TypeOf((*utils.OptionExtender)(nil)).Elem() {
   196  			panic(errors.New("component constructor only receive utils.OptionExtender variadic input"))
   197  		}
   198  	default:
   199  		panic(errors.New("component constructor should receive one or three inputs looks like " +
   200  			"func(context.Context), func(context.Context, *serializableConf, ...utils.OptionExtender)"))
   201  	}
   202  	return
   203  }