diff --git a/conf/bind.go b/conf/bind.go index 687826a8..80d50ec8 100644 --- a/conf/bind.go +++ b/conf/bind.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import ( "strings" "github.com/go-spring/spring-core/util" - "github.com/go-spring/spring-core/util/macro" + "github.com/go-spring/spring-core/util/errutil" ) var ( @@ -94,22 +94,27 @@ type BindParam struct { } func (param *BindParam) BindTag(tag string, validate reflect.StructTag) error { + param.Validate = validate parsedTag, err := ParseTag(tag) if err != nil { return err } + if parsedTag.Key == "" { // ${:=} 默认值语法 + if parsedTag.HasDef { + param.Tag = parsedTag + return nil + } + return fmt.Errorf("xxxx") // todo + } if parsedTag.Key == "ROOT" { parsedTag.Key = "" - } else if parsedTag.Key == "" { - parsedTag.Key = "ANONYMOUS" } - param.Tag = parsedTag if param.Key == "" { param.Key = parsedTag.Key } else if parsedTag.Key != "" { param.Key = param.Key + "." + parsedTag.Key } - param.Validate = validate + param.Tag = parsedTag return nil } @@ -118,11 +123,11 @@ type Filter interface { } // BindValue binds properties to a value. -func BindValue(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) (RetErr error) { +func BindValue(p ReadOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) (RetErr error) { - if !util.IsValueType(t) { + if !util.IsPropBindingTarget(t) { err := errors.New("target should be value type") - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return fmt.Errorf("bind path=%s type=%s error: %w", param.Path, v.Type().String(), err) } defer func() { @@ -144,21 +149,21 @@ func BindValue(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bind return bindSlice(p, v, t, param, filter) case reflect.Array: err := errors.New("use slice instead of array") - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return fmt.Errorf("bind path=%s type=%s error: %w", param.Path, v.Type().String(), err) default: // for linter } fn := converters[t] if fn == nil && v.Kind() == reflect.Struct { if err := bindStruct(p, v, t, param, filter); err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return err // no wrap } return nil } val, err := resolve(p, param) if err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) } if fn != nil { @@ -166,7 +171,7 @@ func BindValue(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bind out := fnValue.Call([]reflect.Value{reflect.ValueOf(val)}) if !out[1].IsNil() { err = out[1].Interface().(error) - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) } v.Set(out[0]) return nil @@ -179,45 +184,45 @@ func BindValue(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bind v.SetUint(u) return nil } - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var i int64 if i, err = strconv.ParseInt(val, 0, 0); err == nil { v.SetInt(i) return nil } - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) case reflect.Float32, reflect.Float64: var f float64 if f, err = strconv.ParseFloat(val, 64); err == nil { v.SetFloat(f) return nil } - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) case reflect.Bool: var b bool if b, err = strconv.ParseBool(val); err == nil { v.SetBool(b) return nil } - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) case reflect.String: v.SetString(val) return nil default: // for linter } - err = fmt.Errorf("unsupported bind type %q", t.String()) - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + err = errors.New("unsupported bind type") + return fmt.Errorf("bind path=%s type=%s error: %w", param.Path, v.Type().String(), err) } // bindSlice binds properties to a slice value. -func bindSlice(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { +func bindSlice(p ReadOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { et := t.Elem() p, err := getSlice(p, et, param) if err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) } slice := reflect.MakeSlice(t, 0, 0) @@ -238,14 +243,14 @@ func bindSlice(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bind break } if err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) } slice = reflect.Append(slice, e) } return nil } -func getSlice(p readOnlyProperties, et reflect.Type, param BindParam) (readOnlyProperties, error) { +func getSlice(p ReadOnlyProperties, et reflect.Type, param BindParam) (ReadOnlyProperties, error) { // properties that defined as list. if p.Has(param.Key + "[0]") { @@ -259,13 +264,13 @@ func getSlice(p readOnlyProperties, et reflect.Type, param BindParam) (readOnlyP strVal = p.Get(param.Key) } else { if !param.Tag.HasDef { - return nil, fmt.Errorf("%s: property %q %w", macro.FileLine(), param.Key, ErrNotExist) + return nil, fmt.Errorf("property %q %w", param.Key, ErrNotExist) } if param.Tag.Def == "" { return nil, nil } if !util.IsPrimitiveValueType(et) && converters[et] == nil { - return nil, fmt.Errorf("%s: can't find converter for %s", macro.FileLine(), et.String()) + return nil, fmt.Errorf("can't find converter for %s", et.String()) } strVal = param.Tag.Def } @@ -286,35 +291,44 @@ func getSlice(p readOnlyProperties, et reflect.Type, param BindParam) (readOnlyP } } else if fn, ok := splitters[s]; ok && fn != nil { if arrVal, err = fn(strVal); err != nil { - return nil, fmt.Errorf("%s: split error: %w, value: %q", macro.FileLine(), err, strVal) + return nil, fmt.Errorf("split error: %w, value: %q", err, strVal) } } else { - return nil, fmt.Errorf("%s: unknown splitter %q", macro.FileLine(), s) + return nil, fmt.Errorf("unknown splitter %q", s) } r := New() for i, s := range arrVal { k := fmt.Sprintf("%s[%d]", param.Key, i) - _ = r.storage.Set(k, s) + if err = r.storage.Set(k, s); err != nil { + return nil, err + } } return r, nil } // bindMap binds properties to a map value. -func bindMap(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { +func bindMap(p ReadOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { if param.Tag.HasDef && param.Tag.Def != "" { err := errors.New("map can't have a non-empty default value") - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return fmt.Errorf("bind path=%s type=%s error: %w", param.Path, v.Type().String(), err) } et := t.Elem() ret := reflect.MakeMap(t) defer func() { v.Set(ret) }() + // 当成默认值处理 + if param.Tag.Key == "" { + if param.Tag.HasDef { + return nil + } + } + keys, err := p.SubKeys(param.Key) if err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) } for _, key := range keys { @@ -327,9 +341,8 @@ func bindMap(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindPa Key: subKey, Path: param.Path, } - err = BindValue(p, e, et, subParam, filter) - if err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + if err = BindValue(p, e, et, subParam, filter); err != nil { + return err // no wrap } ret.SetMapIndex(reflect.ValueOf(key), e) } @@ -337,11 +350,11 @@ func bindMap(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindPa } // bindStruct binds properties to a struct value. -func bindStruct(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { +func bindStruct(p ReadOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { if param.Tag.HasDef && param.Tag.Def != "" { err := errors.New("struct can't have a non-empty default value") - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return fmt.Errorf("bind path=%s type=%s error: %w", param.Path, v.Type().String(), err) } for i := 0; i < t.NumField(); i++ { @@ -359,7 +372,7 @@ func bindStruct(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bin if tag, ok := ft.Tag.Lookup("value"); ok { if err := subParam.BindTag(tag, ft.Tag); err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String()) } if filter != nil { ret, err := filter.Do(fv.Addr().Interface(), subParam) @@ -371,7 +384,7 @@ func bindStruct(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bin } } if err := BindValue(p, fv, ft.Type, subParam, filter); err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return err // no wrap } continue } @@ -382,12 +395,12 @@ func bindStruct(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bin continue } if err := bindStruct(p, fv, ft.Type, subParam, filter); err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return err // no wrap } continue } - if util.IsValueType(ft.Type) { + if util.IsPropBindingTarget(ft.Type) { if subParam.Key == "" { subParam.Key = ft.Name } else { @@ -396,7 +409,7 @@ func bindStruct(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bin subParam.Key = strings.ToLower(subParam.Key) subParam.Key = strings.ReplaceAll(subParam.Key, "_", ".") if err := BindValue(p, fv, ft.Type, subParam, filter); err != nil { - return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + return err // no wrap } } } @@ -404,25 +417,23 @@ func bindStruct(p readOnlyProperties, v reflect.Value, t reflect.Type, param Bin } // resolve returns property references processed property value. -func resolve(p readOnlyProperties, param BindParam) (string, error) { +func resolve(p ReadOnlyProperties, param BindParam) (string, error) { const defVal = "@@def@@" - val := p.Get(param.Key, Def(defVal)) + val := p.Get(param.Key, defVal) if val != defVal { return resolveString(p, val) } if p.Has(param.Key) { - err := fmt.Errorf("property %q isn't simple value", param.Key) - return "", fmt.Errorf("%s: resolve property %q error, %w", macro.FileLine(), param.Key, err) + return "", fmt.Errorf("property key=%s isn't simple value", param.Key) } if param.Tag.HasDef { return resolveString(p, param.Tag.Def) } - err := fmt.Errorf("property %q %w", param.Key, ErrNotExist) - return "", fmt.Errorf("%s: resolve property %q error, %w", macro.FileLine(), param.Key, err) + return "", fmt.Errorf("property key=%s %w", param.Key, ErrNotExist) } // resolveString returns property references processed string. -func resolveString(p readOnlyProperties, s string) (string, error) { +func resolveString(p ReadOnlyProperties, s string) (string, error) { var ( length = len(s) @@ -456,7 +467,7 @@ func resolveString(p readOnlyProperties, s string) (string, error) { if end < 0 || count > 0 { err := ErrInvalidSyntax - return "", fmt.Errorf("%s: resolve string %q error, %w", macro.FileLine(), s, err) + return "", fmt.Errorf("resolve string %q error: %w", s, err) } var param BindParam @@ -464,12 +475,12 @@ func resolveString(p readOnlyProperties, s string) (string, error) { s1, err := resolve(p, param) if err != nil { - return "", fmt.Errorf("%s: resolve string %q error, %w", macro.FileLine(), s, err) + return "", errutil.WrapError(err, "resolve string %q error", s) } s2, err := resolveString(p, s[end+1:]) if err != nil { - return "", fmt.Errorf("%s: resolve string %q error, %w", macro.FileLine(), s, err) + return "", errutil.WrapError(err, "resolve string %q error", s) } return s[:start] + s1 + s2, nil diff --git a/conf/bind_test.go b/conf/bind_test.go index f7dfdb0e..315a5bcd 100644 --- a/conf/bind_test.go +++ b/conf/bind_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,7 +100,7 @@ func TestProperties_Bind(t *testing.T) { assert.Nil(t, err) dbConfig2 := DbConfig{} - err = p.Bind(&dbConfig2, conf.Tag("${prefix}")) + err = p.Bind(&dbConfig2, "${prefix}") assert.Nil(t, err) // 实际上是取的两个节点,只是值是一样的而已 @@ -129,7 +129,7 @@ func TestProperties_Bind(t *testing.T) { assert.Nil(t, err) dbConfig2 := NestedDbConfig{} - err = p.Bind(&dbConfig2, conf.Tag("${prefix}")) + err = p.Bind(&dbConfig2, "${prefix}") assert.Nil(t, err) // 实际上是取的两个节点,只是值是一样的而已 @@ -148,7 +148,7 @@ func TestProperties_Bind(t *testing.T) { assert.Nil(t, err) var m map[string]string - err = p.Bind(&m, conf.Tag("${a}")) + err = p.Bind(&m, "${a}") assert.Nil(t, err) assert.Equal(t, len(m), 3) @@ -161,7 +161,7 @@ func TestProperties_Bind(t *testing.T) { assert.Nil(t, err) var m map[string]string - err = p.Bind(&m, conf.Tag("${camera}")) + err = p.Bind(&m, "${camera}") assert.Nil(t, err) assert.Equal(t, len(m), 3) @@ -174,14 +174,14 @@ func TestProperties_Bind(t *testing.T) { assert.Nil(t, err) var m map[string]NestedDB - err = p.Bind(&m, conf.Tag("${db_map}")) + err = p.Bind(&m, "${db_map}") assert.Nil(t, err) assert.Equal(t, len(m), 2) assert.Equal(t, m["d1"].DB, "db1") dbConfig2 := NestedDbMapConfig{} - err = p.Bind(&dbConfig2, conf.Tag("${prefix_map}")) + err = p.Bind(&dbConfig2, "${prefix_map}") assert.Nil(t, err) assert.Equal(t, len(dbConfig2.DB), 2) diff --git a/conf/conf.go b/conf/conf.go index 0135c459..7b220156 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import ( "github.com/go-spring/spring-core/conf/reader/prop" "github.com/go-spring/spring-core/conf/reader/toml" "github.com/go-spring/spring-core/conf/reader/yaml" - "github.com/go-spring/spring-core/conf/store" + "github.com/go-spring/spring-core/conf/storage" "github.com/go-spring/spring-core/util" "github.com/spf13/cast" ) @@ -85,35 +85,27 @@ func RegisterConverter[T any](fn Converter[T]) { converters[t.Out(0)] = fn } -// readOnlyProperties is the interface for read-only properties. -type readOnlyProperties interface { - +// ReadOnlyProperties is the interface for read-only properties. +type ReadOnlyProperties interface { // Data returns key-value pairs of the properties. Data() map[string]string - // Keys returns keys of the properties. Keys() []string - // Has returns whether the key exists. Has(key string) bool - // SubKeys returns the sorted sub keys of the key. SubKeys(key string) ([]string, error) - - // Get returns key's value, using Def to return a default value. - Get(key string, opts ...GetOption) string - + // Get returns key's value. + Get(key string, def ...string) string // Resolve resolves string that contains references. Resolve(s string) (string, error) - // Bind binds properties into a value. - Bind(i interface{}, args ...BindArg) error - + Bind(i interface{}, tag ...string) error // CopyTo copies properties into another by override. CopyTo(out *Properties) error } -var _ readOnlyProperties = (*Properties)(nil) +var _ ReadOnlyProperties = (*Properties)(nil) // Properties stores the data with map[string]string and the keys are case-sensitive, // you can get one of them by its key, or bind some of them to a value. @@ -128,13 +120,13 @@ var _ readOnlyProperties = (*Properties)(nil) // but it costs more CPU time when getting properties because it reads property node // by node. So `conf` uses a tree to strictly verify and a flat map to store. type Properties struct { - storage *store.Storage + storage *storage.Storage } // New creates empty *Properties. func New() *Properties { return &Properties{ - storage: store.NewStorage(), + storage: storage.NewStorage(), } } @@ -212,30 +204,13 @@ func (p *Properties) SubKeys(key string) ([]string, error) { return p.storage.SubKeys(key) } -type getArg struct { - def string -} - -type GetOption func(arg *getArg) - -// Def returns v when key not exits. -func Def(v string) GetOption { - return func(arg *getArg) { - arg.def = v - } -} - // Get returns key's value, using Def to return a default value. -func (p *Properties) Get(key string, opts ...GetOption) string { +func (p *Properties) Get(key string, def ...string) string { val, ok := p.storage.Get(key) - if ok { - return val - } - arg := getArg{} - for _, opt := range opts { - opt(&arg) + if !ok && len(def) > 0 { + return def[0] } - return arg.def + return val } // Set sets key's value to be a primitive type as int or string, @@ -259,53 +234,13 @@ func (p *Properties) Resolve(s string) (string, error) { return resolveString(p, s) } -type BindArg interface { - getParam() (BindParam, error) -} - -type paramArg struct { - param BindParam -} - -func (tag paramArg) getParam() (BindParam, error) { - return tag.param, nil -} - -type tagArg struct { - tag string -} - -func (tag tagArg) getParam() (BindParam, error) { - var param BindParam - err := param.BindTag(tag.tag, "") - if err != nil { - return BindParam{}, err - } - return param, nil -} - -// Key binds properties using one key. -func Key(key string) BindArg { - return tagArg{tag: "${" + key + "}"} -} - -// Tag binds properties using one tag. -func Tag(tag string) BindArg { - return tagArg{tag: tag} -} - -// Param binds properties using BindParam. -func Param(param BindParam) BindArg { - return paramArg{param: param} -} - // Bind binds properties to a value, the bind value can be primitive type, // map, slice, struct. When binding to struct, the tag 'value' indicates -// which properties should be bind. The 'value' tags are defined by +// which properties should be bind. The 'value' tag are defined by // value:"${a:=b}>>splitter", 'a' is the key, 'b' is the default value, // 'splitter' is the Splitter's name when you want split string value // into []string value. -func (p *Properties) Bind(i interface{}, args ...BindArg) error { +func (p *Properties) Bind(i interface{}, tag ...string) error { var v reflect.Value { @@ -321,17 +256,19 @@ func (p *Properties) Bind(i interface{}, args ...BindArg) error { } } - if len(args) == 0 { - args = []BindArg{tagArg{tag: "${ROOT}"}} - } - t := v.Type() typeName := t.Name() if typeName == "" { // primitive type has no name typeName = t.String() } - param, err := args[0].getParam() + s := "${ROOT}" + if len(tag) > 0 { + s = tag[0] + } + + var param BindParam + err := param.BindTag(s, "") if err != nil { return err } diff --git a/conf/conf_test.go b/conf/conf_test.go index 751327a2..d658f46a 100644 --- a/conf/conf_test.go +++ b/conf/conf_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -87,29 +87,29 @@ func TestProperties_Get(t *testing.T) { assert.False(t, p.Has("NULL")) assert.Equal(t, p.Get("NULL"), "") - v = p.Get("NULL", conf.Def("OK")) + v = p.Get("NULL", "OK") assert.Equal(t, v, "OK") v = p.Get("Int") assert.Equal(t, v, "3") var v2 int - err = p.Bind(&v2, conf.Key("Int")) + err = p.Bind(&v2, "${Int}") assert.Nil(t, err) assert.Equal(t, v2, 3) var u2 uint - err = p.Bind(&u2, conf.Key("Uint")) + err = p.Bind(&u2, "${Uint}") assert.Nil(t, err) assert.Equal(t, u2, uint(3)) var u3 uint32 - err = p.Bind(&u3, conf.Key("Uint")) + err = p.Bind(&u3, "${Uint}") assert.Nil(t, err) assert.Equal(t, u3, uint32(3)) var f2 float32 - err = p.Bind(&f2, conf.Key("Float")) + err = p.Bind(&f2, "${Float}") assert.Nil(t, err) assert.Equal(t, f2, float32(3)) @@ -118,7 +118,7 @@ func TestProperties_Get(t *testing.T) { assert.Equal(t, b, true) var b2 bool - err = p.Bind(&b2, conf.Key("Bool")) + err = p.Bind(&b2, "${Bool}") assert.Nil(t, err) assert.Equal(t, b2, true) @@ -146,12 +146,12 @@ func TestProperties_Get(t *testing.T) { assert.Equal(t, d, time.Second*3) var ti time.Time - err = p.Bind(&ti, conf.Key("Time")) + err = p.Bind(&ti, "${Time}") assert.Nil(t, err) assert.Equal(t, ti, time.Date(2020, 02, 04, 20, 02, 04, 0, time.UTC)) var ss2 []string - err = p.Bind(&ss2, conf.Key("StringSlice")) + err = p.Bind(&ss2, "${StringSlice}") assert.Nil(t, err) assert.Equal(t, ss2, []string{"3", "4"}) }) @@ -200,7 +200,7 @@ func TestProperties_Ref(t *testing.T) { t.Run("not config", func(t *testing.T) { p := conf.New() err := p.Bind(&httpLog) - assert.Error(t, err, "property \\\"app.dir\\\" not exist") + assert.Error(t, err, "property key=app.dir not exist") }) t.Run("config", func(t *testing.T) { @@ -240,7 +240,7 @@ func TestBindSlice(t *testing.T) { p := conf.New() p.Set("a", []string{"1", "2"}) var ss []string - err := p.Bind(&ss, conf.Key("a")) + err := p.Bind(&ss, "${a}") if err != nil { t.Fatal(err) } @@ -252,19 +252,19 @@ func TestBindMap(t *testing.T) { t.Run("", func(t *testing.T) { var r [3]map[string]string err := conf.New().Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind \\[3]map\\[string]string error, target should be value type") + assert.Error(t, err, "bind path=.* type=.* error: target should be value type") }) t.Run("", func(t *testing.T) { var r []map[string]string err := conf.New().Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind \\[]map\\[string]string error, target should be value type") + assert.Error(t, err, "bind path=.* type=.* error: target should be value type") }) t.Run("", func(t *testing.T) { var r map[string]map[string]string err := conf.New().Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]map\\[string]string error, target should be value type") + assert.Error(t, err, "bind path=.* type=.* error: target should be value type") }) m := map[string]interface{}{ @@ -277,36 +277,27 @@ func TestBindMap(t *testing.T) { } t.Run("", func(t *testing.T) { - type S struct { - M [3]map[string]string `value:"${}"` - } - var r map[string]S + var r map[string][3]map[string]string p, err := conf.Map(m) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error, target should be value type") + assert.Error(t, err, "bind path=.* type=.* error: target should be value type") }) t.Run("", func(t *testing.T) { - type S struct { - M []map[string]string `value:"${}"` - } - var r map[string]S + var r map[string][]map[string]string p, err := conf.Map(m) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error, target should be value type") + assert.Error(t, err, "bind path=.* type=.* error: target should be value type") }) t.Run("", func(t *testing.T) { - type S struct { - M map[string]map[string]string `value:"${}"` - } - var r map[string]S + var r map[string]map[string]map[string]string p, err := conf.Map(m) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error, target should be value type") + assert.Error(t, err, "bind path=.* type=.* error: target should be value type") }) t.Run("", func(t *testing.T) { @@ -327,7 +318,7 @@ func TestBindMap(t *testing.T) { assert.Nil(t, err) var r map[string]string err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]string error, .*bind.go:.* resolve property \"a\" error, property \"a\" isn't simple value") + assert.Error(t, err, "bind path=map\\[string]string type=string error << property key=a isn't simple value") }) t.Run("", func(t *testing.T) { @@ -341,7 +332,7 @@ func TestBindMap(t *testing.T) { }) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind S error, .*bind.go:.* bind S.A error, property 'a' is value") + assert.Error(t, err, "bind path=S\\.A type=map\\[string]string error << property 'a' is value") }) t.Run("", func(t *testing.T) { @@ -362,7 +353,7 @@ func TestResolve(t *testing.T) { err := p.Set("name", "Jim") assert.Nil(t, err) _, err = p.Resolve("my name is ${name") - assert.Error(t, err, ".*bind.go:.* resolve string \"my name is \\${name\" error, invalid syntax") + assert.Error(t, err, "resolve string \"my name is \\${name\" error: invalid syntax") str, _ := p.Resolve("my name is ${name}") assert.Equal(t, str, "my name is Jim") str, _ = p.Resolve("my name is ${name}${name}") @@ -398,7 +389,7 @@ func TestProperties_Set(t *testing.T) { p := conf.New() err := p.Set("m", nil) assert.Nil(t, err) - assert.True(t, p.Has("m")) + assert.False(t, p.Has("m")) assert.Equal(t, p.Get("m"), "") err = p.Set("m", map[string]string{"a": "b"}) @@ -427,7 +418,7 @@ func TestProperties_Set(t *testing.T) { p := conf.New() err := p.Set("a", nil) assert.Nil(t, err) - assert.True(t, p.Has("a")) + assert.False(t, p.Has("a")) assert.Equal(t, p.Get("a"), "") err = p.Set("a", []string{"b"}) assert.Nil(t, err) @@ -491,7 +482,7 @@ func TestSplitter(t *testing.T) { conf.RegisterConverter(PointConverter) conf.RegisterSplitter("PointSplitter", PointSplitter) var points []image.Point - err := conf.New().Bind(&points, conf.Tag("${:=(1,2)(3,4)}>>PointSplitter")) + err := conf.New().Bind(&points, "${:=(1,2)(3,4)}>>PointSplitter") assert.Nil(t, err) assert.Equal(t, points, []image.Point{{X: 1, Y: 2}, {X: 3, Y: 4}}) } diff --git a/conf/expr.go b/conf/expr.go index 90b24369..c413bd75 100644 --- a/conf/expr.go +++ b/conf/expr.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,15 +22,22 @@ import ( "github.com/expr-lang/expr" ) -type ValidateFunc[T interface{}] func(T) bool +// ValidateFunc defines a type for validation functions, which accept +// a value of type T and return a boolean result. +type ValidateFunc[T any] func(T) bool +// validateFuncs holds a map of registered validation functions. var validateFuncs = map[string]interface{}{} -func RegisterValidateFunc[T interface{}](name string, fn ValidateFunc[T]) { +// RegisterValidateFunc registers a validation function with a specific name. +// The function can then be used in validation expressions. +func RegisterValidateFunc[T any](name string, fn ValidateFunc[T]) { validateFuncs[name] = fn } -// validateField validates the field with the given tag and value. +// validateField validates a field using a validation expression (tag) and the field value (i). +// It evaluates the expression and checks if the result is true (i.e., the validation passes). +// If any error occurs during evaluation or if the validation fails, an error is returned. func validateField(tag string, i interface{}) error { env := map[string]interface{}{"$": i} for k, v := range validateFuncs { diff --git a/conf/expr_test.go b/conf/expr_test.go index c9d25b41..c7d8b726 100644 --- a/conf/expr_test.go +++ b/conf/expr_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/json/json.go b/conf/reader/json/json.go index 1f895bec..79221025 100644 --- a/conf/reader/json/json.go +++ b/conf/reader/json/json.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/json/json_test.go b/conf/reader/json/json_test.go index 0875fe78..707b58ae 100644 --- a/conf/reader/json/json_test.go +++ b/conf/reader/json/json_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/prop/prop.go b/conf/reader/prop/prop.go index 9e345004..5c72501f 100644 --- a/conf/reader/prop/prop.go +++ b/conf/reader/prop/prop.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/prop/prop_test.go b/conf/reader/prop/prop_test.go index b6bfffea..c899a6fb 100644 --- a/conf/reader/prop/prop_test.go +++ b/conf/reader/prop/prop_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/toml/toml.go b/conf/reader/toml/toml.go index dcc382cc..c1e60032 100644 --- a/conf/reader/toml/toml.go +++ b/conf/reader/toml/toml.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/toml/toml_test.go b/conf/reader/toml/toml_test.go index d054e186..94745d5b 100644 --- a/conf/reader/toml/toml_test.go +++ b/conf/reader/toml/toml_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/yaml/yaml.go b/conf/reader/yaml/yaml.go index 29712618..c335684e 100644 --- a/conf/reader/yaml/yaml.go +++ b/conf/reader/yaml/yaml.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/reader/yaml/yaml_test.go b/conf/reader/yaml/yaml_test.go index 36d91892..e33d6216 100644 --- a/conf/reader/yaml/yaml_test.go +++ b/conf/reader/yaml/yaml_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/store/path.go b/conf/storage/path.go similarity index 96% rename from conf/store/path.go rename to conf/storage/path.go index 4dc9233f..e1e38820 100644 --- a/conf/store/path.go +++ b/conf/storage/path.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package store +package storage import ( "fmt" @@ -56,7 +56,7 @@ func JoinPath(path []Path) string { // SplitPath splits key into individual path elements. func SplitPath(key string) ([]Path, error) { if key == "" { - return nil, nil + return nil, fmt.Errorf("invalid key '%s'", key) } var ( path []Path diff --git a/conf/store/path_test.go b/conf/storage/path_test.go similarity index 74% rename from conf/store/path_test.go rename to conf/storage/path_test.go index e3afb346..5ee69214 100644 --- a/conf/store/path_test.go +++ b/conf/storage/path_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,14 @@ * limitations under the License. */ -package store_test +package storage_test import ( "errors" "fmt" "testing" - "github.com/go-spring/spring-core/conf/store" + "github.com/go-spring/spring-core/conf/storage" "github.com/go-spring/spring-core/util/assert" ) @@ -29,10 +29,11 @@ func TestSplitPath(t *testing.T) { var testcases = []struct { Key string Err error - Path []store.Path + Path []storage.Path }{ { Key: "", + Err: errors.New("invalid key ''"), }, { Key: " ", @@ -56,8 +57,8 @@ func TestSplitPath(t *testing.T) { }, { Key: "[0]", - Path: []store.Path{ - {store.PathTypeIndex, "0"}, + Path: []storage.Path{ + {storage.PathTypeIndex, "0"}, }, }, { @@ -82,8 +83,8 @@ func TestSplitPath(t *testing.T) { }, { Key: "a", - Path: []store.Path{ - {store.PathTypeKey, "a"}, + Path: []storage.Path{ + {storage.PathTypeKey, "a"}, }, }, { @@ -92,9 +93,9 @@ func TestSplitPath(t *testing.T) { }, { Key: "a.b", - Path: []store.Path{ - {store.PathTypeKey, "a"}, - {store.PathTypeKey, "b"}, + Path: []storage.Path{ + {storage.PathTypeKey, "a"}, + {storage.PathTypeKey, "b"}, }, }, { @@ -107,9 +108,9 @@ func TestSplitPath(t *testing.T) { }, { Key: "a[0]", - Path: []store.Path{ - {store.PathTypeKey, "a"}, - {store.PathTypeIndex, "0"}, + Path: []storage.Path{ + {storage.PathTypeKey, "a"}, + {storage.PathTypeIndex, "0"}, }, }, { @@ -118,10 +119,10 @@ func TestSplitPath(t *testing.T) { }, { Key: "a[0].b", - Path: []store.Path{ - {store.PathTypeKey, "a"}, - {store.PathTypeIndex, "0"}, - {store.PathTypeKey, "b"}, + Path: []storage.Path{ + {storage.PathTypeKey, "a"}, + {storage.PathTypeIndex, "0"}, + {storage.PathTypeKey, "b"}, }, }, { @@ -130,10 +131,10 @@ func TestSplitPath(t *testing.T) { }, { Key: "a[0][0]", - Path: []store.Path{ - {store.PathTypeKey, "a"}, - {store.PathTypeIndex, "0"}, - {store.PathTypeIndex, "0"}, + Path: []storage.Path{ + {storage.PathTypeKey, "a"}, + {storage.PathTypeIndex, "0"}, + {storage.PathTypeIndex, "0"}, }, }, { @@ -142,11 +143,11 @@ func TestSplitPath(t *testing.T) { }, } for i, c := range testcases { - p, err := store.SplitPath(c.Key) + p, err := storage.SplitPath(c.Key) assert.Equal(t, err, c.Err, fmt.Sprintf("index: %d key: %q", i, c.Key)) assert.Equal(t, p, c.Path, fmt.Sprintf("index:%d key: %q", i, c.Key)) if err == nil { - s := store.JoinPath(p) + s := storage.JoinPath(p) assert.Equal(t, s, c.Key, fmt.Sprintf("index:%d key: %q", i, c.Key)) } } diff --git a/conf/store/store.go b/conf/storage/store.go similarity index 93% rename from conf/store/store.go rename to conf/storage/store.go index 1bd02366..bebe3c71 100644 --- a/conf/store/store.go +++ b/conf/storage/store.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package store +package storage import ( "cmp" @@ -45,7 +45,7 @@ type Storage struct { func NewStorage() *Storage { return &Storage{ tree: &treeNode{ - node: nodeTypeMap, + node: nodeTypeNil, data: make(map[string]*treeNode), }, data: make(map[string]string), @@ -72,10 +72,12 @@ func (s *Storage) Keys() []string { } // SubKeys returns the sorted sub keys of the key. -func (s *Storage) SubKeys(key string) ([]string, error) { - path, err := SplitPath(key) - if err != nil { - return nil, err +func (s *Storage) SubKeys(key string) (_ []string, err error) { + var path []Path + if key != "" { + if path, err = SplitPath(key); err != nil { + return nil, err + } } tree := s.tree for i, pathNode := range path { @@ -102,6 +104,12 @@ func (s *Storage) SubKeys(key string) ([]string, error) { // Has returns whether the key exists. func (s *Storage) Has(key string) bool { + if key == "" { + return false + } + if _, ok := s.data[key]; ok { + return true + } path, err := SplitPath(key) if err != nil { return false @@ -140,6 +148,9 @@ func (s *Storage) Get(key string) (string, bool) { // Set stores the value of the key. func (s *Storage) Set(key, val string) error { + if key == "" { + return fmt.Errorf("key is empty") + } tree, err := s.merge(key, val) if err != nil { return err @@ -158,9 +169,6 @@ func (s *Storage) merge(key, val string) (*treeNode, error) { if err != nil { return nil, err } - if path[0].Type == PathTypeIndex { - return nil, fmt.Errorf("invalid key '%s'", key) - } tree := s.tree for i, pathNode := range path { if tree.node == nodeTypeMap { diff --git a/conf/store/store_test.go b/conf/storage/store_test.go similarity index 95% rename from conf/store/store_test.go rename to conf/storage/store_test.go index fc64c32e..4fff7b6a 100644 --- a/conf/store/store_test.go +++ b/conf/storage/store_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,21 @@ * limitations under the License. */ -package store_test +package storage_test import ( "testing" - "github.com/go-spring/spring-core/conf/store" + "github.com/go-spring/spring-core/conf/storage" "github.com/go-spring/spring-core/util/assert" ) func TestStorage(t *testing.T) { - var s *store.Storage + var s *storage.Storage { - s = store.NewStorage() + s = storage.NewStorage() assert.Equal(t, s.Data(), map[string]string{}) assert.Equal(t, s.Keys(), []string{}) @@ -45,12 +45,12 @@ func TestStorage(t *testing.T) { assert.Error(t, err, "invalid key 'm\\[b]'") err = s.Set("[0].x", "123") - assert.Error(t, err, "invalid key '\\[0].x'") + assert.Nil(t, err) } // 初始值是简单的 KV 值 { - s = store.NewStorage() + s = storage.NewStorage() err := s.Set("a", "b") assert.Nil(t, err) @@ -92,7 +92,7 @@ func TestStorage(t *testing.T) { // 初始值是嵌套的 KV 值 { - s = store.NewStorage() + s = storage.NewStorage() err := s.Set("m.x", "y") assert.Nil(t, err) @@ -150,7 +150,7 @@ func TestStorage(t *testing.T) { // 初始值是数组 KV 值 { - s = store.NewStorage() + s = storage.NewStorage() err := s.Set("s[0]", "p") assert.Nil(t, err) @@ -205,7 +205,7 @@ func TestStorage(t *testing.T) { } { - s = store.NewStorage() + s = storage.NewStorage() err := s.Set("a.b[0].c", "") assert.Nil(t, err) diff --git a/gs/sysconf/sysconf.go b/conf/sysconf/sysconf.go similarity index 73% rename from gs/sysconf/sysconf.go rename to conf/sysconf/sysconf.go index 218ac9d2..47859d24 100644 --- a/gs/sysconf/sysconf.go +++ b/conf/sysconf/sysconf.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,10 +28,17 @@ var ( ) // Get returns the property of the key. -func Get(key string, opts ...conf.GetOption) string { +func Get(key string) string { lock.Lock() defer lock.Unlock() - return prop.Get(key, opts...) + return prop.Get(key) +} + +// MustGet returns the property of the key, if not exist, returns the default value. +func MustGet(key string, def string) string { + lock.Lock() + defer lock.Unlock() + return prop.Get(key, def) } // Set sets the property of the key. @@ -41,8 +48,8 @@ func Set(key string, val interface{}) error { return prop.Set(key, val) } -// Unset removes the property. -func Unset(key string) { +// Delete removes the property. +func Delete(key string) { lock.Lock() defer lock.Unlock() } @@ -57,11 +64,9 @@ func Clear() { // Clone copies all properties into another properties. func Clone() *conf.Properties { lock.Lock() - m := prop.Data() - lock.Unlock() + defer lock.Unlock() p := conf.New() - for k, v := range m { - _ = p.Set(k, v) - } + err := prop.CopyTo(p) + _ = err // should no error return p } diff --git a/docs/version-management.md b/docs/version-management.md new file mode 100644 index 00000000..3f655636 --- /dev/null +++ b/docs/version-management.md @@ -0,0 +1,30 @@ +**From ChatGPT:** + +For a dependency injection framework like go-spring, using the version management approach where only the version number +is updated while keeping the package path unchanged (similar to Java's method) is generally more suitable. Here’s why: + +### Single Instance Principle + +Go-spring is designed to have a unique IoC (Inversion of Control) container per service. Allowing multiple versions to +coexist—by modifying the package path for each version—could lead to conflicts or unexpected behavior since the +framework expects a single container instance. This restriction makes the second approach (changing both version number +and package path) less ideal. + +### Simplified Dependency Management + +By keeping the package path constant and only updating the version number (typically via the go.mod file), developers +avoid the need to alter numerous import statements throughout the code. This streamlined update process reduces the risk +of errors during an upgrade, which is especially important in large-scale projects. + +### Consistency with Ecosystem Practices + +While Go’s ecosystem often favors explicit package paths to manage versioning (thus allowing multiple versions to +coexist), frameworks like go-spring do not benefit from this flexibility due to their design constraints. Instead, +following a Java-like approach leverages mature practices that simplify dependency management, ensuring stability and +reducing maintenance overhead. + +### Conclusion + +Given that go-spring is not designed to support multiple versions of its dependency injection container within the same +service, the first approach—updating only the version number—is preferable. It minimizes potential conflicts, simplifies +the upgrade process, and aligns better with the framework's architectural principles. \ No newline at end of file diff --git a/gs/gs.go b/gs/gs.go index 941706bb..1e61e421 100644 --- a/gs/gs.go +++ b/gs/gs.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,207 +21,207 @@ import ( "reflect" "strings" + "github.com/go-spring/spring-core/conf/sysconf" "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/gs/internal/gs_app" "github.com/go-spring/spring-core/gs/internal/gs_arg" - "github.com/go-spring/spring-core/gs/internal/gs_bean" "github.com/go-spring/spring-core/gs/internal/gs_cond" "github.com/go-spring/spring-core/gs/internal/gs_conf" "github.com/go-spring/spring-core/gs/internal/gs_core" "github.com/go-spring/spring-core/gs/internal/gs_dync" - "github.com/go-spring/spring-core/gs/sysconf" ) const ( - Version = "go-spring@v1.1.3" + Version = "go-spring@v1.2.0.rc" Website = "https://go-spring.com/" ) -type ( - Arg = gs.Arg - BeanSelector = gs.BeanSelector - BeanInit = gs_bean.BeanInit - BeanDestroy = gs_bean.BeanDestroy - RegisteredBean = gs.RegisteredBean - UnregisteredBean = gs.UnregisteredBean - CondContext = gs.CondContext - Condition = gs.Condition - Properties = gs.Properties - Context = gs.Context - ContextAware = gs.ContextAware - Dync[T any] = gs_dync.Value[T] - AppContext = gs_app.AppContext - AppRunner = gs_app.AppRunner - AppServer = gs_app.AppServer -) - /************************************ arg ***********************************/ -// NilArg return a ValueArg with a value of nil. +type Arg = gs.Arg + +// TagArg returns a TagArg with the specified tag. +func TagArg(tag string) gs_arg.TagArg { + return gs_arg.TagArg{Tag: tag} +} + +// NilArg returns a ValueArg with a nil value. func NilArg() gs_arg.ValueArg { return gs_arg.Nil() } -// ValueArg return a ValueArg with a value of v. +// ValueArg returns a ValueArg with the specified value. func ValueArg(v interface{}) gs_arg.ValueArg { return gs_arg.Value(v) } -// IndexArg returns an IndexArg. +// IndexArg returns an IndexArg with the specified index and argument. func IndexArg(n int, arg Arg) gs_arg.IndexArg { return gs_arg.Index(n, arg) } -// OptionArg 返回 Option 函数的参数绑定。 -func OptionArg(fn interface{}, args ...Arg) *gs_arg.OptionArg { - return gs_arg.Option(fn, args...) -} - -func BindArg(fn interface{}, args []Arg, skip int) (*gs_arg.Callable, error) { - return gs_arg.Bind(fn, args, skip) +// BindArg binds runtime arguments to a given function. +func BindArg(fn interface{}, args ...Arg) *gs_arg.Callable { + return gs_arg.MustBind(fn, args...) } -// MustBindArg 为 Option 方法绑定运行时参数。 -func MustBindArg(fn interface{}, args ...Arg) *gs_arg.Callable { - return gs_arg.MustBind(fn, args...) +// OptionArg returns an OptionArg for the specified function and arguments. +func OptionArg(fn interface{}, args ...Arg) *gs_arg.OptionArg { + return gs_arg.Option(fn, args...) } /************************************ cond ***********************************/ type ( - Conditional = gs_cond.Conditional - PropertyOption = gs_cond.PropertyOption + CondBean = gs.CondBean + CondFunc = gs.CondFunc + Condition = gs.Condition + CondContext = gs.CondContext ) -// OK returns a Condition that always returns true. -func OK() Condition { - return gs_cond.OK() +// OnFunc creates a Condition based on the provided function. +func OnFunc(fn CondFunc) Condition { + return gs_cond.OnFunc(fn) } -// Not returns a Condition that returns true when the given Condition returns false. -func Not(c Condition) Condition { - return gs_cond.Not(c) +// OnProperty creates a Condition based on a property name and options. +func OnProperty(name string) gs_cond.OnPropertyInterface { + return gs_cond.OnProperty(name) } -// Or returns a Condition that returns true when any of the given Conditions returns true. -func Or(cond ...Condition) Condition { - return gs_cond.Or(cond...) +// OnMissingProperty creates a Condition that checks for a missing property. +func OnMissingProperty(name string) Condition { + return gs_cond.OnMissingProperty(name) } -// And returns a Condition that returns true when all the given Conditions return true. -func And(cond ...Condition) Condition { - return gs_cond.And(cond...) +// OnBean creates a Condition based on a BeanSelector. +func OnBean(s BeanSelector) Condition { + return gs_cond.OnBean(s) } -// None returns a Condition that returns true when none of the given Conditions returns true. -func None(cond ...Condition) Condition { - return gs_cond.None(cond...) +// OnMissingBean creates a Condition for when a specific bean is missing. +func OnMissingBean(s BeanSelector) Condition { + return gs_cond.OnMissingBean(s) } -func MatchIfMissing() PropertyOption { - return gs_cond.MatchIfMissing() +// OnSingleBean creates a Condition for when only one instance of a bean exists. +func OnSingleBean(s BeanSelector) Condition { + return gs_cond.OnSingleBean(s) } -func HavingValue(havingValue string) PropertyOption { - return gs_cond.HavingValue(havingValue) +// RegisterExpressFunc registers a custom expression function. +func RegisterExpressFunc(name string, fn interface{}) { + gs_cond.RegisterExpressFunc(name, fn) } -func OnProperty(name string, options ...PropertyOption) *Conditional { - return gs_cond.OnProperty(name, options...) +// OnExpression creates a Condition based on a custom expression. +func OnExpression(expression string) Condition { + return gs_cond.OnExpression(expression) } -func OnMissingProperty(name string) *Conditional { - return gs_cond.OnMissingProperty(name) +// Not creates a Condition that negates the given Condition. +func Not(c Condition) Condition { + return gs_cond.Not(c) } -func OnBean(selector BeanSelector) *Conditional { - return gs_cond.OnBean(selector) +// Or creates a Condition that is true if any of the given Conditions are true. +func Or(conditions ...Condition) Condition { + return gs_cond.Or(conditions...) } -func OnMissingBean(selector BeanSelector) *Conditional { - return gs_cond.OnMissingBean(selector) +// And creates a Condition that is true if all the given Conditions are true. +func And(conditions ...Condition) Condition { + return gs_cond.And(conditions...) } -func OnSingleBean(selector BeanSelector) *Conditional { - return gs_cond.OnSingleBean(selector) +// None creates a Condition that is true if none of the given Conditions are true. +func None(conditions ...Condition) Condition { + return gs_cond.None(conditions...) } -func OnExpression(expression string) *Conditional { - return gs_cond.OnExpression(expression) +// OnProfile creates a Condition based on the active profile. +func OnProfile(profile string) Condition { + return OnProperty("spring.profiles.active").HavingValue(profile) } -func OnMatches(fn func(ctx CondContext) (bool, error)) *Conditional { - return gs_cond.OnMatches(fn) -} +/************************************ ioc ************************************/ + +type ( + Context = gs.Context + ContextAware = gs.ContextAware +) + +type ( + Properties = gs.Properties + Refreshable = gs.Refreshable + Dync[T any] = gs_dync.Value[T] +) -func OnProfile(profile string) *Conditional { - return gs_cond.OnProfile(profile) +type ( + RegisteredBean = gs.RegisteredBean + BeanDefinition = gs.BeanDefinition +) + +type ( + BeanSelector = gs.BeanSelector + BeanInitFunc = gs.BeanInitFunc + BeanDestroyFunc = gs.BeanDestroyFunc + BeanInitInterface = gs.BeanInitInterface + BeanDestroyInterface = gs.BeanDestroyInterface +) + +// NewBean creates a new BeanDefinition. +var NewBean = gs_core.NewBean + +// BeanSelectorForType returns a BeanSelector for the given type. +func BeanSelectorForType[T any]() BeanSelector { + return gs.BeanSelectorForType[T]() } /************************************ boot ***********************************/ var boot *gs_app.Boot +// Boot initializes and returns a [*gs_app.Boot] instance. +func Boot() *gs_app.Boot { + if boot == nil { + boot = gs_app.NewBoot() + } + return boot +} + +// bootRun runs the boot process. func bootRun() error { if boot != nil { if err := boot.Run(); err != nil { return err } - boot = nil // Boot 阶段结束,释放资源 + boot = nil } return nil } -func getBoot() *gs_app.Boot { - if boot == nil { - boot = gs_app.NewBoot() - } - return boot -} - -func BootConfig() *gs_conf.BootConfig { - return getBoot().P -} - -func BootObject(i interface{}) *gs.RegisteredBean { - b := gs_core.NewBean(reflect.ValueOf(i)) - return getBoot().C.Accept(b) -} - -func BootProvide(ctor interface{}, args ...gs.Arg) *gs.RegisteredBean { - b := gs_core.NewBean(ctor, args...) - return getBoot().C.Accept(b) -} - -func BootAccept(bd *gs.UnregisteredBean) *gs.RegisteredBean { - return getBoot().C.Accept(bd) -} - -func BootGroup(fn func(p gs.Properties) ([]*gs.UnregisteredBean, error)) { - getBoot().C.Group(fn) -} - -func BootRunner(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.RegisteredBean { - bd := gs_core.NewBean(objOrCtor, ctorArgs...) - bd.Export((*AppRunner)(nil)) - return getBoot().C.Accept(bd) -} - /*********************************** app *************************************/ +type ( + AppRunner = gs_app.AppRunner + AppServer = gs_app.AppServer + AppContext = gs_app.AppContext +) + var app = gs_app.NewApp() -// Start 启动程序。 +// Start starts the app, usually for testing purposes. func Start() error { return app.Start() } -// Stop 停止程序。 +// Stop stops the app, usually for testing purposes. func Stop() { app.Stop() } -// Run 启动程序。 +// Run runs the app and waits for an interrupt signal to exit. func Run() error { printBanner() if err := bootRun(); err != nil { @@ -230,69 +230,75 @@ func Run() error { return app.Run() } -// ShutDown 停止程序。 +// ShutDown shuts down the app with an optional message. func ShutDown(msg ...string) { app.ShutDown(msg...) } +// Config returns the app configuration. func Config() *gs_conf.AppConfig { return app.P } -// Object 参考 Container.Object 的解释。 +// RefreshProperties refreshes the app configuration. +func RefreshProperties(p Properties) error { + return app.C.RefreshProperties(p) +} + +// Object registers a bean definition for a given object. func Object(i interface{}) *RegisteredBean { - b := gs_core.NewBean(reflect.ValueOf(i)) - return app.C.Accept(b) + b := NewBean(reflect.ValueOf(i)) + return app.C.Register(b) } -// Provide 参考 Container.Provide 的解释。 +// Provide registers a bean definition for a given constructor. func Provide(ctor interface{}, args ...Arg) *RegisteredBean { - b := gs_core.NewBean(ctor, args...) - return app.C.Accept(b) + b := NewBean(ctor, args...) + return app.C.Register(b) } -// Accept 参考 Container.Accept 的解释。 -func Accept(b *UnregisteredBean) *RegisteredBean { - return app.C.Accept(b) +// Register registers a bean definition. +func Register(b *BeanDefinition) *RegisteredBean { + return app.C.Register(b) } -func Group(fn func(p Properties) ([]*UnregisteredBean, error)) { - app.C.Group(fn) +// GroupRegister registers a group of bean definitions. +func GroupRegister(fn func(p Properties) ([]*BeanDefinition, error)) { + app.C.GroupRegister(fn) } +// Runner registers a bean definition for an [AppRunner]. func Runner(objOrCtor interface{}, ctorArgs ...Arg) *RegisteredBean { - b := gs_core.NewBean(objOrCtor, ctorArgs...) - b.Export((*AppRunner)(nil)) - return app.C.Accept(b) + b := NewBean(objOrCtor, ctorArgs...).Export( + reflect.TypeFor[AppRunner](), + ) + return app.C.Register(b) } +// Server registers a bean definition for an [AppServer]. func Server(objOrCtor interface{}, ctorArgs ...Arg) *RegisteredBean { - b := gs_core.NewBean(objOrCtor, ctorArgs...) - b.Export((*AppServer)(nil)) - return app.C.Accept(b) -} - -func RefreshProperties(p Properties) error { - return app.C.RefreshProperties(p) + b := NewBean(objOrCtor, ctorArgs...).Export( + reflect.TypeFor[AppServer](), + ) + return app.C.Register(b) } /********************************** banner ***********************************/ var appBanner = ` - (_) - __ _ ___ ___ _ __ _ __ _ _ __ __ _ - / _' | / _ \ ______ / __| | '_ \ | '__| | | | '_ \ / _' | -| (_| | | (_) | |______| \__ \ | |_) | | | | | | | | | | (_| | - \__, | \___/ |___/ | .__/ |_| |_| |_| |_| \__, | - __/ | | | __/ | - |___/ |_| |___/ + ____ ___ ____ ____ ____ ___ _ _ ____ + / ___| / _ \ / ___| | _ \ | _ \ |_ _| | \ | | / ___| + | | _ | | | | _____ \___ \ | |_) | | |_) | | | | \| | | | _ + | |_| | | |_| | |_____| ___) | | __/ | _ < | | | |\ | | |_| | + \____| \___/ |____/ |_| |_| \_\ |___| |_| \_| \____| ` -// Banner 自定义 banner 字符串。 +// Banner sets a custom app banner. func Banner(banner string) { appBanner = banner } +// printBanner prints the app banner. func printBanner() { if len(appBanner) == 0 { return @@ -326,16 +332,14 @@ func printBanner() { /********************************** utility **********************************/ +// AllowCircularReferences enables or disables circular references between beans. func AllowCircularReferences(enable bool) { err := sysconf.Set("spring.allow-circular-references", enable) - if err != nil { - panic(err) - } + _ = err // Ignore error } +// ForceAutowireIsNullable forces autowire to be nullable. func ForceAutowireIsNullable(enable bool) { err := sysconf.Set("spring.force-autowire-is-nullable", enable) - if err != nil { - panic(err) - } + _ = err // Ignore error } diff --git a/gs/gstest/gstest.go b/gs/gstest/gstest.go index bbd70fc7..3d61c191 100644 --- a/gs/gstest/gstest.go +++ b/gs/gstest/gstest.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,74 +17,70 @@ package gstest import ( - "context" "testing" - "github.com/go-spring/spring-core/conf" "github.com/go-spring/spring-core/gs" ) var ctx = &gs.ContextAware{} -// Init 初始化测试环境 +func init() { + gs.ForceAutowireIsNullable(true) +} + +// Init initializes the test environment. func Init() error { gs.Object(ctx) return gs.Start() } -// Run 运行测试用例 +// Run executes test cases and ensures shutdown of the app context. func Run(m *testing.M) (code int) { defer func() { gs.Stop() }() return m.Run() } -func Context() context.Context { - return ctx.GSContext.Context() -} - +// Keys retrieves all the property keys. func Keys() []string { return ctx.GSContext.Keys() } -// Has 判断属性是否存在 +// Has checks whether a specific property exists. func Has(key string) bool { return ctx.GSContext.Has(key) } +// SubKeys retrieves the sub-keys of a specified key. func SubKeys(key string) ([]string, error) { return ctx.GSContext.SubKeys(key) } -// Prop 获取属性值 -func Prop(key string, opts ...conf.GetOption) string { - return ctx.GSContext.Prop(key, opts...) +// Prop retrieves the value of a property specified by the key. +func Prop(key string, def ...string) string { + return ctx.GSContext.Prop(key, def...) } -// Resolve 解析字符串 +// Resolve resolves a given string with placeholders. func Resolve(s string) (string, error) { return ctx.GSContext.Resolve(s) } -// Bind 绑定对象 -func Bind(i interface{}, opts ...conf.BindArg) error { - return ctx.GSContext.Bind(i, opts...) +// Bind binds an object to the properties. +func Bind(i interface{}, tag ...string) error { + return ctx.GSContext.Bind(i, tag...) } -// Get 获取对象 -func Get(i interface{}, selectors ...gs.BeanSelector) error { - return ctx.GSContext.Get(i, selectors...) +// Get retrieves an object using specified selectors. +func Get(i interface{}, tag ...string) error { + return ctx.GSContext.Get(i, tag...) } -// Wire 注入对象 +// Wire injects dependencies into an object or constructor. func Wire(objOrCtor interface{}, ctorArgs ...gs.Arg) (interface{}, error) { return ctx.GSContext.Wire(objOrCtor, ctorArgs...) } -// Invoke 调用函数 +// Invoke calls a function with arguments injected. func Invoke(fn interface{}, args ...gs.Arg) ([]interface{}, error) { return ctx.GSContext.Invoke(fn, args...) } - -func RefreshProperties(p gs.Properties) error { - return gs.RefreshProperties(p) -} diff --git a/gs/gstest/gstest_test.go b/gs/gstest/gstest_test.go index c76d02cd..4b3b5535 100644 --- a/gs/gstest/gstest_test.go +++ b/gs/gstest/gstest_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/gs/internal/gs/gs.go b/gs/internal/gs/gs.go index faf14e90..d5787690 100644 --- a/gs/internal/gs/gs.go +++ b/gs/internal/gs/gs.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,230 +14,307 @@ * limitations under the License. */ +// Package gs provides all the concepts required for Go-Spring implementation. package gs import ( - "context" "reflect" + "strings" "unsafe" "github.com/go-spring/spring-core/conf" ) -// A BeanSelector can be the ID of a bean, a `reflect.Type`, an interface{} -// pointer such as `(*error)(nil)`, or `new(error)`. -type BeanSelector interface{} +// BeanInitFunc defines the prototype for initialization functions. +// For example: `func(bean)` or `func(bean) error`. +type BeanInitFunc = interface{} + +// BeanDestroyFunc defines the prototype for destroy functions. +// For example: `func(bean)` or `func(bean) error`. +type BeanDestroyFunc = interface{} + +// BeanInitInterface defines an interface for bean initialization. +type BeanInitInterface interface { + OnBeanInit(ctx Context) error +} + +// BeanDestroyInterface defines an interface for bean destruction. +type BeanDestroyInterface interface { + OnBeanDestroy() +} + +// BeanSelectorInterface is an interface for selecting beans. +type BeanSelectorInterface interface { + TypeAndName() (reflect.Type, string) +} + +// BeanSelector is an identifier for a bean. +type BeanSelector struct { + Type reflect.Type // Type of the bean + Name string // Name of the bean +} + +// BeanSelectorForType returns a BeanSelector for the given type. +func BeanSelectorForType[T any]() BeanSelector { + return BeanSelector{Type: reflect.TypeFor[T]()} +} + +// TypeAndName returns the type and name of the bean. +func (s BeanSelector) TypeAndName() (reflect.Type, string) { + return s.Type, s.Name +} + +func (s BeanSelector) String() string { + var sb strings.Builder + sb.WriteString("{") + if s.Type != nil { + sb.WriteString("Type:") + sb.WriteString(s.Type.String()) + } + if s.Name != "" { + if s.Type != nil { + sb.WriteString(",") + } + sb.WriteString("Name:") + sb.WriteString(s.Name) + } + sb.WriteString("}") + return sb.String() +} /********************************** condition ********************************/ +// Condition is a conditional logic interface used when registering beans. +type Condition interface { + Matches(ctx CondContext) (bool, error) +} + +// CondBean represents a bean with Name and Type. type CondBean interface { - ID() string Name() string - TypeName() string Type() reflect.Type } -// CondContext defines some methods of IoC container that conditions use. +// CondContext defines methods for the IoC container used by conditions. type CondContext interface { - // Has returns whether the IoC container has a property. + // Has checks whether the IoC container has a property with the given key. Has(key string) bool - // Prop returns the property's value when the IoC container has it, or - // returns empty string when the IoC container doesn't have it. - Prop(key string, opts ...conf.GetOption) string - // Find returns bean definitions that matched with the bean selector. - Find(selector BeanSelector) ([]CondBean, error) + // Prop retrieves the value of a property from the IoC container. + Prop(key string, def ...string) string + // Find searches for bean definitions that match the provided BeanSelector. + Find(s BeanSelectorInterface) ([]CondBean, error) } -// Condition is used when registering a bean to determine whether it's valid. -type Condition interface { - Matches(ctx CondContext) (bool, error) -} +// CondFunc is a function type that determines whether a condition is satisfied. +type CondFunc func(ctx CondContext) (bool, error) /************************************* arg ***********************************/ -// Arg 用于为函数参数提供绑定值。可以是 bean.Selector 类型,表示注入 bean ; -// 可以是 ${X:=Y} 形式的字符串,表示属性绑定或者注入 bean ;可以是 ValueArg -// 类型,表示不从 IoC 容器获取而是用户传入的普通值;可以是 IndexArg 类型,表示 -// 带有下标的参数绑定;可以是 *optionArg 类型,用于为 Option 方法提供参数绑定。 -type Arg interface{} +// Arg is used to provide binding values for function parameters. +type Arg interface { + GetArgValue(ctx ArgContext, t reflect.Type) (reflect.Value, error) +} -// ArgContext defines some methods of IoC container that Callable use. +// ArgContext defines methods for the IoC container used by Callable types. type ArgContext interface { - // Matches returns true when the Condition returns true, - // and returns false when the Condition returns false. + // Matches checks if the given condition is met. Matches(c Condition) (bool, error) - // Bind binds properties value by the "value" tag. + // Bind binds property values to the provided [reflect.Value]. Bind(v reflect.Value, tag string) error - // Wire wires dependent beans by the "autowire" tag. + // Wire wires dependent beans to the provided [reflect.Value]. Wire(v reflect.Value, tag string) error } +// Callable represents an entity that can be invoked with an ArgContext. type Callable interface { - Arg(i int) (Arg, bool) - In(i int) (reflect.Type, bool) Call(ctx ArgContext) ([]reflect.Value, error) } /*********************************** conf ************************************/ -type Properties interface { - Data() map[string]string - Keys() []string - Has(key string) bool - SubKeys(key string) ([]string, error) - Get(key string, opts ...conf.GetOption) string - Resolve(s string) (string, error) - Bind(i interface{}, args ...conf.BindArg) error - CopyTo(out *conf.Properties) error -} +// Properties represents read-only configuration properties. +type Properties = conf.ReadOnlyProperties -// Refreshable 可动态刷新的对象 +// Refreshable represents an object that can be dynamically refreshed. type Refreshable interface { + // OnRefresh is called to refresh the properties when they change. OnRefresh(prop Properties, param conf.BindParam) error } /*********************************** bean ************************************/ +// ConfigurationParam holds configuration parameters for bean setup. type ConfigurationParam struct { - Enable bool // 是否扫描成员方法 - Include []string // 包含哪些成员方法 - Exclude []string // 排除那些成员方法 + Includes []string // List of methods to include + Excludes []string // List of methods to exclude } +// BeanRegistration provides methods to configure and register bean metadata. type BeanRegistration interface { - ID() string + // Name returns the name of the bean. + Name() string + // Type returns the [reflect.Type] of the bean. Type() reflect.Type - SetCaller(skip int) + // Value returns the [reflect.Value] of the bean. + Value() reflect.Value + // SetName sets the name of the bean. SetName(name string) - SetOn(cond Condition) - SetDependsOn(selectors ...BeanSelector) - SetPrimary() - SetInit(fn interface{}) - SetDestroy(fn interface{}) - SetExport(exports ...interface{}) + // SetInit sets the initialization function for the bean. + SetInit(fn BeanInitFunc) + // SetDestroy sets the destruction function for the bean. + SetDestroy(fn BeanDestroyFunc) + // SetCondition adds a condition for the bean. + SetCondition(conditions ...Condition) + // SetDependsOn sets the beans that this bean depends on. + SetDependsOn(selectors ...BeanSelectorInterface) + // SetExport defines the interfaces to be exported by the bean. + SetExport(exports ...reflect.Type) + // SetConfiguration applies the bean configuration. SetConfiguration(param ...ConfigurationParam) - SetEnableRefresh(tag string) + // SetRefreshable marks the bean as refreshable with the given tag. + SetRefreshable(tag string) } +// beanBuilder helps configure a bean during its creation. type beanBuilder[T any] struct { b BeanRegistration } -func (d *beanBuilder[T]) BeanRegistration() BeanRegistration { - return d.b +// TypeAndName returns the type and name of the bean. +func (d *beanBuilder[T]) TypeAndName() (reflect.Type, string) { + r := d.BeanRegistration() + return r.Type(), r.Name() } -func (d *beanBuilder[T]) ID() string { - return d.b.ID() +// GetArgValue returns the value of the bean. +func (d *beanBuilder[T]) GetArgValue(ctx ArgContext, t reflect.Type) (reflect.Value, error) { + return d.BeanRegistration().Value(), nil } -func (d *beanBuilder[T]) Type() reflect.Type { - return d.b.Type() +// BeanRegistration returns the underlying BeanRegistration instance. +func (d *beanBuilder[T]) BeanRegistration() BeanRegistration { + return d.b } +// Name sets the name of the bean. func (d *beanBuilder[T]) Name(name string) *T { d.b.SetName(name) return *(**T)(unsafe.Pointer(&d)) } -// On 设置 bean 的 Condition。 -func (d *beanBuilder[T]) On(cond Condition) *T { - d.b.SetOn(cond) - return *(**T)(unsafe.Pointer(&d)) -} - -// DependsOn 设置 bean 的间接依赖项。 -func (d *beanBuilder[T]) DependsOn(selectors ...BeanSelector) *T { - d.b.SetDependsOn(selectors...) +// Init sets the initialization function for the bean. +func (d *beanBuilder[T]) Init(fn BeanInitFunc) *T { + d.b.SetInit(fn) return *(**T)(unsafe.Pointer(&d)) } -// Primary 设置 bean 为主版本。 -func (d *beanBuilder[T]) Primary() *T { - d.b.SetPrimary() +// Destroy sets the destruction function for the bean. +func (d *beanBuilder[T]) Destroy(fn BeanDestroyFunc) *T { + d.b.SetDestroy(fn) return *(**T)(unsafe.Pointer(&d)) } -// Init 设置 bean 的初始化函数。 -func (d *beanBuilder[T]) Init(fn interface{}) *T { - d.b.SetInit(fn) +// Condition adds a condition to validate the bean. +func (d *beanBuilder[T]) Condition(conditions ...Condition) *T { + d.b.SetCondition(conditions...) return *(**T)(unsafe.Pointer(&d)) } -// Destroy 设置 bean 的销毁函数。 -func (d *beanBuilder[T]) Destroy(fn interface{}) *T { - d.b.SetDestroy(fn) +// DependsOn sets the beans that this bean depends on. +func (d *beanBuilder[T]) DependsOn(selectors ...BeanSelectorInterface) *T { + d.b.SetDependsOn(selectors...) return *(**T)(unsafe.Pointer(&d)) } -// Export 设置 bean 的导出接口。 -func (d *beanBuilder[T]) Export(exports ...interface{}) *T { +// Export sets the interfaces that the bean will export. +func (d *beanBuilder[T]) Export(exports ...reflect.Type) *T { d.b.SetExport(exports...) return *(**T)(unsafe.Pointer(&d)) } -// Configuration 设置 bean 为配置类。 +// Configuration applies the configuration parameters to the bean. func (d *beanBuilder[T]) Configuration(param ...ConfigurationParam) *T { d.b.SetConfiguration(param...) return *(**T)(unsafe.Pointer(&d)) } -// EnableRefresh 设置 bean 为可刷新的。 -func (d *beanBuilder[T]) EnableRefresh(tag string) *T { - d.b.SetEnableRefresh(tag) +// Refreshable marks the bean as refreshable with the provided tag. +func (d *beanBuilder[T]) Refreshable(tag string) *T { + d.b.SetRefreshable(tag) return *(**T)(unsafe.Pointer(&d)) } +// RegisteredBean represents a bean that has been registered in the IoC container. type RegisteredBean struct { beanBuilder[RegisteredBean] } +// NewRegisteredBean creates a new RegisteredBean instance. func NewRegisteredBean(d BeanRegistration) *RegisteredBean { return &RegisteredBean{ beanBuilder: beanBuilder[RegisteredBean]{d}, } } -type UnregisteredBean struct { - beanBuilder[UnregisteredBean] +// BeanDefinition represents a bean that has not yet been registered in the IoC container. +type BeanDefinition struct { + beanBuilder[BeanDefinition] } -func NewUnregisteredBean(d BeanRegistration) *UnregisteredBean { - return &UnregisteredBean{ - beanBuilder: beanBuilder[UnregisteredBean]{d}, +// NewBeanDefinition creates a new BeanDefinition instance. +func NewBeanDefinition(d BeanRegistration) *BeanDefinition { + return &BeanDefinition{ + beanBuilder: beanBuilder[BeanDefinition]{d}, } } -/*********************************** ioc ************************************/ +/************************************ ioc ************************************/ -// Container ... +// Container represents the modifiable aspects of an IoC (Inversion of Control) container. +// It provides methods for registering, refreshing, and managing beans within the container. type Container interface { + // Object registers a bean using the provided object instance. Object(i interface{}) *RegisteredBean + // Provide registers a bean using the provided constructor function and optional arguments. Provide(ctor interface{}, args ...Arg) *RegisteredBean - Accept(b *UnregisteredBean) *RegisteredBean - Group(fn func(p Properties) ([]*UnregisteredBean, error)) + // Register registers a bean using the provided bean definition. + Register(b *BeanDefinition) *RegisteredBean + // GroupRegister registers multiple beans by executing the given function that returns [*BeanDefinition]s. + GroupRegister(fn func(p Properties) ([]*BeanDefinition, error)) + // RefreshProperties updates the properties of the container. RefreshProperties(p Properties) error + // Refresh initializes and wires all beans in the container. Refresh() error - SimplifyMemory() + // ReleaseUnusedMemory cleans up unused resources and releases memory. + ReleaseUnusedMemory() + // Close shuts down the container and cleans up all resources. Close() } -// Context ... +// Context represents the unmodifiable (or runtime) aspects of an IoC container. +// It provides methods for accessing properties, resolving values, and retrieving beans. type Context interface { - Context() context.Context + // Keys returns all the keys present in the container's properties. Keys() []string + // Has checks if the specified key exists in the container's properties. Has(key string) bool + // SubKeys returns the sub-keys under a specific key in the container's properties. SubKeys(key string) ([]string, error) - Prop(key string, opts ...conf.GetOption) string + // Prop retrieves the value of the specified key from the container's properties. + Prop(key string, def ...string) string + // Resolve resolves placeholders or references (e.g., ${KEY}) in the given string to actual values. Resolve(s string) (string, error) - Bind(i interface{}, opts ...conf.BindArg) error - Get(i interface{}, selectors ...BeanSelector) error + // Bind binds the value of the specified key to the provided struct or variable. + Bind(i interface{}, tag ...string) error + // Get retrieves a bean of the specified type using the provided tag. + Get(i interface{}, tag ...string) error + // Wire creates and returns a bean by wiring it with the provided constructor or object. Wire(objOrCtor interface{}, ctorArgs ...Arg) (interface{}, error) + // Invoke calls the provided function with the specified arguments and returns the result. Invoke(fn interface{}, args ...Arg) ([]interface{}, error) - Go(fn func(ctx context.Context)) } -// ContextAware injects the Context into a struct as the field GSContext. +// ContextAware is used to inject the container's Context into a bean. type ContextAware struct { GSContext Context `autowire:""` } diff --git a/gs/internal/gs_app/app.go b/gs/internal/gs_app/app.go index cc886003..e40b19de 100644 --- a/gs/internal/gs_app/app.go +++ b/gs/internal/gs_app/app.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * limitations under the License. */ +// Package gs_app provides a framework for building and managing Go-Spring applications. package gs_app import ( @@ -28,28 +29,45 @@ import ( "github.com/go-spring/spring-core/gs/internal/gs_core" ) -type AppRunner interface { - Run(ctx *AppContext) -} - -type AppServer interface { - OnAppStart(ctx *AppContext) // 应用启动的事件 - OnAppStop(ctx context.Context) // 应用停止的事件 -} - +// AppContext provides a wrapper around the application's [gs.Context]. +// It offers controlled access to the internal context and is designed +// to ensure safe usage in the application's lifecycle. type AppContext struct { c gs.Context } +// Unsafe exposes the underlying [gs.Context]. Using this method in new +// goroutines is unsafe because [gs.Context] may release its resources +// (e.g., bean definitions), making binding and injection operations invalid. func (p *AppContext) Unsafe() gs.Context { return p.c } +// Go executes a function in a new goroutine. The provided function will receive +// a cancellation signal when the application begins shutting down. func (p *AppContext) Go(fn func(ctx context.Context)) { - p.c.Go(fn) + p.c.(interface { + Go(fn func(ctx context.Context)) + }).Go(fn) +} + +// AppRunner defines an interface for tasks that should be executed after all +// beans are injected but before the application's servers are started. +// It is commonly used to initialize background jobs or tasks. +type AppRunner interface { + Run(ctx *AppContext) } -// App 应用 +// AppServer defines an interface for managing the lifecycle of application servers, +// such as HTTP, gRPC, Thrift, or MQ servers. Servers must implement methods for +// starting and stopping gracefully. +type AppServer interface { + OnAppStart(ctx *AppContext) + OnAppStop(ctx context.Context) +} + +// App represents the core application, managing its lifecycle, configuration, +// and the injection of dependencies. type App struct { C gs.Container P *gs_conf.AppConfig @@ -60,7 +78,7 @@ type App struct { Servers []AppServer `autowire:"${spring.app.servers:=*?}"` } -// NewApp application 的构造函数 +// NewApp creates and initializes a new application instance. func NewApp() *App { app := &App{ C: gs_core.New(), @@ -71,66 +89,87 @@ func NewApp() *App { return app } +// Run starts the application and listens for termination signals +// (e.g., SIGINT, SIGTERM). When a signal is received, it shuts down +// the application gracefully. Use ShutDown but not Stop to end +// the application lifecycle. func (app *App) Run() error { if err := app.Start(); err != nil { return err } + + // listens for OS termination signals go func() { ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt, syscall.SIGTERM) sig := <-ch - app.ShutDown(fmt.Sprintf("signal %v", sig)) + app.ShutDown(fmt.Sprintf("Received signal: %v", sig)) }() + + // waits for the shutdown signal <-app.exitChan app.Stop() return nil } +// Start initializes and starts the application. It loads configuration properties, +// refreshes the IoC container, performs dependency injection, and runs runners +// and servers. func (app *App) Start() error { - + // loads the layered app properties p, err := app.P.Refresh() if err != nil { return err } + // refreshes the container properties err = app.C.RefreshProperties(p) if err != nil { return err } + // refreshes the container err = app.C.Refresh() if err != nil { return err } - ctx := app.C.(gs.Context) + c := &AppContext{c: app.C.(gs.Context)} - // 执行命令行启动器 + // runs all runners for _, r := range app.Runners { - r.Run(&AppContext{ctx}) + r.Run(c) } - // 通知应用启动事件 + // starts all servers for _, svr := range app.Servers { - svr.OnAppStart(&AppContext{ctx}) + svr.OnAppStart(c) } - app.C.SimplifyMemory() + // listens the cancel signal then stop the servers + c.Go(func(ctx context.Context) { + <-ctx.Done() + for _, svr := range app.Servers { + svr.OnAppStop(ctx) + } + }) + + app.C.ReleaseUnusedMemory() return nil } +// Stop gracefully stops the application. This method is used to clean up +// resources and stop servers started by the Start method. func (app *App) Stop() { - for _, svr := range app.Servers { - svr.OnAppStop(context.Background()) - } app.C.Close() } -// ShutDown 关闭执行器 +// ShutDown gracefully terminates the application. It should be used when +// shutting down the application started by Run. func (app *App) ShutDown(msg ...string) { select { case <-app.exitChan: - // chan 已关闭,无需再次关闭。 + // do nothing if the exit channel is already closed default: close(app.exitChan) } diff --git a/gs/internal/gs_app/boot.go b/gs/internal/gs_app/boot.go index 5e9cd1f8..9290082c 100644 --- a/gs/internal/gs_app/boot.go +++ b/gs/internal/gs_app/boot.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,49 +17,91 @@ package gs_app import ( + "reflect" + "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/gs/internal/gs_conf" "github.com/go-spring/spring-core/gs/internal/gs_core" ) +// Boot is the bootstrapper of the application. type Boot struct { - C gs.Container - P *gs_conf.BootConfig + c gs.Container + p *gs_conf.BootConfig Runners []AppRunner `autowire:"${spring.boot.runners:=*?}"` } +// NewBoot creates a new Boot instance. func NewBoot() *Boot { b := &Boot{ - C: gs_core.New(), - P: gs_conf.NewBootConfig(), + c: gs_core.New(), + p: gs_conf.NewBootConfig(), } - b.C.Object(b) + b.c.Object(b) return b } -func (b *Boot) Run() error { +// Config returns the boot configuration. +func (b *Boot) Config() *gs_conf.BootConfig { + return b.p +} + +// Object registers an object bean. +func (b *Boot) Object(i interface{}) *gs.RegisteredBean { + bd := gs_core.NewBean(reflect.ValueOf(i)) + return b.c.Register(bd) +} + +// Provide registers a bean using a constructor function. +func (b *Boot) Provide(ctor interface{}, args ...gs.Arg) *gs.RegisteredBean { + bd := gs_core.NewBean(ctor, args...) + return b.c.Register(bd) +} - p, err := b.P.Refresh() +// Register registers a BeanDefinition instance. +func (b *Boot) Register(bd *gs.BeanDefinition) *gs.RegisteredBean { + return b.c.Register(bd) +} + +// GroupRegister registers a group of BeanDefinitions. +func (b *Boot) GroupRegister(fn func(p gs.Properties) ([]*gs.BeanDefinition, error)) { + b.c.GroupRegister(fn) +} + +// Runner registers an AppRunner instance. +func (b *Boot) Runner(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.RegisteredBean { + bd := gs_core.NewBean(objOrCtor, ctorArgs...).Export( + reflect.TypeFor[AppRunner](), + ) + return b.c.Register(bd) +} + +// Run executes the application's bootstrap process. +func (b *Boot) Run() error { + // Refresh the boot configuration. + p, err := b.p.Refresh() if err != nil { return err } - err = b.C.RefreshProperties(p) + // Refresh properties in the container. + err = b.c.RefreshProperties(p) if err != nil { return err } - err = b.C.Refresh() + // Refresh the container. + err = b.c.Refresh() if err != nil { return err } - // 执行命令行启动器 + // Execute all registered AppRunners. for _, r := range b.Runners { - r.Run(&AppContext{b.C.(gs.Context)}) + r.Run(&AppContext{b.c.(gs.Context)}) } - b.C.Close() + b.c.Close() return nil } diff --git a/gs/internal/gs_arg/arg.go b/gs/internal/gs_arg/arg.go index 3372ec7d..aa758ea7 100644 --- a/gs/internal/gs_arg/arg.go +++ b/gs/internal/gs_arg/arg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,7 @@ * limitations under the License. */ -//go:generate mockgen -build_flags="-mod=mod" -package=arg -source=arg.go -destination=arg_mock.go - -// Package gs_arg 用于实现函数参数绑定。 +// Package gs_arg provides a set of tools for working with function arguments. package gs_arg import ( @@ -26,51 +24,101 @@ import ( "runtime" "github.com/go-spring/spring-core/gs/internal/gs" - "github.com/go-spring/spring-core/gs/syslog" "github.com/go-spring/spring-core/util" - "github.com/go-spring/spring-core/util/macro" + "github.com/go-spring/spring-core/util/errutil" ) -// IndexArg is an Arg that has an index. +// TagArg represents an argument that has a tag for binding or autowiring. +type TagArg struct { + Tag string +} + +// Tag creates a TagArg with the given tag. +func Tag(tag string) TagArg { + return TagArg{Tag: tag} +} + +// GetArgValue returns the value of the argument based on its type. +func (arg TagArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) { + + // Binds property values based on the argument type. + if util.IsPropBindingTarget(t) { + v := reflect.New(t).Elem() + if err := ctx.Bind(v, arg.Tag); err != nil { + return reflect.Value{}, err + } + return v, nil + } + + // Wires dependent beans based on the argument type. + if util.IsBeanInjectionTarget(t) { + v := reflect.New(t).Elem() + if err := ctx.Wire(v, arg.Tag); err != nil { + return reflect.Value{}, err + } + return v, nil + } + + // If none of the conditions match, return an error. + err := fmt.Errorf("error type %s", t.String()) + return reflect.Value{}, errutil.WrapError(err, "get arg error: %v", arg.Tag) +} + +// IndexArg represents an argument that has an index. type IndexArg struct { - n int - arg gs.Arg + Idx int // Index of the argument. + Arg gs.Arg // The actual argument value. } -// Index returns an IndexArg. +// Index creates an IndexArg with the given index and argument. func Index(n int, arg gs.Arg) IndexArg { - return IndexArg{n: n, arg: arg} + return IndexArg{Idx: n, Arg: arg} +} + +// GetArgValue is not implemented for IndexArg, it panics if called. +func (arg IndexArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) { + panic(util.UnimplementedMethod) } -// ValueArg is an Arg that has a value. +// ValueArg represents an argument with a fixed value. type ValueArg struct { - v interface{} + v interface{} // The fixed value associated with this argument. } -// Nil return a ValueArg with a value of nil. +// Nil returns a ValueArg with a value of nil. func Nil() ValueArg { return ValueArg{v: nil} } -// Value return a ValueArg with a value of v. +// Value returns a ValueArg with the specified value. func Value(v interface{}) ValueArg { return ValueArg{v: v} } -// ArgList stores the arguments of a function. +// GetArgValue returns the value of the fixed argument. +func (arg ValueArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) { + if arg.v == nil { + return reflect.Zero(t), nil + } + return reflect.ValueOf(arg.v), nil +} + +// ArgList represents a list of arguments for a function. type ArgList struct { - fnType reflect.Type - args []gs.Arg + fnType reflect.Type // Type of the function to be invoked. + args []gs.Arg // List of arguments for the function. } -// NewArgList returns a new ArgList. +// NewArgList creates and validates an ArgList for the specified function. func NewArgList(fnType reflect.Type, args []gs.Arg) (*ArgList, error) { + // Calculates the number of fixed arguments in the function. fixedArgCount := fnType.NumIn() if fnType.IsVariadic() { fixedArgCount-- } + // Determines if the arguments use indexing. shouldIndex := func() bool { if len(args) == 0 { return false @@ -80,18 +128,22 @@ func NewArgList(fnType reflect.Type, args []gs.Arg) (*ArgList, error) { }() fnArgs := make([]gs.Arg, fixedArgCount) + + // Processes the first argument separately to determine its type. if len(args) > 0 { if args[0] == nil { - return nil, util.Errorf(macro.FileLine(), "the first arg must not be nil") + err := errors.New("the first arg must not be nil") + return nil, errutil.WrapError(err, "%v", args) } switch arg := args[0].(type) { case *OptionArg: fnArgs = append(fnArgs, arg) case IndexArg: - if arg.n < 0 || arg.n >= fixedArgCount { - return nil, util.Errorf(macro.FileLine(), "arg index %d exceeds max index %d", arg.n, fixedArgCount) + if arg.Idx < 0 || arg.Idx >= fixedArgCount { + err := fmt.Errorf("arg index %d exceeds max index %d", arg.Idx, fixedArgCount) + return nil, errutil.WrapError(err, "%v", args) } else { - fnArgs[arg.n] = arg.arg + fnArgs[arg.Idx] = arg.Arg } default: if fixedArgCount > 0 { @@ -99,57 +151,66 @@ func NewArgList(fnType reflect.Type, args []gs.Arg) (*ArgList, error) { } else if fnType.IsVariadic() { fnArgs = append(fnArgs, arg) } else { - return nil, util.Errorf(macro.FileLine(), "function has no args but given %d", len(args)) + err := fmt.Errorf("function has no args but given %d", len(args)) + return nil, errutil.WrapError(err, "%v", args) } } } + // Processes the remaining arguments. for i := 1; i < len(args); i++ { switch arg := args[i].(type) { case *OptionArg: fnArgs = append(fnArgs, arg) case IndexArg: if !shouldIndex { - return nil, util.Errorf(macro.FileLine(), "the Args must have or have no index") + err := fmt.Errorf("the Args must have or have no index") + return nil, errutil.WrapError(err, "%v", args) } - if arg.n < 0 || arg.n >= fixedArgCount { - return nil, util.Errorf(macro.FileLine(), "arg index %d exceeds max index %d", arg.n, fixedArgCount) - } else if fnArgs[arg.n] != nil { - return nil, util.Errorf(macro.FileLine(), "found same index %d", arg.n) + if arg.Idx < 0 || arg.Idx >= fixedArgCount { + err := fmt.Errorf("arg index %d exceeds max index %d", arg.Idx, fixedArgCount) + return nil, errutil.WrapError(err, "%v", args) + } else if fnArgs[arg.Idx] != nil { + err := fmt.Errorf("found same index %d", arg.Idx) + return nil, errutil.WrapError(err, "%v", args) } else { - fnArgs[arg.n] = arg.arg + fnArgs[arg.Idx] = arg.Arg } default: if shouldIndex { - return nil, util.Errorf(macro.FileLine(), "the Args must have or have no index") + err := fmt.Errorf("the Args must have or have no index") + return nil, errutil.WrapError(err, "%v", args) } if i < fixedArgCount { fnArgs[i] = arg } else if fnType.IsVariadic() { fnArgs = append(fnArgs, arg) } else { - return nil, util.Errorf(macro.FileLine(), "the count %d of Args exceeds max index %d", len(args), fixedArgCount) + err := fmt.Errorf("the count %d of Args exceeds max index %d", len(args), fixedArgCount) + return nil, errutil.WrapError(err, "%v", args) } } } + // Fills any unassigned fixed arguments with default values. for i := 0; i < fixedArgCount; i++ { if fnArgs[i] == nil { - fnArgs[i] = "" + fnArgs[i] = Tag("") } } return &ArgList{fnType: fnType, args: fnArgs}, nil } -// get returns all processed Args value. fileLine is the binding position of Callable. -func (r *ArgList) get(ctx gs.ArgContext, fileLine string) ([]reflect.Value, error) { +// get returns the processed argument values for the function call. +func (r *ArgList) get(ctx gs.ArgContext) ([]reflect.Value, error) { fnType := r.fnType numIn := fnType.NumIn() variadic := fnType.IsVariadic() result := make([]reflect.Value, 0) + // Processes each argument and converts it to a [reflect.Value]. for idx, arg := range r.args { var t reflect.Type @@ -159,129 +220,51 @@ func (r *ArgList) get(ctx gs.ArgContext, fileLine string) ([]reflect.Value, erro t = fnType.In(idx) } - // option arg may not return a value when the condition is not met. - v, err := r.getArg(ctx, arg, t, fileLine) + v, err := arg.GetArgValue(ctx, t) if err != nil { - return nil, util.Wrapf(err, macro.FileLine(), "returns error when getting %d arg", idx) + err = errutil.WrapError(err, "returns error when getting %d arg", idx) + return nil, errutil.WrapError(err, "get arg list error: %v", arg) } if v.IsValid() { result = append(result, v) } } - return result, nil } -func (r *ArgList) getArg(ctx gs.ArgContext, arg gs.Arg, t reflect.Type, fileLine string) (reflect.Value, error) { - - var ( - err error - tag string - ) - - description := fmt.Sprintf("arg:\"%v\" %s", arg, fileLine) - syslog.Debug("get value %s", description) - defer func() { - if err == nil { - syslog.Debug("get value %s success", description) - } else { - syslog.Debug("get value %s error:%s", err.Error(), description) - } - }() - - switch g := arg.(type) { - case *Callable: - if results, err := g.Call(ctx); err != nil { - return reflect.Value{}, util.Wrapf(err, macro.FileLine(), "") - } else if len(results) < 1 { - return reflect.Value{}, errors.New("") - } else { - return results[0], nil - } - case ValueArg: - if g.v == nil { - return reflect.Zero(t), nil - } - return reflect.ValueOf(g.v), nil - case *OptionArg: - return g.call(ctx) - // case *gs_bean.BeanDefinition: - // tag = g.ID() - case string: - tag = g - default: - tag = util.TypeName(g) + ":" - } +// CallableFunc is a function that can be called. +type CallableFunc = interface{} - // binds properties value by the "value" tag. - if util.IsValueType(t) { - if tag == "" { - tag = "${}" - } - v := reflect.New(t).Elem() - if err = ctx.Bind(v, tag); err != nil { - return reflect.Value{}, err - } - return v, nil - } - - // wires dependent beans by the "autowire" tag. - if util.IsBeanReceiver(t) { - v := reflect.New(t).Elem() - if err = ctx.Wire(v, tag); err != nil { - return reflect.Value{}, err - } - return v, nil - } - - return reflect.Value{}, util.Errorf(macro.FileLine(), "error type %s", t.String()) -} - -// OptionArg Option 函数的参数绑定。 +// OptionArg represents a binding for an option function argument. type OptionArg struct { r *Callable - c gs.Condition + c []gs.Condition } -// Option 返回 Option 函数的参数绑定。 -func Option(fn interface{}, args ...gs.Arg) *OptionArg { +// Option creates a binding for an option function argument. +func Option(fn CallableFunc, args ...gs.Arg) *OptionArg { t := reflect.TypeOf(fn) if t.Kind() != reflect.Func || t.NumOut() != 1 { panic(errors.New("invalid option func")) } - r, err := Bind(fn, args, 1) - if err != nil { - panic(err) - } - return &OptionArg{r: r} + _, file, line, _ := runtime.Caller(1) + r := MustBind(fn, args...) + return &OptionArg{r: r.SetFileLine(file, line)} } -// On 设置一个 gs_cond.Condition 对象。 -func (arg *OptionArg) On(c gs.Condition) *OptionArg { - arg.c = c +// Condition sets a condition for invoking the option function. +func (arg *OptionArg) Condition(conditions ...gs.Condition) *OptionArg { + arg.c = append(arg.c, conditions...) return arg } -func (arg *OptionArg) call(ctx gs.ArgContext) (reflect.Value, error) { +func (arg *OptionArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) { - var ( - ok bool - err error - ) - - syslog.Debug("call option func %s", arg.r.fileLine) - defer func() { - if err == nil { - syslog.Debug("call option func success %s", arg.r.fileLine) - } else { - syslog.Debug("call option func error %s %s", err.Error(), arg.r.fileLine) - } - }() - - if arg.c != nil { - ok, err = ctx.Matches(arg.c) + // Checks if the condition is met. + for _, c := range arg.c { + ok, err := ctx.Matches(c) if err != nil { return reflect.Value{}, err } else if !ok { @@ -289,6 +272,7 @@ func (arg *OptionArg) call(ctx gs.ArgContext) (reflect.Value, error) { } } + // Calls the function and returns its result. out, err := arg.r.Call(ctx) if err != nil { return reflect.Value{}, err @@ -296,64 +280,44 @@ func (arg *OptionArg) call(ctx gs.ArgContext) (reflect.Value, error) { return out[0], nil } -// Callable wrappers a function and its binding arguments, then you can invoke -// the Call method of Callable to get the function's result. +// Callable wraps a function and its binding arguments. type Callable struct { - fn interface{} - fnType reflect.Type - argList *ArgList - fileLine string + fn CallableFunc // The function to be called. + fnType reflect.Type // The type of the function. + argList *ArgList // The argument list for the function. + fileLine string // File and line number where the function is defined. } -// MustBind 为 Option 方法绑定运行时参数。 -func MustBind(fn interface{}, args ...gs.Arg) *Callable { - r, err := Bind(fn, args, 1) +// MustBind binds arguments to a function and panics if an error occurs. +func MustBind(fn CallableFunc, args ...gs.Arg) *Callable { + r, err := Bind(fn, args) if err != nil { panic(err) } - return r + _, file, line, _ := runtime.Caller(1) + return r.SetFileLine(file, line) } -// Bind returns a Callable that wrappers a function and its binding arguments. -// The argument skip is the number of frames to skip over. -func Bind(fn interface{}, args []gs.Arg, skip int) (*Callable, error) { - +// Bind creates a Callable by binding arguments to a function. +func Bind(fn CallableFunc, args []gs.Arg) (*Callable, error) { fnType := reflect.TypeOf(fn) argList, err := NewArgList(fnType, args) if err != nil { return nil, err } - - _, file, line, _ := runtime.Caller(skip + 1) - r := &Callable{ - fn: fn, - fnType: fnType, - argList: argList, - fileLine: fmt.Sprintf("%s:%d", file, line), - } - return r, nil + return &Callable{fn: fn, fnType: fnType, argList: argList}, nil } -// Arg returns the ith binding argument. -func (r *Callable) Arg(i int) (gs.Arg, bool) { - if i >= len(r.argList.args) { - return nil, false - } - return r.argList.args[i], true -} - -func (r *Callable) In(i int) (reflect.Type, bool) { - if i >= r.fnType.NumIn() { - return nil, false - } - return r.fnType.In(i), true +// SetFileLine sets the file and line number of the function call. +func (r *Callable) SetFileLine(file string, line int) *Callable { + r.fileLine = fmt.Sprintf("%s:%d", file, line) + return r } -// Call invokes the function with its binding arguments processed in the IoC -// container. If the function returns an error, then the Call returns it. +// Call invokes the function with its bound arguments processed in the IoC container. func (r *Callable) Call(ctx gs.ArgContext) ([]reflect.Value, error) { - in, err := r.argList.get(ctx, r.fileLine) + in, err := r.argList.get(ctx) if err != nil { return nil, err } @@ -373,3 +337,13 @@ func (r *Callable) Call(ctx gs.ArgContext) ([]reflect.Value, error) { } return out, nil } + +func (r *Callable) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) { + if results, err := r.Call(ctx); err != nil { + return reflect.Value{}, err + } else if len(results) < 1 { + return reflect.Value{}, errors.New("xxx") + } else { + return results[0], nil + } +} diff --git a/gs/internal/gs_arg/arg_mock.go b/gs/internal/gs_arg/arg_mock.go deleted file mode 100644 index 9f6faf35..00000000 --- a/gs/internal/gs_arg/arg_mock.go +++ /dev/null @@ -1,108 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: arg.go -// -// Generated by this command: -// -// mockgen -build_flags="-mod=mod" -package=arg -source=arg.go -destination=arg_mock.go -// - -// Package arg is a generated GoMock package. -package gs_arg - -import ( - "reflect" - - "github.com/go-spring/spring-core/gs/internal/gs" - "go.uber.org/mock/gomock" -) - -// MockContext is a mock of Context interface. -type MockContext struct { - ctrl *gomock.Controller - recorder *MockContextMockRecorder - isgomock struct{} -} - -// MockContextMockRecorder is the mock recorder for MockContext. -type MockContextMockRecorder struct { - mock *MockContext -} - -// NewMockContext creates a new mock instance. -func NewMockContext(ctrl *gomock.Controller) *MockContext { - mock := &MockContext{ctrl: ctrl} - mock.recorder = &MockContextMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockContext) EXPECT() *MockContextMockRecorder { - return m.recorder -} - -// Bind mocks base method. -func (m *MockContext) Bind(v reflect.Value, tag string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Bind", v, tag) - ret0, _ := ret[0].(error) - return ret0 -} - -// Bind indicates an expected call of Bind. -func (mr *MockContextMockRecorder) Bind(v, tag any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bind", reflect.TypeOf((*MockContext)(nil).Bind), v, tag) -} - -// Matches mocks base method. -func (m *MockContext) Matches(c gs.Condition) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Matches", c) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Matches indicates an expected call of Matches. -func (mr *MockContextMockRecorder) Matches(c any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Matches", reflect.TypeOf((*MockContext)(nil).Matches), c) -} - -// Wire mocks base method. -func (m *MockContext) Wire(v reflect.Value, tag string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Wire", v, tag) - ret0, _ := ret[0].(error) - return ret0 -} - -// Wire indicates an expected call of Wire. -func (mr *MockContextMockRecorder) Wire(v, tag any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wire", reflect.TypeOf((*MockContext)(nil).Wire), v, tag) -} - -// MockArg is a mock of Arg interface. -type MockArg struct { - ctrl *gomock.Controller - recorder *MockArgMockRecorder - isgomock struct{} -} - -// MockArgMockRecorder is the mock recorder for MockArg. -type MockArgMockRecorder struct { - mock *MockArg -} - -// NewMockArg creates a new mock instance. -func NewMockArg(ctrl *gomock.Controller) *MockArg { - mock := &MockArg{ctrl: ctrl} - mock.recorder = &MockArgMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockArg) EXPECT() *MockArgMockRecorder { - return m.recorder -} diff --git a/gs/internal/gs_bean/bean.go b/gs/internal/gs_bean/bean.go index 594d3391..3b931e0a 100644 --- a/gs/internal/gs_bean/bean.go +++ b/gs/internal/gs_bean/bean.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,348 +14,306 @@ * limitations under the License. */ +// Package gs_bean provides the bean definition for the Go-Spring framework. package gs_bean import ( - "errors" "fmt" "reflect" - "runtime" - "github.com/go-spring/spring-core/conf" "github.com/go-spring/spring-core/gs/internal/gs" - "github.com/go-spring/spring-core/gs/internal/gs_cond" "github.com/go-spring/spring-core/util" ) -var refreshableType = reflect.TypeFor[gs.Refreshable]() - +// BeanStatus represents the different lifecycle statuses of a bean. type BeanStatus int8 const ( - Deleted = BeanStatus(-1) // 已删除 - Default = BeanStatus(iota) // 未处理 - Resolving // 正在决议 - Resolved // 已决议 - Creating // 正在创建 - Created // 已创建 - Wired // 注入完成 + StatusDeleted = BeanStatus(-1) // Bean has been deleted. + StatusDefault = BeanStatus(iota) // Default status of the bean. + StatusResolving // Bean is being resolved. + StatusResolved // Bean has been resolved. + StatusCreating // Bean is being created. + StatusCreated // Bean has been created. + StatusWired // Bean has been wired. ) -// GetStatusString 获取 bean 状态的字符串表示。 -func GetStatusString(status BeanStatus) string { +// String returns a human-readable string of the bean status. +func (status BeanStatus) String() string { switch status { - case Deleted: - return "Deleted" - case Default: - return "Default" - case Resolving: - return "Resolving" - case Resolved: - return "Resolved" - case Creating: - return "Creating" - case Created: - return "Created" - case Wired: - return "Wired" + case StatusDeleted: + return "deleted" + case StatusDefault: + return "default" + case StatusResolving: + return "resolving" + case StatusResolved: + return "resolved" + case StatusCreating: + return "creating" + case StatusCreated: + return "created" + case StatusWired: + return "wired" default: - return "" + return "unknown" } } -type BeanInit interface { - OnInit(ctx gs.Context) error -} - -type BeanDestroy interface { - OnDestroy() -} +// refreshableType is the [reflect.Type] of the interface [gs.Refreshable]. +var refreshableType = reflect.TypeFor[gs.Refreshable]() +// BeanMetadata holds the metadata information of a bean. type BeanMetadata struct { - f gs.Callable // 构造函数 - cond gs.Condition // 判断条件 - init interface{} // 初始化函数 - destroy interface{} // 销毁函数 - depends []gs.BeanSelector // 间接依赖项 - exports []reflect.Type // 导出的接口 - file string // 注册点所在文件 - line int // 注册点所在行数 - status BeanStatus // 状态 + f gs.Callable // Callable for the bean (ctor or method). + init gs.BeanInitFunc // Initialization function for the bean. + destroy gs.BeanDestroyFunc // Destruction function for the bean. + dependsOn []gs.BeanSelectorInterface // List of dependencies for the bean. + exports []reflect.Type // List of exported types for the bean. + conditions []gs.Condition // List of conditions for the bean. + status BeanStatus // Current status of the bean. - configuration gs.ConfigurationParam + file string // The file where the bean is defined. + line int // The line number in the file where the bean is defined. - enableRefresh bool - refreshParam conf.BindParam -} + configurationBean bool // Whether the bean is a configuration bean. + configurationParam gs.ConfigurationParam // Configuration parameters for the bean. -func (d *BeanMetadata) SetStatus(status BeanStatus) { - d.status = status + refreshable bool // Whether the bean can be refreshed. + refreshTag string // Refresh tag for the bean. } -func (d *BeanMetadata) Cond() gs.Condition { - return d.cond +// validLifeCycleFunc checks whether the provided function is a valid lifecycle function. +func validLifeCycleFunc(fnType reflect.Type, beanType reflect.Type) bool { + if !util.IsFuncType(fnType) || fnType.NumIn() != 1 { + return false + } + if t := fnType.In(0); t.Kind() == reflect.Interface { + if !beanType.Implements(t) { + return false + } + } else if t != beanType { + return false + } + return util.ReturnNothing(fnType) || util.ReturnOnlyError(fnType) } -func (d *BeanMetadata) Init() interface{} { +// Init returns the initialization function of the bean. +func (d *BeanMetadata) Init() gs.BeanInitFunc { return d.init } -func (d *BeanMetadata) Destroy() interface{} { - return d.destroy +// SetInit sets the initialization function for the bean. +func (d *BeanDefinition) SetInit(fn gs.BeanInitFunc) { + if validLifeCycleFunc(reflect.TypeOf(fn), d.Type()) { + d.init = fn + return + } + panic("init should be func(bean) or func(bean)error") } -func (d *BeanMetadata) Depends() []gs.BeanSelector { - return d.depends +// Destroy returns the destruction function of the bean. +func (d *BeanMetadata) Destroy() gs.BeanDestroyFunc { + return d.destroy } -func (d *BeanMetadata) Exports() []reflect.Type { - return d.exports +// SetDestroy sets the destruction function for the bean. +func (d *BeanDefinition) SetDestroy(fn gs.BeanDestroyFunc) { + if validLifeCycleFunc(reflect.TypeOf(fn), d.Type()) { + d.destroy = fn + return + } + panic("destroy should be func(bean) or func(bean)error") } -func (d *BeanMetadata) IsConfiguration() bool { - return d.configuration.Enable +// DependsOn returns the list of dependencies for the bean. +func (d *BeanMetadata) DependsOn() []gs.BeanSelectorInterface { + return d.dependsOn } -func (d *BeanMetadata) GetIncludeMethod() []string { - return d.configuration.Include +// SetDependsOn sets the list of dependencies for the bean. +func (d *BeanDefinition) SetDependsOn(selectors ...gs.BeanSelectorInterface) { + d.dependsOn = append(d.dependsOn, selectors...) } -func (d *BeanMetadata) GetExcludeMethod() []string { - return d.configuration.Exclude +// Exports returns the list of exported types for the bean. +func (d *BeanMetadata) Exports() []reflect.Type { + return d.exports } -func (d *BeanMetadata) EnableRefresh() bool { - return d.enableRefresh +// SetExport sets the exported interfaces for the bean. +func (d *BeanDefinition) SetExport(exports ...reflect.Type) { + for _, t := range exports { + if t.Kind() != reflect.Interface { + panic("only interface type can be exported") + } + exported := false + for _, export := range d.exports { + if t == export { + exported = true + break + } + } + if exported { + continue + } + d.exports = append(d.exports, t) + } } -func (d *BeanMetadata) RefreshParam() conf.BindParam { - return d.refreshParam +// Conditions returns the list of conditions for the bean. +func (d *BeanMetadata) Conditions() []gs.Condition { + return d.conditions } -func (d *BeanMetadata) File() string { - return d.file +// SetCondition adds a condition to the list of conditions for the bean. +func (d *BeanDefinition) SetCondition(conditions ...gs.Condition) { + d.conditions = append(d.conditions, conditions...) } -func (d *BeanMetadata) Line() int { - return d.line +// ConfigurationBean returns whether the bean is a configuration bean. +func (d *BeanMetadata) ConfigurationBean() bool { + return d.configurationBean } -// FileLine 返回 bean 的注册点。 -func (d *BeanMetadata) FileLine() string { - return fmt.Sprintf("%s:%d", d.file, d.line) +// ConfigurationParam returns the configuration parameters for the bean. +func (d *BeanMetadata) ConfigurationParam() gs.ConfigurationParam { + return d.configurationParam } -// Class 返回 bean 的类型描述。 -func (d *BeanMetadata) Class() string { - if d.f == nil { - return "object bean" +// SetConfiguration sets the configuration flag and parameters for the bean. +func (d *BeanDefinition) SetConfiguration(param ...gs.ConfigurationParam) { + d.configurationBean = true + if len(param) == 0 { + return + } + x := param[0] + if len(x.Includes) > 0 { + d.configurationParam.Includes = x.Includes + } + if len(x.Excludes) > 0 { + d.configurationParam.Excludes = x.Excludes } - return "constructor bean" } -type BeanRuntime struct { - v reflect.Value // 值 - t reflect.Type // 类型 - name string // 名称 - typeName string // 原始类型的全限定名 - primary bool // 是否为主版本 +// Refreshable returns whether the bean is refreshable. +func (d *BeanMetadata) Refreshable() bool { + return d.refreshable } -// ID 返回 bean 的 ID 。 -func (d *BeanRuntime) ID() string { - return d.typeName + ":" + d.name +// RefreshTag returns the refresh tag of the bean. +func (d *BeanMetadata) RefreshTag() string { + return d.refreshTag } -// Name 返回 bean 的名称。 -func (d *BeanRuntime) Name() string { - return d.name +// SetRefreshable sets the refreshable flag and tag for the bean. +func (d *BeanDefinition) SetRefreshable(tag string) { + if !d.Type().Implements(refreshableType) { + panic("must implement gs.Refreshable interface") + } + d.refreshable = true + d.refreshTag = tag } -// TypeName 返回 bean 的原始类型的全限定名。 -func (d *BeanRuntime) TypeName() string { - return d.typeName +// FileLine returns the file and line number for the bean. +func (d *BeanMetadata) FileLine() (string, int) { + return d.file, d.line } -func (d *BeanRuntime) Callable() gs.Callable { - return nil +// SetFileLine sets the file and line number for the bean. +func (d *BeanDefinition) SetFileLine(file string, line int) { + d.file, d.line = file, line } -// Interface 返回 bean 的真实值。 -func (d *BeanRuntime) Interface() interface{} { - return d.v.Interface() +// BeanRuntime holds runtime information about the bean. +type BeanRuntime struct { + v reflect.Value // The value of the bean. + t reflect.Type // The type of the bean. + name string // The name of the bean. } -func (d *BeanRuntime) IsPrimary() bool { - return d.primary +// Name returns the name of the bean. +func (d *BeanRuntime) Name() string { + return d.name } +// Type returns the type of the bean. func (d *BeanRuntime) Type() reflect.Type { return d.t } +// Value returns the value of the bean as [reflect.Value]. func (d *BeanRuntime) Value() reflect.Value { return d.v } -func (d *BeanRuntime) Status() BeanStatus { - return Wired +// Interface returns the underlying value of the bean. +func (d *BeanRuntime) Interface() interface{} { + return d.v.Interface() } -func (d *BeanRuntime) Match(typeName string, beanName string) bool { - - typeIsSame := false - if typeName == "" || d.typeName == typeName { - typeIsSame = true - } - - nameIsSame := false - if beanName == "" || d.name == beanName { - nameIsSame = true - } +// Callable returns the callable for the bean. +func (d *BeanRuntime) Callable() gs.Callable { + return nil +} - return typeIsSame && nameIsSame +// Status returns the current status of the bean. +func (d *BeanRuntime) Status() BeanStatus { + return StatusWired } +// String returns a string representation of the bean. func (d *BeanRuntime) String() string { return d.name } -// BeanDefinition bean 元数据。 +// BeanDefinition contains both metadata and runtime information of a bean. type BeanDefinition struct { *BeanMetadata *BeanRuntime } -func (d *BeanDefinition) Callable() gs.Callable { - return d.f -} - -func (d *BeanDefinition) Status() BeanStatus { - return d.status +// NewBean creates a new bean definition. +func NewBean(t reflect.Type, v reflect.Value, f gs.Callable, name string) *BeanDefinition { + return &BeanDefinition{ + BeanMetadata: &BeanMetadata{ + f: f, + status: StatusDefault, + }, + BeanRuntime: &BeanRuntime{ + t: t, + v: v, + name: name, + }, + } } -func (d *BeanDefinition) String() string { - return fmt.Sprintf("%s name:%q %s", d.Class(), d.name, d.FileLine()) +// Callable returns the callable for the bean. +func (d *BeanDefinition) Callable() gs.Callable { + return d.f } -// SetName 设置 bean 的名称。 +// SetName sets the name of the bean. func (d *BeanDefinition) SetName(name string) { d.name = name } -func (d *BeanDefinition) SetCaller(skip int) { - _, d.file, d.line, _ = runtime.Caller(skip) -} - -// SetOn 设置 bean 的 Condition。 -func (d *BeanDefinition) SetOn(cond gs.Condition) { - if d.cond == nil { - d.cond = cond - } else { - d.cond = gs_cond.And(d.cond, cond) - } -} - -// SetDependsOn 设置 bean 的间接依赖项。 -func (d *BeanDefinition) SetDependsOn(selectors ...gs.BeanSelector) { - d.depends = append(d.depends, selectors...) -} - -// SetPrimary 设置 bean 为主版本。 -func (d *BeanDefinition) SetPrimary() { - d.primary = true -} - -// validLifeCycleFunc 判断是否是合法的用于 bean 生命周期控制的函数,生命周期函数 -// 的要求:只能有一个入参并且必须是 bean 的类型,没有返回值或者只返回 error 类型值。 -func validLifeCycleFunc(fnType reflect.Type, beanValue reflect.Value) bool { - if !util.IsFuncType(fnType) { - return false - } - if fnType.NumIn() != 1 || !util.HasReceiver(fnType, beanValue) { - return false - } - return util.ReturnNothing(fnType) || util.ReturnOnlyError(fnType) -} - -// SetInit 设置 bean 的初始化函数。 -func (d *BeanDefinition) SetInit(fn interface{}) { - if validLifeCycleFunc(reflect.TypeOf(fn), d.Value()) { - d.init = fn - return - } - panic(errors.New("init should be func(bean) or func(bean)error")) -} - -// SetDestroy 设置 bean 的销毁函数。 -func (d *BeanDefinition) SetDestroy(fn interface{}) { - if validLifeCycleFunc(reflect.TypeOf(fn), d.Value()) { - d.destroy = fn - return - } - panic(errors.New("destroy should be func(bean) or func(bean)error")) -} - -// SetExport 设置 bean 的导出接口。 -func (d *BeanDefinition) SetExport(exports ...interface{}) { - for _, o := range exports { - t, ok := o.(reflect.Type) - if !ok { - t = reflect.TypeOf(o) - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - } - if t.Kind() != reflect.Interface { - panic(errors.New("only interface type can be exported")) - } - exported := false - for _, export := range d.exports { - if t == export { - exported = true - break - } - } - if exported { - continue - } - d.exports = append(d.exports, t) - } +// Status returns the current status of the bean. +func (d *BeanDefinition) Status() BeanStatus { + return d.status } -func (d *BeanDefinition) SetConfiguration(param ...gs.ConfigurationParam) { - if len(param) > 0 { - d.configuration = param[0] - } - d.configuration.Enable = true +// SetStatus sets the current status of the bean. +func (d *BeanMetadata) SetStatus(status BeanStatus) { + d.status = status } -func (d *BeanDefinition) SetEnableRefresh(tag string) { - if !d.Type().Implements(refreshableType) { - panic(errors.New("must implement dync.Refreshable interface")) - } - d.enableRefresh = true - err := d.refreshParam.BindTag(tag, "") - if err != nil { - panic(err) - } +// TypeAndName returns the type and name of the bean. +func (d *BeanDefinition) TypeAndName() (reflect.Type, string) { + return d.Type(), d.Name() } -// NewBean 普通函数注册时需要使用 reflect.ValueOf(fn) 形式以避免和构造函数发生冲突。 -func NewBean(t reflect.Type, v reflect.Value, f gs.Callable, name string, file string, line int) *BeanDefinition { - return &BeanDefinition{ - BeanMetadata: &BeanMetadata{ - f: f, - file: file, - line: line, - status: Default, - }, - BeanRuntime: &BeanRuntime{ - t: t, - v: v, - name: name, - typeName: util.TypeName(t), - }, - } +// String returns a string representation of the bean. +func (d *BeanDefinition) String() string { + return fmt.Sprintf("name:%q %s:%d", d.name, d.file, d.line) } diff --git a/gs/internal/gs_cond/cond.go b/gs/internal/gs_cond/cond.go index cde1f5d5..e1ecf899 100755 --- a/gs/internal/gs_cond/cond.go +++ b/gs/internal/gs_cond/cond.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,71 +14,101 @@ * limitations under the License. */ -//go:generate mockgen -build_flags="-mod=mod" -package=cond -source=cond.go -destination=cond_mock.go - -// Package gs_cond provides many conditions used when registering bean. +// Package gs_cond provides a set of conditions that can be used for evaluating and +// combining logical conditions in a flexible way. package gs_cond import ( - "errors" "fmt" "strconv" "strings" "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/errutil" ) -type FuncCond func(ctx gs.CondContext) (bool, error) +/********************************* OnFunc ************************************/ -func (c FuncCond) Matches(ctx gs.CondContext) (bool, error) { - return c(ctx) +// onFunc is an implementation of [gs.Condition] that wraps a function. +// It allows a condition to be evaluated based on the result of a function. +type onFunc struct { + fn gs.CondFunc } -// OK returns a Condition that always returns true. -func OK() gs.Condition { - return FuncCond(func(ctx gs.CondContext) (bool, error) { - return true, nil - }) +// OnFunc creates a Conditional that evaluates using a custom function. +func OnFunc(fn gs.CondFunc) gs.Condition { + return &onFunc{fn: fn} } -// not is a Condition that negating to another. -type not struct { - c gs.Condition +func (c *onFunc) Matches(ctx gs.CondContext) (bool, error) { + ok, err := c.fn(ctx) + if err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", c) + } + return ok, nil } -// Not returns a Condition that negating to another. -func Not(c gs.Condition) gs.Condition { - return ¬{c: c} +func (c *onFunc) String() string { + _, _, fnName := util.FileLine(c.fn) + return fmt.Sprintf("OnFunc(fn=%s)", fnName) } -func (c *not) Matches(ctx gs.CondContext) (bool, error) { - ok, err := c.c.Matches(ctx) - return !ok, err +/******************************* OnProperty **********************************/ + +// OnPropertyInterface defines the methods for evaluating a condition based on a property. +// This interface provides flexibility for matching missing properties and checking their values. +type OnPropertyInterface interface { + gs.Condition + MatchIfMissing() OnPropertyInterface + HavingValue(s string) OnPropertyInterface } -// onProperty is a Condition that checks a property and its value. +// onProperty evaluates a condition based on the existence and value of a property +// in the context. It allows for complex matching behaviors such as matching missing +// properties or evaluating expressions. type onProperty struct { - name string - havingValue string - matchIfMissing bool + name string // The name of the property to check. + havingValue string // The expected value or expression to match. + matchIfMissing bool // Whether to match if the property is missing. +} + +// OnProperty creates a condition based on the presence and value of a specified property. +func OnProperty(name string) OnPropertyInterface { + return &onProperty{name: name} +} + +// MatchIfMissing sets the condition to match if the property is missing. +func (c *onProperty) MatchIfMissing() OnPropertyInterface { + c.matchIfMissing = true + return c +} + +// HavingValue sets the expected value or expression to match. +func (c *onProperty) HavingValue(s string) OnPropertyInterface { + c.havingValue = s + return c } func (c *onProperty) Matches(ctx gs.CondContext) (bool, error) { + // If the context doesn't have the property, handle accordingly. if !ctx.Has(c.name) { return c.matchIfMissing, nil } + // If there's no expected value to match, simply return true (property exists). if c.havingValue == "" { return true, nil } + // Retrieve the property's value and compare it with the expected value. val := ctx.Prop(c.name) if !strings.HasPrefix(c.havingValue, "expr:") { return val == c.havingValue, nil } + // If the expected value is an expression, evaluate it. getValue := func(val string) interface{} { if b, err := strconv.ParseBool(val); err == nil { return b @@ -94,331 +124,290 @@ func (c *onProperty) Matches(ctx gs.CondContext) (bool, error) { } return val } - return evalExpr(c.havingValue[5:], getValue(val)) -} -// onMissingProperty is a Condition that returns true when a property doesn't exist. -type onMissingProperty struct { - name string + // Evaluate the expression and return the result. + ok, err := EvalExpr(c.havingValue[5:], getValue(val)) + if err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", c) + } + return ok, nil } -func (c *onMissingProperty) Matches(ctx gs.CondContext) (bool, error) { - return !ctx.Has(c.name), nil +func (c *onProperty) String() string { + var sb strings.Builder + sb.WriteString("OnProperty(name=") + sb.WriteString(c.name) + if c.havingValue != "" { + sb.WriteString(", havingValue=") + sb.WriteString(c.havingValue) + } + if c.matchIfMissing { + sb.WriteString(", matchIfMissing=true") + } + sb.WriteString(")") + return sb.String() } -// onBean is a Condition that returns true when finding more than one beans. -type onBean struct { - selector gs.BeanSelector -} +/*************************** OnMissingProperty *******************************/ -func (c *onBean) Matches(ctx gs.CondContext) (bool, error) { - beans, err := ctx.Find(c.selector) - return len(beans) > 0, err +// onMissingProperty is a condition that matches when a specified property is +// absent from the context. +type onMissingProperty struct { + name string // The name of the property to check for absence. } -// onMissingBean is a Condition that returns true when finding no beans. -type onMissingBean struct { - selector gs.BeanSelector +// OnMissingProperty creates a condition that matches if the specified property is missing. +func OnMissingProperty(name string) gs.Condition { + return &onMissingProperty{name: name} } -func (c *onMissingBean) Matches(ctx gs.CondContext) (bool, error) { - beans, err := ctx.Find(c.selector) - return len(beans) == 0, err +func (c *onMissingProperty) Matches(ctx gs.CondContext) (bool, error) { + return !ctx.Has(c.name), nil } -// onSingleBean is a Condition that returns true when finding only one bean. -type onSingleBean struct { - selector gs.BeanSelector +func (c *onMissingProperty) String() string { + return fmt.Sprintf("OnMissingProperty(name=%s)", c.name) } -func (c *onSingleBean) Matches(ctx gs.CondContext) (bool, error) { - beans, err := ctx.Find(c.selector) - return len(beans) == 1, err -} +/********************************* OnBean ************************************/ -// onExpression is a Condition that returns true when an expression returns true. -type onExpression struct { - expression string +// onBean checks for the existence of beans that match a selector. +// It returns true if at least one bean matches the selector, and false otherwise. +type onBean struct { + s gs.BeanSelectorInterface // The selector used to match beans in the context. } -func (c *onExpression) Matches(ctx gs.CondContext) (bool, error) { - return false, util.UnimplementedMethod +// OnBean creates a condition that evaluates to true if at least one bean matches the provided selector. +func OnBean(s gs.BeanSelectorInterface) gs.Condition { + return &onBean{s: s} } -// Operator defines operation between conditions, including Or、And、None. -type Operator int - -const ( - opOr = Operator(iota) // at least one of the conditions must be met. - opAnd // all conditions must be met. - opNone // all conditions must be not met. -) - -// group is a Condition implemented by operation of Condition(s). -type group struct { - op Operator - cond []gs.Condition +func (c *onBean) Matches(ctx gs.CondContext) (bool, error) { + beans, err := ctx.Find(c.s) + if err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", c) + } + return len(beans) > 0, nil } -func Or(cond ...gs.Condition) gs.Condition { - return &group{op: opOr, cond: cond} +func (c *onBean) String() string { + return fmt.Sprintf("OnBean(selector=%s)", c.s) } -func And(cond ...gs.Condition) gs.Condition { - return &group{op: opAnd, cond: cond} -} +/***************************** OnMissingBean *********************************/ -func None(cond ...gs.Condition) gs.Condition { - return &group{op: opNone, cond: cond} +// onMissingBean checks for the absence of beans matching a selector. +// It returns true if no beans match the selector, and false otherwise. +type onMissingBean struct { + s gs.BeanSelectorInterface // The selector used to find beans. } -func (g *group) matchesOr(ctx gs.CondContext) (bool, error) { - for _, c := range g.cond { - if ok, err := c.Matches(ctx); err != nil { - return false, err - } else if ok { - return true, nil - } - } - return false, nil +// OnMissingBean creates a condition that evaluates to true if no beans match the provided selector. +func OnMissingBean(s gs.BeanSelectorInterface) gs.Condition { + return &onMissingBean{s: s} } -func (g *group) matchesAnd(ctx gs.CondContext) (bool, error) { - for _, c := range g.cond { - if ok, err := c.Matches(ctx); err != nil { - return false, err - } else if !ok { - return false, nil - } +func (c *onMissingBean) Matches(ctx gs.CondContext) (bool, error) { + beans, err := ctx.Find(c.s) + if err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", c) } - return true, nil + return len(beans) == 0, nil } -func (g *group) matchesNone(ctx gs.CondContext) (bool, error) { - for _, c := range g.cond { - if ok, err := c.Matches(ctx); err != nil { - return false, err - } else if ok { - return false, nil - } - } - return true, nil +func (c *onMissingBean) String() string { + return fmt.Sprintf("OnMissingBean(selector=%s)", c.s) } -// Matches evaluates the group of conditions based on the specified operator. -// - If the operator is Or, it returns true if at least one condition is satisfied. -// - If the operator is And, it returns true if all conditions are satisfied. -// - If the operator is None, it returns true if none of the conditions are satisfied. -func (g *group) Matches(ctx gs.CondContext) (bool, error) { - if len(g.cond) == 0 { - return false, errors.New("no conditions in group") - } - switch g.op { - case opOr: - return g.matchesOr(ctx) - case opAnd: - return g.matchesAnd(ctx) - case opNone: - return g.matchesNone(ctx) - default: - return false, fmt.Errorf("error condition operator %d", g.op) - } -} +/***************************** OnSingleBean **********************************/ -// node is a Condition implemented by link of Condition(s). -type node struct { - cond gs.Condition - op Operator - next *node +// onSingleBean checks if exactly one matching bean exists in the context. +// It returns true if exactly one bean matches the selector, and false otherwise. +type onSingleBean struct { + s gs.BeanSelectorInterface // The selector used to find beans. } -func (n *node) Matches(ctx gs.CondContext) (bool, error) { - - if n.cond == nil { - return true, nil - } +// OnSingleBean creates a condition that evaluates to true if exactly one bean matches the provided selector. +func OnSingleBean(s gs.BeanSelectorInterface) gs.Condition { + return &onSingleBean{s: s} +} - ok, err := n.cond.Matches(ctx) +func (c *onSingleBean) Matches(ctx gs.CondContext) (bool, error) { + beans, err := ctx.Find(c.s) if err != nil { - return false, err + return false, errutil.WrapError(err, "condition matches error: %s", c) } + return len(beans) == 1, nil +} - if n.next == nil { - return ok, nil - } else if n.next.cond == nil { - return false, errors.New("no condition in last node") - } +func (c *onSingleBean) String() string { + return fmt.Sprintf("OnSingleBean(selector=%s)", c.s) +} - switch n.op { - case opOr: - if ok { - return ok, nil - } else { - return n.next.Matches(ctx) - } - case opAnd: - if ok { - return n.next.Matches(ctx) - } else { - return false, nil - } - } +/***************************** OnExpression **********************************/ - return false, fmt.Errorf("error condition operator %d", n.op) +// onExpression evaluates a custom expression within the context. The expression should +// return true or false, and the evaluation is expected to happen within the context. +type onExpression struct { + expression string // The string expression to evaluate. } -// Conditional is a Condition implemented by link of Condition(s). -type Conditional struct { - head *node - curr *node +// OnExpression creates a condition that evaluates based on a custom string expression. +// The expression is expected to return true or false. +func OnExpression(expression string) gs.Condition { + return &onExpression{expression: expression} } -// New returns a Condition implemented by link of Condition(s). -func New() *Conditional { - n := &node{} - return &Conditional{head: n, curr: n} +func (c *onExpression) Matches(ctx gs.CondContext) (bool, error) { + err := util.UnimplementedMethod + return false, errutil.WrapError(err, "condition matches error: %s", c) } -func (c *Conditional) Matches(ctx gs.CondContext) (bool, error) { - return c.head.Matches(ctx) +func (c *onExpression) String() string { + return fmt.Sprintf("OnExpression(expression=%s)", c.expression) } -// Or sets a Or operator. -func (c *Conditional) Or() *Conditional { - n := &node{} - c.curr.op = opOr - c.curr.next = n - c.curr = n - return c -} +/********************************** Not ***************************************/ -// And sets a And operator. -func (c *Conditional) And() *Conditional { - n := &node{} - c.curr.op = opAnd - c.curr.next = n - c.curr = n - return c +// onNot is a condition that negates another condition. It returns true if the wrapped +// condition evaluates to false, and false if the wrapped condition evaluates to true. +type onNot struct { + c gs.Condition // The condition to negate. } -// On returns a Conditional that starts with one Condition. -func On(cond gs.Condition) *Conditional { - return New().On(cond) +// Not creates a condition that inverts the result of the provided condition. +func Not(c gs.Condition) gs.Condition { + return &onNot{c: c} } -// On adds one Condition. -func (c *Conditional) On(cond gs.Condition) *Conditional { - if c.curr.cond != nil { - c.And() +func (c *onNot) Matches(ctx gs.CondContext) (bool, error) { + ok, err := c.c.Matches(ctx) + if err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", c) } - c.curr.cond = cond - return c + return !ok, nil } -type PropertyOption func(*onProperty) - -// MatchIfMissing sets a Condition to return true when property doesn't exist. -func MatchIfMissing() PropertyOption { - return func(c *onProperty) { - c.matchIfMissing = true - } +func (c *onNot) String() string { + return fmt.Sprintf("Not(%s)", c.c) } -// HavingValue sets a Condition to return true when property value equals to havingValue. -func HavingValue(havingValue string) PropertyOption { - return func(c *onProperty) { - c.havingValue = havingValue - } -} +/********************************** Or ***************************************/ -// OnProperty returns a Conditional that starts with a Condition that checks a property -// and its value. -func OnProperty(name string, options ...PropertyOption) *Conditional { - return New().OnProperty(name, options...) +// onOr is a condition that combines multiple conditions with an OR operator. +// It evaluates to true if at least one condition is satisfied. +type onOr struct { + conditions []gs.Condition // The list of conditions to evaluate with OR. } -// OnProperty adds a Condition that checks a property and its value. -func (c *Conditional) OnProperty(name string, options ...PropertyOption) *Conditional { - cond := &onProperty{name: name} - for _, option := range options { - option(cond) +// Or combines multiple conditions with an OR operator, returning true if at +// least one condition is satisfied. +func Or(conditions ...gs.Condition) gs.Condition { + if n := len(conditions); n == 0 { + return nil + } else if n == 1 { + return conditions[0] } - return c.On(cond) + return &onOr{conditions: conditions} } -// OnMissingProperty returns a Conditional that starts with a Condition that returns -// true when property doesn't exist. -func OnMissingProperty(name string) *Conditional { - return New().OnMissingProperty(name) +func (g *onOr) Matches(ctx gs.CondContext) (bool, error) { + for _, c := range g.conditions { + if ok, err := c.Matches(ctx); err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", g) + } else if ok { + return true, nil + } + } + return false, nil } -// OnMissingProperty adds a Condition that returns true when property doesn't exist. -func (c *Conditional) OnMissingProperty(name string) *Conditional { - return c.On(&onMissingProperty{name: name}) +func (g *onOr) String() string { + return FormatGroup("Or", g.conditions) } -// OnBean returns a Conditional that starts with a Condition that returns true when -// finding more than one beans. -func OnBean(selector gs.BeanSelector) *Conditional { - return New().OnBean(selector) -} +/********************************* And ***************************************/ -// OnBean adds a Condition that returns true when finding more than one beans. -func (c *Conditional) OnBean(selector gs.BeanSelector) *Conditional { - return c.On(&onBean{selector: selector}) +// onAnd is a condition that combines multiple conditions with an AND operator. +// It evaluates to true only if all conditions are satisfied. +type onAnd struct { + conditions []gs.Condition // The list of conditions to evaluate with AND. } -// OnMissingBean returns a Conditional that starts with a Condition that returns -// true when finding no beans. -func OnMissingBean(selector gs.BeanSelector) *Conditional { - return New().OnMissingBean(selector) +// And combines multiple conditions with an AND operator, returning true if +// all conditions are satisfied. +func And(conditions ...gs.Condition) gs.Condition { + if n := len(conditions); n == 0 { + return nil + } else if n == 1 { + return conditions[0] + } + return &onAnd{conditions: conditions} } -// OnMissingBean adds a Condition that returns true when finding no beans. -func (c *Conditional) OnMissingBean(selector gs.BeanSelector) *Conditional { - return c.On(&onMissingBean{selector: selector}) +func (g *onAnd) Matches(ctx gs.CondContext) (bool, error) { + for _, c := range g.conditions { + ok, err := c.Matches(ctx) + if err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", g) + } else if !ok { + return false, nil + } + } + return true, nil } -// OnSingleBean returns a Conditional that starts with a Condition that returns -// true when finding only one bean. -func OnSingleBean(selector gs.BeanSelector) *Conditional { - return New().OnSingleBean(selector) +func (g *onAnd) String() string { + return FormatGroup("And", g.conditions) } -// OnSingleBean adds a Condition that returns true when finding only one bean. -func (c *Conditional) OnSingleBean(selector gs.BeanSelector) *Conditional { - return c.On(&onSingleBean{selector: selector}) -} +/********************************** None *************************************/ -// OnExpression returns a Conditional that starts with a Condition that returns -// true when an expression returns true. -func OnExpression(expression string) *Conditional { - return New().OnExpression(expression) +// onNone is a condition that combines multiple conditions with a NONE operator. +// It evaluates to true only if none of the conditions are satisfied. +type onNone struct { + conditions []gs.Condition // The list of conditions to evaluate with NONE. } -// OnExpression adds a Condition that returns true when an expression returns true. -func (c *Conditional) OnExpression(expression string) *Conditional { - return c.On(&onExpression{expression: expression}) +// None combines multiple conditions with a NONE operator, returning true if +// none of the conditions are satisfied. +func None(conditions ...gs.Condition) gs.Condition { + if n := len(conditions); n == 0 { + return nil + } else if n == 1 { + return Not(conditions[0]) + } + return &onNone{conditions: conditions} } -// OnMatches returns a Conditional that starts with a Condition that returns true -// when function returns true. -func OnMatches(fn func(ctx gs.CondContext) (bool, error)) *Conditional { - return New().OnMatches(fn) +func (g *onNone) Matches(ctx gs.CondContext) (bool, error) { + for _, c := range g.conditions { + if ok, err := c.Matches(ctx); err != nil { + return false, errutil.WrapError(err, "condition matches error: %s", g) + } else if ok { + return false, nil + } + } + return true, nil } -// OnMatches adds a Condition that returns true when function returns true. -func (c *Conditional) OnMatches(fn func(ctx gs.CondContext) (bool, error)) *Conditional { - return c.On(FuncCond(fn)) +func (g *onNone) String() string { + return FormatGroup("None", g.conditions) } -// OnProfile returns a Conditional that starts with a Condition that returns true -// when property value equals to profile. -func OnProfile(profile string) *Conditional { - return New().OnProfile(profile) -} +/******************************* utilities ***********************************/ -// OnProfile adds a Condition that returns true when property value equals to profile. -func (c *Conditional) OnProfile(profile string) *Conditional { - return c.OnProperty("spring.profiles.active", HavingValue(profile)) +// FormatGroup generates a formatted string for a group of conditions (AND, OR, NONE). +func FormatGroup(op string, conditions []gs.Condition) string { + var sb strings.Builder + sb.WriteString(op) + sb.WriteString("(") + for i, c := range conditions { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(fmt.Sprint(c)) + } + sb.WriteString(")") + return sb.String() } diff --git a/gs/internal/gs_cond/cond_mock.go b/gs/internal/gs_cond/cond_mock.go deleted file mode 100644 index 07285c89..00000000 --- a/gs/internal/gs_cond/cond_mock.go +++ /dev/null @@ -1,129 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: cond.go -// -// Generated by this command: -// -// mockgen -build_flags="-mod=mod" -package=cond -source=cond.go -destination=cond_mock.go -// - -// Package cond is a generated GoMock package. -package gs_cond - -import ( - "reflect" - - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/gs/internal/gs" - "go.uber.org/mock/gomock" -) - -// MockContext is a mock of Context interface. -type MockContext struct { - ctrl *gomock.Controller - recorder *MockContextMockRecorder - isgomock struct{} -} - -// MockContextMockRecorder is the mock recorder for MockContext. -type MockContextMockRecorder struct { - mock *MockContext -} - -// NewMockContext creates a new mock instance. -func NewMockContext(ctrl *gomock.Controller) *MockContext { - mock := &MockContext{ctrl: ctrl} - mock.recorder = &MockContextMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockContext) EXPECT() *MockContextMockRecorder { - return m.recorder -} - -// Find mocks base method. -func (m *MockContext) Find(selector gs.BeanSelector) ([]gs.CondBean, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Find", selector) - ret0, _ := ret[0].([]gs.CondBean) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Find indicates an expected call of Find. -func (mr *MockContextMockRecorder) Find(selector any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockContext)(nil).Find), selector) -} - -// Has mocks base method. -func (m *MockContext) Has(key string) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Has", key) - ret0, _ := ret[0].(bool) - return ret0 -} - -// Has indicates an expected call of Has. -func (mr *MockContextMockRecorder) Has(key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockContext)(nil).Has), key) -} - -// Prop mocks base method. -func (m *MockContext) Prop(key string, opts ...conf.GetOption) string { - m.ctrl.T.Helper() - varargs := []any{key} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "Prop", varargs...) - ret0, _ := ret[0].(string) - return ret0 -} - -// Prop indicates an expected call of Prop. -func (mr *MockContextMockRecorder) Prop(key any, opts ...any) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]any{key}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prop", reflect.TypeOf((*MockContext)(nil).Prop), varargs...) -} - -// MockCondition is a mock of Condition interface. -type MockCondition struct { - ctrl *gomock.Controller - recorder *MockConditionMockRecorder - isgomock struct{} -} - -// MockConditionMockRecorder is the mock recorder for MockCondition. -type MockConditionMockRecorder struct { - mock *MockCondition -} - -// NewMockCondition creates a new mock instance. -func NewMockCondition(ctrl *gomock.Controller) *MockCondition { - mock := &MockCondition{ctrl: ctrl} - mock.recorder = &MockConditionMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockCondition) EXPECT() *MockConditionMockRecorder { - return m.recorder -} - -// Matches mocks base method. -func (m *MockCondition) Matches(ctx gs.CondContext) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Matches", ctx) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Matches indicates an expected call of Matches. -func (mr *MockConditionMockRecorder) Matches(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Matches", reflect.TypeOf((*MockCondition)(nil).Matches), ctx) -} diff --git a/gs/internal/gs_cond/cond_test.go b/gs/internal/gs_cond/cond_test.go index 44f7fc73..2b1f940d 100644 --- a/gs/internal/gs_cond/cond_test.go +++ b/gs/internal/gs_cond/cond_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,451 +17,73 @@ package gs_cond_test import ( - "errors" + "fmt" "testing" "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/gs/internal/gs_cond" "github.com/go-spring/spring-core/util/assert" - "go.uber.org/mock/gomock" ) -func TestOK(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.OK().Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) -} +func TestConditionString(t *testing.T) { -func TestNot(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.Not(gs_cond.OK()).Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) -} + c := gs_cond.OnFunc(func(ctx gs.CondContext) (bool, error) { return false, nil }) + assert.Equal(t, fmt.Sprint(c), `OnFunc(fn=gs_cond_test.TestConditionString.func1)`) -func TestOnProperty(t *testing.T) { - t.Run("no property & no HavingValue & no MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(false) - ok, err := gs_cond.OnProperty("a").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("has property & no HavingValue & no MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ok, err := gs_cond.OnProperty("a").Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("no property & has HavingValue & no MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(false) - ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("diff property & has HavingValue & no MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ctx.EXPECT().Prop("a").Return("b") - ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("same property & has HavingValue & no MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ctx.EXPECT().Prop("a").Return("a") - ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("no property & no HavingValue & has MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(false) - ok, err := gs_cond.OnProperty("a", gs_cond.MatchIfMissing()).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("has property & no HavingValue & has MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ok, err := gs_cond.OnProperty("a", gs_cond.MatchIfMissing()).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("no property & has HavingValue & has MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(false) - ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("diff property & has HavingValue & has MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ctx.EXPECT().Prop("a").Return("b") - ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("same property & has HavingValue & has MatchIfMissing", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ctx.EXPECT().Prop("a").Return("a") - ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("go expression", func(t *testing.T) { - testcases := []struct { - propValue string - expression string - expectResult bool - }{ - { - "a", - "expr:$==\"a\"", - true, - }, - { - "a", - "expr:$==\"b\"", - false, - }, - { - "3", - "expr:$==3", - true, - }, - { - "3", - "expr:$==4", - false, - }, - { - "3", - "expr:$>1&&$<5", - true, - }, - { - "false", - "expr:$", - false, - }, - { - "false", - "expr:!$", - true, - }, - } - for _, testcase := range testcases { - ctrl := gomock.NewController(t) - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ctx.EXPECT().Prop("a").Return(testcase.propValue) - ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue(testcase.expression)).Matches(ctx) - assert.Nil(t, err) - assert.Equal(t, ok, testcase.expectResult) - ctrl.Finish() - } - }) -} + c = gs_cond.OnProperty("a").HavingValue("123") + assert.Equal(t, fmt.Sprint(c), `OnProperty(name=a, havingValue=123)`) -func TestOnMissingProperty(t *testing.T) { - t.Run("no property", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(false) - ok, err := gs_cond.OnMissingProperty("a").Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("has property", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("a").Return(true) - ok, err := gs_cond.OnMissingProperty("a").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) -} + c = gs_cond.OnMissingProperty("a") + assert.Equal(t, fmt.Sprint(c), `OnMissingProperty(name=a)`) -func TestOnBean(t *testing.T) { - t.Run("return error", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return(nil, errors.New("error")) - ok, err := gs_cond.OnBean("a").Matches(ctx) - assert.Error(t, err, "error") - assert.False(t, ok) - }) - t.Run("no bean", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return(nil, nil) - ok, err := gs_cond.OnBean("a").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("one bean", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]gs.CondBean{ - nil, - }, nil) - ok, err := gs_cond.OnBean("a").Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("more than one beans", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]gs.CondBean{ - nil, nil, - }, nil) - ok, err := gs_cond.OnBean("a").Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) -} + c = gs_cond.OnBean(gs.BeanSelector{Name: "a"}) + assert.Equal(t, fmt.Sprint(c), `OnBean(selector={Name:a})`) -func TestOnMissingBean(t *testing.T) { - t.Run("return error", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return(nil, errors.New("error")) - ok, err := gs_cond.OnMissingBean("a").Matches(ctx) - assert.Error(t, err, "error") - assert.False(t, ok) - }) - t.Run("no bean", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return(nil, nil) - ok, err := gs_cond.OnMissingBean("a").Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("one bean", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]gs.CondBean{ - nil, - }, nil) - ok, err := gs_cond.OnMissingBean("a").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("more than one beans", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]gs.CondBean{ - nil, nil, - }, nil) - ok, err := gs_cond.OnMissingBean("a").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) -} + c = gs_cond.OnBean(gs.BeanSelectorForType[error]()) + assert.Equal(t, fmt.Sprint(c), `OnBean(selector={Type:error})`) -func TestOnSingleBean(t *testing.T) { - t.Run("return error", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return(nil, errors.New("error")) - ok, err := gs_cond.OnSingleBean("a").Matches(ctx) - assert.Error(t, err, "error") - assert.False(t, ok) - }) - t.Run("no bean", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return(nil, nil) - ok, err := gs_cond.OnSingleBean("a").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("one bean", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]gs.CondBean{ - nil, - }, nil) - ok, err := gs_cond.OnSingleBean("a").Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("more than one beans", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]gs.CondBean{ - nil, nil, - }, nil) - ok, err := gs_cond.OnSingleBean("a").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) -} + c = gs_cond.OnMissingBean(gs.BeanSelector{Name: "a"}) + assert.Equal(t, fmt.Sprint(c), `OnMissingBean(selector={Name:a})`) -func TestOnExpression(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.OnExpression("").Matches(ctx) - assert.Error(t, err, "unimplemented method") - assert.False(t, ok) -} + c = gs_cond.OnMissingBean(gs.BeanSelectorForType[error]()) + assert.Equal(t, fmt.Sprint(c), `OnMissingBean(selector={Type:error})`) -func TestOnMatches(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.OnMatches(func(ctx gs.CondContext) (bool, error) { - return false, nil - }).Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) -} + c = gs_cond.OnSingleBean(gs.BeanSelector{Name: "a"}) + assert.Equal(t, fmt.Sprint(c), `OnSingleBean(selector={Name:a})`) -func TestOnProfile(t *testing.T) { - t.Run("no property", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("spring.profiles.active").Return(false) - ok, err := gs_cond.OnProfile("test").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("diff property", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("spring.profiles.active").Return(true) - ctx.EXPECT().Prop("spring.profiles.active").Return("dev") - ok, err := gs_cond.OnProfile("test").Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("same property", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ctx.EXPECT().Has("spring.profiles.active").Return(true) - ctx.EXPECT().Prop("spring.profiles.active").Return("test") - ok, err := gs_cond.OnProfile("test").Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) -} + c = gs_cond.OnSingleBean(gs.BeanSelectorForType[error]()) + assert.Equal(t, fmt.Sprint(c), `OnSingleBean(selector={Type:error})`) -func TestConditional(t *testing.T) { - t.Run("ok && ", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.On(gs_cond.OK()).And().Matches(ctx) - assert.Error(t, err, "no condition in last node") - assert.False(t, ok) - }) - t.Run("ok && !ok", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.On(gs_cond.OK()).And().On(gs_cond.Not(gs_cond.OK())).Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("ok || ", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.On(gs_cond.OK()).Or().Matches(ctx) - assert.Error(t, err, "no condition in last node") - assert.False(t, ok) - }) - t.Run("ok || !ok", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.On(gs_cond.OK()).Or().On(gs_cond.Not(gs_cond.OK())).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) -} + c = gs_cond.OnExpression("a") + assert.Equal(t, fmt.Sprint(c), `OnExpression(expression=a)`) + + c = gs_cond.Not(gs_cond.OnBean(gs.BeanSelector{Name: "a"})) + assert.Equal(t, fmt.Sprint(c), `Not(OnBean(selector={Name:a}))`) + + c = gs_cond.Or(gs_cond.OnBean(gs.BeanSelector{Name: "a"})) + assert.Equal(t, fmt.Sprint(c), `OnBean(selector={Name:a})`) + + c = gs_cond.Or(gs_cond.OnBean(gs.BeanSelector{Name: "a"}), gs_cond.OnBean(gs.BeanSelector{Name: "b"})) + assert.Equal(t, fmt.Sprint(c), `Or(OnBean(selector={Name:a}), OnBean(selector={Name:b}))`) + + c = gs_cond.And(gs_cond.OnBean(gs.BeanSelector{Name: "a"})) + assert.Equal(t, fmt.Sprint(c), `OnBean(selector={Name:a})`) + + c = gs_cond.And(gs_cond.OnBean(gs.BeanSelector{Name: "a"}), gs_cond.OnBean(gs.BeanSelector{Name: "b"})) + assert.Equal(t, fmt.Sprint(c), `And(OnBean(selector={Name:a}), OnBean(selector={Name:b}))`) + + c = gs_cond.None(gs_cond.OnBean(gs.BeanSelector{Name: "a"})) + assert.Equal(t, fmt.Sprint(c), `Not(OnBean(selector={Name:a}))`) + + c = gs_cond.None(gs_cond.OnBean(gs.BeanSelector{Name: "a"}), gs_cond.OnBean(gs.BeanSelector{Name: "b"})) + assert.Equal(t, fmt.Sprint(c), `None(OnBean(selector={Name:a}), OnBean(selector={Name:b}))`) -func TestGroup(t *testing.T) { - t.Run("ok && ", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.And(gs_cond.OK()).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("ok && !ok", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.And(gs_cond.OK(), gs_cond.Not(gs_cond.OK())).Matches(ctx) - assert.Nil(t, err) - assert.False(t, ok) - }) - t.Run("ok || ", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.Or(gs_cond.OK()).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) - t.Run("ok || !ok", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_cond.NewMockContext(ctrl) - ok, err := gs_cond.Or(gs_cond.OK(), gs_cond.Not(gs_cond.OK())).Matches(ctx) - assert.Nil(t, err) - assert.True(t, ok) - }) + c = gs_cond.And( + gs_cond.OnBean(gs.BeanSelector{Name: "a"}), + gs_cond.Or( + gs_cond.OnBean(gs.BeanSelector{Name: "b"}), + gs_cond.Not(gs_cond.OnBean(gs.BeanSelector{Name: "c"})), + ), + ) + assert.Equal(t, fmt.Sprint(c), `And(OnBean(selector={Name:a}), Or(OnBean(selector={Name:b}), Not(OnBean(selector={Name:c}))))`) } diff --git a/gs/internal/gs_cond/expr.go b/gs/internal/gs_cond/expr.go index daa3902f..284639b6 100644 --- a/gs/internal/gs_cond/expr.go +++ b/gs/internal/gs_cond/expr.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,16 +22,22 @@ import ( "github.com/expr-lang/expr" ) -var exprFuncs = map[string]interface{}{} +// funcMap is used to store express functions that can be referenced in expressions. +var funcMap = map[string]interface{}{} -func RegisterExprFunc(name string, fn interface{}) { - exprFuncs[name] = fn +// RegisterExpressFunc registers an express function with a specified name. +// The function can later be used in expressions evaluated by EvalExpr. +func RegisterExpressFunc(name string, fn interface{}) { + funcMap[name] = fn } -// evalExpr returns the value for the expression expr. -func evalExpr(input string, val interface{}) (bool, error) { +// EvalExpr evaluates a boolean expression (input) using the provided value (val) +// and any registered express functions. The string expression to evaluate (should +// return a boolean value). The value used in the evaluation (referred to as `$` +// in the expression context). +func EvalExpr(input string, val interface{}) (bool, error) { env := map[string]interface{}{"$": val} - for k, v := range exprFuncs { + for k, v := range funcMap { env[k] = v } r, err := expr.Eval(input, env) diff --git a/gs/internal/gs_cond/expr_test.go b/gs/internal/gs_cond/expr_test.go new file mode 100644 index 00000000..5741b3e3 --- /dev/null +++ b/gs/internal/gs_cond/expr_test.go @@ -0,0 +1,38 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_cond_test + +import ( + "strconv" + "testing" + + "github.com/go-spring/spring-core/gs/internal/gs_cond" + "github.com/go-spring/spring-core/util/assert" +) + +func TestEvalExpr(t *testing.T) { + ok, err := gs_cond.EvalExpr("$==3", 3) + assert.Nil(t, err) + assert.True(t, ok) + + gs_cond.RegisterExpressFunc("check", func(s string, i int) bool { + return s == strconv.Itoa(i) + }) + ok, err = gs_cond.EvalExpr("check($,9)", "9") + assert.Nil(t, err) + assert.True(t, ok) +} diff --git a/gs/internal/gs_conf/args.go b/gs/internal/gs_conf/arg.go similarity index 69% rename from gs/internal/gs_conf/args.go rename to gs/internal/gs_conf/arg.go index 7999df4d..116b42e7 100644 --- a/gs/internal/gs_conf/args.go +++ b/gs/internal/gs_conf/arg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,21 +24,26 @@ import ( "github.com/go-spring/spring-core/conf" ) +// CommandArgsPrefix defines the environment variable name used to override the default option prefix. const CommandArgsPrefix = "GS_ARGS_PREFIX" -// CommandArgs command-line parameters +// CommandArgs represents a structure for handling command-line parameters. type CommandArgs struct{} +// NewCommandArgs creates and returns a new CommandArgs instance. func NewCommandArgs() *CommandArgs { return &CommandArgs{} } -// CopyTo loads parameters passed in the form of -D key[=value/true]. +// CopyTo processes command-line parameters and sets them as key-value pairs in the +// provided conf.Properties. Parameters should be passed in the form of `-D key[=value/true]`. func (c *CommandArgs) CopyTo(out *conf.Properties) error { if len(os.Args) == 0 { return nil } + // Default option prefix is "-D", but it can be overridden by the + // environment variable `GS_ARGS_PREFIX`. option := "-D" if s := strings.TrimSpace(os.Getenv(CommandArgsPrefix)); s != "" { option = s @@ -48,7 +53,7 @@ func (c *CommandArgs) CopyTo(out *conf.Properties) error { n := len(cmdArgs) for i := 0; i < n; i++ { if cmdArgs[i] == option { - if i >= n-1 { + if i+1 >= n { return fmt.Errorf("cmd option %s needs arg", option) } next := cmdArgs[i+1] diff --git a/gs/internal/gs_conf/conf.go b/gs/internal/gs_conf/conf.go index 38de02cc..13fbeb93 100644 --- a/gs/internal/gs_conf/conf.go +++ b/gs/internal/gs_conf/conf.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ import ( "strings" "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/conf/sysconf" "github.com/go-spring/spring-core/gs/internal/gs" - "github.com/go-spring/spring-core/gs/sysconf" ) /******************************** AppConfig **********************************/ @@ -185,7 +185,7 @@ func (p *PropertySources) getDefaultLocations(resolver *conf.Properties) (_ []st var configDir string if p.configType == ConfigTypeLocal { - configDir, err = resolver.Resolve("${spring.application.config.dir:=./conf}") + configDir, err = resolver.Resolve("${spring.app.config.dir:=./conf}") } else if p.configType == ConfigTypeRemote { configDir, err = resolver.Resolve("${spring.cloud.config.dir:=./conf/remote}") } else { diff --git a/gs/internal/gs_conf/envs.go b/gs/internal/gs_conf/env.go similarity index 98% rename from gs/internal/gs_conf/envs.go rename to gs/internal/gs_conf/env.go index 45c8e81c..090c08c2 100644 --- a/gs/internal/gs_conf/envs.go +++ b/gs/internal/gs_conf/env.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/gs/internal/gs_conf/envs_test.go b/gs/internal/gs_conf/env_test.go similarity index 92% rename from gs/internal/gs_conf/envs_test.go rename to gs/internal/gs_conf/env_test.go index 147322ce..bbd5fe9d 100644 --- a/gs/internal/gs_conf/envs_test.go +++ b/gs/internal/gs_conf/env_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/gs/internal/gs_arg/arg_test.go b/gs/internal/gs_core/arg_test.go similarity index 60% rename from gs/internal/gs_arg/arg_test.go rename to gs/internal/gs_core/arg_test.go index 922ab78a..1bc4e233 100644 --- a/gs/internal/gs_arg/arg_test.go +++ b/gs/internal/gs_core/arg_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,30 @@ * limitations under the License. */ -package gs_arg_test +package gs_core_test import ( - "reflect" "testing" + "github.com/go-spring/spring-core/conf" "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/gs/internal/gs_arg" + "github.com/go-spring/spring-core/gs/internal/gs_core" "github.com/go-spring/spring-core/util/assert" - "go.uber.org/mock/gomock" ) func TestBind(t *testing.T) { t.Run("zero argument", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_arg.NewMockContext(ctrl) + c := container(t, nil) + stack := gs_core.NewWiringStack() + ctx := gs_core.NewArgContext(c.(*gs_core.Container), stack) fn := func() {} - c, err := gs_arg.Bind(fn, []gs.Arg{}, 1) + p, err := gs_arg.Bind(fn, []gs.Arg{}) if err != nil { t.Fatal(err) } - values, err := c.Call(ctx) + values, err := p.Call(ctx) if err != nil { t.Fatal(err) } @@ -45,20 +45,20 @@ func TestBind(t *testing.T) { }) t.Run("one value argument", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_arg.NewMockContext(ctrl) + c := container(t, nil) + stack := gs_core.NewWiringStack() + ctx := gs_core.NewArgContext(c.(*gs_core.Container), stack) expectInt := 0 fn := func(i int) { expectInt = i } - c, err := gs_arg.Bind(fn, []gs.Arg{ + p, err := gs_arg.Bind(fn, []gs.Arg{ gs_arg.Value(3), - }, 1) + }) if err != nil { t.Fatal(err) } - values, err := c.Call(ctx) + values, err := p.Call(ctx) if err != nil { t.Fatal(err) } @@ -67,24 +67,22 @@ func TestBind(t *testing.T) { }) t.Run("one ctx value argument", func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_arg.NewMockContext(ctrl) - ctx.EXPECT().Bind(gomock.Any(), "${a.b.c}").DoAndReturn(func(v, tag interface{}) error { - v.(reflect.Value).SetInt(3) - return nil + c := container(t, func(p *conf.Properties, c *gs_core.Container) error { + return p.Set("a.b.c", 3) }) + stack := gs_core.NewWiringStack() + ctx := gs_core.NewArgContext(c.(*gs_core.Container), stack) expectInt := 0 fn := func(i int) { expectInt = i } - c, err := gs_arg.Bind(fn, []gs.Arg{ - "${a.b.c}", - }, 1) + p, err := gs_arg.Bind(fn, []gs.Arg{ + gs_arg.Tag("${a.b.c}"), + }) if err != nil { t.Fatal(err) } - values, err := c.Call(ctx) + values, err := p.Call(ctx) if err != nil { t.Fatal(err) } @@ -96,24 +94,23 @@ func TestBind(t *testing.T) { type st struct { i int } - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_arg.NewMockContext(ctrl) - ctx.EXPECT().Wire(gomock.Any(), "a").DoAndReturn(func(v, tag interface{}) error { - v.(reflect.Value).Set(reflect.ValueOf(&st{3})) + c := container(t, func(p *conf.Properties, c *gs_core.Container) error { + c.Object(&st{3}).Name("a") return nil }) + stack := gs_core.NewWiringStack() + ctx := gs_core.NewArgContext(c.(*gs_core.Container), stack) expectInt := 0 fn := func(v *st) { expectInt = v.i } - c, err := gs_arg.Bind(fn, []gs.Arg{ - "a", - }, 1) + p, err := gs_arg.Bind(fn, []gs.Arg{ + gs_arg.Tag("a"), + }) if err != nil { t.Fatal(err) } - values, err := c.Call(ctx) + values, err := p.Call(ctx) if err != nil { t.Fatal(err) } @@ -125,22 +122,21 @@ func TestBind(t *testing.T) { type st struct { i int } - ctrl := gomock.NewController(t) - defer ctrl.Finish() - ctx := gs_arg.NewMockContext(ctrl) - ctx.EXPECT().Wire(gomock.Any(), "").DoAndReturn(func(v, tag interface{}) error { - v.(reflect.Value).Set(reflect.ValueOf(&st{3})) + c := container(t, func(p *conf.Properties, c *gs_core.Container) error { + c.Object(&st{3}).Name("a") return nil }) + stack := gs_core.NewWiringStack() + ctx := gs_core.NewArgContext(c.(*gs_core.Container), stack) expectInt := 0 fn := func(v *st) { expectInt = v.i } - c, err := gs_arg.Bind(fn, []gs.Arg{}, 1) + p, err := gs_arg.Bind(fn, []gs.Arg{}) if err != nil { t.Fatal(err) } - values, err := c.Call(ctx) + values, err := p.Call(ctx) if err != nil { t.Fatal(err) } diff --git a/gs/internal/gs_core/bean.go b/gs/internal/gs_core/bean.go index 6c7ad15b..81f11644 100644 --- a/gs/internal/gs_core/bean.go +++ b/gs/internal/gs_core/bean.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ package gs_core import ( - "errors" "fmt" "reflect" "runtime" @@ -31,7 +30,7 @@ import ( ) // NewBean 普通函数注册时需要使用 reflect.ValueOf(fn) 形式以避免和构造函数发生冲突。 -func NewBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { +func NewBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.BeanDefinition { var v reflect.Value var fromValue bool @@ -48,32 +47,36 @@ func NewBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { t := v.Type() if !util.IsBeanType(t) { - panic(errors.New("bean must be ref type")) + panic("bean must be ref type") } + // Ensure the value is valid and not nil if !v.IsValid() || v.IsNil() { - panic(errors.New("bean can't be nil")) + panic("bean can't be nil") } - const skip = 2 var f gs.Callable - _, file, line, _ := runtime.Caller(skip) + _, file, line, _ := runtime.Caller(2) - // 以 reflect.ValueOf(fn) 形式注册的函数被视为函数对象 bean 。 + // If objOrCtor is a function (not from reflect.Value), + // process it as a constructor if !fromValue && t.Kind() == reflect.Func { if !util.IsConstructor(t) { t1 := "func(...)bean" t2 := "func(...)(bean, error)" - panic(fmt.Errorf("constructor should be %s or %s", t1, t2)) + panic(fmt.Sprintf("constructor should be %s or %s", t1, t2)) } - var err error - f, err = gs_arg.Bind(objOrCtor, ctorArgs, skip) - if err != nil { - panic(err) + // Bind the constructor arguments + f = gs_arg.MustBind(objOrCtor, ctorArgs...).SetFileLine(file, line) + + var in0 reflect.Type + if t.NumIn() > 0 { + in0 = t.In(0) } + // Obtain the return type of the constructor out0 := t.Out(0) v = reflect.New(out0) if util.IsBeanType(out0) { @@ -82,10 +85,10 @@ func NewBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { t = v.Type() if !util.IsBeanType(t) { - panic(errors.New("bean must be ref type")) + panic("bean must be ref type") } - // 成员方法一般是 xxx/gs_test.(*Server).Consumer 形式命名 + // Extract function name for naming the bean fnPtr := reflect.ValueOf(objOrCtor).Pointer() fnInfo := runtime.FuncForPC(fnPtr) funcName := fnInfo.Name() @@ -94,18 +97,38 @@ func NewBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { if name[0] == '(' { name = name[strings.Index(name, ".")+1:] } + + // Check if the function is a method and set a condition if needed method := strings.LastIndexByte(fnInfo.Name(), ')') > 0 if method { - selector, ok := f.Arg(0) - if !ok || selector == "" { - selector, _ = f.In(0) + var s gs.BeanSelectorInterface = gs.BeanSelector{Type: in0} + if len(ctorArgs) > 0 { + switch a := ctorArgs[0].(type) { + case *gs.RegisteredBean: + s = a + case *gs.BeanDefinition: + s = a + case gs_arg.IndexArg: + if a.Idx == 0 { + switch x := a.Arg.(type) { + case *gs.RegisteredBean: + s = x + case *gs.BeanDefinition: + s = x + default: + panic("the arg of IndexArg[0] should be *RegisteredBean or *BeanDefinition") + } + } + default: + panic("ctorArgs[0] should be *RegisteredBean or *BeanDefinition or IndexArg[0]") + } } - cond = gs_cond.OnBean(selector) + cond = gs_cond.OnBean(s) } } - if t.Kind() == reflect.Ptr && !util.IsValueType(t.Elem()) { - panic(errors.New("bean should be *val but not *ref")) + if t.Kind() == reflect.Ptr && !util.IsPropBindingTarget(t.Elem()) { + panic("bean should be *val but not *ref") } // Type.String() 一般返回 *pkg.Type 形式的字符串, @@ -115,6 +138,12 @@ func NewBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { name = strings.TrimPrefix(s[len(s)-1], "*") } - d := gs_bean.NewBean(t, v, f, name, file, line) - return gs.NewUnregisteredBean(d).On(cond) + d := gs_bean.NewBean(t, v, f, name) + d.SetFileLine(file, line) + + bd := gs.NewBeanDefinition(d) + if cond != nil { + bd.Condition(cond) + } + return bd } diff --git a/gs/internal/gs_core/bean_test.go b/gs/internal/gs_core/bean_test.go index b56b4779..d2bb407a 100644 --- a/gs/internal/gs_core/bean_test.go +++ b/gs/internal/gs_core/bean_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,14 +26,12 @@ import ( "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/gs/internal/gs_bean" "github.com/go-spring/spring-core/gs/internal/gs_core" - pkg1 "github.com/go-spring/spring-core/gs/testdata/pkg/bar" - pkg2 "github.com/go-spring/spring-core/gs/testdata/pkg/foo" "github.com/go-spring/spring-core/util" "github.com/go-spring/spring-core/util/assert" ) // newBean 该方法是为了平衡调用栈的深度,一般情况下 gs.NewBean 不应该被直接使用。 -func newBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { +func newBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.BeanDefinition { return gs_core.NewBean(objOrCtor, ctorArgs...) } @@ -106,35 +104,6 @@ func TestIsFuncBeanType(t *testing.T) { } } -func TestBeanDefinition_Match(t *testing.T) { - - data := []struct { - bd *gs.UnregisteredBean - typeName string - beanName string - expect bool - }{ - {newBean(new(pkg2.SamePkg)), "github.com/go-spring/spring-core/gs/testdata/pkg/foo/pkg.SamePkg", "SamePkg", true}, - {newBean(new(pkg2.SamePkg)), "", "SamePkg", true}, - {newBean(new(pkg2.SamePkg)), "github.com/go-spring/spring-core/gs/testdata/pkg/foo/pkg.SamePkg", "", true}, - {newBean(new(pkg2.SamePkg)).Name("pkg2"), "github.com/go-spring/spring-core/gs/testdata/pkg/foo/pkg.SamePkg", "pkg2", true}, - {newBean(new(pkg2.SamePkg)).Name("pkg2"), "", "pkg2", true}, - {newBean(new(pkg2.SamePkg)).Name("pkg2"), "github.com/go-spring/spring-core/gs/testdata/pkg/foo/pkg.SamePkg", "pkg2", true}, - {newBean(new(pkg1.SamePkg)), "github.com/go-spring/spring-core/gs/testdata/pkg/bar/pkg.SamePkg", "SamePkg", true}, - {newBean(new(pkg1.SamePkg)), "", "SamePkg", true}, - {newBean(new(pkg1.SamePkg)), "github.com/go-spring/spring-core/gs/testdata/pkg/bar/pkg.SamePkg", "", true}, - {newBean(new(pkg1.SamePkg)).Name("pkg1"), "github.com/go-spring/spring-core/gs/testdata/pkg/bar/pkg.SamePkg", "pkg1", true}, - {newBean(new(pkg1.SamePkg)).Name("pkg1"), "", "pkg1", true}, - {newBean(new(pkg1.SamePkg)).Name("pkg1"), "github.com/go-spring/spring-core/gs/testdata/pkg/bar/pkg.SamePkg", "pkg1", true}, - } - - for i, s := range data { - if ok := s.bd.BeanRegistration().(*gs_bean.BeanDefinition).Match(s.typeName, s.beanName); ok != s.expect { - t.Errorf("%d expect %v but %v", i, s.expect, ok) - } - } -} - func TestObjectBean(t *testing.T) { // t.Run("bean must be ref type", func(t *testing.T) { @@ -160,34 +129,14 @@ func TestObjectBean(t *testing.T) { }) t.Run("check name && typename", func(t *testing.T) { - - data := map[*gs.UnregisteredBean]struct { - name string - typeName string + data := map[*gs.BeanDefinition]struct { + name string }{ - newBean(io.Writer(os.Stdout)): { - "File", "os/os.File", - }, - - newBean(newHistoryTeacher("")): { - "historyTeacher", - "github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.historyTeacher", - }, - - newBean(new(pkg2.SamePkg)): { - "SamePkg", - "github.com/go-spring/spring-core/gs/testdata/pkg/foo/pkg.SamePkg", - }, - - newBean(new(pkg2.SamePkg)).Name("pkg2"): { - "pkg2", - "github.com/go-spring/spring-core/gs/testdata/pkg/foo/pkg.SamePkg", - }, + newBean(io.Writer(os.Stdout)): {"File"}, + newBean(newHistoryTeacher("")): {"historyTeacher"}, } - for bd, v := range data { assert.Equal(t, bd.BeanRegistration().(*gs_bean.BeanDefinition).Name(), v.name) - assert.Equal(t, bd.BeanRegistration().(*gs_bean.BeanDefinition).TypeName(), v.typeName) } }) } @@ -195,10 +144,10 @@ func TestObjectBean(t *testing.T) { func TestConstructorBean(t *testing.T) { bd := newBean(NewStudent) - assert.Equal(t, bd.Type().String(), "*gs_core_test.Student") + assert.Equal(t, bd.BeanRegistration().Type().String(), "*gs_core_test.Student") bd = newBean(NewPtrStudent) - assert.Equal(t, bd.Type().String(), "*gs_core_test.Student") + assert.Equal(t, bd.BeanRegistration().Type().String(), "*gs_core_test.Student") // mapFn := func() map[int]string { return make(map[int]string) } // bd = newBean(mapFn) @@ -210,11 +159,11 @@ func TestConstructorBean(t *testing.T) { funcFn := func() func(int) { return nil } bd = newBean(funcFn) - assert.Equal(t, bd.Type().String(), "func(int)") + assert.Equal(t, bd.BeanRegistration().Type().String(), "func(int)") interfaceFn := func(name string) Teacher { return newHistoryTeacher(name) } bd = newBean(interfaceFn) - assert.Equal(t, bd.Type().String(), "gs_core_test.Teacher") + assert.Equal(t, bd.BeanRegistration().Type().String(), "gs_core_test.Teacher") // assert.Panic(t, func() { // _ = newBean(func() (*int, *int) { return nil, nil }) diff --git a/gs/internal/gs_core/cond_test.go b/gs/internal/gs_core/cond_test.go new file mode 100644 index 00000000..f4a80a6b --- /dev/null +++ b/gs/internal/gs_core/cond_test.go @@ -0,0 +1,316 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_core_test + +// +//func TestNot(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.Not(gs_cond.OnMissingProperty("a")).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// ok, err = gs_cond.Not(gs_cond.Not(gs_cond.OnMissingProperty("a"))).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +//} +// +//func TestOnProperty(t *testing.T) { +// t.Run("no property & no HavingValue & no MatchIfMissing", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnProperty("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("has property & no HavingValue & no MatchIfMissing", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", "b") +// }) +// ok, err := gs_cond.OnProperty("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("no property & has HavingValue & no MatchIfMissing", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("diff property & has HavingValue & no MatchIfMissing", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", "b") +// }) +// ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("same property & has HavingValue & no MatchIfMissing", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", "a") +// }) +// ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("no property & no HavingValue & has MatchIfMissing", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnProperty("a", gs_cond.MatchIfMissing()).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("has property & no HavingValue & has MatchIfMissing", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", "b") +// }) +// ok, err := gs_cond.OnProperty("a", gs_cond.MatchIfMissing()).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("no property & has HavingValue & has MatchIfMissing", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("diff property & has HavingValue & has MatchIfMissing", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", "b") +// }) +// ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("same property & has HavingValue & has MatchIfMissing", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", "a") +// }) +// ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("go expression", func(t *testing.T) { +// testcases := []struct { +// propValue string +// expression string +// expectResult bool +// }{ +// { +// "a", +// "expr:$==\"a\"", +// true, +// }, +// { +// "a", +// "expr:$==\"b\"", +// false, +// }, +// { +// "3", +// "expr:$==3", +// true, +// }, +// { +// "3", +// "expr:$==4", +// false, +// }, +// { +// "3", +// "expr:$>1&&$<5", +// true, +// }, +// { +// "false", +// "expr:$", +// false, +// }, +// { +// "false", +// "expr:!$", +// true, +// }, +// } +// for _, testcase := range testcases { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", testcase.propValue) +// }) +// ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue(testcase.expression)).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.Equal(t, ok, testcase.expectResult) +// } +// }) +//} +// +//func TestOnMissingProperty(t *testing.T) { +// t.Run("no property", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnMissingProperty("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("has property", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// return p.Set("a", "b") +// }) +// ok, err := gs_cond.OnMissingProperty("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +//} +// +//func TestOnBean(t *testing.T) { +// t.Run("return error", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnBean("${a}").Matches(c.(gs.CondContext)) +// assert.Error(t, err, "property \"a\" not exist") +// assert.False(t, ok) +// }) +// t.Run("no bean", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("one bean", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// c.Provide(conf.New).Name("a") +// return nil +// }) +// ok, err := gs_cond.OnBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("more than one beans", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// c.Provide(conf.New).Name("a") +// c.Provide(NewVarInterfaceObj).Name("a") +// return nil +// }) +// ok, err := gs_cond.OnBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +//} +// +//func TestOnMissingBean(t *testing.T) { +// t.Run("return error", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnMissingBean("${x}").Matches(c.(gs.CondContext)) +// assert.Error(t, err, "property \"x\" not exist") +// assert.False(t, ok) +// }) +// t.Run("no bean", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnMissingBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("one bean", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// c.Provide(conf.New).Name("a") +// return nil +// }) +// ok, err := gs_cond.OnMissingBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("more than one beans", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// c.Provide(conf.New).Name("a") +// c.Provide(NewVarInterfaceObj).Name("a") +// return nil +// }) +// ok, err := gs_cond.OnMissingBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +//} +// +//func TestOnSingleBean(t *testing.T) { +// t.Run("return error", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnSingleBean("${x}").Matches(c.(gs.CondContext)) +// assert.Error(t, err, "property \"x\" not exist") +// assert.False(t, ok) +// }) +// t.Run("no bean", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnSingleBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("one bean", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// c.Provide(conf.New).Name("a") +// return nil +// }) +// ok, err := gs_cond.OnSingleBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("more than one beans", func(t *testing.T) { +// c := container(t, func(p *conf.Properties, c *gs_core.Container) error { +// c.Provide(conf.New).Name("a") +// c.Provide(NewVarInterfaceObj).Name("a") +// return nil +// }) +// ok, err := gs_cond.OnSingleBean("a").Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +//} +// +//func TestOnExpression(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnExpression("").Matches(c.(gs.CondContext)) +// assert.Error(t, err, "unimplemented method") +// assert.False(t, ok) +//} +// +//func TestOnMatches(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.OnFunc(func(ctx gs.CondContext) (bool, error) { +// return false, nil +// }).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +//} +// +//func TestGroup(t *testing.T) { +// t.Run("ok && ", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.And(gs_cond.OnMissingProperty("a")).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("ok && !ok", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.And(gs_cond.OnMissingProperty("a"), gs_cond.Not(gs_cond.OnMissingProperty("a"))).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.False(t, ok) +// }) +// t.Run("ok || ", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.Or(gs_cond.OnMissingProperty("a")).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +// t.Run("ok || !ok", func(t *testing.T) { +// c := container(t, nil) +// ok, err := gs_cond.Or(gs_cond.OnMissingProperty("a"), gs_cond.Not(gs_cond.OnMissingProperty("a"))).Matches(c.(gs.CondContext)) +// assert.Nil(t, err) +// assert.True(t, ok) +// }) +//} diff --git a/gs/internal/gs_core/core.go b/gs/internal/gs_core/core.go index cd74736d..8b8bfb49 100755 --- a/gs/internal/gs_core/core.go +++ b/gs/internal/gs_core/core.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,19 +22,20 @@ import ( "fmt" "reflect" "regexp" + "runtime" "sort" "strings" "sync" "time" - "github.com/go-spring/spring-core/conf" "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/gs/internal/gs_arg" "github.com/go-spring/spring-core/gs/internal/gs_bean" "github.com/go-spring/spring-core/gs/internal/gs_cond" "github.com/go-spring/spring-core/gs/internal/gs_dync" - "github.com/go-spring/spring-core/gs/syslog" "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/goutil" + "github.com/go-spring/spring-core/util/syslog" ) type refreshState int @@ -46,9 +47,9 @@ const ( Refreshed // 已刷新 ) -var UnregisteredBeanType = reflect.TypeOf((*gs.UnregisteredBean)(nil)) +var beanDefinitionType = reflect.TypeOf((*gs.BeanDefinition)(nil)) -type GroupFunc = func(p gs.Properties) ([]*gs.UnregisteredBean, error) +type GroupFunc = func(p gs.Properties) ([]*gs.BeanDefinition, error) type BeanRuntime interface { Name() string @@ -56,9 +57,7 @@ type BeanRuntime interface { Value() reflect.Value Interface() interface{} Callable() gs.Callable - Match(typeName string, beanName string) bool Status() gs_bean.BeanStatus - IsPrimary() bool String() string } @@ -70,10 +69,9 @@ type BeanRuntime interface { // go-spring 严格区分了这两种概念,在描述对 bean 的处理时要么单独使用依赖注入或属 // 性绑定,要么同时使用依赖注入和属性绑定。 type Container struct { - beans []*gs_bean.BeanDefinition - beansByName map[string][]BeanRuntime + resolving *resolvingStage + beansByName map[string][]BeanRuntime // 用于查找未导出接口 beansByType map[reflect.Type][]BeanRuntime - groupFuncs []GroupFunc p *gs_dync.Properties ctx context.Context cancel context.CancelFunc @@ -93,144 +91,118 @@ func New() gs.Container { ctx: ctx, cancel: cancel, p: gs_dync.New(), + resolving: &resolvingStage{}, beansByName: make(map[string][]BeanRuntime), beansByType: make(map[reflect.Type][]BeanRuntime), } - c.Object(c).Export((*gs.Context)(nil)) + c.Object(c).Export( + reflect.TypeFor[gs.Context](), + ) return c } // Object 注册对象形式的 bean ,需要注意的是该方法在注入开始后就不能再调用了。 func (c *Container) Object(i interface{}) *gs.RegisteredBean { b := NewBean(reflect.ValueOf(i)) - return c.Accept(b) + return c.Register(b) } // Provide 注册构造函数形式的 bean ,需要注意的是该方法在注入开始后就不能再调用了。 func (c *Container) Provide(ctor interface{}, args ...gs.Arg) *gs.RegisteredBean { b := NewBean(ctor, args...) - return c.Accept(b) + return c.Register(b) } -func (c *Container) Accept(b *gs.UnregisteredBean) *gs.RegisteredBean { +func (c *Container) Register(b *gs.BeanDefinition) *gs.RegisteredBean { if c.state >= Refreshing { - panic(errors.New("should call before Refresh")) + return nil } - c.beans = append(c.beans, b.BeanRegistration().(*gs_bean.BeanDefinition)) + x := b.BeanRegistration().(*gs_bean.BeanDefinition) + c.resolving.beans = append(c.resolving.beans, x) return gs.NewRegisteredBean(b.BeanRegistration()) } -func (c *Container) Group(fn GroupFunc) { - c.groupFuncs = append(c.groupFuncs, fn) -} - -// Context 返回 IoC 容器的 ctx 对象。 -func (c *Container) Context() context.Context { - return c.ctx +func (c *Container) GroupRegister(fn GroupFunc) { + c.resolving.groupFuncs = append(c.resolving.groupFuncs, fn) } +// Keys returns all keys present in the container's properties. func (c *Container) Keys() []string { return c.p.Data().Keys() } +// Has checks if a key exists in the container's properties. func (c *Container) Has(key string) bool { return c.p.Data().Has(key) } +// SubKeys returns sub-keys under the specified key in the container's properties. func (c *Container) SubKeys(key string) ([]string, error) { return c.p.Data().SubKeys(key) } -func (c *Container) Prop(key string, opts ...conf.GetOption) string { - return c.p.Data().Get(key, opts...) +// Prop retrieves the value of the specified key from the container's properties. +func (c *Container) Prop(key string, def ...string) string { + return c.p.Data().Get(key, def...) } +// Resolve resolves placeholders or references in the given string. func (c *Container) Resolve(s string) (string, error) { return c.p.Data().Resolve(s) } -func (c *Container) Bind(i interface{}, opts ...conf.BindArg) error { - return c.p.Data().Bind(i, opts...) +// Bind binds the value of the specified key to the provided struct or variable. +func (c *Container) Bind(i interface{}, tag ...string) error { + return c.p.Data().Bind(i, tag...) } +// RefreshProperties updates the properties of the container. func (c *Container) RefreshProperties(p gs.Properties) error { return c.p.Refresh(p) } +// Refresh initializes and wires all beans in the container. func (c *Container) Refresh() (err error) { - if c.state != RefreshDefault { - return errors.New("Container already refreshed") + return errors.New("container is refreshing or refreshed") } c.state = RefreshInit - start := time.Now() - // 处理 group 逻辑 - for _, fn := range c.groupFuncs { - var beans []*gs.UnregisteredBean - beans, err = fn(c.p.Data()) - if err != nil { - return err - } - for _, b := range beans { - c.beans = append(c.beans, b.BeanRegistration().(*gs_bean.BeanDefinition)) - } - } - c.groupFuncs = nil - - // 处理 configuration 逻辑 - for _, bd := range c.beans { - if !bd.IsConfiguration() { - continue - } - var newBeans []*gs_bean.BeanDefinition - newBeans, err = c.scanConfiguration(bd) - if err != nil { - return err - } - c.beans = append(c.beans, newBeans...) + c.resolving.p = c.p.Data() + err = c.resolving.RefreshInit() + if err != nil { + return err } c.state = Refreshing - for _, b := range c.beans { - c.registerBean(b) + beansById, err := c.resolving.Refresh() + if err != nil { + return err } - for _, b := range c.beans { - if err = c.resolveBean(b); err != nil { - return err + // registers all beans + for _, b := range c.resolving.beans { + if b.Status() == gs_bean.StatusDeleted { + continue } - } - - beansById := make(map[string]*gs_bean.BeanDefinition) - { - for _, b := range c.beans { - if b.Status() == gs_bean.Deleted { - continue - } - if b.Status() != gs_bean.Resolved { - return fmt.Errorf("unexpected status %d", b.Status()) - } - beanID := b.ID() - if d, ok := beansById[beanID]; ok { - return fmt.Errorf("found duplicate beans [%s] [%s]", b, d) - } - beansById[beanID] = b + c.beansByName[b.Name()] = append(c.beansByName[b.Name()], b) + c.beansByType[b.Type()] = append(c.beansByType[b.Type()], b) + for _, t := range b.Exports() { + c.beansByType[t] = append(c.beansByType[t], b) } } - stack := newWiringStack() - + stack := NewWiringStack() defer func() { if err != nil || len(stack.beans) > 0 { err = fmt.Errorf("%s ↩\n%s", err, stack.path()) - syslog.Error("%s", err.Error()) + syslog.Errorf("%s", err.Error()) } }() - // 按照 bean id 升序注入,保证注入过程始终一致。 + // injects all beans in ascending order of their IDs { var keys []string for s := range beansById { @@ -238,53 +210,52 @@ func (c *Container) Refresh() (err error) { } sort.Strings(keys) for _, s := range keys { - b := beansById[s] - if err = c.wireBeanInRefreshing(b, stack); err != nil { + if err = c.wireBean(beansById[s], stack); err != nil { return err } } } if c.AllowCircularReferences { - // 处理被标记为延迟注入的那些 bean 字段 + // processes the bean fields that are marked for lazy injection. for _, f := range stack.lazyFields { tag := strings.TrimSuffix(f.tag, ",lazy") - if err := c.wireByTag(f.v, tag, stack); err != nil { + if err = c.wireStructField(f.v, tag, stack); err != nil { return fmt.Errorf("%q wired error: %s", f.path, err.Error()) } } } else if len(stack.lazyFields) > 0 { - return errors.New("remove the dependency cycle between beans") + return errors.New("found circular references in beans") } - c.destroyers = stack.sortDestroyers() + c.destroyers, err = stack.getSortedDestroyers() + if err != nil { + return err + } - // 精简内存 - { - c.beansByName = make(map[string][]BeanRuntime) - c.beansByType = make(map[reflect.Type][]BeanRuntime) - for _, b := range c.beans { - if b.Status() == gs_bean.Deleted { - continue - } - c.beansByName[b.Name()] = append(c.beansByName[b.Name()], b.BeanRuntime) - c.beansByType[b.Type()] = append(c.beansByType[b.Type()], b.BeanRuntime) - for _, t := range b.Exports() { - c.beansByType[t] = append(c.beansByType[t], b.BeanRuntime) - } + // registers all beans + c.beansByName = make(map[string][]BeanRuntime) + c.beansByType = make(map[reflect.Type][]BeanRuntime) + for _, b := range c.resolving.beans { + if b.Status() == gs_bean.StatusDeleted { + continue + } + c.beansByName[b.Name()] = append(c.beansByName[b.Name()], b.BeanRuntime) + c.beansByType[b.Type()] = append(c.beansByType[b.Type()], b.BeanRuntime) + for _, t := range b.Exports() { + c.beansByType[t] = append(c.beansByType[t], b.BeanRuntime) } } + c.resolving = nil c.state = Refreshed - - cost := time.Now().Sub(start) - syslog.Info("refresh %d beans cost %v", len(beansById), cost) - syslog.Info("refreshed successfully") + syslog.Debugf("container is refreshed successfully, %d beans cost %v", + len(beansById), time.Now().Sub(start)) return nil } -// SimplifyMemory 清理运行时不需要的空间。 -func (c *Container) SimplifyMemory() { +// ReleaseUnusedMemory releases unused memory by cleaning up unnecessary resources. +func (c *Container) ReleaseUnusedMemory() { if !c.ContextAware { // 保留核心数据 if c.p.ObjectsCount() == 0 { c.p = nil @@ -292,15 +263,185 @@ func (c *Container) SimplifyMemory() { c.beansByName = nil c.beansByType = nil } - c.beans = nil + c.resolving = nil +} + +// Get retrieves a bean of the specified type using the provided selector. +func (c *Container) Get(i interface{}, tag ...string) error { + if i == nil { + return errors.New("i can't be nil") + } + v := reflect.ValueOf(i) + if v.Kind() != reflect.Ptr { + return errors.New("i must be pointer") + } + stack := NewWiringStack() + defer func() { + if len(stack.beans) > 0 { + syslog.Infof("wiring path %s", stack.path()) + } + }() + var s string + if len(tag) > 0 { + s = tag[0] + } + return c.wireStructField(v.Elem(), s, stack) } -func (c *Container) scanConfiguration(bd *gs_bean.BeanDefinition) ([]*gs_bean.BeanDefinition, error) { +// Wire creates and returns a wired bean using the provided object or constructor function. +func (c *Container) Wire(objOrCtor interface{}, ctorArgs ...gs.Arg) (interface{}, error) { + + x := NewBean(objOrCtor, ctorArgs...) + b := x.BeanRegistration().(*gs_bean.BeanDefinition) + + stack := NewWiringStack() + defer func() { + if len(stack.beans) > 0 { + syslog.Infof("wiring path %s", stack.path()) + } + }() + + v, err := c.getBeanValue(b, stack) + if err != nil { + return nil, err + } + + t := v.Type() + err = c.wireBeanValue(v, t, false, stack) + if err != nil { + return nil, err + } + + // 如果 bean 实现了 BeanInit 接口,则执行其 OnInit 方法。 + if f, ok := b.Interface().(gs.BeanInitInterface); ok { + if err = f.OnBeanInit(c); err != nil { + return nil, err + } + } + + return b.Interface(), nil +} + +// Invoke calls the provided function with the specified arguments. +func (c *Container) Invoke(fn interface{}, args ...gs.Arg) ([]interface{}, error) { + + if !util.IsFuncType(reflect.TypeOf(fn)) { + return nil, errors.New("fn should be func type") + } + + stack := NewWiringStack() + + defer func() { + if len(stack.beans) > 0 { + syslog.Infof("wiring path %s", stack.path()) + } + }() + + _, file, line, _ := runtime.Caller(1) + r, err := gs_arg.Bind(fn, args) + if err != nil { + return nil, err + } + r.SetFileLine(file, line) + + ret, err := r.Call(NewArgContext(c, stack)) + if err != nil { + return nil, err + } + + var a []interface{} + for _, v := range ret { + a = append(a, v.Interface()) + } + return a, nil +} + +// Go runs the provided function in a new goroutine. When the container is closed, +// the context.Context will be canceled. +func (c *Container) Go(fn func(ctx context.Context)) { + c.wg.Add(1) + goutil.Go(c.ctx, func(ctx context.Context) { + defer c.wg.Done() + fn(ctx) + }) +} + +// Close closes the container and cleans up resources. +func (c *Container) Close() { + c.cancel() + c.wg.Wait() + for _, f := range c.destroyers { + f() + } +} + +type resolvingStage struct { + beans []*gs_bean.BeanDefinition + groupFuncs []GroupFunc + p gs.Properties +} + +func (c *resolvingStage) RefreshInit() error { + // processes all group functions to register beans. + for _, fn := range c.groupFuncs { + beans, err := fn(c.p) + if err != nil { + return err + } + for _, b := range beans { + d := b.BeanRegistration().(*gs_bean.BeanDefinition) + c.beans = append(c.beans, d) + } + } + + // processes configuration beans to register beans. + for _, b := range c.beans { + if !b.ConfigurationBean() { + continue + } + newBeans, err := c.scanConfiguration(b) + if err != nil { + return err + } + c.beans = append(c.beans, newBeans...) + } + return nil +} + +func (c *resolvingStage) Refresh() (beansById map[string]*gs_bean.BeanDefinition, err error) { + + // resolves all beans on their condition. + for _, b := range c.beans { + if err = c.resolveBean(b); err != nil { + return nil, err + } + } + + // caches all beans by id and checks for duplicates. + beansById = make(map[string]*gs_bean.BeanDefinition) + for _, b := range c.beans { + if b.Status() == gs_bean.StatusDeleted { + continue + } + if b.Status() != gs_bean.StatusResolved { + return nil, fmt.Errorf("unexpected status %d", b.Status()) + } + beanID := b.Name() + if d, ok := beansById[beanID]; ok { + return nil, fmt.Errorf("found duplicate beans [%s] [%s]", b, d) + } + beansById[beanID] = b + } + return beansById, nil +} + +func (c *resolvingStage) scanConfiguration(bd *gs_bean.BeanDefinition) ([]*gs_bean.BeanDefinition, error) { var ( includes []*regexp.Regexp excludes []*regexp.Regexp ) - ss := bd.GetIncludeMethod() + param := bd.ConfigurationParam() + ss := param.Includes if len(ss) == 0 { ss = []string{"New*"} } @@ -312,7 +453,7 @@ func (c *Container) scanConfiguration(bd *gs_bean.BeanDefinition) ([]*gs_bean.Be } includes = append(includes, x) } - ss = bd.GetExcludeMethod() + ss = param.Excludes for _, s := range ss { var x *regexp.Regexp x, err := regexp.Compile(s) @@ -341,33 +482,39 @@ func (c *Container) scanConfiguration(bd *gs_bean.BeanDefinition) ([]*gs_bean.Be } fnType := m.Func.Type() out0 := fnType.Out(0) - if out0 == UnregisteredBeanType { + if out0 == beanDefinitionType { ret := m.Func.Call([]reflect.Value{bd.Value()}) if len(ret) > 1 { if err := ret[1].Interface().(error); err != nil { return nil, err } } - b := ret[0].Interface().(*gs.UnregisteredBean) - newBeans = append(newBeans, b.BeanRegistration().(*gs_bean.BeanDefinition)) - retBeans, err := c.scanConfiguration(b.BeanRegistration().(*gs_bean.BeanDefinition)) + b := ret[0].Interface().(*gs.BeanDefinition).BeanRegistration().(*gs_bean.BeanDefinition) + file, line, _ := util.FileLine(m.Func.Interface()) + b.SetFileLine(file, line) + newBeans = append(newBeans, b) + retBeans, err := c.scanConfiguration(b) if err != nil { return nil, err } newBeans = append(newBeans, retBeans...) } else { - var f gs.Callable - f, err := gs_arg.Bind(m.Func.Interface(), []gs.Arg{bd.ID()}, 0) + file, line, _ := util.FileLine(m.Func.Interface()) + f, err := gs_arg.Bind(m.Func.Interface(), []gs.Arg{ + gs_arg.Tag(bd.Name()), + }) if err != nil { return nil, err } + f.SetFileLine(file, line) v := reflect.New(out0) if util.IsBeanType(out0) { v = v.Elem() } name := bd.Name() + "_" + m.Name - b := gs_bean.NewBean(v.Type(), v, f, name, bd.File(), bd.Line()) - gs.NewUnregisteredBean(b).On(gs_cond.OnBean(bd)) + b := gs_bean.NewBean(v.Type(), v, f, name) + b.SetFileLine(file, line) + b.SetCondition(gs_cond.OnBean(bd)) newBeans = append(newBeans, b) } break @@ -376,216 +523,65 @@ func (c *Container) scanConfiguration(bd *gs_bean.BeanDefinition) ([]*gs_bean.Be return newBeans, nil } -func (c *Container) registerBean(b *gs_bean.BeanDefinition) { - syslog.Debug("register %s name:%q type:%q %s", b.Class(), b.Name(), b.Type(), b.FileLine()) - c.beansByName[b.Name()] = append(c.beansByName[b.Name()], b) - c.beansByType[b.Type()] = append(c.beansByType[b.Type()], b) - for _, t := range b.Exports() { - syslog.Debug("register %s name:%q type:%q %s", b.Class(), b.Name(), t, b.FileLine()) - c.beansByType[t] = append(c.beansByType[t], b) - } -} - -// resolveBean 判断 bean 的有效性,如果 bean 是无效的则被标记为已删除。 -func (c *Container) resolveBean(b *gs_bean.BeanDefinition) error { - - if b.Status() >= gs_bean.Resolving { +// resolveBean determines the validity of the bean. +func (c *resolvingStage) resolveBean(b *gs_bean.BeanDefinition) error { + if b.Status() >= gs_bean.StatusResolving { return nil } - - b.SetStatus(gs_bean.Resolving) - - if b.Cond() != nil { - if ok, err := b.Cond().Matches(c); err != nil { + b.SetStatus(gs_bean.StatusResolving) + for _, cond := range b.Conditions() { + if ok, err := cond.Matches(c); err != nil { return err } else if !ok { - b.SetStatus(gs_bean.Deleted) + b.SetStatus(gs_bean.StatusDeleted) return nil } } - - b.SetStatus(gs_bean.Resolved) + b.SetStatus(gs_bean.StatusResolved) return nil } -// Find 查找符合条件的 bean 对象,注意该函数只能保证返回的 bean 是有效的, -// 即未被标记为删除的,而不能保证已经完成属性绑定和依赖注入。 -func (c *Container) Find(selector gs.BeanSelector) ([]gs.CondBean, error) { - - finder := func(fn func(*gs_bean.BeanDefinition) bool) ([]gs.CondBean, error) { - var result []gs.CondBean - for _, b := range c.beans { - if b.Status() == gs_bean.Resolving || b.Status() == gs_bean.Deleted || !fn(b) { - continue - } - if err := c.resolveBean(b); err != nil { - return nil, err - } - if b.Status() == gs_bean.Deleted { - continue - } - result = append(result, b) - } - return result, nil - } - - var t reflect.Type - switch st := selector.(type) { - case string, *gs_bean.BeanDefinition: - tag, err := c.toWireTag(selector) - if err != nil { - return nil, err - } - return finder(func(b *gs_bean.BeanDefinition) bool { - return b.Match(tag.typeName, tag.beanName) - }) - case reflect.Type: - t = st - default: - t = reflect.TypeOf(st) - } - - if t.Kind() == reflect.Ptr { - if e := t.Elem(); e.Kind() == reflect.Interface { - t = e // 指 (*error)(nil) 形式的 bean 选择器 - } - } - - return finder(func(b *gs_bean.BeanDefinition) bool { - if b.Type() == t { - return true - } - return false - }) +func (c *resolvingStage) Has(key string) bool { + return c.p.Has(key) } -// Get 根据类型和选择器获取符合条件的 bean 对象。当 i 是一个基础类型的 bean 接收 -// 者时,表示符合条件的 bean 对象只能有一个,没有找到或者多于一个时会返回 error。 -// 当 i 是一个 map 类型的 bean 接收者时,表示获取任意数量的 bean 对象,map 的 -// key 是 bean 的名称,map 的 value 是 bean 的地址。当 i 是一个 array 或者 -// slice 时,也表示获取任意数量的 bean 对象,但是它会对获取到的 bean 对象进行排序, -// 如果没有传入选择器或者传入的选择器是 * ,则根据 bean 的 order 值进行排序,这种 -// 工作模式称为自动模式,否则根据传入的选择器列表进行排序,这种工作模式成为指派模式。 -// 该方法和 Find 方法的区别是该方法保证返回的所有 bean 对象都已经完成属性绑定和依 -// 赖注入,而 Find 方法只能保证返回的 bean 对象是有效的,即未被标记为删除的。 -func (c *Container) Get(i interface{}, selectors ...gs.BeanSelector) error { - - if i == nil { - return errors.New("i can't be nil") - } - - v := reflect.ValueOf(i) - if v.Kind() != reflect.Ptr { - return errors.New("i must be pointer") - } - - stack := newWiringStack() +func (c *resolvingStage) Prop(key string, def ...string) string { + return c.p.Get(key, def...) +} - defer func() { - if len(stack.beans) > 0 { - syslog.Info("wiring path %s", stack.path()) +// Find 查找符合条件的 bean 对象,注意该函数只能保证返回的 bean 是有效的, +// 即未被标记为删除的,而不能保证已经完成属性绑定和依赖注入。 +func (c *resolvingStage) Find(s gs.BeanSelectorInterface) ([]gs.CondBean, error) { + t, name := s.TypeAndName() + var result []gs.CondBean + for _, b := range c.beans { + if b.Status() == gs_bean.StatusResolving || b.Status() == gs_bean.StatusDeleted { + continue } - }() - - var tags []wireTag - for _, s := range selectors { - g, err := c.toWireTag(s) - if err != nil { - return err + if t != nil { + if b.Type() != t { + foundType := false + for _, typ := range b.Exports() { + if typ == t { + foundType = true + break + } + } + if !foundType { + continue + } + } } - tags = append(tags, g) - } - return c.autowire(v.Elem(), tags, false, stack) -} - -// Wire 如果传入的是 bean 对象,则对 bean 对象进行属性绑定和依赖注入,如果传入的 -// 是构造函数,则立即执行该构造函数,然后对返回的结果进行属性绑定和依赖注入。无论哪 -// 种方式,该函数执行完后都会返回 bean 对象的真实值。 -func (c *Container) Wire(objOrCtor interface{}, ctorArgs ...gs.Arg) (interface{}, error) { - - stack := newWiringStack() - - defer func() { - if len(stack.beans) > 0 { - syslog.Info("wiring path %s", stack.path()) + if name != "" && name != b.Name() { + continue } - }() - - b := NewBean(objOrCtor, ctorArgs...) - var err error - switch c.state { - case Refreshing: - err = c.wireBeanInRefreshing(b.BeanRegistration().(*gs_bean.BeanDefinition), stack) - case Refreshed: - err = c.wireBeanAfterRefreshed(b.BeanRegistration().(*gs_bean.BeanDefinition), stack) - default: - err = errors.New("state is error for wiring") - } - if err != nil { - return nil, err - } - return b.BeanRegistration().(*gs_bean.BeanDefinition).Interface(), nil -} - -// Invoke 调用函数,函数的参数会自动注入,函数的返回值也会自动注入。 -func (c *Container) Invoke(fn interface{}, args ...gs.Arg) ([]interface{}, error) { - - if !util.IsFuncType(reflect.TypeOf(fn)) { - return nil, errors.New("fn should be func type") - } - - stack := newWiringStack() - - defer func() { - if len(stack.beans) > 0 { - syslog.Info("wiring path %s", stack.path()) + if err := c.resolveBean(b); err != nil { + return nil, err } - }() - - r, err := gs_arg.Bind(fn, args, 1) - if err != nil { - return nil, err - } - - ret, err := r.Call(&argContext{c: c, stack: stack}) - if err != nil { - return nil, err - } - - var a []interface{} - for _, v := range ret { - a = append(a, v.Interface()) - } - return a, nil -} - -// Go 创建安全可等待的 goroutine,fn 要求的 ctx 对象由 IoC 容器提供,当 IoC 容 -// 器关闭时 ctx会 发出 Done 信号, fn 在接收到此信号后应当立即退出。 -func (c *Container) Go(fn func(ctx context.Context)) { - c.wg.Add(1) - go func() { - defer c.wg.Done() - defer func() { - if r := recover(); r != nil { - syslog.Error("%v", r) - } - }() - fn(c.ctx) - }() -} - -// Close 关闭容器,此方法必须在 Refresh 之后调用。该方法会触发 ctx 的 Done 信 -// 号,然后等待所有 goroutine 结束,最后按照被依赖先销毁的原则执行所有的销毁函数。 -func (c *Container) Close() { - - c.cancel() - c.wg.Wait() - - syslog.Info("goroutines exited") - - for _, f := range c.destroyers { - f() + if b.Status() == gs_bean.StatusDeleted { + continue + } + result = append(result, b) } - - syslog.Info("container closed") + return result, nil } diff --git a/gs/internal/gs_core/core_test.go b/gs/internal/gs_core/core_test.go index 6736b614..6e03baf2 100755 --- a/gs/internal/gs_core/core_test.go +++ b/gs/internal/gs_core/core_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,15 +34,30 @@ import ( "github.com/go-spring/spring-core/gs/internal/gs_bean" "github.com/go-spring/spring-core/gs/internal/gs_cond" "github.com/go-spring/spring-core/gs/internal/gs_core" + pkg1 "github.com/go-spring/spring-core/gs/internal/gs_core/testdata/pkg/bar" + pkg2 "github.com/go-spring/spring-core/gs/internal/gs_core/testdata/pkg/foo" "github.com/go-spring/spring-core/gs/internal/gs_dync" - pkg1 "github.com/go-spring/spring-core/gs/testdata/pkg/bar" - pkg2 "github.com/go-spring/spring-core/gs/testdata/pkg/foo" - "github.com/go-spring/spring-core/util" "github.com/go-spring/spring-core/util/assert" - "github.com/go-spring/spring-core/util/macro" "github.com/spf13/cast" ) +func container(t *testing.T, fn func(p *conf.Properties, c *gs_core.Container) error) gs.Context { + p := conf.New() + c := gs_core.New().(*gs_core.Container) + if fn != nil { + if err := fn(p, c); err != nil { + t.Fatal(err) + } + } + if err := c.RefreshProperties(p); err != nil { + t.Fatal(err) + } + if err := c.Refresh(); err != nil { + t.Fatal(err) + } + return c +} + func runTest(c gs.Container, fn func(gs.Context)) error { type PandoraAware struct{} c.Provide(func(p gs.Context) PandoraAware { @@ -150,7 +165,9 @@ func TestApplicationContext_AutoWireBeans(t *testing.T) { c.Object(obj) b := TestBincoreng{1} - c.Object(&b).Name("struct_ptr").Export((*fmt.Stringer)(nil)) + c.Object(&b).Name("struct_ptr").Export( + reflect.TypeFor[fmt.Stringer](), + ) err := runTest(c, func(p gs.Context) {}) assert.Nil(t, err) @@ -331,7 +348,7 @@ type DbConfig struct { } func TestApplicationContext_TypeConverter(t *testing.T) { - prop, _ := conf.Load("../../testdata/config/application.yaml") + prop, _ := conf.Load("testdata/config/application.yaml") c := gs_core.New() @@ -380,7 +397,9 @@ type ProxyGrouper struct { func TestApplicationContext_NestedBean(t *testing.T) { c := gs_core.New() - c.Object(new(MyGrouper)).Export((*Grouper)(nil)) + c.Object(new(MyGrouper)).Export( + reflect.TypeFor[Grouper](), + ) c.Object(new(ProxyGrouper)) err := c.Refresh() assert.Nil(t, err) @@ -390,54 +409,12 @@ type Pkg interface { Package() } -type SamePkgHolder struct { - // Pkg `autowire:""` // 这种方式会找到多个符合条件的 Object - Pkg `autowire:"github.com/go-spring/spring-core/gs/testdata/pkg/bar/pkg.SamePkg:SamePkg"` -} - -func TestApplicationContext_SameNameBean(t *testing.T) { - c := gs_core.New() - c.Object(new(SamePkgHolder)) - c.Object(&pkg1.SamePkg{}).Export((*Pkg)(nil)) - c.Object(&pkg2.SamePkg{}).Export((*Pkg)(nil)) - err := c.Refresh() - assert.Nil(t, err) -} - -type DiffPkgOne struct { -} - -func (d *DiffPkgOne) Package() { - fmt.Println("github.com/go-spring/spring-core/gs_test.DiffPkgOne") -} - -type DiffPkgTwo struct { -} - -func (d *DiffPkgTwo) Package() { - fmt.Println("github.com/go-spring/spring-core/gs_test.DiffPkgTwo") -} - -type DiffPkgHolder struct { - // Pkg `autowire:"same"` // 如果两个 Object 不小心重名了,也会找到多个符合条件的 Object - Pkg `autowire:"github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.DiffPkgTwo:same"` -} - -func TestApplicationContext_DiffNameBean(t *testing.T) { - c := gs_core.New() - c.Object(&DiffPkgOne{}).Name("same").Export((*Pkg)(nil)) - c.Object(&DiffPkgTwo{}).Name("same").Export((*Pkg)(nil)) - c.Object(new(DiffPkgHolder)) - err := c.Refresh() - assert.Nil(t, err) -} - func TestApplicationContext_LoadProperties(t *testing.T) { c := gs_core.New() - prop, _ := conf.Load("../../testdata/config/application.yaml") - p, _ := conf.Load("../../testdata/config/application.properties") + prop, _ := conf.Load("testdata/config/application.yaml") + p, _ := conf.Load("testdata/config/application.properties") for _, key := range p.Keys() { prop.Set(key, p.Get(key)) } @@ -498,7 +475,9 @@ func TestApplicationContext_Get(t *testing.T) { c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) - c.Object(new(BeanTwo)).Export((*Grouper)(nil)) + c.Object(new(BeanTwo)).Export( + reflect.TypeFor[Grouper](), + ) err := runTest(c, func(p gs.Context) { var two *BeanTwo @@ -509,12 +488,6 @@ func TestApplicationContext_Get(t *testing.T) { err = p.Get(&grouper) assert.Nil(t, err) - err = p.Get(&two, (*BeanTwo)(nil)) - assert.Nil(t, err) - - err = p.Get(&grouper, (*BeanTwo)(nil)) - assert.Nil(t, err) - err = p.Get(&two) assert.Nil(t, err) @@ -527,24 +500,6 @@ func TestApplicationContext_Get(t *testing.T) { err = p.Get(&grouper, "BeanTwo") assert.Nil(t, err) - err = p.Get(&two, ":BeanTwo") - assert.Nil(t, err) - - err = p.Get(&grouper, ":BeanTwo") - assert.Nil(t, err) - - err = p.Get(&two, "github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.BeanTwo:BeanTwo") - assert.Nil(t, err) - - err = p.Get(&grouper, "github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.BeanTwo:BeanTwo") - assert.Nil(t, err) - - err = p.Get(&two, "xxx:BeanTwo") - assert.Error(t, err, "can't find bean, bean:\"xxx:BeanTwo\"") - - err = p.Get(&grouper, "xxx:BeanTwo") - assert.Error(t, err, "can't find bean, bean:\"xxx:BeanTwo\"") - var three *BeanThree err = p.Get(&three) assert.Error(t, err, "can't find bean, bean:\"\"") @@ -650,12 +605,14 @@ func TestApplicationContext_RegisterBeanFn(t *testing.T) { c := gs_core.New() // 用接口注册时实际使用的是原始类型 - c.Object(Teacher(newHistoryTeacher(""))).Export((*Teacher)(nil)) + c.Object(Teacher(newHistoryTeacher(""))).Export( + reflect.TypeFor[Teacher](), + ) - c.Provide(NewStudent, "", "${room}").Name("st1") - c.Provide(NewPtrStudent, "", "${room}").Name("st2") - c.Provide(NewStudent, "?", "${room:=https://}").Name("st3") - c.Provide(NewPtrStudent, "?", "${room:=4567}").Name("st4") + c.Provide(NewStudent, gs_arg.Tag(""), gs_arg.Tag("${room}")).Name("st1") + c.Provide(NewPtrStudent, gs_arg.Tag(""), gs_arg.Tag("${room}")).Name("st2") + c.Provide(NewStudent, gs_arg.Tag("?"), gs_arg.Tag("${room:=https://}")).Name("st3") + c.Provide(NewPtrStudent, gs_arg.Tag("?"), gs_arg.Tag("${room:=4567}")).Name("st4") c.Object(newTeacher("history", "")).Init(func(teacher Teacher) { fmt.Println(teacher.Course()) @@ -742,23 +699,19 @@ func TestApplicationContext_DependsOn(t *testing.T) { }) t.Run("dependsOn", func(t *testing.T) { - - dependsOn := []gs.BeanSelector{ - (*BeanOne)(nil), // 通过类型定义查找 - "github.com/go-spring/spring-core/gs/gs_test.BeanZero:BeanZero", - } - c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) - c.Object(new(BeanFour)).DependsOn(dependsOn...) + c.Object(new(BeanFour)).DependsOn( + gs.BeanSelectorForType[*BeanOne](), + gs.BeanSelector{Name: "BeanZero"}, + ) err := c.Refresh() assert.Nil(t, err) }) } -func TestApplicationContext_Primary(t *testing.T) { - +func TestApplicationContext_Duplicate(t *testing.T) { t.Run("duplicate", func(t *testing.T) { c := gs_core.New() c.Object(&BeanZero{5}) @@ -768,46 +721,6 @@ func TestApplicationContext_Primary(t *testing.T) { err := c.Refresh() assert.Error(t, err, "duplicate beans ") }) - - t.Run("duplicate", func(t *testing.T) { - c := gs_core.New() - c.Object(&BeanZero{5}) - // primary 是在多个候选 bean 里面选择,而不是允许同名同类型的两个 bean - c.Object(&BeanZero{6}).Primary() - c.Object(new(BeanOne)) - c.Object(new(BeanTwo)) - err := c.Refresh() - assert.Error(t, err, "duplicate beans ") - }) - - t.Run("not primary", func(t *testing.T) { - c := gs_core.New() - c.Object(&BeanZero{5}) - c.Object(new(BeanOne)) - c.Object(new(BeanTwo)) - err := runTest(c, func(p gs.Context) { - var b *BeanTwo - err := p.Get(&b) - assert.Nil(t, err) - assert.Equal(t, b.One.Zero.Int, 5) - }) - assert.Nil(t, err) - }) - - t.Run("primary", func(t *testing.T) { - c := gs_core.New() - c.Object(&BeanZero{5}) - c.Object(&BeanZero{6}).Name("zero_6").Primary() - c.Object(new(BeanOne)) - c.Object(new(BeanTwo)) - err := runTest(c, func(p gs.Context) { - var b *BeanTwo - err := p.Get(&b) - assert.Nil(t, err) - assert.Equal(t, b.One.Zero.Int, 6) - }) - assert.Nil(t, err) - }) } type FuncObj struct { @@ -834,7 +747,7 @@ func NewManager() Manager { } func NewManagerRetError() (Manager, error) { - return localManager{}, util.Error(macro.FileLine(), "error") + return localManager{}, errors.New("NewManagerRetError error") } func NewManagerRetErrorNil() (Manager, error) { @@ -889,9 +802,7 @@ func TestApplicationContext_RegisterBeanFn2(t *testing.T) { c := gs_core.New() c.RefreshProperties(prop) - bd := c.Provide(NewManager) - assert.Matches(t, bd.ID(), ".*:NewManager") - + c.Provide(NewManager) err := runTest(c, func(p gs.Context) { var m Manager @@ -912,7 +823,7 @@ func TestApplicationContext_RegisterBeanFn2(t *testing.T) { c.Provide(NewManagerRetError) c.RefreshProperties(prop) err := c.Refresh() - assert.Error(t, err, "core_test.go:\\d* error") + assert.Error(t, err, "NewManagerRetError error") }) t.Run("manager return error nil", func(t *testing.T) { @@ -966,7 +877,7 @@ func (d *callDestroy) InitWithError() error { d.inited = true return nil } - return util.Error(macro.FileLine(), "error") + return errors.New("InitWithError error") } func (d *callDestroy) DestroyWithError() error { @@ -974,7 +885,7 @@ func (d *callDestroy) DestroyWithError() error { d.destroyed = true return nil } - return util.Error(macro.FileLine(), "error") + return errors.New("DestroyWithError error") } type nestedCallDestroy struct { @@ -1175,8 +1086,7 @@ func TestApplicationContext_Collect(t *testing.T) { c := gs_core.New() c.Object(new(RecoresCluster)).Name("a") c.Object(new(RecoresCluster)).Name("b") - - intBean := c.Provide(func(p gs.Context) func() { + c.Provide(func(p gs.Context) func() { var rcs []*RecoresCluster err := p.Get(&rcs) @@ -1187,7 +1097,6 @@ func TestApplicationContext_Collect(t *testing.T) { return func() {} }) - assert.Equal(t, intBean.ID(), "func():TestApplicationContext_Collect.func6.1") c.RefreshProperties(prop) err := c.Refresh() @@ -1320,7 +1229,7 @@ func TestOptionConstructorArg(t *testing.T) { prop.Set("president", "CaiYuanPei") c := gs_core.New() - c.Provide(NewClassRoom, gs_arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}")) + c.Provide(NewClassRoom, gs_arg.Option(withClassName, gs_arg.Tag("${class_name:=二年级03班}"), gs_arg.Tag("${class_floor:=3}"))) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1366,7 +1275,7 @@ func TestOptionConstructorArg(t *testing.T) { c := gs_core.New() c.Provide(NewClassRoom, gs_arg.Option(withStudents), - gs_arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}"), + gs_arg.Option(withClassName, gs_arg.Tag("${class_name:=二年级03班}"), gs_arg.Tag("${class_floor:=3}")), gs_arg.Option(withBuilder, gs_arg.MustBind(func(param string) *ClassBuilder { return &ClassBuilder{param: param} }, gs_arg.Value("1"))), @@ -1409,7 +1318,7 @@ type Consumer struct { func (s *Server) Consumer() *Consumer { if nil == s { - panic(errors.New("server is nil")) + panic("server is nil") } return &Consumer{s} } @@ -1420,7 +1329,7 @@ func (s *Server) ConsumerT() *Consumer { func (s *Server) ConsumerArg(_ int) *Consumer { if nil == s { - panic(errors.New("server is nil")) + panic("server is nil") } return &Consumer{s} } @@ -1437,7 +1346,7 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { c := gs_core.New() parent := c.Object(new(Server)) - bd := c.Provide((*Server).Consumer, parent.ID()) + c.Provide((*Server).Consumer, parent) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1455,7 +1364,6 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { assert.Equal(t, consumer.s.Version, "2.0.0") }) assert.Nil(t, err) - assert.Matches(t, bd.ID(), ".*:Consumer") }) t.Run("method bean condition", func(t *testing.T) { @@ -1463,8 +1371,8 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { prop.Set("server.version", "1.0.0") c := gs_core.New() - parent := c.Object(new(Server)).On(gs_cond.Not(gs_cond.OK())) - bd := c.Provide((*Server).Consumer, parent) + parent := c.Object(new(Server)).Condition(gs_cond.Not(gs_cond.OnMissingProperty("a"))) + c.Provide((*Server).Consumer, parent) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1478,7 +1386,6 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { assert.Error(t, err, "can't find bean, bean:\"\" type:\"\\*gs_core_test.Consumer\"") }) assert.Nil(t, err) - assert.Matches(t, bd.ID(), ".*:Consumer") }) t.Run("method bean arg", func(t *testing.T) { @@ -1487,7 +1394,7 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { c := gs_core.New() parent := c.Object(new(Server)) - c.Provide((*Server).ConsumerArg, parent.ID(), "${i:=9}") + c.Provide((*Server).ConsumerArg, parent, gs_arg.Tag("${i:=9}")) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1513,7 +1420,7 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { c := gs_core.New() parent := c.Provide(NewServerInterface) - c.Provide(ServerInterface.Consumer, parent.ID()).DependsOn("ServerInterface") + c.Provide(ServerInterface.Consumer, parent).DependsOn(gs.BeanSelector{Name: "ServerInterface"}) c.Object(new(Service)) c.RefreshProperties(prop) @@ -1555,7 +1462,7 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { } if !strings.Contains(v, "found circle autowire") { - panic(errors.New("test error")) + panic("test error") } } else { okCount++ @@ -1566,8 +1473,8 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { prop.Set("server.version", "1.0.0") c := gs_core.New() - parent := c.Object(new(Server)).DependsOn("Service") - c.Provide((*Server).Consumer, parent.ID()).DependsOn("Server") + parent := c.Object(new(Server)).DependsOn(gs.BeanSelector{Name: "Service"}) + c.Provide((*Server).Consumer, parent).DependsOn(gs.BeanSelector{Name: "Service"}) c.Object(new(Service)) c.RefreshProperties(prop) err := c.Refresh() @@ -1602,7 +1509,7 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { c := gs_core.New() c.Object(new(Server)) - c.Provide(func(s *Server) *Consumer { return s.Consumer() }, (*Server)(nil)) + c.Provide(func(s *Server) *Consumer { return s.Consumer() }) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1622,18 +1529,18 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { assert.Nil(t, err) }) - t.Run("method bean selector type error", func(t *testing.T) { - prop := conf.New() - prop.Set("server.version", "1.0.0") - - c := gs_core.New() - c.Object(new(Server)) - c.Provide(func(s *Server) *Consumer { return s.Consumer() }, (*int)(nil)) - - c.RefreshProperties(prop) - err := c.Refresh() - assert.Error(t, err, "can't find bean, bean:\"int:\" type:\"\\*gs_core_test.Server\"") - }) + // t.Run("method bean selector type error", func(t *testing.T) { + // prop := conf.New() + // prop.Set("server.version", "1.0.0") + // + // c := gs_core.New() + // c.Object(new(Server)) + // c.Provide(func(s *Server) *Consumer { return s.Consumer() }) // gs_arg.BeanTag[int](), + // + // c.RefreshProperties(prop) + // err := c.Refresh() + // assert.Error(t, err, "can't find bean, bean:\"int:\" type:\"\\*gs_core_test.Server\"") + // }) t.Run("method bean selector beanId", func(t *testing.T) { prop := conf.New() @@ -1641,7 +1548,7 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { c := gs_core.New() c.Object(new(Server)) - c.Provide(func(s *Server) *Consumer { return s.Consumer() }, "Server") + c.Provide(func(s *Server) *Consumer { return s.Consumer() }, gs_arg.Tag("Server")) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1667,7 +1574,7 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { c := gs_core.New() c.Object(new(Server)) - c.Provide(func(s *Server) *Consumer { return s.Consumer() }, "NULL") + c.Provide(func(s *Server) *Consumer { return s.Consumer() }, gs_arg.Tag("NULL")) c.RefreshProperties(prop) err := c.Refresh() @@ -1770,7 +1677,7 @@ func TestApplicationContext_CircleAutowire(t *testing.T) { return new(CircleC) }) err := c.Refresh() - assert.Error(t, err, "found circle autowire") + assert.Error(t, err, "found circular autowire") }) } @@ -1864,7 +1771,7 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { c := gs_core.New() c.Object(&Var{"v1"}).Name("v1") c.Object(&Var{"v2"}).Name("v2") - c.Provide(NewVarObj, "${var.obj}", gs_arg.Option(withVar, "v1")) + c.Provide(NewVarObj, gs_arg.Tag("${var.obj}"), gs_arg.Option(withVar, gs_arg.Tag("v1"))) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1885,7 +1792,7 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { c := gs_core.New() c.Object(&Var{"v1"}).Name("v1") c.Object(&Var{"v2"}).Name("v2") - c.Provide(NewVarObj, gs_arg.Value("description"), gs_arg.Option(withVar, "v1", "v2")) + c.Provide(NewVarObj, gs_arg.Value("description"), gs_arg.Option(withVar, gs_arg.Tag("v1"), gs_arg.Tag("v2"))) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -1902,9 +1809,13 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { t.Run("variable option interface param 1", func(t *testing.T) { c := gs_core.New() - c.Object(&Var{"v1"}).Name("v1").Export((*interface{})(nil)) - c.Object(&Var{"v2"}).Name("v2").Export((*interface{})(nil)) - c.Provide(NewVarInterfaceObj, gs_arg.Option(withVarInterface, "v1")) + c.Object(&Var{"v1"}).Name("v1").Export( + reflect.TypeFor[interface{}](), + ) + c.Object(&Var{"v2"}).Name("v2").Export( + reflect.TypeFor[interface{}](), + ) + c.Provide(NewVarInterfaceObj, gs_arg.Option(withVarInterface, gs_arg.Tag("v1"))) err := runTest(c, func(p gs.Context) { var obj *VarInterfaceObj err := p.Get(&obj) @@ -1916,9 +1827,13 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { t.Run("variable option interface param 1", func(t *testing.T) { c := gs_core.New() - c.Object(&Var{"v1"}).Name("v1").Export((*interface{})(nil)) - c.Object(&Var{"v2"}).Name("v2").Export((*interface{})(nil)) - c.Provide(NewVarInterfaceObj, gs_arg.Option(withVarInterface, "v1", "v2")) + c.Object(&Var{"v1"}).Name("v1").Export( + reflect.TypeFor[interface{}](), + ) + c.Object(&Var{"v2"}).Name("v2").Export( + reflect.TypeFor[interface{}](), + ) + c.Provide(NewVarInterfaceObj, gs_arg.Option(withVarInterface, gs_arg.Tag("v1"), gs_arg.Tag("v2"))) err := runTest(c, func(p gs.Context) { var obj *VarInterfaceObj err := p.Get(&obj) @@ -2162,8 +2077,12 @@ func TestApplicationContext_FnArgCollectBean(t *testing.T) { t.Run("interface type", func(t *testing.T) { c := gs_core.New() - c.Provide(newHistoryTeacher("t1")).Name("t1").Export((*Teacher)(nil)) - c.Provide(newHistoryTeacher("t2")).Name("t2").Export((*Teacher)(nil)) + c.Provide(newHistoryTeacher("t1")).Name("t1").Export( + reflect.TypeFor[Teacher](), + ) + c.Provide(newHistoryTeacher("t2")).Name("t2").Export( + reflect.TypeFor[Teacher](), + ) c.Provide(func(teachers []Teacher) func() { names := make([]string, 0) for _, teacher := range teachers { @@ -2193,7 +2112,9 @@ func TestApplicationContext_BeanCache(t *testing.T) { t.Run("not implement interface", func(t *testing.T) { c := gs_core.New() - c.Object(func() {}).Export((*filter)(nil)) + c.Object(func() {}).Export( + reflect.TypeFor[filter](), + ) err := c.Refresh() assert.Error(t, err, "doesn't implement interface gs_core_test.filter") }) @@ -2207,7 +2128,9 @@ func TestApplicationContext_BeanCache(t *testing.T) { c := gs_core.New() c.Provide(func() filter { return new(filterImpl) }).Name("f1") - c.Object(new(filterImpl)).Export((*filter)(nil)).Name("f2") + c.Object(new(filterImpl)).Export( + reflect.TypeFor[filter](), + ).Name("f2") c.Object(&server) err := c.Refresh() @@ -2351,7 +2274,7 @@ func TestApplicationContext_CreateBean(t *testing.T) { c := gs_core.New() c.Object(&ObjFactory{}) err := runTest(c, func(p gs.Context) { - b, err := p.Wire((*ObjFactory).NewObj, gs_arg.Index(1, "${i:=5}")) + b, err := p.Wire((*ObjFactory).NewObj, gs_arg.Index(1, gs_arg.Tag("${i:=5}"))) fmt.Println(b, err) }) assert.Nil(t, err) @@ -2359,57 +2282,21 @@ func TestApplicationContext_CreateBean(t *testing.T) { func TestDefaultSpringContext(t *testing.T) { - t.Run("bean:test_ctx:", func(t *testing.T) { - - c := gs_core.New() - - c.Object(&BeanZero{5}).On(gs_cond. - OnProfile("test"). - And(). - OnMissingBean("null"). - And(). - On(gs_cond.OK()), - ) - - err := runTest(c, func(p gs.Context) { - var b *BeanZero - err := p.Get(&b) - assert.Error(t, err, "can't find bean, bean:\"\"") - }) - assert.Nil(t, err) - }) - - t.Run("bean:test_ctx:test", func(t *testing.T) { - prop := conf.New() - prop.Set("spring.profiles.active", "test") - - c := gs_core.New() - c.Object(&BeanZero{5}).On(gs_cond.OnProfile("test")) - - c.RefreshProperties(prop) - err := runTest(c, func(p gs.Context) { - var b *BeanZero - err := p.Get(&b) - assert.Nil(t, err) - }) - assert.Nil(t, err) - }) - - t.Run("bean:test_ctx:stable", func(t *testing.T) { - prop := conf.New() - prop.Set("spring.profiles.active", "stable") - - c := gs_core.New() - c.Object(&BeanZero{5}).On(gs_cond.OnProfile("test")) - - c.RefreshProperties(prop) - err := runTest(c, func(p gs.Context) { - var b *BeanZero - err := p.Get(&b) - assert.Error(t, err, "can't find bean, bean:\"\"") - }) - assert.Nil(t, err) - }) + // t.Run("bean:test_ctx:", func(t *testing.T) { + // + // c := gs_core.New() + // + // c.Object(&BeanZero{5}). + // Condition(gs_cond.OnMissingBean("null")). + // Condition(gs_cond.OnMissingProperty("a")) + // + // err := runTest(c, func(p gs.Context) { + // var b *BeanZero + // err := p.Get(&b) + // assert.Error(t, err, "can't find bean, bean:\"\"") + // }) + // assert.Nil(t, err) + // }) t.Run("option withClassName Condition", func(t *testing.T) { @@ -2419,9 +2306,9 @@ func TestDefaultSpringContext(t *testing.T) { c := gs_core.New() c.Provide(NewClassRoom, gs_arg.Option(withClassName, - "${class_name:=二年级03班}", - "${class_floor:=3}", - ).On(gs_cond.OnProperty("class_name_enable"))) + gs_arg.Tag("${class_name:=二年级03班}"), + gs_arg.Tag("${class_floor:=3}"), + ).Condition(gs_cond.OnProperty("class_name_enable"))) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -2444,9 +2331,9 @@ func TestDefaultSpringContext(t *testing.T) { c := gs_core.New() c.Provide(NewClassRoom, gs_arg.Option(withClassName, - "${class_name:=二年级03班}", - "${class_floor:=3}", - ).On(onProperty), + gs_arg.Tag("${class_name:=二年级03班}"), + gs_arg.Tag("${class_floor:=3}"), + ).Condition(onProperty), ) c.RefreshProperties(prop) @@ -2468,7 +2355,7 @@ func TestDefaultSpringContext(t *testing.T) { c := gs_core.New() parent := c.Object(new(Server)) - c.Provide((*Server).Consumer, parent.ID()).On(gs_cond.OnProperty("consumer.enable")) + c.Provide((*Server).Consumer, parent).Condition(gs_cond.OnProperty("consumer.enable")) c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { @@ -2490,8 +2377,8 @@ func TestDefaultSpringContext(t *testing.T) { // func TestDefaultSpringContext_ParentNotRegister(t *testing.T) { // // c := gs.New() -// parent := c.Provide(NewServerInterface).On(cond.OnProperty("server.is.nil")) -// c.Provide(ServerInterface.Consumer, parent.ID()) +// parent := c.Provide(NewServerInterface).Condition(cond.OnProperty("server.is.nil")) +// c.Provide(ServerInterface.Consumer, parent.Name()) // // c.Refresh() // @@ -2507,13 +2394,25 @@ func TestDefaultSpringContext(t *testing.T) { func TestDefaultSpringContext_ConditionOnBean(t *testing.T) { c := gs_core.New() - c1 := gs_cond.OnProperty("null", gs_cond.MatchIfMissing()).Or().OnProfile("test") - - c.Object(&BeanZero{5}).On(gs_cond.On(c1).And().OnMissingBean("null")) - c.Object(new(BeanOne)).On(gs_cond.On(c1).And().OnMissingBean("null")) - - c.Object(new(BeanTwo)).On(gs_cond.OnBean("BeanOne")) - c.Object(new(BeanTwo)).Name("another_two").On(gs_cond.OnBean("Null")) + c1 := gs_cond.Or( + gs_cond.OnProperty("null").MatchIfMissing(), + ) + + c.Object(&BeanZero{5}).Condition( + gs_cond.And( + c1, + gs_cond.OnMissingBean(gs.BeanSelector{Name: "null"}), + ), + ) + c.Object(new(BeanOne)).Condition( + gs_cond.And( + c1, + gs_cond.OnMissingBean(gs.BeanSelector{Name: "null"}), + ), + ) + + c.Object(new(BeanTwo)).Condition(gs_cond.OnBean(gs.BeanSelector{Name: "BeanOne"})) + c.Object(new(BeanTwo)).Name("another_two").Condition(gs_cond.OnBean(gs.BeanSelector{Name: "Null"})) err := runTest(c, func(p gs.Context) { @@ -2532,8 +2431,8 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) - c.Object(new(BeanTwo)).On(gs_cond.OnMissingBean("BeanOne")) - c.Object(new(BeanTwo)).Name("another_two").On(gs_cond.OnMissingBean("Null")) + c.Object(new(BeanTwo)).Condition(gs_cond.OnMissingBean(gs.BeanSelector{Name: "BeanOne"})) + c.Object(new(BeanTwo)).Name("another_two").Condition(gs_cond.OnMissingBean(gs.BeanSelector{Name: "Null"})) err := runTest(c, func(p gs.Context) { var two *BeanTwo @@ -2740,7 +2639,7 @@ func TestApplicationContext_Invoke(t *testing.T) { err := runTest(c, func(p gs.Context) { _, _ = p.Invoke(func(f func(), version string) { fmt.Println("version:", version) - }, "", "${version}") + }, gs_arg.Tag(""), gs_arg.Tag("${version}")) }) assert.Nil(t, err) }) @@ -2758,7 +2657,7 @@ func TestApplicationContext_Invoke(t *testing.T) { fn := func(f func(), version string) { fmt.Println("version:", version) } - _, _ = p.Invoke(fn, "", "${version}") + _, _ = p.Invoke(fn, gs_arg.Tag(""), gs_arg.Tag("${version}")) }) assert.Nil(t, err) }) @@ -2793,7 +2692,7 @@ func TestMapCollection(t *testing.T) { c := gs_core.New() c.Object(&mapValue{"a"}).Name("a") c.Object(&mapValue{"b"}).Name("b") - c.Object(&mapValue{"c"}).Name("c").On(gs_cond.Not(gs_cond.OK())) + c.Object(&mapValue{"c"}).Name("c").Condition(gs_cond.Not(gs_cond.OnMissingProperty("a"))) err := runTest(c, func(p gs.Context) { var vSlice []*mapValue @@ -3037,13 +2936,13 @@ func (c *ConfigurationBean) NewChild() *ChildBean { return &ChildBean{c.s} } -func (c *ConfigurationBean) NewBean() *gs.UnregisteredBean { +func (c *ConfigurationBean) NewBean() *gs.BeanDefinition { return gs_core.NewBean(&ChildBean{"100"}).Name("100") } func TestConfiguration(t *testing.T) { c := gs_core.New() - c.Object(&ConfigurationBean{"123"}).Configuration(gs.ConfigurationParam{Exclude: []string{"NewBean"}}).Name("123") + c.Object(&ConfigurationBean{"123"}).Configuration(gs.ConfigurationParam{Excludes: []string{"NewBean"}}).Name("123") c.Provide(NewConfigurationBean, gs_arg.Value("456")).Configuration().Name("456") ctx := &gs.ContextAware{} c.Object(ctx) diff --git a/gs/internal/gs_core/dync_test.go b/gs/internal/gs_core/dync_test.go new file mode 100644 index 00000000..927f0605 --- /dev/null +++ b/gs/internal/gs_core/dync_test.go @@ -0,0 +1,195 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_core_test + +// import ( +// "encoding/json" +// "reflect" +// "testing" +// +// "github.com/go-spring/spring-core/conf" +// "github.com/go-spring/spring-core/gs/internal/gs_dync" +// "github.com/go-spring/spring-core/util/assert" +// ) +// +// // todo 自定义类型通过类型转换器实现刷新机制 +// +// type StructValue struct { +// Str string `value:"${string:=abc}" expr:"len($)<6"` +// Int int `value:"${int:=3}" expr:"$<6"` +// } +// +// type Config struct { +// Int gs_dync.Value[int64] `value:"${int:=3}" expr:"$<6"` +// Float gs_dync.Value[float64] `value:"${float:=1.2}"` +// Map gs_dync.Value[map[string]string] `value:"${map:=}"` +// Slice gs_dync.Value[[]string] `value:"${slice:=}"` +// Object gs_dync.Value[StructValue] `value:"${object:=}"` +// } +// +// func newTest() (*gs_dync.Properties, *Config, error) { +// mgr := gs_dync.New() +// cfg := new(Config) +// err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) +// if err != nil { +// return nil, nil, err +// } +// return mgr, cfg, nil +// } +// +// func TestDynamic(t *testing.T) { +// +// t.Run("default", func(t *testing.T) { +// _, cfg, err := newTest() +// if err != nil { +// return +// } +// b, _ := json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) +// }) +// +// t.Run("init", func(t *testing.T) { +// _, cfg, err := newTest() +// if err != nil { +// return +// } +// // cfg.Slice.Init(make([]string, 0)) +// // cfg.Map.Init(make(map[string]string)) +// // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { +// // fmt.Println("event fired.") +// // return nil +// // }) +// b, _ := json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) +// }) +// +// // t.Run("default validate error", func(t *testing.T) { +// // mgr := dync.New() +// // cfg := new(Config) +// // // cfg.Int.OnValidate(func(v int64) error { +// // // if v < 6 { +// // // return errors.New("should greeter than 6") +// // // } +// // // return nil +// // // }) +// // err := mgr.BindValue(reflect.ValueOf(cfg), conf.BindParam{}) +// // assert.Error(t, err, "should greeter than 6") +// // }) +// +// t.Run("init validate error", func(t *testing.T) { +// +// mgr := gs_dync.New() +// cfg := new(Config) +// // cfg.Int.OnValidate(func(v int64) error { +// // if v < 3 { +// // return errors.New("should greeter than 3") +// // } +// // return nil +// // }) +// // cfg.Slice.Init(make([]string, 0)) +// // cfg.Map.Init(make(map[string]string)) +// // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { +// // fmt.Println("event fired.") +// // return nil +// // }) +// err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) +// if err != nil { +// t.Fatal(err) +// } +// +// b, _ := json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) +// +// p := conf.New() +// p.Set("int", 1) +// p.Set("float", 5.4) +// p.Set("map.a", 3) +// p.Set("map.b", 7) +// p.Set("slice[0]", 2) +// p.Set("slice[1]", 9) +// err = mgr.Refresh(p) +// // assert.Error(t, err, "should greeter than 3") +// +// b, _ = json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":1,"Float":5.4,"Map":{"a":"3","b":"7"},"Slice":["2","9"],"Object":{"Str":"abc","Int":3}}`) +// }) +// +// t.Run("success", func(t *testing.T) { +// +// mgr := gs_dync.New() +// cfg := new(Config) +// // cfg.Int.OnValidate(func(v int64) error { +// // if v < 3 { +// // return errors.New("should greeter than 3") +// // } +// // return nil +// // }) +// // cfg.Slice.Init(make([]string, 0)) +// // cfg.Map.Init(make(map[string]string)) +// // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { +// // fmt.Println("event fired.") +// // return nil +// // }) +// err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) +// if err != nil { +// t.Fatal(err) +// } +// +// b, _ := json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) +// +// p := conf.New() +// p.Set("int", 1) +// p.Set("float", 5.4) +// p.Set("map.a", 3) +// p.Set("map.b", 7) +// p.Set("slice[0]", 2) +// p.Set("slice[1]", 9) +// err = mgr.Refresh(p) +// // assert.Error(t, err, "should greeter than 3") +// +// b, _ = json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":1,"Float":5.4,"Map":{"a":"3","b":"7"},"Slice":["2","9"],"Object":{"Str":"abc","Int":3}}`) +// +// p = conf.New() +// p.Set("int", 6) +// p.Set("float", 2.3) +// p.Set("map.a", 1) +// p.Set("map.b", 2) +// p.Set("slice[0]", 3) +// p.Set("slice[1]", 4) +// err = mgr.Refresh(p) +// assert.Error(t, err, "validate failed on \"\\$<6\" for value 6") +// +// b, _ = json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":1,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Object":{"Str":"abc","Int":3}}`) +// +// p = conf.New() +// p.Set("int", 4) +// p.Set("float", 2.3) +// p.Set("map.a", 1) +// p.Set("map.b", 2) +// p.Set("slice[0]", 3) +// p.Set("slice[1]", 4) +// mgr.Refresh(p) +// +// assert.Equal(t, cfg.Int.Value(), int64(4)) +// +// b, _ = json.Marshal(cfg) +// assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Object":{"Str":"abc","Int":3}}`) +// }) +// } diff --git a/gs/testdata/config/application-test.yaml b/gs/internal/gs_core/testdata/config/application-test.yaml similarity index 100% rename from gs/testdata/config/application-test.yaml rename to gs/internal/gs_core/testdata/config/application-test.yaml diff --git a/gs/testdata/config/application.properties b/gs/internal/gs_core/testdata/config/application.properties similarity index 100% rename from gs/testdata/config/application.properties rename to gs/internal/gs_core/testdata/config/application.properties diff --git a/gs/testdata/config/application.yaml b/gs/internal/gs_core/testdata/config/application.yaml similarity index 100% rename from gs/testdata/config/application.yaml rename to gs/internal/gs_core/testdata/config/application.yaml diff --git a/gs/testdata/config/extension.properties b/gs/internal/gs_core/testdata/config/extension.properties similarity index 100% rename from gs/testdata/config/extension.properties rename to gs/internal/gs_core/testdata/config/extension.properties diff --git a/gs/testdata/pkg/bar/pkg.go b/gs/internal/gs_core/testdata/pkg/bar/pkg.go similarity index 73% rename from gs/testdata/pkg/bar/pkg.go rename to gs/internal/gs_core/testdata/pkg/bar/pkg.go index 4136bfbe..f1ddc4fb 100644 --- a/gs/testdata/pkg/bar/pkg.go +++ b/gs/internal/gs_core/testdata/pkg/bar/pkg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import ( "fmt" ) -// golang 允许不同的路径下存在相同的包,而且允许存在相同的包。 +// SamePkg go 允许不同的路径下存在相同的包,而且允许存在相同的包。 type SamePkg struct{} func (p *SamePkg) Package() { - fmt.Println("github.com/go-spring/spring-core/gs/testdata/pkg/bar/pkg.SamePkg") + fmt.Println("github.com/go-spring/spring-core/gs/internal/gs_core/testdata/pkg/bar/pkg.SamePkg") } diff --git a/util/testdata/pkg/foo/pkg.go b/gs/internal/gs_core/testdata/pkg/foo/pkg.go similarity index 73% rename from util/testdata/pkg/foo/pkg.go rename to gs/internal/gs_core/testdata/pkg/foo/pkg.go index 7023e620..25d03a8e 100644 --- a/util/testdata/pkg/foo/pkg.go +++ b/gs/internal/gs_core/testdata/pkg/foo/pkg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import ( "fmt" ) -// SamePkg golang allows packages with the same name under different paths. +// SamePkg go 允许不同的路径下存在相同的包,而且允许存在相同的包。 type SamePkg struct{} func (p *SamePkg) Package() { - fmt.Println("github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg") + fmt.Println("github.com/go-spring/spring-core/gs/internal/gs_core/testdata/pkg/foo/pkg.SamePkg") } diff --git a/gs/internal/gs_core/util.go b/gs/internal/gs_core/util.go deleted file mode 100644 index ddc438bf..00000000 --- a/gs/internal/gs_core/util.go +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs_core - -import ( - "container/list" - "errors" -) - -// GetBeforeItems 获取 sorting 中排在 current 前面的元素 -type GetBeforeItems func(sorting *list.List, current interface{}) *list.List - -// TripleSort 三路排序 -func TripleSort(sorting *list.List, fn GetBeforeItems) *list.List { - - toSort := list.New() // 待排序列表 - sorted := list.New() // 已排序列表 - processing := list.New() // 正在处理列表 - - toSort.PushBackList(sorting) - - for toSort.Len() > 0 { // 递归选出依赖链条最前端的元素 - tripleSortByAfter(sorting, toSort, sorted, processing, nil, fn) - } - return sorted -} - -// searchInList 在列表中查询指定元素,存在则返回列表项指针,不存在返回 nil。 -func searchInList(l *list.List, v interface{}) *list.Element { - for e := l.Front(); e != nil; e = e.Next() { - if e.Value == v { - return e - } - } - return nil -} - -// tripleSortByAfter 递归选出依赖链条最前端的元素 -func tripleSortByAfter(sorting *list.List, toSort *list.List, sorted *list.List, - processing *list.List, current interface{}, fn GetBeforeItems) { - - if current == nil { - current = toSort.Remove(toSort.Front()) - } - - // 将当前元素标记为正在处理 - processing.PushBack(current) - - // 获取排在当前元素前面的列表项,然后依次对它们进行排序 - for e := fn(sorting, current).Front(); e != nil; e = e.Next() { - c := e.Value - - // 自己不可能是自己前面的元素,除非出现了循环依赖,因此抛出 Panic - if searchInList(processing, c) != nil { - panic(errors.New("found sorting cycle")) - } - - inSorted := searchInList(sorted, c) != nil - inToSort := searchInList(toSort, c) != nil - - if !inSorted && inToSort { // 如果是待排元素则对其进行排序 - tripleSortByAfter(sorting, toSort, sorted, processing, c, fn) - } - } - - if e := searchInList(processing, current); e != nil { - processing.Remove(e) - } - - if e := searchInList(toSort, current); e != nil { - toSort.Remove(e) - } - - // 将当前元素标记为已完成 - sorted.PushBack(current) -} diff --git a/gs/internal/gs_core/wire.go b/gs/internal/gs_core/wire.go index 794f5de2..2902dee6 100644 --- a/gs/internal/gs_core/wire.go +++ b/gs/internal/gs_core/wire.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,26 +28,24 @@ import ( "github.com/go-spring/spring-core/conf" "github.com/go-spring/spring-core/gs/internal/gs" "github.com/go-spring/spring-core/gs/internal/gs_bean" - "github.com/go-spring/spring-core/gs/syslog" + "github.com/go-spring/spring-core/gs/internal/gs_util" "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/syslog" ) var ( GsContextType = reflect.TypeOf((*gs.Context)(nil)).Elem() ) -type lazyField struct { - v reflect.Value - path string - tag string -} +/************************************ destroyer ******************************/ -// destroyer 保存具有销毁函数的 bean 以及销毁函数的调用顺序。 +// destroyer stores beans with destroy functions and their call order. type destroyer struct { - current *gs_bean.BeanDefinition - earlier []*gs_bean.BeanDefinition + current *gs_bean.BeanDefinition // The current bean being processed. + earlier []*gs_bean.BeanDefinition // Beans that must be destroyed before the current bean. } +// foundEarlier checks if a bean is already in the earlier list. func (d *destroyer) foundEarlier(b *gs_bean.BeanDefinition) bool { for _, c := range d.earlier { if c == b { @@ -57,7 +55,7 @@ func (d *destroyer) foundEarlier(b *gs_bean.BeanDefinition) bool { return false } -// after 添加一个需要在该 bean 的销毁函数执行之前调用销毁函数的 bean 。 +// after adds a bean to the earlier list, ensuring it is destroyed before the current bean. func (d *destroyer) after(b *gs_bean.BeanDefinition) { if d.foundEarlier(b) { return @@ -65,8 +63,8 @@ func (d *destroyer) after(b *gs_bean.BeanDefinition) { d.earlier = append(d.earlier, b) } -// getBeforeDestroyers 获取排在 i 前面的 destroyer,用于 sort.Triple 排序。 -func getBeforeDestroyers(destroyers *list.List, i interface{}) *list.List { +// getBeforeDestroyers retrieves destroyers that should be processed before a given one for sorting purposes. +func getBeforeDestroyers(destroyers *list.List, i interface{}) (*list.List, error) { d := i.(*destroyer) result := list.New() for e := destroyers.Front(); e != nil; e = e.Next() { @@ -75,68 +73,78 @@ func getBeforeDestroyers(destroyers *list.List, i interface{}) *list.List { result.PushBack(c) } } - return result + return result, nil } -// wiringStack 记录 bean 的注入路径。 -type wiringStack struct { +/******************************* wiring stack ********************************/ + +// lazyField represents a lazy-injected field with metadata. +type lazyField struct { + v reflect.Value // The value to be injected. + path string // Path for the field in the injection hierarchy. + tag string // Associated tag for the field. +} + +// WiringStack tracks the injection path of beans and their destroyers. +type WiringStack struct { destroyers *list.List destroyerMap map[string]*destroyer beans []*gs_bean.BeanDefinition lazyFields []lazyField } -func newWiringStack() *wiringStack { - return &wiringStack{ +// NewWiringStack creates a new WiringStack instance. +func NewWiringStack() *WiringStack { + return &WiringStack{ destroyers: list.New(), destroyerMap: make(map[string]*destroyer), } } -// pushBack 添加一个即将注入的 bean 。 -func (s *wiringStack) pushBack(b *gs_bean.BeanDefinition) { - syslog.Debug("push %s %s", b, gs_bean.GetStatusString(b.Status())) +// pushBack adds a bean to the injection path. +func (s *WiringStack) pushBack(b *gs_bean.BeanDefinition) { + syslog.Debugf("push %s %s", b, b.Status()) s.beans = append(s.beans, b) } -// popBack 删除一个已经注入的 bean 。 -func (s *wiringStack) popBack() { +// popBack removes the last bean from the injection path. +func (s *WiringStack) popBack() { n := len(s.beans) b := s.beans[n-1] s.beans = s.beans[:n-1] - syslog.Debug("pop %s %s", b, gs_bean.GetStatusString(b.Status())) + syslog.Debugf("pop %s %s", b, b.Status()) } -// path 返回 bean 的注入路径。 -func (s *wiringStack) path() (path string) { +// path returns the injection path as a string. +func (s *WiringStack) path() (path string) { for _, b := range s.beans { path += fmt.Sprintf("=> %s ↩\n", b) } - return path[:len(path)-1] + return path[:len(path)-1] // Remove the trailing newline. } -// saveDestroyer 记录具有销毁函数的 bean ,因为可能有多个依赖,因此需要排重处理。 -func (s *wiringStack) saveDestroyer(b *gs_bean.BeanDefinition) *destroyer { - d, ok := s.destroyerMap[b.ID()] +// saveDestroyer tracks a bean with a destroy function, ensuring no duplicates. +func (s *WiringStack) saveDestroyer(b *gs_bean.BeanDefinition) *destroyer { + d, ok := s.destroyerMap[b.Name()] // todo if !ok { d = &destroyer{current: b} - s.destroyerMap[b.ID()] = d + s.destroyerMap[b.Name()] = d } return d } -// sortDestroyers 对具有销毁函数的 bean 按照销毁函数的依赖顺序进行排序。 -func (s *wiringStack) sortDestroyers() []func() { +// getSortedDestroyers sorts beans with destroy functions by dependency order. +func (s *WiringStack) getSortedDestroyers() ([]func(), error) { destroy := func(v reflect.Value, fn interface{}) func() { return func() { if fn == nil { - v.Interface().(gs_bean.BeanDestroy).OnDestroy() + v.Interface().(gs.BeanDestroyInterface).OnBeanDestroy() } else { fnValue := reflect.ValueOf(fn) out := fnValue.Call([]reflect.Value{v}) if len(out) > 0 && !out[0].IsNil() { - syslog.Error(out[0].Interface().(error).Error()) + syslog.Errorf("%s", out[0].Interface().(error).Error()) } } } @@ -146,53 +154,78 @@ func (s *wiringStack) sortDestroyers() []func() { for _, d := range s.destroyerMap { destroyers.PushBack(d) } - destroyers = TripleSort(destroyers, getBeforeDestroyers) + destroyers, err := gs_util.TripleSort(destroyers, getBeforeDestroyers) + if err != nil { + return nil, err + } var ret []func() for e := destroyers.Front(); e != nil; e = e.Next() { d := e.Value.(*destroyer).current ret = append(ret, destroy(d.Value(), d.Destroy())) } - return ret + return ret, nil } -// wireTag 注入语法的 tag 分解式,字符串形式的完整格式为 TypeName:BeanName? 。 -// 注入语法的字符串表示形式分为三个部分,TypeName 是原始类型的全限定名,BeanName -// 是 bean 注册时设置的名称,? 表示注入结果允许为空。 -type wireTag struct { - typeName string - beanName string - nullable bool +/************************************ arg ************************************/ + +// ArgContext holds a Container and a WiringStack to manage dependency injection. +type ArgContext struct { + c *Container + stack *WiringStack } -func parseWireTag(str string) (tag wireTag) { +// NewArgContext creates a new ArgContext with a given Container and WiringStack. +func NewArgContext(c *Container, stack *WiringStack) *ArgContext { + return &ArgContext{c: c, stack: stack} +} - if str == "" { - return - } +func (a *ArgContext) Has(key string) bool { + return a.c.Has(key) +} - if n := len(str) - 1; str[n] == '?' { - tag.nullable = true - str = str[:n] - } +func (a *ArgContext) Prop(key string, def ...string) string { + return a.c.Prop(key, def...) +} - i := strings.Index(str, ":") - if i < 0 { - tag.beanName = str - return +func (a *ArgContext) Find(s gs.BeanSelectorInterface) ([]gs.CondBean, error) { + beans, err := a.c.findBeans(s) + if err != nil { + return nil, err + } + var ret []gs.CondBean + for _, bean := range beans { + ret = append(ret, bean) } + return ret, nil +} - tag.typeName = str[:i] - tag.beanName = str[i+1:] - return +// Matches checks if a given condition matches the container. +func (a *ArgContext) Matches(c gs.Condition) (bool, error) { + return c.Matches(a) +} + +// Bind binds a value to a specific tag in the container. +func (a *ArgContext) Bind(v reflect.Value, tag string) error { + return a.c.p.Data().Bind(v, tag) +} + +// Wire wires a value based on a specific tag in the container. +func (a *ArgContext) Wire(v reflect.Value, tag string) error { + return a.c.wireStructField(v, tag, a.stack) +} + +/************************************ wire ***********************************/ + +// wireTag represents a parsed injection tag in the format TypeName:BeanName?. +type wireTag struct { + beanName string // Bean name for injection. + nullable bool // Whether the injection can be nil. } +// String converts a wireTag back to its string representation. func (tag wireTag) String() string { b := bytes.NewBuffer(nil) - if tag.typeName != "" { - b.WriteString(tag.typeName) - b.WriteString(":") - } b.WriteString(tag.beanName) if tag.nullable { b.WriteString("?") @@ -200,6 +233,7 @@ func (tag wireTag) String() string { return b.String() } +// toWireString converts a slice of wireTags to a comma-separated string. func toWireString(tags []wireTag) string { var buf bytes.Buffer for i, tag := range tags { @@ -211,111 +245,159 @@ func toWireString(tags []wireTag) string { return buf.String() } -// resolveTag tag 预处理,可能通过属性值进行指定。 -func (c *Container) resolveTag(tag string) (string, error) { - if strings.HasPrefix(tag, "${") { - s, err := c.p.Data().Resolve(tag) - if err != nil { - return "", err - } - return s, nil +// parseWireTag parses a wire tag string and returns a wireTag struct. +func parseWireTag(p gs.Properties, str string, needResolve bool) (tag wireTag, err error) { + + if str == "" { + return } - return tag, nil -} -func (c *Container) toWireTag(selector gs.BeanSelector) (wireTag, error) { - switch s := selector.(type) { - case string: - s, err := c.resolveTag(s) - if err != nil { - return wireTag{}, err + if needResolve { + if strings.HasPrefix(str, "${") { + str, err = p.Resolve(str) + if err != nil { + return + } } - return parseWireTag(s), nil - case gs_bean.BeanDefinition: - return parseWireTag(s.ID()), nil - case *gs_bean.BeanDefinition: - return parseWireTag(s.ID()), nil - default: - return parseWireTag(util.TypeName(s) + ":"), nil } + + if n := len(str) - 1; str[n] == '?' { + tag.nullable = true + str = str[:n] + } + + tag.beanName = str + return } -func (c *Container) autowire(v reflect.Value, tags []wireTag, nullable bool, stack *wiringStack) error { - if c.ForceAutowireIsNullable { - for i := 0; i < len(tags); i++ { - tags[i].nullable = true - } +// findBeans finds beans based on a given selector. +func (c *Container) findBeans(s gs.BeanSelectorInterface) ([]BeanRuntime, error) { + t, name := s.TypeAndName() + var beans []BeanRuntime + if t != nil { + beans = c.beansByType[t] } - switch v.Kind() { - case reflect.Map, reflect.Slice, reflect.Array: - return c.collectBeans(v, tags, nullable, stack) - default: - var tag wireTag - if len(tags) > 0 { - tag = tags[0] - } else if nullable { - tag.nullable = true + if name != "" { + if beans == nil { + beans = c.beansByName[name] } - return c.getBean(v, tag, stack) + var ret []BeanRuntime + for _, b := range beans { + if name == b.Name() { + ret = append(ret, b) + } + } + beans = ret } + return beans, nil } -type byBeanName []BeanRuntime - -func (b byBeanName) Len() int { return len(b) } -func (b byBeanName) Less(i, j int) bool { return b[i].Name() < b[j].Name() } -func (b byBeanName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +// getSingleBean retrieves the bean corresponding to the specified tag and assigns it to `v`. +// `v` should be an uninitialized value. +func (c *Container) getBean(t reflect.Type, tag wireTag, stack *WiringStack) (BeanRuntime, error) { -// filterBean 返回 tag 对应的 bean 在数组中的索引,找不到返回 -1。 -func filterBean(beans []BeanRuntime, tag wireTag, t reflect.Type) (int, error) { + // Check if the type of `v` is a valid bean receiver type. + if !util.IsBeanInjectionTarget(t) { + return nil, fmt.Errorf("%s is not a valid receiver type", t.String()) + } - var found []int - for i, b := range beans { - if b.Match(tag.typeName, tag.beanName) { - found = append(found, i) + var foundBeans []BeanRuntime + // Iterate through all beans of the given type and match against the tag. + for _, b := range c.beansByType[t] { + if b.Status() == gs_bean.StatusDeleted { + continue + } + if tag.beanName == "" || tag.beanName == b.Name() { + foundBeans = append(foundBeans, b) } } - if len(found) > 1 { - msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(found), tag, t) - for _, i := range found { - msg += "( " + beans[i].String() + " ), " + // When a specific bean name is provided, find it by name. + if t.Kind() == reflect.Interface && tag.beanName != "" { + for _, b := range c.beansByName[tag.beanName] { + if b.Status() == gs_bean.StatusDeleted { + continue + } + if !b.Type().AssignableTo(t) { + continue + } + if tag.beanName != "" && tag.beanName != b.Name() { + continue + } + + // Deduplicate the results. + found := false + for _, r := range foundBeans { + if r == b { + found = true + break + } + } + if !found { + foundBeans = append(foundBeans, b) + syslog.Warnf("you should call Export() on %s", b) + } } - msg = msg[:len(msg)-2] + "]" - return -1, errors.New(msg) } - if len(found) > 0 { - i := found[0] - return i, nil + // If no matching beans are found and the tag allows nullable beans, return nil. + if len(foundBeans) == 0 { + if tag.nullable { + return nil, nil + } + return nil, fmt.Errorf("can't find bean, bean:%q type:%q", tag, t) } - if tag.nullable { - return -1, nil + // If more than one matching bean is found, return an error. + if len(foundBeans) > 1 { + msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(foundBeans), tag, t) + for _, b := range foundBeans { + msg += "( " + b.String() + " ), " + } + msg = msg[:len(msg)-2] + "]" + return nil, errors.New(msg) } - return -1, fmt.Errorf("can't find bean, bean:%q type:%q", tag, t) + // Retrieve the single matching bean. + b := foundBeans[0] + + // Ensure the found bean has completed dependency injection. + switch c.state { + case Refreshing: + if err := c.wireBean(b.(*gs_bean.BeanDefinition), stack); err != nil { + return nil, err + } + case Refreshed: + if b.Status() != gs_bean.StatusWired { + return nil, fmt.Errorf("unexpected bean status %d", b.Status()) + } + default: + return nil, fmt.Errorf("state is invalid for wiring") + } + return b, nil } -func (c *Container) collectBeans(v reflect.Value, tags []wireTag, nullable bool, stack *wiringStack) error { +// getMultiBeans collects beans into the given slice or map value `v`. +// It supports dependency injection by resolving matching beans based on tags. +func (c *Container) getBeans(t reflect.Type, tags []wireTag, nullable bool, stack *WiringStack) ([]BeanRuntime, error) { - t := v.Type() if t.Kind() != reflect.Slice && t.Kind() != reflect.Map { - return fmt.Errorf("should be slice or map in collection mode") + return nil, fmt.Errorf("should be slice or map in collection mode") } et := t.Elem() - if !util.IsBeanReceiver(et) { - return fmt.Errorf("%s is not valid receiver type", t.String()) + if !util.IsBeanInjectionTarget(et) { + return nil, fmt.Errorf("%s is not a valid receiver type", t.String()) } var beans []BeanRuntime beans = c.beansByType[et] + // Filter out deleted beans { var arr []BeanRuntime for _, b := range beans { - if b.Status() == gs_bean.Deleted { + if b.Status() == gs_bean.StatusDeleted { continue } arr = append(arr, b) @@ -323,34 +405,47 @@ func (c *Container) collectBeans(v reflect.Value, tags []wireTag, nullable bool, beans = arr } + // Process bean tags to filter and order beans if len(tags) > 0 { - var ( anyBeans []BeanRuntime afterAny []BeanRuntime beforeAny []BeanRuntime ) - foundAny := false for _, item := range tags { // 是否遇到了"无序"标记 if item.beanName == "*" { if foundAny { - return fmt.Errorf("more than one * in collection %q", tags) + return nil, fmt.Errorf("more than one * in collection %q", tags) } foundAny = true continue } - index, err := filterBean(beans, item, et) - if err != nil { - return err + var founds []int + for i, b := range beans { + if item.beanName == b.Name() { + founds = append(founds, i) + } } - if index < 0 { - continue + if len(founds) > 1 { + msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(founds), item, t) + for _, i := range founds { + msg += "( " + beans[i].String() + " ), " + } + msg = msg[:len(msg)-2] + "]" + return nil, errors.New(msg) + } + if len(founds) == 0 { + if item.nullable { + continue + } + return nil, fmt.Errorf("can't find bean, bean:%q type:%q", item, t) } + index := founds[0] if foundAny { afterAny = append(afterAny, beans[index]) } else { @@ -373,185 +468,64 @@ func (c *Container) collectBeans(v reflect.Value, tags []wireTag, nullable bool, beans = arr } + // Handle empty beans if len(beans) == 0 && !nullable { if len(tags) == 0 { - return fmt.Errorf("no beans collected for %q", toWireString(tags)) + return nil, fmt.Errorf("no beans collected for %q", toWireString(tags)) } for _, tag := range tags { if !tag.nullable { - return fmt.Errorf("no beans collected for %q", toWireString(tags)) + return nil, fmt.Errorf("no beans collected for %q", toWireString(tags)) } } - return nil + return nil, nil } + // Wire the beans based on the current state of the container for _, b := range beans { switch c.state { case Refreshing: - if err := c.wireBeanInRefreshing(b.(*gs_bean.BeanDefinition), stack); err != nil { - return err + if err := c.wireBean(b.(*gs_bean.BeanDefinition), stack); err != nil { + return nil, err } case Refreshed: - if err := c.wireBeanAfterRefreshed(b.(*gs_bean.BeanRuntime), stack); err != nil { - return err + if b.Status() != gs_bean.StatusWired { + return nil, fmt.Errorf("unexpected bean status %d", b.Status()) } default: - return fmt.Errorf("state is error for wiring") + return nil, fmt.Errorf("state is error for wiring") } } - - var ret reflect.Value - switch t.Kind() { - case reflect.Slice: - sort.Sort(byBeanName(beans)) - ret = reflect.MakeSlice(t, 0, 0) - for _, b := range beans { - ret = reflect.Append(ret, b.Value()) - } - case reflect.Map: - ret = reflect.MakeMap(t) - for _, b := range beans { - ret.SetMapIndex(reflect.ValueOf(b.Name()), b.Value()) - } - default: - } - v.Set(ret) - return nil + return beans, nil } -// getBean 获取 tag 对应的 bean 然后赋值给 v,因此 v 应该是一个未初始化的值。 -func (c *Container) getBean(v reflect.Value, tag wireTag, stack *wiringStack) error { - - if !v.IsValid() { - return fmt.Errorf("receiver must be ref type, bean:%q", tag) - } +// wireBean performs property binding and dependency injection for the specified bean. +// It also tracks its injection path. If the bean has an initialization function, it +// is executed after the injection is completed. If the bean depends on other beans, +// it attempts to instantiate and inject those dependencies first. +func (c *Container) wireBean(b *gs_bean.BeanDefinition, stack *WiringStack) error { - t := v.Type() - if !util.IsBeanReceiver(t) { - return fmt.Errorf("%s is not valid receiver type", t.String()) + // Check if the bean is deleted. + if b.Status() == gs_bean.StatusDeleted { + return fmt.Errorf("bean:%q has been deleted", b.String()) } - var foundBeans []BeanRuntime - for _, b := range c.beansByType[t] { - if b.Status() == gs_bean.Deleted { - continue - } - if !b.Match(tag.typeName, tag.beanName) { - continue - } - foundBeans = append(foundBeans, b) - } - - // 指定 bean 名称时通过名称获取,防止未通过 Export 方法导出接口。 - if t.Kind() == reflect.Interface && tag.beanName != "" { - for _, b := range c.beansByName[tag.beanName] { - if b.Status() == gs_bean.Deleted { - continue - } - if !b.Type().AssignableTo(t) { - continue - } - if !b.Match(tag.typeName, tag.beanName) { - continue - } - - found := false // 对结果排重 - for _, r := range foundBeans { - if r == b { - found = true - break - } - } - if !found { - foundBeans = append(foundBeans, b) - syslog.Warn("you should call Export() on %s", b) - } - } - } - - if len(foundBeans) == 0 { - if tag.nullable { - return nil - } - return fmt.Errorf("can't find bean, bean:%q type:%q", tag, t) - } - - // 优先使用设置成主版本的 bean - var primaryBeans []BeanRuntime - - for _, b := range foundBeans { - if b.IsPrimary() { - primaryBeans = append(primaryBeans, b) - } - } - - if len(primaryBeans) > 1 { - msg := fmt.Sprintf("found %d primary beans, bean:%q type:%q [", len(primaryBeans), tag, t) - for _, b := range primaryBeans { - msg += "( " + b.String() + " ), " - } - msg = msg[:len(msg)-2] + "]" - return errors.New(msg) - } - - if len(primaryBeans) == 0 && len(foundBeans) > 1 { - msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(foundBeans), tag, t) - for _, b := range foundBeans { - msg += "( " + b.String() + " ), " - } - msg = msg[:len(msg)-2] + "]" - return errors.New(msg) - } - - var result BeanRuntime - if len(primaryBeans) == 1 { - result = primaryBeans[0] - } else { - result = foundBeans[0] - } - - // 确保找到的 bean 已经完成依赖注入。 - switch c.state { - case Refreshing: - if err := c.wireBeanInRefreshing(result.(*gs_bean.BeanDefinition), stack); err != nil { - return err - } - case Refreshed: - if err := c.wireBeanAfterRefreshed(result, stack); err != nil { - return err - } - default: - return fmt.Errorf("state is error for wiring") - } - - v.Set(result.Value()) - return nil -} - -// wireBean 对 bean 进行属性绑定和依赖注入,同时追踪其注入路径。如果 bean 有初始 -// 化函数,则在注入完成之后执行其初始化函数。如果 bean 依赖了其他 bean,则首先尝试 -// 实例化被依赖的 bean 然后对它们进行注入。 -func (c *Container) wireBeanInRefreshing(b *gs_bean.BeanDefinition, stack *wiringStack) error { - - if b.Status() == gs_bean.Deleted { - return fmt.Errorf("bean:%q have been deleted", b.ID()) - } - - // 运行时 Get 或者 Wire 会出现下面这种情况。 - if c.state == Refreshed && b.Status() == gs_bean.Wired { + // If the container is refreshed and the bean is already wired, do nothing. + if c.state == Refreshed && b.Status() == gs_bean.StatusWired { return nil } haveDestroy := false + // Ensure destroy functions are cleaned up in case of failure. defer func() { if haveDestroy { stack.destroyers.Remove(stack.destroyers.Back()) } }() - // 记录注入路径上的销毁函数及其执行的先后顺序。 - if _, ok := b.Interface().(gs_bean.BeanDestroy); ok || b.Destroy() != nil { + // Record the destroy function for the bean, if it exists. + if _, ok := b.Interface().(gs.BeanDestroyInterface); ok || b.Destroy() != nil { haveDestroy = true d := stack.saveDestroyer(b) if i := stack.destroyers.Back(); i != nil { @@ -562,41 +536,46 @@ func (c *Container) wireBeanInRefreshing(b *gs_bean.BeanDefinition, stack *wirin stack.pushBack(b) - if b.Status() == gs_bean.Creating && b.Callable() != nil { + // Detect circular dependency. + if b.Status() == gs_bean.StatusCreating && b.Callable() != nil { prev := stack.beans[len(stack.beans)-2] - if prev.Status() == gs_bean.Creating { - return errors.New("found circle autowire") + if prev.Status() == gs_bean.StatusCreating { + return errors.New("found circular autowire") } } - if b.Status() >= gs_bean.Creating { + // If the bean is already being created, return early. + if b.Status() >= gs_bean.StatusCreating { stack.popBack() return nil } - b.SetStatus(gs_bean.Creating) + // Mark the bean as being created. + b.SetStatus(gs_bean.StatusCreating) - // 对当前 bean 的间接依赖项进行注入。 - for _, s := range b.Depends() { - beans, err := c.Find(s) + // Inject dependencies for the current bean. + for _, s := range b.DependsOn() { + beans, err := c.findBeans(s) // todo 唯一 if err != nil { return err } for _, d := range beans { - err = c.wireBeanInRefreshing(d.(*gs_bean.BeanDefinition), stack) + err = c.wireBean(d.(*gs_bean.BeanDefinition), stack) if err != nil { return err } } } + // Get the value of the current bean. v, err := c.getBeanValue(b, stack) if err != nil { return err } - b.SetStatus(gs_bean.Created) + b.SetStatus(gs_bean.StatusCreated) + // Validate that the bean exports the appropriate interfaces. t := v.Type() for _, typ := range b.Exports() { if !t.Implements(typ) { @@ -604,98 +583,69 @@ func (c *Container) wireBeanInRefreshing(b *gs_bean.BeanDefinition, stack *wirin } } - err = c.wireBeanValue(v, t, stack) - if err != nil { - return err - } - - // 如果 bean 有初始化函数,则执行其初始化函数。 - if b.Init() != nil { - fnValue := reflect.ValueOf(b.Init()) - out := fnValue.Call([]reflect.Value{b.Value()}) - if len(out) > 0 && !out[0].IsNil() { - return out[0].Interface().(error) - } - } + watchRefresh := true - // 如果 bean 实现了 BeanInit 接口,则执行其 OnInit 方法。 - if f, ok := b.Interface().(gs_bean.BeanInit); ok { - if err = f.OnInit(c); err != nil { + // If the bean is refreshable, add it to the refreshable list. + if b.Refreshable() { + i := b.Interface().(gs.Refreshable) + var param conf.BindParam + err = param.BindTag(b.RefreshTag(), "") + if err != nil { return err } - } - - // 如果 bean 实现了 dync.Refreshable 接口,则将 bean 添加到可刷新对象列表中。 - if b.EnableRefresh() { - i := b.Interface().(gs.Refreshable) - refreshParam := b.RefreshParam() - watch := c.state == Refreshing - if err = c.p.RefreshBean(i, refreshParam, watch); err != nil { + if err = c.p.RefreshBean(i, param, true); err != nil { return err } + watchRefresh = false } - b.SetStatus(gs_bean.Wired) - stack.popBack() - return nil -} - -func (c *Container) wireBeanAfterRefreshed(b BeanRuntime, stack *wiringStack) error { - - v, err := c.getBeanValue(b, stack) + // Wire the value of the bean. + err = c.wireBeanValue(v, t, watchRefresh, stack) if err != nil { return err } - t := v.Type() - err = c.wireBeanValue(v, t, stack) - if err != nil { - return err + // Execute the bean's initialization function, if it exists. + if b.Init() != nil { + fnValue := reflect.ValueOf(b.Init()) + out := fnValue.Call([]reflect.Value{b.Value()}) + if len(out) > 0 && !out[0].IsNil() { + return out[0].Interface().(error) + } } - // 如果 bean 实现了 BeanInit 接口,则执行其 OnInit 方法。 - if f, ok := b.Interface().(gs_bean.BeanInit); ok { - if err = f.OnInit(c); err != nil { + // If the bean implements the BeanInit interface, execute its OnBeanInit method. + if f, ok := b.Interface().(gs.BeanInitInterface); ok { + if err = f.OnBeanInit(c); err != nil { return err } } + // Mark the bean as wired and pop it from the stack. + b.SetStatus(gs_bean.StatusWired) + stack.popBack() return nil } -type argContext struct { - c *Container - stack *wiringStack -} - -func (a *argContext) Matches(c gs.Condition) (bool, error) { - return c.Matches(a.c) -} - -func (a *argContext) Bind(v reflect.Value, tag string) error { - return a.c.p.Data().Bind(v, conf.Tag(tag)) -} - -func (a *argContext) Wire(v reflect.Value, tag string) error { - return a.c.wireByTag(v, tag, a.stack) -} - -// getBeanValue 获取 bean 的值,如果是构造函数 bean 则执行其构造函数然后返回执行结果。 -func (c *Container) getBeanValue(b BeanRuntime, stack *wiringStack) (reflect.Value, error) { +// getBeanValue retrieves the value of a bean. If it is a constructor bean, +// it executes the constructor and returns the result. +func (c *Container) getBeanValue(b BeanRuntime, stack *WiringStack) (reflect.Value, error) { + // If the bean has no callable function, return its value directly. if b.Callable() == nil { return b.Value(), nil } - out, err := b.Callable().Call(&argContext{c: c, stack: stack}) + // Call the bean's constructor and handle errors. + out, err := b.Callable().Call(NewArgContext(c, stack)) if err != nil { return reflect.Value{}, err /* fmt.Errorf("%s:%s return error: %v", b.getClass(), b.ID(), err) */ } - // 构造函数的返回值为值类型时 b.Type() 返回其指针类型。 + // If the return value is of bean type, handle it accordingly. if val := out[0]; util.IsBeanType(val.Type()) { - // 如果实现接口的是值类型,那么需要转换成指针类型然后再赋值给接口。 - if !val.IsNil() && val.Kind() == reflect.Interface && util.IsValueType(val.Elem().Type()) { + // If it's a non-pointer value type, convert it into a pointer and set it. + if !val.IsNil() && val.Kind() == reflect.Interface && util.IsPropBindingTarget(val.Elem().Type()) { v := reflect.New(val.Elem().Type()) v.Elem().Set(val.Elem()) b.Value().Set(v) @@ -706,47 +656,51 @@ func (c *Container) getBeanValue(b BeanRuntime, stack *wiringStack) (reflect.Val b.Value().Elem().Set(val) } + // Return an error if the value is nil. if b.Value().IsNil() { return reflect.Value{}, fmt.Errorf("%s return nil", b.String()) // b.GetClass(), b.FileLine()) } v := b.Value() - // 结果以接口类型返回时需要将原始值取出来才能进行注入。 + // If the result is an interface, extract the original value. if b.Type().Kind() == reflect.Interface { v = v.Elem() } return v, nil } -// wireBeanValue 对 v 进行属性绑定和依赖注入,v 在传入时应该是一个已经初始化的值。 -func (c *Container) wireBeanValue(v reflect.Value, t reflect.Type, stack *wiringStack) error { +// wireValue binds properties and injects dependencies into the value v. v should already be initialized. +func (c *Container) wireBeanValue(v reflect.Value, t reflect.Type, watchRefresh bool, stack *WiringStack) error { + // Dereference pointer types and adjust the target type. if v.Kind() == reflect.Ptr { v = v.Elem() t = t.Elem() } - // 如整数指针类型的 bean 是无需注入的。 + // If v is not a struct type, no injection is needed. if v.Kind() != reflect.Struct { return nil } typeName := t.Name() - if typeName == "" { // 简单类型没有名字 + if typeName == "" { + // Simple types don't have names, use their string representation. typeName = t.String() } param := conf.BindParam{Path: typeName} - return c.wireStruct(v, t, param, stack) + return c.wireStruct(v, t, watchRefresh, param, stack) } -// wireStruct 对结构体进行依赖注入,需要注意的是这里不需要进行属性绑定。 -func (c *Container) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindParam, stack *wiringStack) error { - +// wireStruct performs dependency injection for a struct. +func (c *Container) wireStruct(v reflect.Value, t reflect.Type, watchRefresh bool, opt conf.BindParam, stack *WiringStack) error { + // Loop through each field of the struct. for i := 0; i < t.NumField(); i++ { ft := t.Field(i) fv := v.Field(i) + // If the field is unexported, try to patch it. if !fv.CanInterface() { fv = util.PatchValue(fv) if !fv.CanInterface() { @@ -756,20 +710,22 @@ func (c *Container) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindPar fieldPath := opt.Path + "." + ft.Name - // 支持 autowire 和 inject 两个标签。 + // Check for autowire or inject tags. tag, ok := ft.Tag.Lookup("autowire") if !ok { tag, ok = ft.Tag.Lookup("inject") } if ok { + // Handle lazy injection. if strings.HasSuffix(tag, ",lazy") { f := lazyField{v: fv, path: fieldPath, tag: tag} stack.lazyFields = append(stack.lazyFields, f) } else { + // Handle context-aware injection. if ft.Type == GsContextType { c.ContextAware = true } - if err := c.wireByTag(fv, tag, stack); err != nil { + if err := c.wireStructField(fv, tag, stack); err != nil { return fmt.Errorf("%q wired error: %w", fieldPath, err) } } @@ -781,18 +737,20 @@ func (c *Container) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindPar Path: fieldPath, } + // Bind values if the field has a "value" tag. if tag, ok = ft.Tag.Lookup("value"); ok { if err := subParam.BindTag(tag, ft.Tag); err != nil { return err } if ft.Anonymous { - err := c.wireStruct(fv, ft.Type, subParam, stack) + // Recursively wire anonymous structs. + err := c.wireStruct(fv, ft.Type, watchRefresh, subParam, stack) if err != nil { return err } } else { - watch := c.state == Refreshing - err := c.p.RefreshField(fv.Addr(), subParam, watch) + // Refresh field value if needed. + err := c.p.RefreshField(fv.Addr(), subParam, watchRefresh) if err != nil { return err } @@ -800,8 +758,9 @@ func (c *Container) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindPar continue } + // Recursively wire anonymous struct fields. if ft.Anonymous && ft.Type.Kind() == reflect.Struct { - if err := c.wireStruct(fv, ft.Type, subParam, stack); err != nil { + if err := c.wireStruct(fv, ft.Type, watchRefresh, subParam, stack); err != nil { return err } } @@ -809,27 +768,70 @@ func (c *Container) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindPar return nil } -func (c *Container) wireByTag(v reflect.Value, tag string, stack *wiringStack) error { - - tag, err := c.resolveTag(tag) - if err != nil { - return err - } - - if tag == "" { - return c.autowire(v, nil, false, stack) - } - - var tags []wireTag - if tag != "?" { - for _, s := range strings.Split(tag, ",") { - var g wireTag - g, err = c.toWireTag(s) +// wireField performs dependency injection by tag. +func (c *Container) wireStructField(v reflect.Value, str string, stack *WiringStack) error { + switch v.Kind() { + case reflect.Map, reflect.Slice, reflect.Array: + { + var tags []wireTag + if str != "" && str != "?" { + for _, s := range strings.Split(str, ",") { + g, err := parseWireTag(c.p.Data(), s, true) + if err != nil { + return err + } + tags = append(tags, g) + } + } + nullable := str == "?" + if c.ForceAutowireIsNullable { + for i := 0; i < len(tags); i++ { + tags[i].nullable = true + } + nullable = true + } + beans, err := c.getBeans(v.Type(), tags, nullable, stack) if err != nil { return err } - tags = append(tags, g) + // Populate the slice or map with the resolved beans + switch v.Kind() { + case reflect.Slice: + sort.Slice(beans, func(i, j int) bool { + return beans[i].Name() < beans[j].Name() + }) + ret := reflect.MakeSlice(v.Type(), 0, 0) + for _, b := range beans { + ret = reflect.Append(ret, b.Value()) + } + v.Set(ret) + case reflect.Map: + ret := reflect.MakeMap(v.Type()) + for _, b := range beans { + ret.SetMapIndex(reflect.ValueOf(b.Name()), b.Value()) + } + v.Set(ret) + default: + } + return nil + } + default: + tag, err := parseWireTag(c.p.Data(), str, true) + if err != nil { + return err + } + if c.ForceAutowireIsNullable { + tag.nullable = true + } + // Ensure the provided value `v` is valid. + if !v.IsValid() { + return fmt.Errorf("receiver must be a reference type, bean:%q", str) } + b, err := c.getBean(v.Type(), tag, stack) + if err != nil { + return err + } + v.Set(b.Value()) + return nil } - return c.autowire(v, tags, tag == "?", stack) } diff --git a/gs/internal/gs_dync/dync.go b/gs/internal/gs_dync/dync.go index cf1bf942..2dd8a740 100644 --- a/gs/internal/gs_dync/dync.go +++ b/gs/internal/gs_dync/dync.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ * limitations under the License. */ +// Package gs_dync provides dynamic properties and refreshable objects. package gs_dync import ( @@ -29,17 +30,23 @@ import ( "github.com/go-spring/spring-core/gs/internal/gs" ) -// Value 可动态刷新的对象 +// Value represents a thread-safe object that can dynamically refresh its value. type Value[T interface{}] struct { v atomic.Value } -// Value 获取值 +// Value retrieves the current value stored in the object. +// If no value is set, it returns the zero value for the type T. func (r *Value[T]) Value() T { - return r.v.Load().(T) + v, ok := r.v.Load().(T) + if !ok { + var zero T + return zero + } + return v } -// OnRefresh 实现 Refreshable 接口 +// OnRefresh refreshes the value of the object by binding new properties. func (r *Value[T]) OnRefresh(prop gs.Properties, param conf.BindParam) error { var o T v := reflect.ValueOf(&o).Elem() @@ -51,50 +58,55 @@ func (r *Value[T]) OnRefresh(prop gs.Properties, param conf.BindParam) error { return nil } -// MarshalJSON 实现 json.Marshaler 接口 +// MarshalJSON serializes the stored value as JSON. func (r *Value[T]) MarshalJSON() ([]byte, error) { return json.Marshal(r.v.Load()) } -// refreshObject 绑定的可刷新对象 +// refreshObject represents an object bound to dynamic properties that can be refreshed. type refreshObject struct { - target gs.Refreshable - param conf.BindParam + target gs.Refreshable // The refreshable object. + param conf.BindParam // Parameters used for refreshing. } -// Properties 动态属性 +// Properties manages dynamic properties and refreshable objects. type Properties struct { - prop gs.Properties - lock sync.RWMutex - objects []*refreshObject + prop gs.Properties // The current properties. + lock sync.RWMutex // A read-write lock for thread-safe access. + objects []*refreshObject // List of refreshable objects bound to the properties. } -// New 创建一个 Properties 对象 +// New creates and returns a new Properties instance. func New() *Properties { return &Properties{ prop: conf.New(), } } -// Data 获取属性列表 +// Data returns the current properties. func (p *Properties) Data() gs.Properties { p.lock.RLock() defer p.lock.RUnlock() return p.prop } -// ObjectsCount 绑定的可刷新对象数量 +// ObjectsCount returns the number of registered refreshable objects. func (p *Properties) ObjectsCount() int { p.lock.RLock() defer p.lock.RUnlock() return len(p.objects) } -// Refresh 更新属性列表以及绑定的可刷新对象 +// Refresh updates the properties and refreshes all bound objects as necessary. func (p *Properties) Refresh(prop gs.Properties) (err error) { p.lock.Lock() defer p.lock.Unlock() + if len(p.objects) == 0 { + p.prop = prop + return nil + } + old := p.prop p.prop = prop @@ -115,6 +127,7 @@ func (p *Properties) Refresh(prop gs.Properties) (err error) { } } + // Refresh objects based on changed keys. keys := make([]string, 0, len(changes)) for k := range changes { keys = append(keys, k) @@ -123,14 +136,13 @@ func (p *Properties) Refresh(prop gs.Properties) (err error) { return p.refreshKeys(keys) } +// refreshKeys refreshes objects bound by the specified keys. func (p *Properties) refreshKeys(keys []string) (err error) { - - // 找出需要更新的对象,一个对象可能对应多个 key,因此需要去重 updateIndexes := make(map[int]*refreshObject) for _, key := range keys { for index, o := range p.objects { s := strings.TrimPrefix(key, o.param.Key) - if len(s) == len(key) { // 是否去除了前缀 + if len(s) == len(key) { // Check if the key starts with the parameter key. continue } if len(s) == 0 || s[0] == '.' || s[0] == '[' { @@ -159,24 +171,24 @@ func (p *Properties) refreshKeys(keys []string) (err error) { return p.refreshObjects(updateObjects) } -// Errors 错误列表 +// Errors represents a collection of errors. type Errors struct { arr []error } -// Len 错误数量 +// Len returns the number of errors. func (e *Errors) Len() int { return len(e.arr) } -// Append 添加一个错误 +// Append adds an error to the collection if it is non-nil. func (e *Errors) Append(err error) { if err != nil { e.arr = append(e.arr, err) } } -// Error 实现 error 接口 +// Error concatenates all errors into a single string. func (e *Errors) Error() string { var sb strings.Builder for i, err := range e.arr { @@ -188,10 +200,11 @@ func (e *Errors) Error() string { return sb.String() } +// refreshObjects refreshes all provided objects and aggregates errors. func (p *Properties) refreshObjects(objects []*refreshObject) error { ret := &Errors{} - for _, f := range objects { - err := p.safeRefreshObject(f) + for _, obj := range objects { + err := p.safeRefreshObject(obj) ret.Append(err) } if ret.Len() == 0 { @@ -200,22 +213,25 @@ func (p *Properties) refreshObjects(objects []*refreshObject) error { return ret } -func (p *Properties) safeRefreshObject(f *refreshObject) (err error) { +// safeRefreshObject refreshes an object and recovers from panics. +func (p *Properties) safeRefreshObject(obj *refreshObject) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic: %v", r) } }() - return f.target.OnRefresh(p.prop, f.param) + return obj.target.OnRefresh(p.prop, obj.param) } -// RefreshBean 刷新一个 bean 对象,根据 watch 的值决定是否添加监听 +// RefreshBean refreshes a single refreshable object. +// Optionally registers the object as refreshable if watch is true. func (p *Properties) RefreshBean(v gs.Refreshable, param conf.BindParam, watch bool) error { p.lock.Lock() defer p.lock.Unlock() return p.doRefresh(v, param, watch) } +// doRefresh performs the refresh operation and registers the object if watch is enabled. func (p *Properties) doRefresh(v gs.Refreshable, param conf.BindParam, watch bool) error { if watch { p.objects = append(p.objects, &refreshObject{ @@ -226,11 +242,13 @@ func (p *Properties) doRefresh(v gs.Refreshable, param conf.BindParam, watch boo return v.OnRefresh(p.prop, param) } +// filter is used to selectively refresh objects and fields. type filter struct { *Properties - watch bool + watch bool // Whether to register objects as refreshable. } +// Do attempts to refresh a single object if it implements the [gs.Refreshable] interface. func (f *filter) Do(i interface{}, param conf.BindParam) (bool, error) { v, ok := i.(gs.Refreshable) if !ok { @@ -239,7 +257,7 @@ func (f *filter) Do(i interface{}, param conf.BindParam) (bool, error) { return true, f.doRefresh(v, param, f.watch) } -// RefreshField 刷新一个 bean 的 field,根据 watch 的值决定是否添加监听 +// RefreshField refreshes a field of a bean, optionally registering it as refreshable. func (p *Properties) RefreshField(v reflect.Value, param conf.BindParam, watch bool) error { p.lock.Lock() defer p.lock.Unlock() diff --git a/gs/internal/gs_dync/dync_test.go b/gs/internal/gs_dync/dync_test.go deleted file mode 100644 index 6dc2cd1f..00000000 --- a/gs/internal/gs_dync/dync_test.go +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs_dync_test - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/gs/internal/gs_dync" - "github.com/go-spring/spring-core/util/assert" -) - -// todo 自定义类型通过类型转换器实现刷新机制 - -type StructValue struct { - Str string `value:"${string:=abc}" expr:"len($)<6"` - Int int `value:"${int:=3}" expr:"$<6"` -} - -type Config struct { - Int gs_dync.Value[int64] `value:"${int:=3}" expr:"$<6"` - Float gs_dync.Value[float64] `value:"${float:=1.2}"` - Map gs_dync.Value[map[string]string] `value:"${map:=}"` - Slice gs_dync.Value[[]string] `value:"${slice:=}"` - Object gs_dync.Value[StructValue] `value:"${object:=}"` -} - -func newTest() (*gs_dync.Properties, *Config, error) { - mgr := gs_dync.New() - cfg := new(Config) - err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) - if err != nil { - return nil, nil, err - } - return mgr, cfg, nil -} - -func TestDynamic(t *testing.T) { - - t.Run("default", func(t *testing.T) { - _, cfg, err := newTest() - if err != nil { - return - } - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) - }) - - t.Run("init", func(t *testing.T) { - _, cfg, err := newTest() - if err != nil { - return - } - // cfg.Slice.Init(make([]string, 0)) - // cfg.Map.Init(make(map[string]string)) - // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { - // fmt.Println("event fired.") - // return nil - // }) - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) - }) - - // t.Run("default validate error", func(t *testing.T) { - // mgr := dync.New() - // cfg := new(Config) - // // cfg.Int.OnValidate(func(v int64) error { - // // if v < 6 { - // // return errors.New("should greeter than 6") - // // } - // // return nil - // // }) - // err := mgr.BindValue(reflect.ValueOf(cfg), conf.BindParam{}) - // assert.Error(t, err, "should greeter than 6") - // }) - - t.Run("init validate error", func(t *testing.T) { - - mgr := gs_dync.New() - cfg := new(Config) - // cfg.Int.OnValidate(func(v int64) error { - // if v < 3 { - // return errors.New("should greeter than 3") - // } - // return nil - // }) - // cfg.Slice.Init(make([]string, 0)) - // cfg.Map.Init(make(map[string]string)) - // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { - // fmt.Println("event fired.") - // return nil - // }) - err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) - if err != nil { - t.Fatal(err) - } - - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) - - p := conf.New() - p.Set("int", 1) - p.Set("float", 5.4) - p.Set("map.a", 3) - p.Set("map.b", 7) - p.Set("slice[0]", 2) - p.Set("slice[1]", 9) - err = mgr.Refresh(p) - // assert.Error(t, err, "should greeter than 3") - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":1,"Float":5.4,"Map":{"a":"3","b":"7"},"Slice":["2","9"],"Object":{"Str":"abc","Int":3}}`) - }) - - t.Run("success", func(t *testing.T) { - - mgr := gs_dync.New() - cfg := new(Config) - // cfg.Int.OnValidate(func(v int64) error { - // if v < 3 { - // return errors.New("should greeter than 3") - // } - // return nil - // }) - // cfg.Slice.Init(make([]string, 0)) - // cfg.Map.Init(make(map[string]string)) - // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { - // fmt.Println("event fired.") - // return nil - // }) - err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) - if err != nil { - t.Fatal(err) - } - - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) - - p := conf.New() - p.Set("int", 1) - p.Set("float", 5.4) - p.Set("map.a", 3) - p.Set("map.b", 7) - p.Set("slice[0]", 2) - p.Set("slice[1]", 9) - err = mgr.Refresh(p) - // assert.Error(t, err, "should greeter than 3") - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":1,"Float":5.4,"Map":{"a":"3","b":"7"},"Slice":["2","9"],"Object":{"Str":"abc","Int":3}}`) - - p = conf.New() - p.Set("int", 6) - p.Set("float", 2.3) - p.Set("map.a", 1) - p.Set("map.b", 2) - p.Set("slice[0]", 3) - p.Set("slice[1]", 4) - err = mgr.Refresh(p) - assert.Error(t, err, "validate failed on \"\\$<6\" for value 6") - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":1,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Object":{"Str":"abc","Int":3}}`) - - p = conf.New() - p.Set("int", 4) - p.Set("float", 2.3) - p.Set("map.a", 1) - p.Set("map.b", 2) - p.Set("slice[0]", 3) - p.Set("slice[1]", 4) - mgr.Refresh(p) - - assert.Equal(t, cfg.Int.Value(), int64(4)) - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Object":{"Str":"abc","Int":3}}`) - }) -} diff --git a/gs/internal/gs_util/util.go b/gs/internal/gs_util/util.go new file mode 100644 index 00000000..851cc300 --- /dev/null +++ b/gs/internal/gs_util/util.go @@ -0,0 +1,121 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_util + +import ( + "container/list" + "errors" +) + +// GetBeforeItems is a function type that returns a list of items +// that must appear before the given current item in the sorting order. +type GetBeforeItems func(sorting *list.List, current interface{}) (*list.List, error) + +// TripleSort performs a three-way sort (processing, toSort, sorted) +// to resolve dependencies and return a sorted list. +// The input `sorting` is a list of all items to be sorted, and `fn` determines dependencies. +func TripleSort(sorting *list.List, fn GetBeforeItems) (*list.List, error) { + toSort := list.New() // List of items that still need to be sorted. + sorted := list.New() // List of items that have been fully sorted. + processing := list.New() // List of items currently being processed. + + // Initialize the toSort list with all elements from the input sorting list. + toSort.PushBackList(sorting) + + // Process items in the toSort list until all items are sorted. + for toSort.Len() > 0 { + // Recursively sort the dependency chain starting with the next item in `toSort`. + err := tripleSortByAfter(sorting, toSort, sorted, processing, nil, fn) + if err != nil { + return nil, err + } + } + return sorted, nil +} + +// searchInList searches for an element `v` in the list `l`. +// If the element exists, it returns a pointer to the list element. Otherwise, it returns nil. +func searchInList(l *list.List, v interface{}) *list.Element { + for e := l.Front(); e != nil; e = e.Next() { + if e.Value == v { + return e + } + } + return nil +} + +// tripleSortByAfter recursively processes an item's dependency chain and adds it to the sorted list. +// Parameters: +// - sorting: The original list of items. +// - toSort: The list of items to be sorted. +// - sorted: The list of items that have been sorted. +// - processing: The list of items currently being processed (to detect cycles). +// - current: The current item being processed (nil for the first item). +// - fn: A function that retrieves the list of items that must appear before the current item. +func tripleSortByAfter(sorting *list.List, toSort *list.List, sorted *list.List, + processing *list.List, current interface{}, fn GetBeforeItems) error { + + // If no current item is specified, remove and process the first item in the `toSort` list. + if current == nil { + current = toSort.Remove(toSort.Front()) + } + + // Retrieve dependencies for the current item. + l, err := fn(sorting, current) + if err != nil { + return err + } + + // Add the current item to the processing list to mark it as being processed. + processing.PushBack(current) + + // Process dependencies for the current item. + for e := l.Front(); e != nil; e = e.Next() { + c := e.Value + + // Detect circular dependencies by checking if `c` is already being processed. + if searchInList(processing, c) != nil { + return errors.New("found sorting cycle") // todo: more details + } + + // Check if the dependency `c` is already sorted or still in the toSort list. + inSorted := searchInList(sorted, c) != nil + inToSort := searchInList(toSort, c) != nil + + // If the dependency is not sorted but still needs sorting, process it recursively. + if !inSorted && inToSort { + err = tripleSortByAfter(sorting, toSort, sorted, processing, c, fn) + if err != nil { + return err + } + } + } + + // Remove the current item from the processing list. + if e := searchInList(processing, current); e != nil { + processing.Remove(e) + } + + // Remove the current item from the toSort list (if it is still there). + if e := searchInList(toSort, current); e != nil { + toSort.Remove(e) + } + + // Add the current item to the sorted list to mark it as fully processed. + sorted.PushBack(current) + return nil +} diff --git a/gs/syslog/syslog.go b/gs/syslog/syslog.go deleted file mode 100644 index 50e01234..00000000 --- a/gs/syslog/syslog.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package syslog - -import ( - "context" - "log/slog" - "os" -) - -func init() { - slog.SetLogLoggerLevel(slog.LevelInfo) - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) -} - -// Debug calls [Logger.Debug] on the default logger. -func Debug(msg string, args ...any) { - slog.Default().Log(context.Background(), slog.LevelDebug, msg, args...) -} - -// Info calls [Logger.Info] on the default logger. -func Info(msg string, args ...any) { - slog.Default().Log(context.Background(), slog.LevelInfo, msg, args...) -} - -// Warn calls [Logger.Warn] on the default logger. -func Warn(msg string, args ...any) { - slog.Default().Log(context.Background(), slog.LevelWarn, msg, args...) -} - -// Error calls [Logger.Error] on the default logger. -func Error(msg string, args ...any) { - slog.Default().Log(context.Background(), slog.LevelError, msg, args...) -} diff --git a/util/assert/assert.go b/util/assert/assert.go index 16e5b9ce..3e1a24a4 100644 --- a/util/assert/assert.go +++ b/util/assert/assert.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/util/assert/assert_test.go b/util/assert/assert_test.go index 059414bc..41da937d 100644 --- a/util/assert/assert_test.go +++ b/util/assert/assert_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/util/assert/string.go b/util/assert/string.go index 595aab51..9aac2daf 100644 --- a/util/assert/string.go +++ b/util/assert/string.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/util/assert/string_test.go b/util/assert/string_test.go index 8ae1ffee..b5a30291 100644 --- a/util/assert/string_test.go +++ b/util/assert/string_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/util/error.go b/util/error.go index 62b89e26..18784932 100644 --- a/util/error.go +++ b/util/error.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package util import ( "errors" - "fmt" ) // ForbiddenMethod throws this error when calling a method is prohibited. @@ -26,34 +25,3 @@ var ForbiddenMethod = errors.New("forbidden method") // UnimplementedMethod throws this error when calling an unimplemented method. var UnimplementedMethod = errors.New("unimplemented method") - -var WrapFormat = func(err error, fileline string, msg string) error { - if err == nil { - return fmt.Errorf("%s %s", fileline, msg) - } - return fmt.Errorf("%s %s; %w", fileline, msg, err) -} - -// Error returns an error with the file and line. -// The file and line may be calculated at the compile time in the future. -func Error(fileline string, text string) error { - return WrapFormat(nil, fileline, text) -} - -// Errorf returns an error with the file and line. -// The file and line may be calculated at the compile time in the future. -func Errorf(fileline string, format string, a ...interface{}) error { - return WrapFormat(nil, fileline, fmt.Sprintf(format, a...)) -} - -// Wrap returns an error with the file and line. -// The file and line may be calculated at the compile time in the future. -func Wrap(err error, fileline string, text string) error { - return WrapFormat(err, fileline, text) -} - -// Wrapf returns an error with the file and line. -// The file and line may be calculated at the compile time in the future. -func Wrapf(err error, fileline string, format string, a ...interface{}) error { - return WrapFormat(err, fileline, fmt.Sprintf(format, a...)) -} diff --git a/util/error_test.go b/util/error_test.go deleted file mode 100644 index 11bc0061..00000000 --- a/util/error_test.go +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package util_test - -import ( - "testing" - - "github.com/go-spring/spring-core/util" - "github.com/go-spring/spring-core/util/assert" - "github.com/go-spring/spring-core/util/macro" -) - -func TestError(t *testing.T) { - - e0 := util.Error(macro.FileLine(), "error: 0") - t.Log(e0) - assert.Error(t, e0, ".*/error_test.go:29 error: 0") - - e1 := util.Errorf(macro.FileLine(), "error: %d", 1) - t.Log(e1) - assert.Error(t, e1, ".*/error_test.go:33 error: 1") - - e2 := util.Wrap(e0, macro.FileLine(), "error: 0") - t.Log(e2) - assert.Error(t, e2, ".*/error_test.go:37 error: 0; .*/error_test.go:29 error: 0") - - e3 := util.Wrapf(e1, macro.FileLine(), "error: %d", 1) - t.Log(e3) - assert.Error(t, e3, ".*/error_test.go:41 error: 1; .*/error_test.go:33 error: 1") - - e4 := util.Wrap(e2, macro.FileLine(), "error: 0") - t.Log(e4) - assert.Error(t, e4, ".*/error_test.go:45 error: 0; .*/error_test.go:37 error: 0; .*/error_test.go:29 error: 0") - - e5 := util.Wrapf(e3, macro.FileLine(), "error: %d", 1) - t.Log(e5) - assert.Error(t, e5, ".*/error_test.go:49 error: 1; .*/error_test.go:41 error: 1; .*/error_test.go:33 error: 1") -} diff --git a/gs/testdata/pkg/foo/pkg.go b/util/errutil/errutil.go similarity index 58% rename from gs/testdata/pkg/foo/pkg.go rename to util/errutil/errutil.go index ab2b9fa5..c6cb1cb6 100644 --- a/gs/testdata/pkg/foo/pkg.go +++ b/util/errutil/errutil.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,17 @@ * limitations under the License. */ -package pkg +package errutil import ( "fmt" ) -// golang 允许不同的路径下存在相同的包,而且允许存在相同的包。 -type SamePkg struct{} +// LineBreak defines the separator used between errors with hierarchical relationships. +var LineBreak = " << " -func (p *SamePkg) Package() { - fmt.Println("github.com/go-spring/spring-core/gs/testdata/pkg/foo/pkg.SamePkg") +// WrapError wraps an existing error, creating a new error with hierarchical relationships. +func WrapError(err error, format string, args ...interface{}) error { + msg := fmt.Sprintf(format, args...) + return fmt.Errorf("%s%s%w", msg, LineBreak, err) } diff --git a/util/errutil/errutil_test.go b/util/errutil/errutil_test.go new file mode 100644 index 00000000..8ebcd4c5 --- /dev/null +++ b/util/errutil/errutil_test.go @@ -0,0 +1,42 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package errutil_test + +import ( + "os" + "testing" + + "github.com/go-spring/spring-core/util/assert" + "github.com/go-spring/spring-core/util/errutil" +) + +func TestWrapError(t *testing.T) { + var err error + + err = os.ErrNotExist + err = errutil.WrapError(err, "open file error: file=%s", "test.php") + err = errutil.WrapError(err, "read file error") + assert.NotNil(t, err) + assert.Equal(t, err.Error(), `read file error << open file error: file=test.php << file does not exist`) + + errutil.LineBreak = " / " + err = os.ErrNotExist + err = errutil.WrapError(err, "open file error: file=%s", "test.php") + err = errutil.WrapError(err, "read file error") + assert.NotNil(t, err) + assert.Equal(t, err.Error(), `read file error / open file error: file=test.php / file does not exist`) +} diff --git a/util/flat.go b/util/flat.go index c5693267..11b62f48 100644 --- a/util/flat.go +++ b/util/flat.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,9 @@ import ( "github.com/spf13/cast" ) -// FlattenMap can expand the nested array, slice and map. +// FlattenMap flattens a nested map, array, or slice into a single-level map +// with string keys and string values. It recursively processes each element +// of the input map and adds its flattened representation to the result map. func FlattenMap(m map[string]interface{}) map[string]string { result := make(map[string]string) for key, val := range m { @@ -32,8 +34,12 @@ func FlattenMap(m map[string]interface{}) map[string]string { return result } -// FlattenValue can expand the nested array, slice and map. +// FlattenValue flattens a single value (which can be a map, array, slice, +// or other types) into the result map. func FlattenValue(key string, val interface{}, result map[string]string) { + if val == nil { + return + } switch v := reflect.ValueOf(val); v.Kind() { case reflect.Map: if v.Len() == 0 { @@ -53,6 +59,12 @@ func FlattenValue(key string, val interface{}, result map[string]string) { for i := 0; i < v.Len(); i++ { subKey := fmt.Sprintf("%s[%d]", key, i) subValue := v.Index(i).Interface() + // If an element is nil, treat it as an empty value and assign an empty string. + // Note: We do not remove the nil element to avoid changing the array's size. + if subValue == nil { + result[subKey] = "" + continue + } FlattenValue(subKey, subValue, result) } default: diff --git a/util/flat_test.go b/util/flat_test.go index 89117638..687a8421 100644 --- a/util/flat_test.go +++ b/util/flat_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,8 +34,9 @@ func TestFlatten(t *testing.T) { "a": "123", "b": "456", }, - ([]interface{})(nil), - (map[string]string)(nil), + nil, + ([]interface{})(nil), // it doesn't equal to nil + (map[string]string)(nil), // it doesn't equal to nil []interface{}{}, map[string]string{}, }, @@ -46,13 +47,15 @@ func TestFlatten(t *testing.T) { "abc", "def", }, - "nil_arr": nil, - "nil_map": nil, + "nil": nil, + "nil_arr": []interface{}(nil), // it doesn't equal to nil + "nil_map": map[string]string(nil), // it doesn't equal to nil "empty_arr": []interface{}{}, "empty_map": map[string]string{}, }, - "nil_arr": nil, - "nil_map": nil, + "nil": nil, + "nil_arr": []interface{}(nil), // it doesn't equal to nil + "nil_map": map[string]string(nil), // it doesn't equal to nil "empty_arr": []interface{}{}, "empty_map": map[string]string{}, }) @@ -79,6 +82,7 @@ func TestFlatten(t *testing.T) { "arr[4]": "", "arr[5]": "", "arr[6]": "", + "arr[7]": "", } assert.Equal(t, m, expect) } diff --git a/util/goutil/goutil.go b/util/goutil/goutil.go new file mode 100644 index 00000000..4460041f --- /dev/null +++ b/util/goutil/goutil.go @@ -0,0 +1,138 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package goutil + +import ( + "context" + "errors" + "runtime/debug" + "sync" + + "github.com/go-spring/spring-core/util/syslog" +) + +// OnPanic is a global callback function triggered when a panic occurs. +var OnPanic = func(r interface{}) { + syslog.Errorf("panic: %v\n%s", r, debug.Stack()) +} + +/********************************** go ***************************************/ + +// Status provides a mechanism to wait for a goroutine to finish. +type Status struct { + wg sync.WaitGroup +} + +// newStatus creates and initializes a new Status object. +func newStatus() *Status { + s := &Status{} + s.wg.Add(1) + return s +} + +// done marks the goroutine as finished. +func (s *Status) done() { + s.wg.Done() +} + +// Wait waits for the goroutine to finish. +func (s *Status) Wait() { + s.wg.Wait() +} + +// Go runs a goroutine safely with context support and panic recovery. +// It ensures the process does not crash due to an uncaught panic in the goroutine. +func Go(ctx context.Context, f func(ctx context.Context)) *Status { + s := newStatus() + go func() { + defer s.done() + defer func() { + if r := recover(); r != nil { + if OnPanic != nil { + OnPanic(r) + } + } + }() + f(ctx) + }() + return s +} + +// GoFunc runs a goroutine safely with panic recovery. +// It ensures the process does not crash due to an uncaught panic in the goroutine. +func GoFunc(f func()) *Status { + s := newStatus() + go func() { + defer s.done() + defer func() { + if r := recover(); r != nil { + if OnPanic != nil { + OnPanic(r) + } + } + }() + f() + }() + return s +} + +/******************************* go with value *******************************/ + +// ValueStatus provides a mechanism to wait for a goroutine that returns a value and an error. +type ValueStatus[T any] struct { + wg sync.WaitGroup + val T + err error +} + +// newValueStatus creates and initializes a new ValueStatus object. +func newValueStatus[T any]() *ValueStatus[T] { + s := &ValueStatus[T]{} + s.wg.Add(1) + return s +} + +// done marks the goroutine as finished. +func (s *ValueStatus[T]) done() { + s.wg.Done() +} + +// Wait blocks until the goroutine finishes and returns its result and error. +func (s *ValueStatus[T]) Wait() (T, error) { + s.wg.Wait() + return s.val, s.err +} + +// GoValue runs a goroutine safely with context support and panic recovery and +// returns its result and error. +// It ensures the process does not crash due to an uncaught panic in the goroutine. +func GoValue[T any](ctx context.Context, f func(ctx context.Context) (T, error)) *ValueStatus[T] { + s := newValueStatus[T]() + go func() { + defer s.done() + defer func() { + if r := recover(); r != nil { + if OnPanic != nil { + OnPanic(r) + } + s.err = errors.New("panic occurred") + } + }() + s.val, s.err = f(ctx) + }() + return s +} diff --git a/util/goutil/goutil_test.go b/util/goutil/goutil_test.go new file mode 100644 index 00000000..1e14bbb8 --- /dev/null +++ b/util/goutil/goutil_test.go @@ -0,0 +1,83 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package goutil_test + +import ( + "context" + "fmt" + "testing" + + "github.com/go-spring/spring-core/util/assert" + "github.com/go-spring/spring-core/util/goutil" +) + +func TestGo(t *testing.T) { + + var s string + goutil.Go(t.Context(), func(ctx context.Context) { + panic("something is wrong") + }).Wait() + assert.Equal(t, s, "") + + goutil.Go(t.Context(), func(ctx context.Context) { + s = "hello world!" + }).Wait() + assert.Equal(t, s, "hello world!") +} + +func TestGoFunc(t *testing.T) { + + var s string + goutil.GoFunc(func() { + panic("something is wrong") + }).Wait() + assert.Equal(t, s, "") + + goutil.GoFunc(func() { + s = "hello world!" + }).Wait() + assert.Equal(t, s, "hello world!") +} + +func TestGoValue(t *testing.T) { + + s, err := goutil.GoValue(t.Context(), func(ctx context.Context) (string, error) { + panic("something is wrong") + }).Wait() + assert.Equal(t, s, "") + assert.Equal(t, err, fmt.Errorf("panic occurred")) + + s, err = goutil.GoValue(t.Context(), func(ctx context.Context) (string, error) { + return "hello world!", nil + }).Wait() + assert.Equal(t, s, "hello world!") + assert.Nil(t, err) + + var arr []*goutil.ValueStatus[int] + for i := 0; i < 3; i++ { + arr = append(arr, goutil.GoValue(t.Context(), func(ctx context.Context) (int, error) { + return i, nil + })) + } + + var v int + for i, g := range arr { + v, err = g.Wait() + assert.Equal(t, v, i) + assert.Nil(t, err) + } +} diff --git a/util/macro/fileline.go b/util/macro/fileline.go deleted file mode 100644 index f611234b..00000000 --- a/util/macro/fileline.go +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package macro - -import ( - "fmt" - "runtime" - "sync" -) - -var frameMap sync.Map - -func fileLine() (string, int) { - rpc := make([]uintptr, 1) - runtime.Callers(3, rpc[:]) - pc := rpc[0] - if v, ok := frameMap.Load(pc); ok { - e := v.(*runtime.Frame) - return e.File, e.Line - } - frame, _ := runtime.CallersFrames(rpc).Next() - frameMap.Store(pc, &frame) - return frame.File, frame.Line -} - -// File returns the file name of the call point. -func File() string { - file, _ := fileLine() - return file -} - -// Line returns the file line of the call point. -func Line() int { - _, line := fileLine() - return line -} - -// FileLine returns the file name and line of the call point. -// In reality macro.FileLine costs less time than debug.Stack. -func FileLine() string { - file, line := fileLine() - return fmt.Sprintf("%s:%d", file, line) -} diff --git a/util/macro/fileline_test.go b/util/macro/fileline_test.go deleted file mode 100644 index b48ff5ed..00000000 --- a/util/macro/fileline_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package macro_test - -import ( - "fmt" - "runtime/debug" - "testing" - "time" - - "github.com/go-spring/spring-core/util/assert" - "github.com/go-spring/spring-core/util/macro" -) - -func f1(t *testing.T) ([]string, time.Duration) { - ret, cost := f2(t) - assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") - assert.Equal(t, macro.Line(), 32) - start := time.Now() - fileLine := macro.FileLine() - cost += time.Since(start) - return append(ret, fileLine), cost -} - -func f2(t *testing.T) ([]string, time.Duration) { - ret, cost := f3(t) - assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") - assert.Equal(t, macro.Line(), 42) - start := time.Now() - fileLine := macro.FileLine() - cost += time.Since(start) - return append(ret, fileLine), cost -} - -func f3(t *testing.T) ([]string, time.Duration) { - ret, cost := f4(t) - assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") - assert.Equal(t, macro.Line(), 52) - start := time.Now() - fileLine := macro.FileLine() - cost += time.Since(start) - return append(ret, fileLine), cost -} - -func f4(t *testing.T) ([]string, time.Duration) { - ret, cost := f5(t) - assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") - assert.Equal(t, macro.Line(), 62) - start := time.Now() - fileLine := macro.FileLine() - cost += time.Since(start) - return append(ret, fileLine), cost -} - -func f5(t *testing.T) ([]string, time.Duration) { - ret, cost := f6(t) - assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") - assert.Equal(t, macro.Line(), 72) - start := time.Now() - fileLine := macro.FileLine() - cost += time.Since(start) - return append(ret, fileLine), cost -} - -func f6(t *testing.T) ([]string, time.Duration) { - ret, cost := f7(t) - assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") - assert.Equal(t, macro.Line(), 82) - start := time.Now() - fileLine := macro.FileLine() - cost += time.Since(start) - return append(ret, fileLine), cost -} - -func f7(t *testing.T) ([]string, time.Duration) { - assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") - assert.Equal(t, macro.Line(), 91) - { - start := time.Now() - _ = debug.Stack() - fmt.Println("\t", "debug.Stack cost", time.Since(start)) - } - start := time.Now() - fileLine := macro.FileLine() - cost := time.Since(start) - return []string{fileLine}, cost -} - -func TestFileLine(t *testing.T) { - for i := 0; i < 5; i++ { - fmt.Printf("loop %d\n", i) - ret, cost := f1(t) - fmt.Println("\t", ret) - fmt.Println("\t", "all macro.FileLine cost", cost) - } - // loop 0 - // debug.Stack cost 37.794µs - // all macro.FileLine cost 14.638µs - // loop 1 - // debug.Stack cost 11.699µs - // all macro.FileLine cost 6.398µs - // loop 2 - // debug.Stack cost 20.62µs - // all macro.FileLine cost 4.185µs - // loop 3 - // debug.Stack cost 11.736µs - // all macro.FileLine cost 4.274µs - // loop 4 - // debug.Stack cost 19.821µs - // all macro.FileLine cost 4.061µs -} diff --git a/util/syslog/syslog.go b/util/syslog/syslog.go new file mode 100644 index 00000000..9368c2e7 --- /dev/null +++ b/util/syslog/syslog.go @@ -0,0 +1,67 @@ +/* + * Copyright 2024 The Go-Spring Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package syslog + +import ( + "context" + "fmt" + "log" + "log/slog" + "runtime" + "time" +) + +func init() { + log.SetFlags(log.Flags() | log.Lshortfile) +} + +// Debugf logs a debug-level message using slog. +func Debugf(format string, a ...any) { + logMsg(slog.LevelDebug, format, a...) +} + +// Infof logs an info-level message using slog. +func Infof(format string, a ...any) { + logMsg(slog.LevelInfo, format, a...) +} + +// Warnf logs a warning-level message using slog. +func Warnf(format string, a ...any) { + logMsg(slog.LevelWarn, format, a...) +} + +// Errorf logs an error-level message using slog. +func Errorf(format string, a ...any) { + logMsg(slog.LevelError, format, a...) +} + +// logMsg constructs and logs a message at the specified log level. +func logMsg(level slog.Level, format string, a ...any) { + ctx := context.Background() + if !slog.Default().Enabled(ctx, level) { + return + } + + // skip [runtime.Callers, syslog.logMsg, syslog.*f] + var pcs [1]uintptr + runtime.Callers(3, pcs[:]) + + msg := fmt.Sprintf(format, a...) + r := slog.NewRecord(time.Now(), level, msg, pcs[0]) + err := slog.Default().Handler().Handle(ctx, r) + _ = err // ignore error +} diff --git a/util/testdata/pkg/bar/pkg.go b/util/syslog/syslog_test.go similarity index 66% rename from util/testdata/pkg/bar/pkg.go rename to util/syslog/syslog_test.go index 3097d24f..ba66eb7e 100644 --- a/util/testdata/pkg/bar/pkg.go +++ b/util/syslog/syslog_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,17 @@ * limitations under the License. */ -package pkg +package syslog_test import ( - "fmt" -) + "testing" -// SamePkg golang allows packages with the same name under different paths. -type SamePkg struct{} + "github.com/go-spring/spring-core/util/syslog" +) -func (p *SamePkg) Package() { - fmt.Println("github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg") +func TestLog(t *testing.T) { + syslog.Debugf("hello %s", "world") + syslog.Infof("hello %s", "world") + syslog.Warnf("hello %s", "world") + syslog.Errorf("hello %s", "world") } diff --git a/util/testdata/pkg.go b/util/testdata/pkg.go deleted file mode 100644 index 31d64a86..00000000 --- a/util/testdata/pkg.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package testdata - -func FnNoArgs() {} - -func FnWithArgs(i int) {} - -type Receiver struct{} - -func (r Receiver) FnNoArgs() {} - -func (r Receiver) FnWithArgs(i int) {} - -func (r *Receiver) PtrFnNoArgs() {} - -func (r *Receiver) PtrFnWithArgs(i int) {} - -func (r *Receiver) String() string { return "" } diff --git a/util/type.go b/util/type.go index f2d6a1d0..fbd32705 100644 --- a/util/type.go +++ b/util/type.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,95 +18,58 @@ package util import ( "reflect" - "strings" ) -// errorType the reflection type of error. +// errorType is the [reflect.Type] of the error interface. var errorType = reflect.TypeOf((*error)(nil)).Elem() -// TypeName returns a fully qualified name consisting of package path and type name. -func TypeName(i interface{}) string { - - var typ reflect.Type - switch o := i.(type) { - case reflect.Type: - typ = o - case reflect.Value: - typ = o.Type() - default: - typ = reflect.TypeOf(o) - } - - for { - if k := typ.Kind(); k == reflect.Ptr || k == reflect.Slice { - typ = typ.Elem() - } else { - break - } - } - - if pkgPath := typ.PkgPath(); pkgPath != "" { - pkgPath = strings.TrimSuffix(pkgPath, "_test") - return pkgPath + "/" + typ.String() - } - return typ.String() // the path of built-in type is empty -} - -// IsFuncType returns whether `t` is func type. +// IsFuncType returns true if the provided type t is a function type. func IsFuncType(t reflect.Type) bool { return t.Kind() == reflect.Func } -// IsErrorType returns whether `t` is error type. +// IsErrorType returns true if the provided type t is an error type, +// either directly (error) or via an implementation (i.e., implements the error interface). func IsErrorType(t reflect.Type) bool { return t == errorType || t.Implements(errorType) } -// ReturnNothing returns whether the function has no return value. +// ReturnNothing returns true if the provided function type t has no return values. func ReturnNothing(t reflect.Type) bool { return t.NumOut() == 0 } -// ReturnOnlyError returns whether the function returns only error value. +// ReturnOnlyError returns true if the provided function type t returns only one value, +// and that value is an error. func ReturnOnlyError(t reflect.Type) bool { return t.NumOut() == 1 && IsErrorType(t.Out(0)) } -// IsStructPtr returns whether it is the pointer type of structure. -func IsStructPtr(t reflect.Type) bool { - return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct -} - -// IsConstructor returns whether `t` is a constructor type. What is a constructor? -// It should be a function first, has any number of inputs and supports the option -// pattern input, has one or two outputs and the second output should be an error. +// IsConstructor returns true if the provided function type t is a constructor. +// A constructor is defined as a function that returns one or two values. +// If it returns two values, the second value must be an error. func IsConstructor(t reflect.Type) bool { - returnError := t.NumOut() == 2 && IsErrorType(t.Out(1)) - return IsFuncType(t) && (t.NumOut() == 1 || returnError) -} - -// HasReceiver returns whether the function has a receiver. -func HasReceiver(t reflect.Type, receiver reflect.Value) bool { - if t.NumIn() < 1 { + if !IsFuncType(t) { return false } - t0 := t.In(0) - if t0.Kind() != reflect.Interface { - return t0 == receiver.Type() + switch t.NumOut() { + case 1: + return !IsErrorType(t.Out(0)) + case 2: + return IsErrorType(t.Out(1)) + default: + return false } - return receiver.Type().Implements(t0) } -// IsPrimitiveValueType returns whether `t` is the primitive value type which only is -// int, unit, float, bool, string and complex. +// IsPrimitiveValueType returns true if the provided type t is a primitive value type, +// such as int, uint, float, bool, or string. func IsPrimitiveValueType(t reflect.Type) bool { switch t.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return true case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return true - case reflect.Complex64, reflect.Complex128: - return true case reflect.Float32, reflect.Float64: return true case reflect.String: @@ -118,22 +81,21 @@ func IsPrimitiveValueType(t reflect.Type) bool { } } -// IsValueType returns whether the input type is the primitive value type and their -// composite type including array, slice, map and struct, such as []int, [3]string, -// []string, map[int]int, map[string]string, etc. -func IsValueType(t reflect.Type) bool { - fn := func(t reflect.Type) bool { - return IsPrimitiveValueType(t) || t.Kind() == reflect.Struct - } +// IsPropBindingTarget returns true if the provided type t is a valid target for property binding. +// This includes primitive value types or composite types (such as array, slice, map, or struct) +// where the elements are primitive value types. +func IsPropBindingTarget(t reflect.Type) bool { switch t.Kind() { case reflect.Map, reflect.Slice, reflect.Array: - return fn(t.Elem()) + t = t.Elem() // for collection types, check the element type default: - return fn(t) + // do nothing } + return IsPrimitiveValueType(t) || t.Kind() == reflect.Struct } -// IsBeanType returns whether `t` is a bean type. +// IsBeanType returns true if the provided type t is considered a "bean" type. +// A "bean" type includes a channel, function, interface, or a pointer to a struct. func IsBeanType(t reflect.Type) bool { switch t.Kind() { case reflect.Chan, reflect.Func, reflect.Interface: @@ -145,13 +107,14 @@ func IsBeanType(t reflect.Type) bool { } } -// IsBeanReceiver returns whether the `t` is a bean receiver, a bean receiver can -// be a bean, a map or slice whose elements are beans. -func IsBeanReceiver(t reflect.Type) bool { +// IsBeanInjectionTarget returns true if the provided type t is a valid target for bean injection. +// This includes maps, slices, arrays, or any other bean type (including pointers to structs). +func IsBeanInjectionTarget(t reflect.Type) bool { switch t.Kind() { case reflect.Map, reflect.Slice, reflect.Array: - return IsBeanType(t.Elem()) + t = t.Elem() // for collection types, check the element type default: - return IsBeanType(t) + // do nothing } + return IsBeanType(t) } diff --git a/util/type_test.go b/util/type_test.go index d2efc204..54685c74 100644 --- a/util/type_test.go +++ b/util/type_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ package util_test import ( "errors" "fmt" - "io" "os" "reflect" "testing" @@ -27,373 +26,39 @@ import ( "github.com/go-spring/spring-core/util" "github.com/go-spring/spring-core/util/assert" - "github.com/go-spring/spring-core/util/testdata" - pkg1 "github.com/go-spring/spring-core/util/testdata/pkg/bar" - pkg2 "github.com/go-spring/spring-core/util/testdata/pkg/foo" ) -type SamePkg struct{} - -func (p *SamePkg) Package() { - fmt.Println("github.com/go-spring/spring-core/util/util_test.SamePkg") +func TestIsErrorType(t *testing.T) { + err := fmt.Errorf("error") + assert.True(t, util.IsErrorType(reflect.TypeOf(err))) + err = os.ErrClosed + assert.True(t, util.IsErrorType(reflect.TypeOf(err))) + assert.False(t, util.IsErrorType(reflect.TypeFor[int]())) } -func TestPkgPath(t *testing.T) { - // the name and package path of built-in type are empty. - - data := []struct { - typ reflect.Type - kind reflect.Kind - name string - pkg string - }{ - { - reflect.TypeOf(false), - reflect.Bool, - "bool", - "", - }, - { - reflect.TypeOf(new(bool)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]bool, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(int(3)), - reflect.Int, - "int", - "", - }, - { - reflect.TypeOf(new(int)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]int, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(uint(3)), - reflect.Uint, - "uint", - "", - }, - { - reflect.TypeOf(new(uint)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]uint, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(float32(3)), - reflect.Float32, - "float32", - "", - }, - { - reflect.TypeOf(new(float32)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]float32, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(complex64(3)), - reflect.Complex64, - "complex64", - "", - }, - { - reflect.TypeOf(new(complex64)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]complex64, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf("3"), - reflect.String, - "string", - "", - }, - { - reflect.TypeOf(new(string)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]string, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(map[int]int{}), - reflect.Map, - "", - "", - }, - { - reflect.TypeOf(new(map[int]int)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]map[int]int, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(pkg1.SamePkg{}), - reflect.Struct, - "SamePkg", - "github.com/go-spring/spring-core/util/testdata/pkg/bar", - }, - { - reflect.TypeOf(new(pkg1.SamePkg)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]pkg1.SamePkg, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(make([]*pkg1.SamePkg, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf(pkg2.SamePkg{}), - reflect.Struct, - "SamePkg", - "github.com/go-spring/spring-core/util/testdata/pkg/foo", - }, - { - reflect.TypeOf(new(pkg2.SamePkg)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf(make([]pkg2.SamePkg, 0)), - reflect.Slice, - "", - "", - }, - { - reflect.TypeOf((*error)(nil)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf((*error)(nil)).Elem(), - reflect.Interface, - "error", - "", - }, - { - reflect.TypeOf((*io.Reader)(nil)), - reflect.Ptr, - "", - "", - }, - { - reflect.TypeOf((*io.Reader)(nil)).Elem(), - reflect.Interface, - "Reader", - "io", - }, - } - - for _, d := range data { - assert.Equal(t, d.typ.Kind(), d.kind) - assert.Equal(t, d.typ.Name(), d.name) - assert.Equal(t, d.typ.PkgPath(), d.pkg) - } +func TestReturnNothing(t *testing.T) { + assert.True(t, util.ReturnNothing(reflect.TypeOf(func() {}))) + assert.True(t, util.ReturnNothing(reflect.TypeOf(func(key string) {}))) + assert.False(t, util.ReturnNothing(reflect.TypeOf(func() string { return "" }))) } -func TestTypeName(t *testing.T) { - - data := map[interface{}]struct { - typeName string - baseName string - }{ - reflect.TypeOf(3): {"int", "int"}, - reflect.TypeOf(new(int)): {"int", "*int"}, - reflect.TypeOf(make([]int, 0)): {"int", "[]int"}, - reflect.TypeOf(&[]int{3}): {"int", "*[]int"}, - reflect.TypeOf(make([]*int, 0)): {"int", "[]*int"}, - reflect.TypeOf(make([][]int, 0)): {"int", "[][]int"}, - reflect.TypeOf(make(map[int]int)): {"map[int]int", "map[int]int"}, - - reflect.TypeOf(int8(3)): {"int8", "int8"}, - reflect.TypeOf(new(int8)): {"int8", "*int8"}, - reflect.TypeOf(make([]int8, 0)): {"int8", "[]int8"}, - reflect.TypeOf(&[]int8{3}): {"int8", "*[]int8"}, - reflect.TypeOf(make(map[int8]int8)): {"map[int8]int8", "map[int8]int8"}, - - reflect.TypeOf(int16(3)): {"int16", "int16"}, - reflect.TypeOf(new(int16)): {"int16", "*int16"}, - reflect.TypeOf(make([]int16, 0)): {"int16", "[]int16"}, - reflect.TypeOf(&[]int16{3}): {"int16", "*[]int16"}, - reflect.TypeOf(make(map[int16]int16)): {"map[int16]int16", "map[int16]int16"}, - - reflect.TypeOf(int32(3)): {"int32", "int32"}, - reflect.TypeOf(new(int32)): {"int32", "*int32"}, - reflect.TypeOf(make([]int32, 0)): {"int32", "[]int32"}, - reflect.TypeOf(&[]int32{3}): {"int32", "*[]int32"}, - reflect.TypeOf(make(map[int32]int32)): {"map[int32]int32", "map[int32]int32"}, - - reflect.TypeOf(int64(3)): {"int64", "int64"}, - reflect.TypeOf(new(int64)): {"int64", "*int64"}, - reflect.TypeOf(make([]int64, 0)): {"int64", "[]int64"}, - reflect.TypeOf(&[]int64{3}): {"int64", "*[]int64"}, - reflect.TypeOf(make(map[int64]int64)): {"map[int64]int64", "map[int64]int64"}, - - reflect.TypeOf(uint(3)): {"uint", "uint"}, - reflect.TypeOf(new(uint)): {"uint", "*uint"}, - reflect.TypeOf(make([]uint, 0)): {"uint", "[]uint"}, - reflect.TypeOf(&[]uint{3}): {"uint", "*[]uint"}, - reflect.TypeOf(make(map[uint]uint)): {"map[uint]uint", "map[uint]uint"}, - - reflect.TypeOf(uint8(3)): {"uint8", "uint8"}, - reflect.TypeOf(new(uint8)): {"uint8", "*uint8"}, - reflect.TypeOf(make([]uint8, 0)): {"uint8", "[]uint8"}, - reflect.TypeOf(&[]uint8{3}): {"uint8", "*[]uint8"}, - reflect.TypeOf(make(map[uint8]uint8)): {"map[uint8]uint8", "map[uint8]uint8"}, - - reflect.ValueOf(uint16(3)): {"uint16", "uint16"}, - reflect.ValueOf(new(uint16)): {"uint16", "*uint16"}, - reflect.ValueOf(make([]uint16, 0)): {"uint16", "[]uint16"}, - reflect.ValueOf(&[]uint16{3}): {"uint16", "*[]uint16"}, - reflect.ValueOf(make(map[uint16]uint16)): {"map[uint16]uint16", "map[uint16]uint16"}, - - reflect.ValueOf(uint32(3)): {"uint32", "uint32"}, - reflect.ValueOf(new(uint32)): {"uint32", "*uint32"}, - reflect.ValueOf(make([]uint32, 0)): {"uint32", "[]uint32"}, - reflect.ValueOf(&[]uint32{3}): {"uint32", "*[]uint32"}, - reflect.ValueOf(make(map[uint32]uint32)): {"map[uint32]uint32", "map[uint32]uint32"}, - - reflect.ValueOf(uint64(3)): {"uint64", "uint64"}, - reflect.ValueOf(new(uint64)): {"uint64", "*uint64"}, - reflect.ValueOf(make([]uint64, 0)): {"uint64", "[]uint64"}, - reflect.ValueOf(&[]uint64{3}): {"uint64", "*[]uint64"}, - reflect.ValueOf(make(map[uint64]uint64)): {"map[uint64]uint64", "map[uint64]uint64"}, - - reflect.ValueOf(true): {"bool", "bool"}, - reflect.ValueOf(new(bool)): {"bool", "*bool"}, - reflect.ValueOf(make([]bool, 0)): {"bool", "[]bool"}, - reflect.ValueOf(&[]bool{true}): {"bool", "*[]bool"}, - reflect.ValueOf(make(map[bool]bool)): {"map[bool]bool", "map[bool]bool"}, - - reflect.ValueOf(float32(3)): {"float32", "float32"}, - reflect.ValueOf(new(float32)): {"float32", "*float32"}, - reflect.ValueOf(make([]float32, 0)): {"float32", "[]float32"}, - reflect.ValueOf(&[]float32{3}): {"float32", "*[]float32"}, - reflect.ValueOf(make(map[float32]float32)): {"map[float32]float32", "map[float32]float32"}, - - float64(3): {"float64", "float64"}, - new(float64): {"float64", "*float64"}, - reflect.TypeOf(make([]float64, 0)): {"float64", "[]float64"}, - reflect.TypeOf(&[]float64{3}): {"float64", "*[]float64"}, - reflect.TypeOf(make(map[float64]float64)): {"map[float64]float64", "map[float64]float64"}, - - complex64(3): {"complex64", "complex64"}, - new(complex64): {"complex64", "*complex64"}, - reflect.TypeOf(make([]complex64, 0)): {"complex64", "[]complex64"}, - reflect.TypeOf(&[]complex64{3}): {"complex64", "*[]complex64"}, - reflect.TypeOf(make(map[complex64]complex64)): {"map[complex64]complex64", "map[complex64]complex64"}, - - complex128(3): {"complex128", "complex128"}, - new(complex128): {"complex128", "*complex128"}, - reflect.TypeOf(make([]complex128, 0)): {"complex128", "[]complex128"}, - reflect.TypeOf(&[]complex128{3}): {"complex128", "*[]complex128"}, - reflect.TypeOf(make(map[complex128]complex128)): {"map[complex128]complex128", "map[complex128]complex128"}, - - make(chan int): {"chan int", "chan int"}, - make(chan struct{}): {"chan struct {}", "chan struct {}"}, - reflect.TypeOf(func() {}): {"func()", "func()"}, - - reflect.TypeOf((*error)(nil)).Elem(): {"error", "error"}, - reflect.TypeOf((*fmt.Stringer)(nil)).Elem(): {"fmt/fmt.Stringer", "fmt.Stringer"}, - - "string": {"string", "string"}, - new(string): {"string", "*string"}, - reflect.TypeOf(make([]string, 0)): {"string", "[]string"}, - reflect.TypeOf(&[]string{"string"}): {"string", "*[]string"}, - reflect.TypeOf(make(map[string]string)): {"map[string]string", "map[string]string"}, - - pkg1.SamePkg{}: {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "pkg.SamePkg"}, - new(pkg1.SamePkg): {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "*pkg.SamePkg"}, - reflect.TypeOf(make([]pkg1.SamePkg, 0)): {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "[]pkg.SamePkg"}, - reflect.TypeOf(&[]pkg1.SamePkg{}): {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "*[]pkg.SamePkg"}, - reflect.TypeOf(make(map[int]pkg1.SamePkg)): {"map[int]pkg.SamePkg", "map[int]pkg.SamePkg"}, - - pkg2.SamePkg{}: {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "pkg.SamePkg"}, - new(pkg2.SamePkg): {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "*pkg.SamePkg"}, - reflect.TypeOf(make([]pkg2.SamePkg, 0)): {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "[]pkg.SamePkg"}, - reflect.TypeOf(&[]pkg2.SamePkg{}): {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "*[]pkg.SamePkg"}, - reflect.TypeOf(make(map[int]pkg2.SamePkg)): {"map[int]pkg.SamePkg", "map[int]pkg.SamePkg"}, - - SamePkg{}: {"github.com/go-spring/spring-core/util/util_test.SamePkg", "util_test.SamePkg"}, - new(SamePkg): {"github.com/go-spring/spring-core/util/util_test.SamePkg", "*util_test.SamePkg"}, - reflect.TypeOf(make([]SamePkg, 0)): {"github.com/go-spring/spring-core/util/util_test.SamePkg", "[]util_test.SamePkg"}, - reflect.TypeOf(&[]SamePkg{}): {"github.com/go-spring/spring-core/util/util_test.SamePkg", "*[]util_test.SamePkg"}, - reflect.TypeOf(make(map[int]SamePkg)): {"map[int]util_test.SamePkg", "map[int]util_test.SamePkg"}, - } - - for i, v := range data { - typeName := util.TypeName(i) - assert.Equal(t, typeName, v.typeName) - switch a := i.(type) { - case reflect.Type: - assert.Equal(t, a.String(), v.baseName) - case reflect.Value: - assert.Equal(t, a.Type().String(), v.baseName) - default: - assert.Equal(t, reflect.TypeOf(a).String(), v.baseName) - } - } +func TestReturnOnlyError(t *testing.T) { + assert.True(t, util.ReturnOnlyError(reflect.TypeOf(func() error { return nil }))) + assert.True(t, util.ReturnOnlyError(reflect.TypeOf(func(string) error { return nil }))) + assert.False(t, util.ReturnOnlyError(reflect.TypeOf(func() (string, error) { return "", nil }))) } -func TestIsValueType(t *testing.T) { +func TestIsConstructor(t *testing.T) { + assert.False(t, util.IsConstructor(reflect.TypeFor[int]())) + assert.False(t, util.IsConstructor(reflect.TypeOf(func() {}))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() string { return "" }))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() *string { return nil }))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() *receiver { return nil }))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() (*receiver, error) { return nil, nil }))) + assert.False(t, util.IsConstructor(reflect.TypeOf(func() (bool, *receiver, error) { return false, nil, nil }))) +} +func TestIsPropBindingTarget(t *testing.T) { data := []struct { i interface{} v bool @@ -412,8 +77,8 @@ func TestIsValueType(t *testing.T) { {uintptr(0), false}, // Uintptr {float32(1), true}, // Float32 {float64(1), true}, // Float64 - {complex64(1), true}, // Complex64 - {complex128(1), true}, // Complex128 + {complex64(1), false}, // Complex64 + {complex128(1), false}, // Complex128 {[1]int{0}, true}, // Array {make(chan struct{}), false}, // Chan {func() {}, false}, // Func @@ -428,7 +93,6 @@ func TestIsValueType(t *testing.T) { {struct{}{}, true}, // Struct {unsafe.Pointer(new(int)), false}, // UnsafePointer } - for _, d := range data { var typ reflect.Type switch i := d.i.(type) { @@ -437,14 +101,13 @@ func TestIsValueType(t *testing.T) { default: typ = reflect.TypeOf(i) } - if r := util.IsValueType(typ); d.v != r { + if r := util.IsPropBindingTarget(typ); d.v != r { t.Errorf("%v expect %v but %v", typ, d.v, r) } } } func TestIsBeanType(t *testing.T) { - data := []struct { i interface{} v bool @@ -479,7 +142,6 @@ func TestIsBeanType(t *testing.T) { {struct{}{}, false}, // Struct {unsafe.Pointer(new(int)), false}, // UnsafePointer } - for _, d := range data { var typ reflect.Type switch i := d.i.(type) { @@ -494,58 +156,14 @@ func TestIsBeanType(t *testing.T) { } } -func TestIsErrorType(t *testing.T) { - err := fmt.Errorf("error") - assert.True(t, util.IsErrorType(reflect.TypeOf(err))) - err = os.ErrClosed - assert.True(t, util.IsErrorType(reflect.TypeOf(err))) -} - -func TestReturnNothing(t *testing.T) { - assert.True(t, util.ReturnNothing(reflect.TypeOf(func() {}))) - assert.True(t, util.ReturnNothing(reflect.TypeOf(func(key string) {}))) - assert.False(t, util.ReturnNothing(reflect.TypeOf(func() string { return "" }))) -} - -func TestReturnOnlyError(t *testing.T) { - assert.True(t, util.ReturnOnlyError(reflect.TypeOf(func() error { return nil }))) - assert.True(t, util.ReturnOnlyError(reflect.TypeOf(func(string) error { return nil }))) - assert.False(t, util.ReturnOnlyError(reflect.TypeOf(func() (string, error) { return "", nil }))) -} - -func TestIsStructPtr(t *testing.T) { - assert.False(t, util.IsStructPtr(reflect.TypeOf(3))) - assert.False(t, util.IsStructPtr(reflect.TypeOf(func() {}))) - assert.False(t, util.IsStructPtr(reflect.TypeOf(struct{}{}))) - assert.False(t, util.IsStructPtr(reflect.TypeOf(struct{ a string }{}))) - assert.True(t, util.IsStructPtr(reflect.TypeOf(&struct{ a string }{}))) -} - -func TestIsConstructor(t *testing.T) { - assert.False(t, util.IsConstructor(reflect.TypeOf(func() {}))) - assert.True(t, util.IsConstructor(reflect.TypeOf(func() string { return "" }))) - assert.True(t, util.IsConstructor(reflect.TypeOf(func() *string { return nil }))) - assert.True(t, util.IsConstructor(reflect.TypeOf(func() *testdata.Receiver { return nil }))) - assert.True(t, util.IsConstructor(reflect.TypeOf(func() (*testdata.Receiver, error) { return nil, nil }))) - assert.False(t, util.IsConstructor(reflect.TypeOf(func() (bool, *testdata.Receiver, error) { return false, nil, nil }))) -} - -func TestHasReceiver(t *testing.T) { - assert.False(t, util.HasReceiver(reflect.TypeOf(func() {}), reflect.ValueOf(new(testdata.Receiver)))) - assert.True(t, util.HasReceiver(reflect.TypeOf(func(*testdata.Receiver) {}), reflect.ValueOf(new(testdata.Receiver)))) - assert.True(t, util.HasReceiver(reflect.TypeOf(func(*testdata.Receiver, int) {}), reflect.ValueOf(new(testdata.Receiver)))) - assert.True(t, util.HasReceiver(reflect.TypeOf(func(fmt.Stringer, int) {}), reflect.ValueOf(new(testdata.Receiver)))) - assert.False(t, util.HasReceiver(reflect.TypeOf(func(error, int) {}), reflect.ValueOf(new(testdata.Receiver)))) -} - -func TestIsBeanReceiver(t *testing.T) { - assert.False(t, util.IsBeanReceiver(reflect.TypeOf("abc"))) - assert.False(t, util.IsBeanReceiver(reflect.TypeOf(new(string)))) - assert.True(t, util.IsBeanReceiver(reflect.TypeOf(errors.New("abc")))) - assert.False(t, util.IsBeanReceiver(reflect.TypeOf([]string{}))) - assert.False(t, util.IsBeanReceiver(reflect.TypeOf([]*string{}))) - assert.True(t, util.IsBeanReceiver(reflect.TypeOf([]fmt.Stringer{}))) - assert.False(t, util.IsBeanReceiver(reflect.TypeOf(map[string]string{}))) - assert.False(t, util.IsBeanReceiver(reflect.TypeOf(map[string]*string{}))) - assert.True(t, util.IsBeanReceiver(reflect.TypeOf(map[string]fmt.Stringer{}))) +func TestIsBeanInjectionTarget(t *testing.T) { + assert.False(t, util.IsBeanInjectionTarget(reflect.TypeOf("abc"))) + assert.False(t, util.IsBeanInjectionTarget(reflect.TypeOf(new(string)))) + assert.True(t, util.IsBeanInjectionTarget(reflect.TypeOf(errors.New("abc")))) + assert.False(t, util.IsBeanInjectionTarget(reflect.TypeOf([]string{}))) + assert.False(t, util.IsBeanInjectionTarget(reflect.TypeOf([]*string{}))) + assert.True(t, util.IsBeanInjectionTarget(reflect.TypeOf([]fmt.Stringer{}))) + assert.False(t, util.IsBeanInjectionTarget(reflect.TypeOf(map[string]string{}))) + assert.False(t, util.IsBeanInjectionTarget(reflect.TypeOf(map[string]*string{}))) + assert.True(t, util.IsBeanInjectionTarget(reflect.TypeOf(map[string]fmt.Stringer{}))) } diff --git a/util/value.go b/util/value.go index 9e14138a..b4ad2b94 100644 --- a/util/value.go +++ b/util/value.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,9 @@ const ( flagRO = flagStickyRO | flagEmbedRO ) -// PatchValue makes an unexported field can be assignable. +// PatchValue modifies an unexported field to make it assignable by modifying the internal flag. +// It takes a [reflect.Value] and returns the patched value that can be written to. +// This is typically used to manipulate unexported fields in struct types. func PatchValue(v reflect.Value) reflect.Value { rv := reflect.ValueOf(&v) flag := rv.Elem().FieldByName("flag") @@ -38,15 +40,9 @@ func PatchValue(v reflect.Value) reflect.Value { return v } -// Indirect returns its element type when t is a pointer type. -func Indirect(t reflect.Type) reflect.Type { - if t.Kind() != reflect.Ptr { - return t - } - return t.Elem() -} - -// FileLine returns a function's name, file name and line number. +// FileLine returns the file, line number, and function name for a given function. +// It uses reflection and runtime information to extract these details. +// 'fn' is expected to be a function or method value. func FileLine(fn interface{}) (file string, line int, fnName string) { fnPtr := reflect.ValueOf(fn).Pointer() @@ -54,9 +50,8 @@ func FileLine(fn interface{}) (file string, line int, fnName string) { file, line = fnInfo.FileLine(fnPtr) s := fnInfo.Name() - if ss := strings.Split(s, "/"); len(ss) > 0 { - s = ss[len(ss)-1] - i := strings.Index(s, ".") + i := strings.LastIndex(s, "/") + if i > 0 { s = s[i+1:] } diff --git a/util/value_test.go b/util/value_test.go index 56906d5c..4b37fbc2 100644 --- a/util/value_test.go +++ b/util/value_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2024 The Go-Spring Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ package util_test import ( + "fmt" "reflect" "testing" "github.com/go-spring/spring-core/util" "github.com/go-spring/spring-core/util/assert" - "github.com/go-spring/spring-core/util/testdata" ) func TestPatchValue(t *testing.T) { @@ -36,30 +36,17 @@ func TestPatchValue(t *testing.T) { v.SetInt(4) } -func TestIndirect(t *testing.T) { - var r struct{ v int } - typ := reflect.TypeOf(r) - assert.Equal(t, util.Indirect(typ), reflect.TypeOf(r)) - typ = reflect.TypeOf(&r) - assert.Equal(t, util.Indirect(typ), reflect.TypeOf(r)) -} - func fnNoArgs() {} func fnWithArgs(i int) {} type receiver struct{} -func (r receiver) fnNoArgs() {} - -func (r receiver) fnWithArgs(i int) {} - func (r *receiver) ptrFnNoArgs() {} func (r *receiver) ptrFnWithArgs(i int) {} func TestFileLine(t *testing.T) { - offset := 62 testcases := []struct { fn interface{} file string @@ -67,130 +54,34 @@ func TestFileLine(t *testing.T) { fnName string }{ { - fn: fnNoArgs, - file: "spring-core/util/value_test.go", - line: offset - 15, - fnName: "fnNoArgs", - }, - { - fnWithArgs, - "spring-core/util/value_test.go", - offset - 13, - "fnWithArgs", - }, - { - receiver{}.fnNoArgs, + fnNoArgs, "spring-core/util/value_test.go", - offset - 9, - "receiver.fnNoArgs", + 39, + "util_test.fnNoArgs", }, { - receiver.fnNoArgs, - "spring-core/util/value_test.go", - offset - 9, - "receiver.fnNoArgs", - }, - { - receiver{}.fnWithArgs, - "spring-core/util/value_test.go", - offset - 7, - "receiver.fnWithArgs", - }, - { - receiver.fnWithArgs, - "spring-core/util/value_test.go", - offset - 7, - "receiver.fnWithArgs", - }, - { - (&receiver{}).ptrFnNoArgs, + fnWithArgs, "spring-core/util/value_test.go", - offset - 5, - "(*receiver).ptrFnNoArgs", + 41, + "util_test.fnWithArgs", }, { (*receiver).ptrFnNoArgs, "spring-core/util/value_test.go", - offset - 5, - "(*receiver).ptrFnNoArgs", - }, - { - (&receiver{}).ptrFnWithArgs, - "spring-core/util/value_test.go", - offset - 3, - "(*receiver).ptrFnWithArgs", + 45, + "util_test.(*receiver).ptrFnNoArgs", }, { (*receiver).ptrFnWithArgs, "spring-core/util/value_test.go", - offset - 3, - "(*receiver).ptrFnWithArgs", - }, - { - testdata.FnNoArgs, - "spring-core/util/testdata/pkg.go", - 19, - "FnNoArgs", - }, - { - testdata.FnWithArgs, - "spring-core/util/testdata/pkg.go", - 21, - "FnWithArgs", - }, - { - testdata.Receiver{}.FnNoArgs, - "spring-core/util/testdata/pkg.go", - 25, - "Receiver.FnNoArgs", - }, - { - testdata.Receiver{}.FnWithArgs, - "spring-core/util/testdata/pkg.go", - 27, - "Receiver.FnWithArgs", - }, - { - (&testdata.Receiver{}).PtrFnNoArgs, - "spring-core/util/testdata/pkg.go", - 29, - "(*Receiver).PtrFnNoArgs", - }, - { - (&testdata.Receiver{}).PtrFnWithArgs, - "spring-core/util/testdata/pkg.go", - 31, - "(*Receiver).PtrFnWithArgs", - }, - { - testdata.Receiver.FnNoArgs, - "spring-core/util/testdata/pkg.go", - 25, - "Receiver.FnNoArgs", - }, - { - testdata.Receiver.FnWithArgs, - "spring-core/util/testdata/pkg.go", - 27, - "Receiver.FnWithArgs", - }, - { - (*testdata.Receiver).PtrFnNoArgs, - "spring-core/util/testdata/pkg.go", - 29, - "(*Receiver).PtrFnNoArgs", - }, - { - (*testdata.Receiver).PtrFnWithArgs, - "spring-core/util/testdata/pkg.go", - 31, - "(*Receiver).PtrFnWithArgs", + 47, + "util_test.(*receiver).ptrFnWithArgs", }, } - for _, c := range testcases { + for i, c := range testcases { file, line, fnName := util.FileLine(c.fn) - assert.String(t, file).HasSuffix(c.file) - assert.Equal(t, line, c.line) - assert.Equal(t, fnName, c.fnName) + assert.Equal(t, line, c.line, fmt.Sprint(i)) + assert.Equal(t, fnName, c.fnName, fmt.Sprint(i)) + assert.String(t, file).HasSuffix(c.file, fmt.Sprint(i)) } }