diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..74296b14
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,29 @@
+name: Run golangci-lint
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ lint:
+ name: Run golangci-lint
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Print All environment variables
+ run: env | sort
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+
+ - name: Install golangci-lint
+ uses: golangci/golangci-lint-action@v7
+ with:
+ version: latest
+
+ - name: Run golangci-lint
+ run: golangci-lint run ./...
diff --git a/.github/workflows/main.yml b/.github/workflows/test.yml
similarity index 100%
rename from .github/workflows/main.yml
rename to .github/workflows/test.yml
diff --git a/.gitignore b/.gitignore
index c5688307..9864f6da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,10 +32,8 @@ go.work.sum
/conf/remote/
+gs/examples/bookman/.cover/
gs/examples/bookman/conf/
gs/examples/bookman/log/*.log
-gs/examples/bookman/.cover/covcounters.*
-gs/examples/bookman/.cover/covmeta.*
-gs/examples/bookman/.cover/cover.txt
coverage.txt
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index bd1318d4..a9ce3811 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,39 +1,3 @@
-# 贡献者行为准则
-
-首先,感谢你关注并支持 Go-Spring 项目!
-
-为营造一个开放、友善、包容和专业的社区氛围,我们制定了本行为准则。无论你是报告问题、提交代码、参与讨论,还是以其他形式参与项目,都请遵循以下准则。
-
-## 我们的承诺
-
-我们承诺为每一位参与者提供一个免受骚扰、歧视和攻击性行为干扰的环境。我们欢迎来自不同背景、经验和身份的贡献者,共同建设一个多元、包容的社区。
-
-## 倡导的行为
-
-- 保持尊重、礼貌的沟通方式;
-- 乐于接受建设性的意见与反馈;
-- 尊重不同的观点与技术选择;
-- 在协作中体现耐心与包容;
-- 积极参与,共建积极向上的社区氛围。
-
-## 不被接受的行为
-
-- 使用歧视性、侮辱性或攻击性的言语与行为;
-- 进行人身攻击、骚扰或威胁;
-- 发布淫秽内容或不当链接;
-- 蓄意干扰他人正常贡献;
-- 冒充他人或侵犯他人隐私。
-
-## 执行与维护
-
-项目维护者有权也有责任删除、修改或拒绝任何违反行为准则的评论、提交、代码、Wiki 编辑、Issue 或其他形式的贡献内容,必要时可采取进一步措施。
-
-## 如何报告问题
-
-若你遇到违反行为准则的情况,请通过电子邮件联系项目维护者,或通过 Issue 匿名举报。我们承诺认真对待每一份举报,并确保信息保密。
-
----
-
# Contributor Code of Conduct
Thank you for your interest in and support of the Go-Spring project!
@@ -73,3 +37,39 @@ further action as necessary.
If you witness or experience a violation of this Code of Conduct, please contact the maintainers via email or submit an
anonymous issue. All reports will be handled with discretion and taken seriously.
+
+---
+
+# 贡献者行为准则
+
+首先,感谢你关注并支持 Go-Spring 项目!
+
+为营造一个开放、友善、包容和专业的社区氛围,我们制定了本行为准则。无论你是报告问题、提交代码、参与讨论,还是以其他形式参与项目,都请遵循以下准则。
+
+## 我们的承诺
+
+我们承诺为每一位参与者提供一个免受骚扰、歧视和攻击性行为干扰的环境。我们欢迎来自不同背景、经验和身份的贡献者,共同建设一个多元、包容的社区。
+
+## 倡导的行为
+
+- 保持尊重、礼貌的沟通方式;
+- 乐于接受建设性的意见与反馈;
+- 尊重不同的观点与技术选择;
+- 在协作中体现耐心与包容;
+- 积极参与,共建积极向上的社区氛围。
+
+## 不被接受的行为
+
+- 使用歧视性、侮辱性或攻击性的言语与行为;
+- 进行人身攻击、骚扰或威胁;
+- 发布淫秽内容或不当链接;
+- 蓄意干扰他人正常贡献;
+- 冒充他人或侵犯他人隐私。
+
+## 执行与维护
+
+项目维护者有权也有责任删除、修改或拒绝任何违反行为准则的评论、提交、代码、Wiki 编辑、Issue 或其他形式的贡献内容,必要时可采取进一步措施。
+
+## 如何报告问题
+
+若你遇到违反行为准则的情况,请通过电子邮件联系项目维护者,或通过 Issue 匿名举报。我们承诺认真对待每一份举报,并确保信息保密。
diff --git a/conf/bind_test.go b/conf/bind_test.go
index f5931c21..9cfea5ae 100644
--- a/conf/bind_test.go
+++ b/conf/bind_test.go
@@ -49,7 +49,7 @@ func PointConverter(val string) (image.Point, error) {
}
func PointSplitter(str string) ([]string, error) {
- if !(strings.HasPrefix(str, "(") && strings.HasSuffix(str, ")")) {
+ if !strings.HasPrefix(str, "(") || !strings.HasSuffix(str, ")") {
return nil, errors.New("split error")
}
var ret []string
diff --git a/gs/examples/bookman/.cover/cover.html b/gs/examples/bookman/.cover/cover.html
deleted file mode 100644
index 3ad5e775..00000000
--- a/gs/examples/bookman/.cover/cover.html
+++ /dev/null
@@ -1,7612 +0,0 @@
-
-
-
-
-
-
-
/*
- * Copyright 2025 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 main
-
-import (
- "context"
- "fmt"
- "io"
- "net/http"
- "time"
-
- "github.com/go-spring/spring-core/gs"
- "github.com/lvan100/go-loop"
-
- _ "github.com/go-spring/spring-core/gs/examples/bookman/src/app"
- _ "github.com/go-spring/spring-core/gs/examples/bookman/src/biz"
-)
-
-func init() {
- gs.SetActiveProfiles("online")
- gs.EnableSimplePProfServer(true)
- gs.FuncJob(runTest).Name("#job")
-}
-
-func main() {
- gs.Run()
-}
-
-// runTest performs a simple test.
-func runTest(ctx context.Context) error {
- time.Sleep(time.Millisecond * 500)
-
- loop.Times(5, func(_ int) {
- url := "http://127.0.0.1:9090/books"
- resp, err := http.Get(url)
- if err != nil {
- panic(err)
- }
- b, err := io.ReadAll(resp.Body)
- if err != nil {
- panic(err)
- }
- defer resp.Body.Close()
- fmt.Print(string(b))
- time.Sleep(time.Millisecond * 400)
- })
-
- // Shut down the application gracefully
- gs.ShutDown()
- return nil
-}
-
-
-
/*
- * 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 conf
-
-import (
- "errors"
- "fmt"
- "reflect"
- "strconv"
- "strings"
-
- "github.com/go-spring/spring-core/util"
- "github.com/go-spring/spring-core/util/errutil"
-)
-
-var (
- ErrNotExist = errors.New("not exist")
- ErrInvalidSyntax = errors.New("invalid syntax")
-)
-
-// ParsedTag represents a parsed configuration tag, including key,
-// default value, and optional custom splitter for list parsing.
-type ParsedTag struct {
- Key string // short property key
- Def string // default value
- HasDef bool // has default value
- Splitter string // splitter's name
-}
-
-func (tag ParsedTag) String() string {
- var sb strings.Builder
- sb.WriteString("${")
- sb.WriteString(tag.Key)
- if tag.HasDef {
- sb.WriteString(":=")
- sb.WriteString(tag.Def)
- }
- sb.WriteString("}")
- if tag.Splitter != "" {
- sb.WriteString(">>")
- sb.WriteString(tag.Splitter)
- }
- return sb.String()
-}
-
-// ParseTag parses a tag string in the format `${key:=default}>>splitter`
-// into a ParsedTag struct. Returns an error if the format is invalid.
-func ParseTag(tag string) (ret ParsedTag, err error) {
- i := strings.LastIndex(tag, ">>")
- if i == 0 {
- err = fmt.Errorf("parse tag '%s' error: %w", tag, ErrInvalidSyntax)
- return
- }
- j := strings.LastIndex(tag, "}")
- if j <= 0 {
- err = fmt.Errorf("parse tag '%s' error: %w", tag, ErrInvalidSyntax)
- return
- }
- k := strings.Index(tag, "${")
- if k < 0 {
- err = fmt.Errorf("parse tag '%s' error: %w", tag, ErrInvalidSyntax)
- return
- }
- if i > j {
- ret.Splitter = strings.TrimSpace(tag[i+2:])
- }
- ss := strings.SplitN(tag[k+2:j], ":=", 2)
- ret.Key = ss[0]
- if len(ss) > 1 {
- ret.HasDef = true
- ret.Def = ss[1]
- }
- return
-}
-
-// BindParam holds binding metadata for a single configuration value.
-type BindParam struct {
- Key string // full key
- Path string // full path
- Tag ParsedTag // parsed tag
- Validate reflect.StructTag // full field tag
-}
-
-// BindTag parses and binds the configuration tag to the BindParam.
-// It handles default values and nested key expansion.
-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("parse tag '%s' error: %w", tag, ErrInvalidSyntax)
- }
- if parsedTag.Key == "ROOT" {
- parsedTag.Key = ""
- }
- if param.Key == "" {
- param.Key = parsedTag.Key
- } else if parsedTag.Key != "" {
- param.Key = param.Key + "." + parsedTag.Key
- }
- param.Tag = parsedTag
- return nil
-}
-
-// Filter defines an interface for filtering configuration fields during binding.
-type Filter interface {
- Do(i any, param BindParam) (bool, error)
-}
-
-// BindValue binds a value from properties `p` to the reflect.Value `v` of type `t`
-// using metadata in `param`. It supports primitives, structs, maps, and slices.
-func BindValue(p Properties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) (RetErr error) {
-
- if !util.IsPropBindingTarget(t) {
- err := errors.New("target should be value type")
- return fmt.Errorf("bind path=%s type=%s error: %w", param.Path, v.Type().String(), err)
- }
-
- defer func() {
- if RetErr == nil {
- tag, ok := param.Validate.Lookup("expr")
- if ok && len(tag) > 0 {
- err := validateField(tag, v.Interface())
- if err != nil {
- RetErr = err
- }
- }
- }
- }()
-
- switch v.Kind() {
- case reflect.Map:
- return bindMap(p, v, t, param, filter)
- case reflect.Slice:
- return bindSlice(p, v, t, param, filter)
- case reflect.Array:
- err := errors.New("use slice instead of array")
- 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 err // no wrap
- }
- return nil
- }
-
- val, err := resolve(p, param)
- if err != nil {
- return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String())
- }
-
- if fn != nil {
- fnValue := reflect.ValueOf(fn)
- out := fnValue.Call([]reflect.Value{reflect.ValueOf(val)})
- if !out[1].IsNil() {
- err = out[1].Interface().(error)
- return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String())
- }
- v.Set(out[0])
- return nil
- }
-
- switch v.Kind() {
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- var u uint64
- if u, err = strconv.ParseUint(val, 0, 0); err == nil {
- v.SetUint(u)
- return nil
- }
- 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 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 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 errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String())
- default:
- v.SetString(val)
- return nil
- }
-}
-
-// bindSlice binds properties to a slice value.
-func bindSlice(p Properties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error {
-
- et := t.Elem()
- p, err := getSlice(p, et, param)
- if err != nil {
- return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String())
- }
-
- slice := reflect.MakeSlice(t, 0, 0)
- defer func() { v.Set(slice) }()
-
- if p == nil {
- return nil
- }
-
- for i := 0; ; i++ {
- ev := reflect.New(et).Elem()
- subParam := BindParam{
- Key: fmt.Sprintf("%s[%d]", param.Key, i),
- Path: fmt.Sprintf("%s[%d]", param.Path, i),
- }
- err = BindValue(p, ev, et, subParam, filter)
- if errors.Is(err, ErrNotExist) {
- break
- }
- if err != nil {
- return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String())
- }
- slice = reflect.Append(slice, ev)
- }
- return nil
-}
-
-// getSlice retrieves and splits a string into slice elements,
-// creating a new Properties instance if necessary.
-func getSlice(p Properties, et reflect.Type, param BindParam) (Properties, error) {
-
- // properties that defined as list.
- if p.Has(param.Key + "[0]") {
- return p, nil
- }
-
- // properties that defined as string and needs to split into []string.
- var strVal string
- {
- if p.Has(param.Key) {
- strVal = p.Get(param.Key)
- } else {
- if !param.Tag.HasDef {
- 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("can't find converter for %s", et.String())
- }
- strVal = param.Tag.Def
- }
- }
- if strVal == "" {
- return nil, nil
- }
-
- var (
- err error
- arrVal []string
- )
-
- if s := param.Tag.Splitter; s == "" {
- arrVal = strings.Split(strVal, ",")
- for i := range arrVal {
- arrVal[i] = strings.TrimSpace(arrVal[i])
- }
- } else if fn, ok := splitters[s]; ok && fn != nil {
- if arrVal, err = fn(strVal); err != nil {
- return nil, fmt.Errorf("split error: %w, value: %q", err, strVal)
- }
- } else {
- return nil, fmt.Errorf("unknown splitter '%s'", s)
- }
-
- r := New()
- for i, s := range arrVal {
- k := fmt.Sprintf("%s[%d]", param.Key, i)
- _ = r.Set(k, s) // always no error
- }
- return r, nil
-}
-
-// bindMap binds properties to a map value.
-func bindMap(p Properties, 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("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
- }
- }
-
- if !p.Has(param.Key) {
- if param.Tag.HasDef {
- return nil
- }
- return fmt.Errorf("property %q %w", param.Key, ErrNotExist)
- }
-
- keys, err := p.SubKeys(param.Key)
- if err != nil {
- return errutil.WrapError(err, "bind path=%s type=%s error", param.Path, v.Type().String())
- }
-
- for _, key := range keys {
- e := reflect.New(et).Elem()
- subKey := key
- if param.Key != "" {
- subKey = param.Key + "." + key
- }
- subParam := BindParam{
- Key: subKey,
- Path: param.Path,
- }
- if err = BindValue(p, e, et, subParam, filter); err != nil {
- return err // no wrap
- }
- ret.SetMapIndex(reflect.ValueOf(key), e)
- }
- return nil
-}
-
-// bindStruct binds properties to a struct value.
-func bindStruct(p Properties, 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("bind path=%s type=%s error: %w", param.Path, v.Type().String(), err)
- }
-
- for i := range t.NumField() {
- ft := t.Field(i)
- fv := v.Field(i)
-
- if !fv.CanInterface() {
- continue
- }
-
- subParam := BindParam{
- Key: param.Key,
- Path: param.Path + "." + ft.Name,
- }
-
- if tag, ok := ft.Tag.Lookup("value"); ok {
- if err := subParam.BindTag(tag, ft.Tag); err != nil {
- 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)
- if err != nil {
- return err
- }
- if ret {
- continue
- }
- }
- if err := BindValue(p, fv, ft.Type, subParam, filter); err != nil {
- return err // no wrap
- }
- continue
- }
-
- if ft.Anonymous {
- // embed pointer type may lead to infinite recursion.
- if ft.Type.Kind() != reflect.Struct {
- continue
- }
- if err := bindStruct(p, fv, ft.Type, subParam, filter); err != nil {
- return err // no wrap
- }
- }
- }
- return nil
-}
-
-// resolve returns property references processed property value.
-func resolve(p Properties, param BindParam) (string, error) {
- const defVal = "@@def@@"
- val := p.Get(param.Key, defVal)
- if val != defVal {
- return resolveString(p, val)
- }
- if p.Has(param.Key) {
- return "", fmt.Errorf("property %s isn't simple value", param.Key)
- }
- if param.Tag.HasDef {
- return resolveString(p, param.Tag.Def)
- }
- return "", fmt.Errorf("property %s %w", param.Key, ErrNotExist)
-}
-
-// resolveString returns property references processed string.
-func resolveString(p Properties, s string) (string, error) {
-
- // If there is no property reference, return the original string.
- start := strings.Index(s, "${")
- if start < 0 {
- return s, nil
- }
-
- var (
- length = len(s)
- count = 1
- end = -1
- )
-
- for i := start + 2; i < length; i++ {
- if s[i] == '$' {
- if i+1 < length && s[i+1] == '{' {
- count++
- }
- } else if s[i] == '}' {
- count--
- if count == 0 {
- end = i
- break
- }
- }
- }
-
- if end < 0 {
- err := ErrInvalidSyntax
- return "", fmt.Errorf("resolve string %q error: %w", s, err)
- }
-
- var param BindParam
- _ = param.BindTag(s[start:end+1], "")
-
- s1, err := resolve(p, param)
- if err != nil {
- return "", errutil.WrapError(err, "resolve string %q error", s)
- }
-
- s2, err := resolveString(p, s[end+1:])
- if err != nil {
- return "", errutil.WrapError(err, "resolve string %q error", s)
- }
-
- return s[:start] + s1 + s2, nil
-}
-
-
-
/*
- * 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 conf
-
-import (
- "errors"
- "fmt"
- "maps"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "time"
-
- "github.com/go-spring/spring-core/conf/reader/json"
- "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/storage"
- "github.com/go-spring/spring-core/util"
- "github.com/spf13/cast"
-)
-
-var (
- readers = map[string]Reader{}
- splitters = map[string]Splitter{}
- converters = map[reflect.Type]any{}
-)
-
-func init() {
-
- RegisterReader(json.Read, ".json")
- RegisterReader(prop.Read, ".properties")
- RegisterReader(yaml.Read, ".yaml", ".yml")
- RegisterReader(toml.Read, ".toml", ".tml")
-
- RegisterConverter(func(s string) (time.Time, error) {
- return cast.ToTimeE(strings.TrimSpace(s))
- })
-
- RegisterConverter(func(s string) (time.Duration, error) {
- return time.ParseDuration(strings.TrimSpace(s))
- })
-}
-
-// Reader parses []byte into nested map[string]any.
-type Reader func(b []byte) (map[string]any, error)
-
-// RegisterReader registers its Reader for some kind of file extension.
-func RegisterReader(r Reader, ext ...string) {
- for _, s := range ext {
- readers[s] = r
- }
-}
-
-// Splitter splits string into []string by some characters.
-type Splitter func(string) ([]string, error)
-
-// RegisterSplitter registers a Splitter and named it.
-func RegisterSplitter(name string, fn Splitter) {
- splitters[name] = fn
-}
-
-// Converter converts a string to a target type T.
-type Converter[T any] func(string) (T, error)
-
-// RegisterConverter registers its converter for non-primitive type such as
-// time.Time, time.Duration, or other user-defined value type.
-func RegisterConverter[T any](fn Converter[T]) {
- t := reflect.TypeOf(fn)
- converters[t.Out(0)] = fn
-}
-
-// Properties is the interface for read-only properties.
-type Properties interface {
- // Data returns key-value pairs of the properties.
- Data() map[string]string
- // Keys returns keys of the properties.
- Keys() []string
- // SubKeys returns the sorted sub keys of the key.
- SubKeys(key string) ([]string, error)
- // Has returns whether the key exists.
- Has(key string) bool
- // 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 any, tag ...string) error
- // CopyTo copies properties into another by override.
- CopyTo(out *MutableProperties) error
-}
-
-var _ Properties = (*MutableProperties)(nil)
-
-// MutableProperties 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.
-// There are too many formats of configuration files, and too many conflicts between
-// them. Each format of configuration file provides its special characteristics, but
-// usually they are not all necessary, and complementary. For example, `conf` disabled
-// Java properties' expansion when reading file, but also provides similar function
-// when getting or binding properties.
-// A good rule of thumb is that treating application configuration as a tree, but not
-// all formats of configuration files designed as a tree or not ideal, for instance
-// Java properties isn't strictly verified. Although configuration can store as a tree,
-// 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 MutableProperties struct {
- *storage.Storage
-}
-
-// New creates empty *MutableProperties.
-func New() *MutableProperties {
- return &MutableProperties{
- Storage: storage.NewStorage(),
- }
-}
-
-// Load creates *MutableProperties from file.
-func Load(file string) (*MutableProperties, error) {
- b, err := os.ReadFile(file)
- if err != nil {
- return nil, err
- }
- ext := filepath.Ext(file)
- r, ok := readers[ext]
- if !ok {
- return nil, fmt.Errorf("unsupported file type %s", ext)
- }
- m, err := r(b)
- if err != nil {
- return nil, err
- }
- return Map(m), nil
-}
-
-// Map creates *MutableProperties from map.
-func Map(m map[string]any) *MutableProperties {
- p := New()
- _ = p.merge(util.FlattenMap(m))
- return p
-}
-
-// merge flattens the map and sets all keys and values.
-func (p *MutableProperties) merge(m map[string]string) error {
- for key, val := range m {
- if err := p.Set(key, val); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Data returns key-value pairs of the properties.
-func (p *MutableProperties) Data() map[string]string {
- m := make(map[string]string)
- maps.Copy(m, p.RawData())
- return m
-}
-
-// Keys returns keys of the properties.
-func (p *MutableProperties) Keys() []string {
- return util.OrderedMapKeys(p.RawData())
-}
-
-// Get returns key's value, using Def to return a default value.
-func (p *MutableProperties) Get(key string, def ...string) string {
- val, ok := p.RawData()[key]
- if !ok && len(def) > 0 {
- return def[0]
- }
- return val
-}
-
-// Resolve resolves string value that contains references to other
-// properties, the references are defined by ${key:=def}.
-func (p *MutableProperties) Resolve(s string) (string, error) {
- return resolveString(p, s)
-}
-
-// 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' 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 *MutableProperties) Bind(i any, tag ...string) error {
-
- var v reflect.Value
- {
- switch e := i.(type) {
- case reflect.Value:
- v = e
- default:
- v = reflect.ValueOf(i)
- if v.Kind() != reflect.Ptr {
- return errors.New("should be a ptr")
- }
- v = v.Elem()
- }
- }
-
- t := v.Type()
- typeName := t.Name()
- if typeName == "" { // primitive type has no name
- typeName = t.String()
- }
-
- s := "${ROOT}"
- if len(tag) > 0 {
- s = tag[0]
- }
-
- var param BindParam
- err := param.BindTag(s, "")
- if err != nil {
- return err
- }
- param.Path = typeName
- return BindValue(p, v, t, param, nil)
-}
-
-// CopyTo copies properties into another by override.
-func (p *MutableProperties) CopyTo(out *MutableProperties) error {
- return out.merge(p.RawData())
-}
-
-
-
/*
- * 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 conf
-
-import (
- "fmt"
- "maps"
-
- "github.com/expr-lang/expr"
-)
-
-// 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]any{}
-
-// 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 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 any) error {
- env := map[string]any{"$": i}
- maps.Copy(env, validateFuncs)
- r, err := expr.Eval(tag, env)
- if err != nil {
- return fmt.Errorf("eval %q returns error, %w", tag, err)
- }
- ret, ok := r.(bool)
- if !ok {
- return fmt.Errorf("eval %q doesn't return bool value", tag)
- }
- if !ret {
- return fmt.Errorf("validate failed on %q for value %v", tag, i)
- }
- return nil
-}
-
-
-
/*
- * 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 json
-
-import (
- "encoding/json"
-)
-
-// Read parses []byte in the json format into map.
-func Read(b []byte) (map[string]any, error) {
- var ret map[string]any
- err := json.Unmarshal(b, &ret)
- if err != nil {
- return nil, err
- }
- return ret, nil
-}
-
-
-
/*
- * 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 prop
-
-import "github.com/magiconair/properties"
-
-// Read parses []byte in the properties format into map.
-func Read(b []byte) (map[string]any, error) {
-
- p := properties.NewProperties()
- p.DisableExpansion = true
- _ = p.Load(b, properties.UTF8) // always no error
-
- ret := make(map[string]any)
- for k, v := range p.Map() {
- ret[k] = v
- }
- return ret, nil
-}
-
-
-
/*
- * 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 toml
-
-import (
- "github.com/pelletier/go-toml"
-)
-
-// Read parses []byte in the toml format into map.
-func Read(b []byte) (map[string]any, error) {
- tree, err := toml.LoadBytes(b)
- if err != nil {
- return nil, err
- }
- return tree.ToMap(), nil
-}
-
-
-
/*
- * 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 yaml
-
-import (
- "gopkg.in/yaml.v2"
-)
-
-// Read parses []byte in the yaml format into map.
-func Read(b []byte) (map[string]any, error) {
- ret := make(map[string]any)
- err := yaml.Unmarshal(b, &ret)
- if err != nil {
- return nil, err
- }
- return ret, nil
-}
-
-
-
/*
- * 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 storage
-
-import (
- "errors"
- "fmt"
- "strconv"
- "strings"
-)
-
-// PathType represents the type of a path segment.
-type PathType int
-
-const (
- PathTypeKey PathType = iota // PathTypeKey indicates a named key in a map.
- PathTypeIndex // PathTypeIndex indicates a numeric index in a list.
-)
-
-// Path represents a segment of a hierarchical path.
-// Each segment is either a key (e.g., "user") or an index (e.g., "0").
-type Path struct {
- Type PathType // Type determines whether the segment is a key or index.
- Elem string // Elem holds the actual key or index value as a string.
-}
-
-// JoinPath constructs a string representation from a slice of Path segments.
-// Keys are joined with '.', and indices are represented as '[i]'.
-func JoinPath(path []Path) string {
- var sb strings.Builder
- for i, p := range path {
- switch p.Type {
- case PathTypeKey:
- if i > 0 {
- sb.WriteString(".")
- }
- sb.WriteString(p.Elem)
- case PathTypeIndex:
- sb.WriteString("[")
- sb.WriteString(p.Elem)
- sb.WriteString("]")
- }
- }
- return sb.String()
-}
-
-// SplitPath parses a string path into a slice of Path segments.
-// It supports keys separated by '.' and indices enclosed in brackets (e.g., "users[0].name").
-func SplitPath(key string) (_ []Path, err error) {
- if key == "" {
- return nil, fmt.Errorf("invalid key '%s'", key)
- }
- var (
- path []Path
- lastPos int
- lastChar int32
- openBracket bool
- )
- for i, c := range key {
- switch c {
- case ' ':
- return nil, fmt.Errorf("invalid key '%s'", key)
- case '.':
- if openBracket || lastChar == '.' {
- return nil, fmt.Errorf("invalid key '%s'", key)
- }
- if lastChar != ']' {
- path = appendKey(path, key[lastPos:i])
- }
- lastPos = i + 1
- lastChar = c
- case '[':
- if openBracket || lastChar == '.' {
- return nil, fmt.Errorf("invalid key '%s'", key)
- }
- if i > 0 && lastChar != ']' {
- path = appendKey(path, key[lastPos:i])
- }
- openBracket = true
- lastPos = i + 1
- lastChar = c
- case ']':
- if !openBracket {
- return nil, fmt.Errorf("invalid key '%s'", key)
- }
- path, err = appendIndex(path, key[lastPos:i])
- if err != nil {
- return nil, fmt.Errorf("invalid key '%s'", key)
- }
- openBracket = false
- lastPos = i + 1
- lastChar = c
- default:
- if lastChar == ']' {
- return nil, fmt.Errorf("invalid key '%s'", key)
- }
- lastChar = c
- }
- }
- if openBracket || lastChar == '.' {
- return nil, fmt.Errorf("invalid key '%s'", key)
- }
- if lastChar != ']' {
- path = appendKey(path, key[lastPos:])
- }
- return path, nil
-}
-
-// appendKey appends a key segment to the path.
-func appendKey(path []Path, s string) []Path {
- return append(path, Path{PathTypeKey, s})
-}
-
-// appendIndex appends an index segment to the path.
-func appendIndex(path []Path, s string) ([]Path, error) {
- _, err := strconv.ParseUint(s, 10, 64)
- if err != nil {
- return nil, errors.New("invalid key")
- }
- path = append(path, Path{PathTypeIndex, s})
- return path, nil
-}
-
-
-
/*
- * 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 storage provides hierarchical configuration storage and path parsing utilities.
-
-Features:
-- Storage manages key-value pairs with support for nested paths, subkey lookup, and conflict detection.
-- Path represents structured access paths with support for parsing (SplitPath) and construction (JoinPath).
-- Supports two path types:
- - Key (e.g., "user.name") for map access
- - Index (e.g., "[0]") for array access
-
-- Maintains a tree structure (treeNode) for consistent and type-safe hierarchy management.
-
-Use cases:
-- Accessing values in JSON/YAML/TOML-like configs
-- Managing nested config data (CRUD)
-- Validating structure and detecting conflicts
-
-Notes:
-- Path syntax follows common config patterns (e.g., "users[0].profile.age")
-- Type-safe path handling (keys vs. indices)
-*/
-package storage
-
-import (
- "errors"
- "fmt"
-
- "github.com/go-spring/spring-core/util"
-)
-
-// treeNode represents a node in the hierarchical key path tree.
-// Each node tracks the type of its path segment and its child nodes.
-type treeNode struct {
- Type PathType
- Data map[string]*treeNode
-}
-
-// Storage stores hierarchical key-value pairs and tracks their structure using a tree.
-// It supports nested paths and detects structural conflicts when paths differ in type.
-type Storage struct {
- root *treeNode // Root of the hierarchical key path tree
- data map[string]string // Flat key-value storage for exact key matches
-}
-
-// NewStorage creates and initializes a new Storage instance.
-func NewStorage() *Storage {
- return &Storage{
- data: make(map[string]string),
- }
-}
-
-// RawData returns the underlying flat key-value map.
-// Note: This exposes internal state; use with caution.
-func (s *Storage) RawData() map[string]string {
- return s.data
-}
-
-// SubKeys returns the immediate subkeys under the given key path.
-// It walks the tree structure and returns child elements if the path exists.
-// Returns an error if there's a type conflict along the path.
-func (s *Storage) SubKeys(key string) (_ []string, err error) {
- var path []Path
- if key != "" {
- if path, err = SplitPath(key); err != nil {
- return nil, err
- }
- }
-
- if s.root == nil {
- return nil, nil
- }
-
- n := s.root
- for i, pathNode := range path {
- if n == nil || pathNode.Type != n.Type {
- return nil, fmt.Errorf("property conflict at path %s", JoinPath(path[:i+1]))
- }
- v, ok := n.Data[pathNode.Elem]
- if !ok {
- return nil, nil
- }
- n = v
- }
-
- if n == nil {
- return nil, fmt.Errorf("property conflict at path %s", key)
- }
- return util.OrderedMapKeys(n.Data), nil
-}
-
-// Has returns true if the given key exists in the storage,
-// either as a direct value or as a valid path in the hierarchical tree structure.
-func (s *Storage) Has(key string) bool {
- if key == "" || s.root == nil {
- return false
- }
-
- if _, ok := s.data[key]; ok {
- return true
- }
-
- path, err := SplitPath(key)
- if err != nil {
- return false
- }
-
- n := s.root
- for _, node := range path {
- if n == nil || node.Type != n.Type {
- return false
- }
- v, ok := n.Data[node.Elem]
- if !ok {
- return false
- }
- n = v
- }
- return true
-}
-
-// Set inserts a key-value pair into the storage.
-// It also constructs or extends the corresponding hierarchical path in the tree.
-// Returns an error if there is a type conflict or if the key is empty.
-func (s *Storage) Set(key, val string) error {
- if key == "" {
- return errors.New("key is empty")
- }
-
- path, err := SplitPath(key)
- if err != nil {
- return err
- }
-
- // Initialize tree root if empty
- if s.root == nil {
- s.root = &treeNode{
- Type: path[0].Type,
- Data: make(map[string]*treeNode),
- }
- }
-
- n := s.root
- for i, pathNode := range path {
- if n == nil || pathNode.Type != n.Type {
- return fmt.Errorf("property conflict at path %s", JoinPath(path[:i+1]))
- }
- v, ok := n.Data[pathNode.Elem]
- if !ok {
- if i < len(path)-1 {
- v = &treeNode{
- Type: path[i+1].Type,
- Data: make(map[string]*treeNode),
- }
- }
- n.Data[pathNode.Elem] = v
- }
- n = v
- }
- if n != nil {
- return fmt.Errorf("property conflict at path %s", key)
- }
-
- s.data[key] = val
- return nil
-}
-
-
-
/*
- * Copyright 2025 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 bootstrap
-
-import (
- "context"
- "fmt"
- "os"
- "time"
-
- "github.com/go-spring/spring-core/gs"
-)
-
-func init() {
- // Register a function runner to initialize the remote configuration setup.
- gs.B.FuncRunner(initRemoteConfig).OnProfiles("online")
-}
-
-// initRemoteConfig initializes the remote configuration setup.
-// It first attempts to retrieve remote config, then starts a background job
-// to periodically refresh the configuration.
-func initRemoteConfig() error {
- if err := getRemoteConfig(); err != nil {
- return err
- }
- // Register a function job to refresh the configuration.
- // A bean can be registered into the app during the bootstrap phase.
- gs.FuncJob(refreshRemoteConfig)
- return nil
-}
-
-// getRemoteConfig fetches and writes the remote configuration to a local file.
-// It creates necessary directories and generates a properties file containing.
-func getRemoteConfig() error {
- err := os.MkdirAll("./conf/remote", os.ModePerm)
- if err != nil {
- return err
- }
-
- const data = `
- server.addr=0.0.0.0:9090
-
- log.access.name=access.log
- log.access.dir=./log
-
- log.biz.name=biz.log
- log.biz.dir=./log
-
- log.dao.name=dao.log
- log.dao.dir=./log
-
- refresh_time=%v
- `
-
- const file = "conf/remote/app-online.properties"
- str := fmt.Sprintf(data, time.Now().UnixMilli())
- return os.WriteFile(file, []byte(str), os.ModePerm)
-}
-
-// refreshRemoteConfig runs a continuous loop to periodically update configuration.
-// It refreshes every 500ms until context cancellation.
-func refreshRemoteConfig(ctx context.Context) error {
- for {
- select {
- case <-ctx.Done(): // Gracefully exit when context is canceled.
- // The context (ctx) is derived from the app instance.
- // When the app exits, the context is canceled,
- // allowing the loop to terminate gracefully.
- fmt.Println("config updater exit")
- return nil
- case <-time.After(time.Millisecond * 500):
- if err := getRemoteConfig(); err != nil {
- fmt.Println("get remote config error:", err)
- return err
- }
- // Refreshes the app configuration.
- if err := gs.RefreshProperties(); err != nil {
- fmt.Println("refresh properties error:", err)
- return err
- }
- fmt.Println("refresh properties success")
- }
- }
-}
-
-
-
/*
- * Copyright 2025 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 log
-
-import (
- "log/slog"
- "os"
- "path/filepath"
-
- "github.com/go-spring/spring-core/conf"
- "github.com/go-spring/spring-core/gs"
-)
-
-func init() {
- // Register a group of Bean definitions during application initialization.
- // GroupRegister dynamically creates multiple Beans based on the configuration (conf.Properties),
- // making it ideal for scenarios like setting up multiple loggers, clients, or resources from config.
- gs.GroupRegister(func(p conf.Properties) ([]*gs.BeanDefinition, error) {
-
- var loggers map[string]struct {
- Name string `value:"${name}"` // Log file name
- Dir string `value:"${dir}"` // Directory where the log file will be stored
- }
-
- // Bind configuration from the "${log}" node into the 'loggers' map.
- err := p.Bind(&loggers, "${log}")
- if err != nil {
- return nil, err
- }
-
- var ret []*gs.BeanDefinition
- for k, l := range loggers {
- var (
- f *os.File
- flag = os.O_WRONLY | os.O_CREATE | os.O_APPEND
- )
-
- // Open (or create) the log file
- f, err = os.OpenFile(filepath.Join(l.Dir, l.Name), flag, os.ModePerm)
- if err != nil {
- return nil, err
- }
-
- // Create a new slog.Logger instance with a text handler writing to the file
- o := slog.New(slog.NewTextHandler(f, nil))
-
- // Wrap the logger into a Bean with a destroy hook to close the file
- b := gs.NewBean(o).Name(k).Destroy(func(_ *slog.Logger) {
- _ = f.Close()
- })
-
- ret = append(ret, b)
- }
- return ret, nil
- })
-}
-
-
-
/*
- * Copyright 2025 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 httpsvr
-
-import (
- "fmt"
- "log/slog"
- "net/http"
-
- "github.com/go-spring/spring-core/gs"
- "github.com/go-spring/spring-core/gs/examples/bookman/src/app/controller"
- "github.com/go-spring/spring-core/gs/examples/bookman/src/idl/http/proto"
-)
-
-func init() {
- // Registers a custom ServeMux to replace the default implementation.
- gs.Provide(
- NewServeMux,
- gs.IndexArg(1, gs.TagArg("access")),
- )
-}
-
-// NewServeMux creates a new HTTP request multiplexer and registers
-// routes with access logging middleware.
-func NewServeMux(c *controller.Controller, logger *slog.Logger) *http.ServeMux {
- mux := http.NewServeMux()
- proto.RegisterRouter(mux, c, Access(logger))
-
- // Users can customize routes by adding handlers to the mux
- mux.Handle("GET /", http.FileServer(http.Dir("./public")))
- return mux
-}
-
-// Access is a middleware to log incoming HTTP requests.
-func Access(logger *slog.Logger) func(http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- logger.Info(fmt.Sprintf("access %s %s", r.Method, r.URL.Path))
- next.ServeHTTP(w, r)
- })
- }
-}
-
-
-
/*
- * Copyright 2025 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 controller
-
-import (
- "encoding/json"
- "net/http"
-
- "github.com/go-spring/spring-core/gs/examples/bookman/src/biz/service/book_service"
- "github.com/go-spring/spring-core/gs/examples/bookman/src/dao/book_dao"
-)
-
-type BookController struct {
- BookService *book_service.BookService `autowire:""`
-}
-
-// ListBooks handles the HTTP request to list all books.
-func (c *BookController) ListBooks(w http.ResponseWriter, r *http.Request) {
- books, err := c.BookService.ListBooks()
- if err != nil {
- _, _ = w.Write([]byte(err.Error()))
- return
- }
- _ = json.NewEncoder(w).Encode(books)
-}
-
-// GetBook handles the HTTP request to get details of a specific book by ISBN.
-func (c *BookController) GetBook(w http.ResponseWriter, r *http.Request) {
- isbn := r.PathValue("isbn")
- book, err := c.BookService.GetBook(isbn)
- if err != nil {
- _, _ = w.Write([]byte(err.Error()))
- return
- }
- _ = json.NewEncoder(w).Encode(book)
-}
-
-// SaveBook handles the HTTP request to save a new book.
-func (c *BookController) SaveBook(w http.ResponseWriter, r *http.Request) {
- var book book_dao.Book
- if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
- _, _ = w.Write([]byte(err.Error()))
- return
- }
- if err := c.BookService.SaveBook(book); err != nil {
- _, _ = w.Write([]byte(err.Error()))
- return
- }
- _ = json.NewEncoder(w).Encode("OK!")
-}
-
-// DeleteBook handles the HTTP request to delete a book by ISBN.
-func (c *BookController) DeleteBook(w http.ResponseWriter, r *http.Request) {
- isbn := r.PathValue("isbn")
- err := c.BookService.DeleteBook(isbn)
- if err != nil {
- _, _ = w.Write([]byte(err.Error()))
- return
- }
- _ = json.NewEncoder(w).Encode("OK!")
-}
-
-
-
/*
- * Copyright 2025 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 controller
-
-import (
- "github.com/go-spring/spring-core/gs"
-)
-
-func init() {
- gs.Object(&Controller{})
-}
-
-// Controller implements the controller interface defined in the idl package.
-// In practice, controller methods can be grouped into different controllers.
-// Each sub-controller can have its own dependencies and be tested independently,
-// making the codebase more modular and maintainable.
-type Controller struct {
- BookController
-}
-
-
-
/*
- * Copyright 2025 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 job
-
-import (
- "context"
- "fmt"
- "time"
-
- "github.com/go-spring/spring-core/gs"
-)
-
-func init() {
- gs.Object(&Job{}).AsJob()
-}
-
-type Job struct{}
-
-// Run executes the background job until the context is canceled.
-func (x *Job) Run(ctx context.Context) error {
- for {
- select {
- case <-ctx.Done():
- // Gracefully exit when the context is canceled
- fmt.Println("job exit")
- return nil
- default:
- // Check if the app is shutting down.
- // In long-running background tasks, checking for shutdown signals
- // during idle periods or between stages helps ensure timely resource cleanup.
- if gs.Exiting() {
- return nil
- }
- time.Sleep(time.Millisecond * 300)
- fmt.Println(time.Now().UnixMilli(), "job sleep end")
- }
- }
-}
-
-
-
/*
- * Copyright 2025 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 book_service
-
-import (
- "fmt"
- "log/slog"
- "strconv"
-
- "github.com/go-spring/spring-core/gs"
- "github.com/go-spring/spring-core/gs/examples/bookman/src/dao/book_dao"
- "github.com/go-spring/spring-core/gs/examples/bookman/src/idl/http/proto"
- "github.com/go-spring/spring-core/gs/examples/bookman/src/sdk/book_sdk"
-)
-
-func init() {
- gs.Object(&BookService{})
-}
-
-type BookService struct {
- BookDao *book_dao.BookDao `autowire:""`
- BookSDK *book_sdk.BookSDK `autowire:""`
- Logger *slog.Logger `autowire:"biz"`
- RefreshTime gs.Dync[int64] `value:"${refresh_time:=0}"`
-}
-
-// ListBooks retrieves all books from the database and enriches them with
-// pricing and refresh time.
-func (s *BookService) ListBooks() ([]proto.Book, error) {
- books, err := s.BookDao.ListBooks()
- if err != nil {
- s.Logger.Error(fmt.Sprintf("ListBooks return err: %s", err.Error()))
- return nil, err
- }
- ret := make([]proto.Book, 0, len(books))
- for _, book := range books {
- ret = append(ret, proto.Book{
- ISBN: book.ISBN,
- Title: book.Title,
- Author: book.Author,
- Publisher: book.Publisher,
- Price: s.BookSDK.GetPrice(book.ISBN),
- RefreshTime: strconv.FormatInt(s.RefreshTime.Value(), 10),
- })
- }
- return ret, nil
-}
-
-// GetBook retrieves a single book by its ISBN and enriches it with
-// pricing and refresh time.
-func (s *BookService) GetBook(isbn string) (proto.Book, error) {
- book, err := s.BookDao.GetBook(isbn)
- if err != nil {
- s.Logger.Error(fmt.Sprintf("GetBook return err: %s", err.Error()))
- return proto.Book{}, err
- }
- return proto.Book{
- ISBN: book.ISBN,
- Title: book.Title,
- Author: book.Author,
- Publisher: book.Publisher,
- Price: s.BookSDK.GetPrice(book.ISBN),
- RefreshTime: strconv.FormatInt(s.RefreshTime.Value(), 10),
- }, nil
-}
-
-// SaveBook stores a new book in the database.
-func (s *BookService) SaveBook(book book_dao.Book) error {
- return s.BookDao.SaveBook(book)
-}
-
-// DeleteBook removes a book from the database by its ISBN.
-func (s *BookService) DeleteBook(isbn string) error {
- return s.BookDao.DeleteBook(isbn)
-}
-
-
-
/*
- * Copyright 2025 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 book_dao
-
-import (
- "log/slog"
- "maps"
- "slices"
- "sort"
-
- "github.com/go-spring/spring-core/gs"
-)
-
-func init() {
- gs.Object(&BookDao{Store: map[string]Book{
- "978-0134190440": {
- Title: "The Go Programming Language",
- Author: "Alan A. A. Donovan, Brian W. Kernighan",
- ISBN: "978-0134190440",
- Publisher: "Addison-Wesley",
- },
- }})
-}
-
-type Book struct {
- Title string `json:"title"`
- Author string `json:"author"`
- ISBN string `json:"isbn"`
- Publisher string `json:"publisher"`
-}
-
-type BookDao struct {
- Store map[string]Book
- Logger *slog.Logger `autowire:"dao"`
-}
-
-// ListBooks returns a sorted list of all books in the store.
-func (dao *BookDao) ListBooks() ([]Book, error) {
- r := slices.Collect(maps.Values(dao.Store))
- sort.Slice(r, func(i, j int) bool {
- return r[i].ISBN < r[j].ISBN
- })
- return r, nil
-}
-
-// GetBook retrieves a book by its ISBN.
-func (dao *BookDao) GetBook(isbn string) (Book, error) {
- r, _ := dao.Store[isbn]
- return r, nil
-}
-
-// SaveBook adds or updates a book in the store.
-func (dao *BookDao) SaveBook(book Book) error {
- dao.Store[book.ISBN] = book
- return nil
-}
-
-// DeleteBook removes a book from the store by its ISBN.
-func (dao *BookDao) DeleteBook(isbn string) error {
- delete(dao.Store, isbn)
- return nil
-}
-
-
-
/*
- * Copyright 2025 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 proto defines the interfaces and route registrations generated from IDL files.
-package proto
-
-import (
- "net/http"
-)
-
-// Book represents the structure of a book entity.
-type Book struct {
- Title string `json:"title"`
- Author string `json:"author"`
- ISBN string `json:"isbn"`
- Publisher string `json:"publisher"`
- Price string `json:"price"`
- RefreshTime string `json:"refreshTime"`
-}
-
-// Controller defines the service interface for book-related operations.
-type Controller interface {
- ListBooks(w http.ResponseWriter, r *http.Request)
- GetBook(w http.ResponseWriter, r *http.Request)
- SaveBook(w http.ResponseWriter, r *http.Request)
- DeleteBook(w http.ResponseWriter, r *http.Request)
-}
-
-// RegisterRouter registers the HTTP routes for the Controller interface.
-// It maps each method to its corresponding HTTP endpoint,
-// and applies the given middleware (wrap) to each handler.
-func RegisterRouter(mux *http.ServeMux, c Controller, wrap func(next http.Handler) http.Handler) {
- mux.Handle("GET /books", wrap(http.HandlerFunc(c.ListBooks)))
- mux.Handle("GET /books/{isbn}", wrap(http.HandlerFunc(c.GetBook)))
- mux.Handle("POST /books", wrap(http.HandlerFunc(c.SaveBook)))
- mux.Handle("DELETE /books/{isbn}", wrap(http.HandlerFunc(c.DeleteBook)))
-}
-
-
-
/*
- * Copyright 2025 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 book_sdk
-
-import (
- "github.com/go-spring/spring-core/gs"
-)
-
-func init() {
- gs.Object(&BookSDK{})
-}
-
-type BookSDK struct{}
-
-// GetPrice returns a fixed price for any book.
-func (s *BookSDK) GetPrice(isbn string) string {
- return "¥10"
-}
-
-
-
/*
- * 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
-
-import (
- "context"
- "fmt"
- "reflect"
- "strings"
-
- "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_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_dync"
- "github.com/go-spring/spring-core/util/syslog"
-)
-
-const (
- Version = "go-spring@v1.2.0.rc2"
- Website = "https://go-spring.com/"
-)
-
-// As returns the [reflect.Type] of the given interface type.
-func As[T any]() reflect.Type {
- return gs.As[T]()
-}
-
-/************************************ arg ***********************************/
-
-type Arg = gs.Arg
-
-// TagArg returns a TagArg with the specified tag.
-// Used for property binding or object injection when providing constructor parameters.
-func TagArg(tag string) Arg {
- return gs_arg.Tag(tag)
-}
-
-// ValueArg returns a ValueArg with the specified value.
-// Used to provide specific values for constructor parameters.
-func ValueArg(v any) Arg {
- return gs_arg.Value(v)
-}
-
-// IndexArg returns an IndexArg with the specified index and argument.
-// When most constructor parameters can use default values, IndexArg helps reduce configuration effort.
-func IndexArg(n int, arg Arg) Arg {
- return gs_arg.Index(n, arg)
-}
-
-// BindArg returns a BindArg for the specified function and arguments.
-// Used to provide argument binding for option-style constructor parameters.
-func BindArg(fn any, args ...Arg) *gs_arg.BindArg {
- return gs_arg.Bind(fn, args...)
-}
-
-/************************************ cond ***********************************/
-
-type (
- Condition = gs.Condition
- CondContext = gs.CondContext
-)
-
-// OnFunc creates a Condition based on the provided function.
-func OnFunc(fn func(ctx CondContext) (bool, error)) Condition {
- return gs_cond.OnFunc(fn)
-}
-
-// OnProperty creates a Condition based on a property name and options.
-func OnProperty(name string) gs_cond.OnPropertyInterface {
- return gs_cond.OnProperty(name)
-}
-
-// OnMissingProperty creates a Condition that checks for a missing property.
-func OnMissingProperty(name string) Condition {
- return gs_cond.OnMissingProperty(name)
-}
-
-// OnBean creates a Condition for when a specific bean exists.
-func OnBean[T any](name ...string) Condition {
- return gs_cond.OnBean[T](name...)
-}
-
-// OnMissingBean creates a Condition for when a specific bean is missing.
-func OnMissingBean[T any](name ...string) Condition {
- return gs_cond.OnMissingBean[T](name...)
-}
-
-// OnSingleBean creates a Condition for when only one instance of a bean exists.
-func OnSingleBean[T any](name ...string) Condition {
- return gs_cond.OnSingleBean[T](name...)
-}
-
-// RegisterExpressFunc registers a custom expression function.
-func RegisterExpressFunc(name string, fn any) {
- gs_cond.RegisterExpressFunc(name, fn)
-}
-
-// OnExpression creates a Condition based on a custom expression.
-func OnExpression(expression string) Condition {
- return gs_cond.OnExpression(expression)
-}
-
-// Not creates a Condition that negates the given Condition.
-func Not(c Condition) Condition {
- return gs_cond.Not(c)
-}
-
-// 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...)
-}
-
-// And creates a Condition that is true if all the given Conditions are true.
-func And(conditions ...Condition) Condition {
- return gs_cond.And(conditions...)
-}
-
-// 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...)
-}
-
-/************************************ ioc ************************************/
-
-type (
- BeanID = gs.BeanID
- BeanMock = gs.BeanMock
-)
-
-type (
- Dync[T any] = gs_dync.Value[T]
-)
-
-type (
- RegisteredBean = gs.RegisteredBean
- BeanDefinition = gs.BeanDefinition
-)
-
-type (
- BeanSelector = gs.BeanSelector
- BeanInitFunc = gs.BeanInitFunc
- BeanDestroyFunc = gs.BeanDestroyFunc
-)
-
-// NewBean creates a new BeanDefinition.
-func NewBean(objOrCtor any, ctorArgs ...gs.Arg) *gs.BeanDefinition {
- return gs_bean.NewBean(objOrCtor, ctorArgs...).Caller(1)
-}
-
-// BeanSelectorFor returns a BeanSelector for the given type.
-func BeanSelectorFor[T any](name ...string) BeanSelector {
- return gs.BeanSelectorFor[T](name...)
-}
-
-/*********************************** app *************************************/
-
-type (
- Runner = gs.Runner
- Job = gs.Job
- Server = gs.Server
- ReadySignal = gs.ReadySignal
-)
-
-var B = gs_app.NewBoot()
-
-// funcRunner is a function type that implements the Runner interface.
-type funcRunner func() error
-
-func (f funcRunner) Run() error {
- return f()
-}
-
-// FuncRunner creates a Runner from a function.
-func FuncRunner(fn func() error) *RegisteredBean {
- return Object(funcRunner(fn)).AsRunner().Caller(1)
-}
-
-// funcJob is a function type that implements the Job interface.
-type funcJob func(ctx context.Context) error
-
-func (f funcJob) Run(ctx context.Context) error {
- return f(ctx)
-}
-
-// FuncJob creates a Job from a function.
-func FuncJob(fn func(ctx context.Context) error) *RegisteredBean {
- return Object(funcJob(fn)).AsJob().Caller(1)
-}
-
-type AppStarter struct{}
-
-// Web enables or disables the built-in web server.
-func Web(enable bool) *AppStarter {
- EnableSimpleHttpServer(enable)
- return &AppStarter{}
-}
-
-// Run runs the app and waits for an interrupt signal to exit.
-func (s *AppStarter) Run() {
- var err error
- defer func() {
- if err != nil {
- syslog.Errorf("app run failed: %s", err.Error())
- }
- }()
- printBanner()
- if err = B.(*gs_app.BootImpl).Run(); err != nil {
- return
- }
- B = nil
- err = gs_app.GS.Run()
-}
-
-// Run runs the app and waits for an interrupt signal to exit.
-func Run() {
- new(AppStarter).Run()
-}
-
-// Exiting returns a boolean indicating whether the application is exiting.
-func Exiting() bool {
- return gs_app.GS.Exiting()
-}
-
-// ShutDown shuts down the app with an optional message.
-func ShutDown() {
- gs_app.GS.ShutDown()
-}
-
-// Config returns the app configuration.
-func Config() *gs_conf.AppConfig {
- return gs_app.GS.P
-}
-
-// Component registers a bean definition for a given object.
-func Component[T any](i T) T {
- b := gs_bean.NewBean(reflect.ValueOf(i))
- gs_app.GS.C.Register(b).Caller(1)
- return i
-}
-
-// Object registers a bean definition for a given object.
-func Object(i any) *RegisteredBean {
- b := gs_bean.NewBean(reflect.ValueOf(i))
- return gs_app.GS.C.Register(b).Caller(1)
-}
-
-// Provide registers a bean definition for a given constructor.
-func Provide(ctor any, args ...Arg) *RegisteredBean {
- b := gs_bean.NewBean(ctor, args...)
- return gs_app.GS.C.Register(b).Caller(1)
-}
-
-// Register registers a bean definition.
-func Register(b *BeanDefinition) *RegisteredBean {
- return gs_app.GS.C.Register(b)
-}
-
-// GroupRegister registers a group of bean definitions.
-func GroupRegister(fn func(p conf.Properties) ([]*BeanDefinition, error)) {
- gs_app.GS.C.GroupRegister(fn)
-}
-
-// RefreshProperties refreshes the app configuration.
-func RefreshProperties() error {
- p, err := gs_app.GS.P.Refresh()
- if err != nil {
- return err
- }
- return gs_app.GS.C.RefreshProperties(p)
-}
-
-/********************************** banner ***********************************/
-
-var appBanner = `
- ____ ___ ____ ____ ____ ___ _ _ ____
- / ___| / _ \ / ___| | _ \ | _ \ |_ _| | \ | | / ___|
- | | _ | | | | _____ \___ \ | |_) | | |_) | | | | \| | | | _
- | |_| | | |_| | |_____| ___) | | __/ | _ < | | | |\ | | |_| |
- \____| \___/ |____/ |_| |_| \_\ |___| |_| \_| \____|
-`
-
-// Banner sets a custom app banner.
-func Banner(banner string) {
- appBanner = banner
-}
-
-// printBanner prints the app banner.
-func printBanner() {
- if len(appBanner) == 0 {
- return
- }
-
- if appBanner[0] != '\n' {
- fmt.Println()
- }
-
- maxLength := 0
- for s := range strings.SplitSeq(appBanner, "\n") {
- fmt.Printf("\x1b[36m%s\x1b[0m\n", s) // CYAN
- if len(s) > maxLength {
- maxLength = len(s)
- }
- }
-
- if appBanner[len(appBanner)-1] != '\n' {
- fmt.Println()
- }
-
- var padding []byte
- if n := (maxLength - len(Version)) / 2; n > 0 {
- padding = make([]byte, n)
- for i := range padding {
- padding[i] = ' '
- }
- }
- fmt.Println(string(padding) + Version + "\n")
-}
-
-
-
/*
- * Copyright 2025 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
-
-import (
- "context"
- "net"
- "net/http"
- "time"
-)
-
-func init() {
- // Register the default ServeMux as a bean if no other ServeMux instance exists
- Object(http.DefaultServeMux).Condition(
- OnMissingBean[*http.ServeMux](),
- OnProperty(EnableSimpleHttpServerProp).HavingValue("true").MatchIfMissing(),
- )
-
- // Provide a new SimpleHttpServer instance with configuration bindings.
- Provide(
- NewSimpleHttpServer,
- IndexArg(1, BindArg(SetHttpServerAddr, TagArg("${http.server.addr:=0.0.0.0:9090}"))),
- IndexArg(1, BindArg(SetHttpServerReadTimeout, TagArg("${http.server.readTimeout:=5s}"))),
- IndexArg(1, BindArg(SetHttpServerWriteTimeout, TagArg("${http.server.writeTimeout:=5s}"))),
- ).Condition(
- OnBean[*http.ServeMux](),
- OnProperty(EnableServersProp).HavingValue("true").MatchIfMissing(),
- OnProperty(EnableSimpleHttpServerProp).HavingValue("true").MatchIfMissing(),
- ).AsServer()
-}
-
-// HttpServerConfig holds configuration options for the HTTP server.
-type HttpServerConfig struct {
- Address string // The address to bind the server to.
- ReadTimeout time.Duration // The read timeout duration.
- WriteTimeout time.Duration // The write timeout duration.
-}
-
-// HttpServerOption is a function type for setting options on HttpServerConfig.
-type HttpServerOption func(arg *HttpServerConfig)
-
-// SetHttpServerAddr sets the address of the HTTP server.
-func SetHttpServerAddr(addr string) HttpServerOption {
- return func(arg *HttpServerConfig) {
- arg.Address = addr
- }
-}
-
-// SetHttpServerReadTimeout sets the read timeout for the HTTP server.
-func SetHttpServerReadTimeout(timeout time.Duration) HttpServerOption {
- return func(arg *HttpServerConfig) {
- arg.ReadTimeout = timeout
- }
-}
-
-// SetHttpServerWriteTimeout sets the write timeout for the HTTP server.
-func SetHttpServerWriteTimeout(timeout time.Duration) HttpServerOption {
- return func(arg *HttpServerConfig) {
- arg.WriteTimeout = timeout
- }
-}
-
-// SimpleHttpServer wraps a [http.Server] instance.
-type SimpleHttpServer struct {
- svr *http.Server // The HTTP server instance.
-}
-
-// NewSimpleHttpServer creates a new instance of SimpleHttpServer.
-func NewSimpleHttpServer(mux *http.ServeMux, opts ...HttpServerOption) *SimpleHttpServer {
- arg := &HttpServerConfig{
- Address: "0.0.0.0:9090",
- ReadTimeout: time.Second * 5,
- WriteTimeout: time.Second * 5,
- }
- for _, opt := range opts {
- opt(arg)
- }
- return &SimpleHttpServer{svr: &http.Server{
- Addr: arg.Address,
- Handler: mux,
- ReadTimeout: arg.ReadTimeout,
- WriteTimeout: arg.WriteTimeout,
- }}
-}
-
-// ListenAndServe starts the HTTP server and listens for incoming connections.
-func (s *SimpleHttpServer) ListenAndServe(sig ReadySignal) error {
- ln, err := net.Listen("tcp", s.svr.Addr)
- if err != nil {
- return err
- }
- <-sig.TriggerAndWait()
- return s.svr.Serve(ln)
-}
-
-// Shutdown gracefully shuts down the HTTP server with the given context.
-func (s *SimpleHttpServer) Shutdown(ctx context.Context) error {
- return s.svr.Shutdown(ctx)
-}
-
-
-
/*
- * 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.
- */
-
-//go:generate mockgen -build_flags="-mod=mod" -package=gs -source=gs.go -destination=gs_test_test.go -exclude_interfaces=BeanSelector,Condition,CondBean,Arg,ReadySignal,BeanInitFunc,BeanDestroyFunc
-
-package gs
-
-import (
- "context"
- "reflect"
- "strings"
- "unsafe"
-)
-
-// anyType is the [reflect.Type] of the [any] type.
-var anyType = reflect.TypeFor[any]()
-
-// As returns the [reflect.Type] of the given interface type.
-// It ensures that the provided generic type parameter T is an interface.
-// If T is not an interface, the function panics.
-func As[T any]() reflect.Type {
- t := reflect.TypeFor[T]()
- if t.Kind() != reflect.Interface {
- panic("T must be interface")
- }
- return t
-}
-
-// BeanSelector is an interface for selecting beans.
-type BeanSelector interface {
- // TypeAndName returns the type and name of the bean.
- TypeAndName() (reflect.Type, string)
-}
-
-// BeanSelectorImpl is an implementation of BeanSelector.
-type BeanSelectorImpl struct {
- Type reflect.Type // The type of the bean
- Name string // The name of the bean
-}
-
-// BeanSelectorFor returns a BeanSelectorImpl for the given type.
-// If a name is provided, it is set; otherwise, only the type is used.
-func BeanSelectorFor[T any](name ...string) BeanSelector {
- if len(name) == 0 {
- return BeanSelectorImpl{Type: reflect.TypeFor[T]()}
- }
- return BeanSelectorImpl{Type: reflect.TypeFor[T](), Name: name[0]}
-}
-
-// TypeAndName returns the type and name of the bean.
-func (s BeanSelectorImpl) TypeAndName() (reflect.Type, string) {
- return s.Type, s.Name
-}
-
-func (s BeanSelectorImpl) String() string {
- var sb strings.Builder
- sb.WriteString("{")
- if s.Type != nil && s.Type != anyType {
- sb.WriteString("Type:")
- sb.WriteString(s.Type.String())
- }
- if s.Name != "" {
- if sb.Len() > 1 {
- sb.WriteString(",")
- }
- sb.WriteString("Name:")
- sb.WriteString(s.Name)
- }
- sb.WriteString("}")
- return sb.String()
-}
-
-/************************************ cond ***********************************/
-
-// Condition is an interface used for defining conditional logic
-// when registering beans in the IoC container.
-type Condition interface {
- // Matches checks whether the condition is satisfied.
- Matches(ctx CondContext) (bool, error)
-}
-
-// CondBean represents a bean with Name and Type.
-type CondBean interface {
- Name() string // Name of the bean
- Type() reflect.Type // Type of the bean
-}
-
-// CondContext defines methods for the IoC container used by conditions.
-type CondContext interface {
- // Has checks whether the IoC container has a property with the given key.
- Has(key string) bool
- // Prop retrieves the value of a property from the IoC container.
- Prop(key string, def ...string) string
- // Find searches for bean definitions matching the given BeanSelector.
- Find(s BeanSelector) ([]CondBean, error)
-}
-
-/************************************* arg ***********************************/
-
-// Arg is an interface for retrieving argument values in function parameter binding.
-type Arg interface {
- // GetArgValue retrieves the argument value based on the type.
- GetArgValue(ctx ArgContext, t reflect.Type) (reflect.Value, error)
-}
-
-// ArgContext defines methods for the IoC container used by Arg types.
-type ArgContext interface {
- // Check checks if the given condition is met.
- Check(c Condition) (bool, error)
- // Bind binds property values to the provided [reflect.Value].
- Bind(v reflect.Value, tag string) error
- // Wire wires dependent beans to the provided [reflect.Value].
- Wire(v reflect.Value, tag string) error
-}
-
-/*********************************** app ************************************/
-
-// Runner defines an interface for components that should run after
-// all beans are injected but before the application servers start.
-type Runner interface {
- Run() error
-}
-
-// Job defines an interface for components that run tasks with a given context
-// after all beans are injected but before the application servers start.
-type Job interface {
- Run(ctx context.Context) error
-}
-
-// ReadySignal defines an interface for components that can trigger a signal
-// when the application is ready to serve requests.
-type ReadySignal interface {
- TriggerAndWait() <-chan struct{}
-}
-
-// Server defines an interface for managing the lifecycle of application servers,
-// such as HTTP, gRPC, Thrift, or MQ servers. It includes methods for starting
-// and shutting down the server gracefully.
-type Server interface {
- ListenAndServe(sig ReadySignal) error
- Shutdown(ctx context.Context) error
-}
-
-/*********************************** bean ************************************/
-
-// BeanMock defines a mock object and its target bean selector for overriding.
-type BeanMock struct {
- Object any // Mock instance to replace the target bean
- Target BeanSelector // Selector to identify the target bean
-}
-
-// BeanID represents the unique identifier for a bean.
-type BeanID struct {
- Type reflect.Type // Type of the bean
- Name string // Name of the bean
-}
-
-// BeanInitFunc defines the prototype for initialization functions.
-// Examples: `func(bean)` or `func(bean) error`.
-type BeanInitFunc = any
-
-// BeanDestroyFunc defines the prototype for destruction functions.
-// Examples: `func(bean)` or `func(bean) error`.
-type BeanDestroyFunc = any
-
-// Configuration holds parameters for bean setup configuration.
-type Configuration struct {
- Includes []string // Methods to include
- Excludes []string // Methods to exclude
-}
-
-// BeanRegistration provides methods for configuring and registering bean metadata.
-type BeanRegistration interface {
- Name() string
- Type() reflect.Type
- Value() reflect.Value
- SetName(name string)
- SetInit(fn BeanInitFunc)
- SetDestroy(fn BeanDestroyFunc)
- SetInitMethod(method string)
- SetDestroyMethod(method string)
- SetCondition(conditions ...Condition)
- SetDependsOn(selectors ...BeanSelector)
- SetExport(exports ...reflect.Type)
- SetConfiguration(c ...Configuration)
- SetCaller(skip int)
- OnProfiles(profiles string)
-}
-
-// beanBuilder helps configure a bean during its creation.
-type beanBuilder[T any] struct {
- b BeanRegistration
-}
-
-// TypeAndName returns the type and name of the bean.
-func (d *beanBuilder[T]) TypeAndName() (reflect.Type, string) {
- return d.b.Type(), d.b.Name()
-}
-
-// GetArgValue returns the value of the bean.
-func (d *beanBuilder[T]) GetArgValue(ctx ArgContext, t reflect.Type) (reflect.Value, error) {
- return d.b.Value(), nil
-}
-
-// 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))
-}
-
-// 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))
-}
-
-// 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))
-}
-
-// InitMethod sets the initialization function for the bean by method name.
-func (d *beanBuilder[T]) InitMethod(method string) *T {
- d.b.SetInitMethod(method)
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// DestroyMethod sets the destruction function for the bean by method name.
-func (d *beanBuilder[T]) DestroyMethod(method string) *T {
- d.b.SetDestroyMethod(method)
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// Condition sets the conditions for the bean.
-func (d *beanBuilder[T]) Condition(conditions ...Condition) *T {
- d.b.SetCondition(conditions...)
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// DependsOn sets the beans that this bean depends on.
-func (d *beanBuilder[T]) DependsOn(selectors ...BeanSelector) *T {
- d.b.SetDependsOn(selectors...)
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// AsRunner marks the bean as a Runner.
-func (d *beanBuilder[T]) AsRunner() *T {
- d.b.SetExport(As[Runner]())
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// AsJob marks the bean as a Job.
-func (d *beanBuilder[T]) AsJob() *T {
- d.b.SetExport(As[Job]())
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// AsServer marks the bean as a Server.
-func (d *beanBuilder[T]) AsServer() *T {
- d.b.SetExport(As[Server]())
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// 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 applies the configuration parameters to the bean.
-func (d *beanBuilder[T]) Configuration(c ...Configuration) *T {
- d.b.SetConfiguration(c...)
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// Caller sets the caller information for the bean.
-func (d *beanBuilder[T]) Caller(skip int) *T {
- d.b.SetCaller(skip)
- return *(**T)(unsafe.Pointer(&d))
-}
-
-// OnProfiles sets the profiles that the bean will be active in.
-func (d *beanBuilder[T]) OnProfiles(profiles string) *T {
- d.b.OnProfiles(profiles)
- 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},
- }
-}
-
-// BeanDefinition represents a bean that has not yet been registered.
-type BeanDefinition struct {
- beanBuilder[BeanDefinition]
-}
-
-// NewBeanDefinition creates a new BeanDefinition instance.
-func NewBeanDefinition(d BeanRegistration) *BeanDefinition {
- return &BeanDefinition{
- beanBuilder: beanBuilder[BeanDefinition]{d},
- }
-}
-
-
-
/*
- * 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_app
-
-import (
- "context"
- "errors"
- "net/http"
- "os"
- "os/signal"
- "sync"
- "sync/atomic"
- "syscall"
- "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_conf"
- "github.com/go-spring/spring-core/gs/internal/gs_core"
- "github.com/go-spring/spring-core/util/goutil"
- "github.com/go-spring/spring-core/util/syslog"
-)
-
-// GS is the global application instance.
-var GS = NewApp()
-
-// App represents the core application, managing its lifecycle,
-// configuration, and dependency injection.
-type App struct {
- C *gs_core.Container
- P *gs_conf.AppConfig
-
- exiting atomic.Bool
- ctx context.Context
- cancel context.CancelFunc
- wg sync.WaitGroup
-
- Runners []gs.Runner `autowire:"${spring.app.runners:=?}"`
- Jobs []gs.Job `autowire:"${spring.app.jobs:=?}"`
- Servers []gs.Server `autowire:"${spring.app.servers:=?}"`
-
- EnableJobs bool `value:"${spring.app.enable-jobs:=true}"`
- EnableServers bool `value:"${spring.app.enable-servers:=true}"`
-
- ShutDownTimeout time.Duration `value:"${spring.app.shutdown-timeout:=15s}"`
-}
-
-// NewApp creates and initializes a new application instance.
-func NewApp() *App {
- ctx, cancel := context.WithCancel(context.Background())
- return &App{
- C: gs_core.New(),
- P: gs_conf.NewAppConfig(),
- ctx: ctx,
- cancel: cancel,
- }
-}
-
-// Run starts the application and listens for termination signals
-// (e.g., SIGINT, SIGTERM). Upon receiving a signal, it initiates
-// a graceful shutdown.
-func (app *App) Run() error {
- app.C.Object(app)
-
- 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
- syslog.Infof("Received signal: %v", sig)
- app.ShutDown()
- }()
-
- // waits for the shutdown signal
- <-app.ctx.Done()
- app.Stop()
- return nil
-}
-
-// Start initializes and starts the application. It performs configuration
-// loading, IoC container refreshing, dependency injection, and runs
-// runners, jobs and servers.
-func (app *App) Start() error {
- var p conf.Properties
-
- // loads the layered app properties
- {
- var err error
- if p, err = app.P.Refresh(); err != nil {
- return err
- }
- }
-
- // refreshes the container
- if err := app.C.Refresh(p); err != nil {
- return err
- }
-
- // runs all runners
- for _, r := range app.Runners {
- if err := r.Run(); err != nil {
- return err
- }
- }
-
- // runs all jobs
- if app.EnableJobs {
- for _, job := range app.Jobs {
- goutil.GoFunc(func() {
- defer func() {
- if r := recover(); r != nil {
- app.ShutDown()
- panic(r)
- }
- }()
- if err := job.Run(app.ctx); err != nil {
- syslog.Errorf("job run error: %s", err.Error())
- app.ShutDown()
- }
- })
- }
- }
-
- // starts all servers
- if app.EnableServers {
- sig := NewReadySignal()
- for _, svr := range app.Servers {
- sig.Add()
- app.wg.Add(1)
- goutil.GoFunc(func() {
- defer app.wg.Done()
- defer func() {
- if r := recover(); r != nil {
- sig.Intercept()
- app.ShutDown()
- panic(r)
- }
- }()
- err := svr.ListenAndServe(sig)
- if err != nil && !errors.Is(err, http.ErrServerClosed) {
- syslog.Errorf("server serve error: %s", err.Error())
- sig.Intercept()
- app.ShutDown()
- }
- })
- }
- sig.Wait()
- if sig.Intercepted() {
- return nil
- }
- syslog.Infof("ready to serve requests")
- sig.Close()
- }
- return nil
-}
-
-// Stop gracefully shuts down the application, ensuring all servers and
-// resources are properly closed.
-func (app *App) Stop() {
- ctx, cancel := context.WithTimeout(context.Background(), app.ShutDownTimeout)
- defer cancel()
-
- waitChan := make(chan struct{})
- goutil.GoFunc(func() {
- for _, svr := range app.Servers {
- goutil.GoFunc(func() {
- if err := svr.Shutdown(ctx); err != nil {
- syslog.Errorf("shutdown server failed: %s", err.Error())
- }
- })
- }
- app.wg.Wait()
- app.C.Close()
- waitChan <- struct{}{}
- })
-
- select {
- case <-waitChan:
- syslog.Infof("shutdown complete")
- case <-ctx.Done():
- syslog.Infof("shutdown timeout")
- }
-}
-
-// Exiting returns a boolean indicating whether the application is exiting.
-func (app *App) Exiting() bool {
- return app.exiting.Load()
-}
-
-// ShutDown gracefully terminates the application. This method should
-// be called to trigger a proper shutdown process.
-func (app *App) ShutDown() {
- app.exiting.Store(true)
- app.cancel()
-}
-
-
-
/*
- * 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_app
-
-import (
- "reflect"
-
- "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/internal/gs_conf"
- "github.com/go-spring/spring-core/gs/internal/gs_core"
-)
-
-// funcRunner is a function type that implements the Runner interface.
-type funcRunner func() error
-
-func (f funcRunner) Run() error {
- return f()
-}
-
-// Boot defines the interface for application bootstrapping.
-type Boot interface {
- // Config returns the boot configuration.
- Config() *gs_conf.BootConfig
- // Object registers an object bean.
- Object(i any) *gs.RegisteredBean
- // Provide registers a bean using a constructor function.
- Provide(ctor any, args ...gs.Arg) *gs.RegisteredBean
- // Register registers a BeanDefinition instance.
- Register(bd *gs.BeanDefinition) *gs.RegisteredBean
- // FuncRunner creates a Runner from a function.
- FuncRunner(fn func() error) *gs.RegisteredBean
-}
-
-// BootImpl is the bootstrapper of the application.
-type BootImpl struct {
- c *gs_core.Container
- p *gs_conf.BootConfig
-
- // flag indicates whether the bootstrapper has been used.
- flag bool
-
- Runners []gs.Runner `autowire:"${spring.boot.runners:=?}"`
-}
-
-// NewBoot creates a new Boot instance.
-func NewBoot() Boot {
- return &BootImpl{
- c: gs_core.New(),
- p: gs_conf.NewBootConfig(),
- }
-}
-
-// Config returns the boot configuration.
-func (b *BootImpl) Config() *gs_conf.BootConfig {
- return b.p
-}
-
-// Object registers an object bean.
-func (b *BootImpl) Object(i any) *gs.RegisteredBean {
- b.flag = true
- bd := gs_bean.NewBean(reflect.ValueOf(i))
- return b.c.Register(bd).Caller(1)
-}
-
-// Provide registers a bean using a constructor function.
-func (b *BootImpl) Provide(ctor any, args ...gs.Arg) *gs.RegisteredBean {
- b.flag = true
- bd := gs_bean.NewBean(ctor, args...)
- return b.c.Register(bd).Caller(1)
-}
-
-// Register registers a BeanDefinition instance.
-func (b *BootImpl) Register(bd *gs.BeanDefinition) *gs.RegisteredBean {
- b.flag = true
- return b.c.Register(bd)
-}
-
-// FuncRunner creates a Runner from a function.
-func (b *BootImpl) FuncRunner(fn func() error) *gs.RegisteredBean {
- b.flag = true
- bd := gs_bean.NewBean(reflect.ValueOf(funcRunner(fn)))
- return b.c.Register(bd).AsRunner().Caller(1)
-}
-
-// Run executes the application's boot process.
-func (b *BootImpl) Run() error {
- if !b.flag {
- return nil
- }
- b.c.Object(b)
-
- var p conf.Properties
-
- // Refresh the boot configuration.
- {
- var err error
- if p, err = b.p.Refresh(); err != nil {
- return err
- }
- }
-
- // Refresh the container.
- if err := b.c.Refresh(p); err != nil {
- return err
- }
-
- // Execute all registered runners.
- for _, r := range b.Runners {
- if err := r.Run(); err != nil {
- return err
- }
- }
-
- b.c.Close()
- return nil
-}
-
-
-
/*
- * Copyright 2025 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_app
-
-import (
- "sync"
- "sync/atomic"
-)
-
-// ReadySignal is used to notify that the application is ready to serve requests.
-type ReadySignal struct {
- wg sync.WaitGroup
- ch chan struct{}
- b atomic.Bool
-}
-
-// NewReadySignal creates a new ReadySignal instance.
-func NewReadySignal() *ReadySignal {
- return &ReadySignal{
- ch: make(chan struct{}),
- }
-}
-
-// Add increments the WaitGroup counter.
-func (s *ReadySignal) Add() {
- s.wg.Add(1)
-}
-
-// TriggerAndWait marks an operation as done by decrementing the WaitGroup counter,
-// and then returns the readiness signal channel for waiting.
-func (s *ReadySignal) TriggerAndWait() <-chan struct{} {
- s.wg.Done()
- return s.ch
-}
-
-// Intercepted returns true if the signal has been intercepted.
-func (s *ReadySignal) Intercepted() bool {
- return s.b.Load()
-}
-
-// Intercept marks the signal as intercepted.
-func (s *ReadySignal) Intercept() {
- s.b.Store(true)
- s.wg.Done()
-}
-
-// Wait blocks until all WaitGroup counters reach zero.
-func (s *ReadySignal) Wait() {
- s.wg.Wait()
-}
-
-// Close closes the signal channel, notifying all goroutines waiting on it.
-func (s *ReadySignal) Close() {
- close(s.ch)
-}
-
-
-
/*
- * 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_arg
-
-import (
- "errors"
- "fmt"
- "reflect"
- "runtime"
-
- "github.com/go-spring/spring-core/gs/internal/gs"
- "github.com/go-spring/spring-core/util"
- "github.com/go-spring/spring-core/util/errutil"
-)
-
-// TagArg represents an argument resolved using a tag for property binding or dependency injection.
-type TagArg struct {
- Tag string
-}
-
-// Tag creates a TagArg with the given tag.
-func Tag(tag string) gs.Arg {
- return TagArg{Tag: tag}
-}
-
-// GetArgValue resolves the tag to a value based on the target type.
-// For primitive types (int, string), it binds from configuration.
-// For structs/interfaces, it wires dependencies from the container.
-// It returns an error if the type is neither bindable nor injectable.
-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{}, errutil.WrapError(err, "TagArg::GetArgValue error")
- }
- 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{}, errutil.WrapError(err, "TagArg::GetArgValue error")
- }
- return v, nil
- }
-
- err := fmt.Errorf("unsupported argument type: %s", t.String())
- return reflect.Value{}, errutil.WrapError(err, "TagArg::GetArgValue error")
-}
-
-// IndexArg represents an argument with an explicit positional index in the function signature.
-type IndexArg struct {
- Idx int //The positional index (0-based).
- Arg gs.Arg //The wrapped argument value.
-}
-
-// Index creates an IndexArg with the given index and argument.
-func Index(n int, arg gs.Arg) gs.Arg {
- return IndexArg{Idx: n, Arg: arg}
-}
-
-// GetArgValue panics if called directly. IndexArg must be processed by ArgList.
-func (arg IndexArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) {
- panic(util.UnimplementedMethod)
-}
-
-// ValueArg represents a fixed-value argument.
-type ValueArg struct {
- v any
-}
-
-// Value creates a fixed-value argument.
-func Value(v any) gs.Arg {
- return ValueArg{v: v}
-}
-
-// GetArgValue returns the fixed value and validates type compatibility.
-// It returns an error if the value type is incompatible with the target type.
-func (arg ValueArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) {
- if arg.v == nil {
- return reflect.Zero(t), nil
- }
- v := reflect.ValueOf(arg.v)
- if !v.Type().AssignableTo(t) {
- err := fmt.Errorf("cannot assign type:%T to type:%s", arg.v, t.String())
- return reflect.Value{}, errutil.WrapError(err, "ValueArg::GetArgValue error")
- }
- return v, nil
-}
-
-// ArgList manages arguments for a function, supporting both fixed and variadic parameters.
-type ArgList struct {
- fnType reflect.Type // The reflected type of the target function.
- args []gs.Arg // The argument list (indexed or non-indexed).
-}
-
-// NewArgList validates and creates an ArgList for a function. It returns errors
-// for invalid function types, mixed indexed/non-indexed args, or out-of-bounds indexes.
-func NewArgList(fnType reflect.Type, args []gs.Arg) (*ArgList, error) {
- if fnType.Kind() != reflect.Func {
- err := fmt.Errorf("invalid function type:%s", fnType.String())
- return nil, errutil.WrapError(err, "NewArgList error")
- }
-
- // Calculates the number of fixed arguments in the function.
- fixedArgCount := fnType.NumIn()
- if fnType.IsVariadic() {
- fixedArgCount--
- }
-
- fnArgs := make([]gs.Arg, fixedArgCount)
- for i := range fnArgs {
- fnArgs[i] = Tag("")
- }
-
- var (
- useIdx bool
- notIdx bool
- )
-
- for i := range args {
- switch arg := args[i].(type) {
- case IndexArg:
- useIdx = true
- if notIdx {
- err := errors.New("arguments must be all indexed or non-indexed")
- return nil, errutil.WrapError(err, "NewArgList error")
- }
- if arg.Idx < 0 || arg.Idx >= fnType.NumIn() {
- err := fmt.Errorf("invalid argument index %d", arg.Idx)
- return nil, errutil.WrapError(err, "NewArgList error")
- }
- if arg.Idx < fixedArgCount {
- fnArgs[arg.Idx] = arg.Arg
- } else {
- fnArgs = append(fnArgs, arg.Arg)
- }
- default:
- notIdx = true
- if useIdx {
- err := errors.New("arguments must be all indexed or non-indexed")
- return nil, errutil.WrapError(err, "NewArgList error")
- }
- if i < fixedArgCount {
- fnArgs[i] = arg
- } else {
- fnArgs = append(fnArgs, arg)
- }
- }
- }
-
- return &ArgList{fnType: fnType, args: fnArgs}, nil
-}
-
-// get resolves all arguments and returns their reflected values.
-func (r *ArgList) get(ctx gs.ArgContext) ([]reflect.Value, error) {
-
- fnType := r.fnType
- numIn := fnType.NumIn()
- variadic := fnType.IsVariadic()
- result := make([]reflect.Value, 0, len(r.args))
-
- // Processes each argument and converts it to a [reflect.Value].
- for idx, arg := range r.args {
-
- var t reflect.Type
- if variadic && idx >= numIn-1 {
- t = fnType.In(numIn - 1).Elem()
- } else {
- t = fnType.In(idx)
- }
-
- v, err := arg.GetArgValue(ctx, t)
- if err != nil {
- return nil, err
- }
- if v.IsValid() {
- result = append(result, v)
- }
- }
- return result, nil
-}
-
-// CallableFunc is a function that can be called.
-type CallableFunc = any
-
-// Callable wraps a function and its bound arguments for invocation.
-type Callable struct {
- fn CallableFunc
- argList *ArgList
-}
-
-// NewCallable binds arguments to a function and creates a Callable. It
-// returns errors for invalid function types or argument validation failures.
-func NewCallable(fn CallableFunc, args []gs.Arg) (*Callable, error) {
- fnType := reflect.TypeOf(fn)
- argList, err := NewArgList(fnType, args)
- if err != nil {
- return nil, err
- }
- return &Callable{fn: fn, argList: argList}, nil
-}
-
-// Call invokes the function with resolved arguments.
-func (r *Callable) Call(ctx gs.ArgContext) ([]reflect.Value, error) {
- ret, err := r.argList.get(ctx)
- if err != nil {
- return nil, err
- }
- return reflect.ValueOf(r.fn).Call(ret), nil
-}
-
-// BindArg represents a bound function with conditions for conditional execution.
-type BindArg struct {
- r *Callable // The wrapped Callable.
- fileline string // Source location of the Bind call (for debugging).
- conditions []gs.Condition // Conditions that must be met to execute the function.
-}
-
-// validBindFunc validates if a function is a valid binding target.
-// Valid signatures:
-// - func(...) error
-// - func(...) (T, error)
-func validBindFunc(fn CallableFunc) error {
- t := reflect.TypeOf(fn)
- if t.Kind() != reflect.Func {
- return errors.New("invalid function type")
- }
- if numOut := t.NumOut(); numOut == 1 {
- if o := t.Out(0); !util.IsErrorType(o) {
- return nil
- }
- } else if numOut == 2 {
- if o := t.Out(t.NumOut() - 1); util.IsErrorType(o) {
- return nil
- }
- }
- return errors.New("invalid function type")
-}
-
-// Bind creates a binding for an option function. It panics on validation errors.
-// `fn` is The target function (must return error or (T, error)). `args` is the
-// bound arguments (indexed or non-indexed).
-func Bind(fn CallableFunc, args ...gs.Arg) *BindArg {
- if err := validBindFunc(fn); err != nil {
- panic(err)
- }
- r, err := NewCallable(fn, args)
- if err != nil {
- panic(err)
- }
- arg := &BindArg{r: r}
- _, file, line, _ := runtime.Caller(1)
- arg.SetFileLine(file, line)
- return arg
-}
-
-// SetFileLine sets the source location of the Bind call.
-func (arg *BindArg) SetFileLine(file string, line int) {
- arg.fileline = fmt.Sprintf("%s:%d", file, line)
-}
-
-// Condition adds pre-execution conditions to the binding.
-func (arg *BindArg) Condition(conditions ...gs.Condition) *BindArg {
- arg.conditions = append(arg.conditions, conditions...)
- return arg
-}
-
-// GetArgValue executes the function if all conditions are met and returns the result.
-// It returns an invalid [reflect.Value] if conditions are not met. It also propagates
-// errors from the function or condition checks.
-func (arg *BindArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) {
-
- // Checks if the condition is met.
- for _, c := range arg.conditions {
- ok, err := ctx.Check(c)
- if err != nil {
- return reflect.Value{}, err
- } else if !ok {
- return reflect.Value{}, nil
- }
- }
-
- // Calls the function and returns its result.
- out, err := arg.r.Call(ctx)
- if err != nil {
- return reflect.Value{}, err
- }
- if len(out) == 1 {
- return out[0], nil
- }
- err, _ = out[1].Interface().(error)
- return out[0], err
-}
-
-
-
/*
- * 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_bean
-
-import (
- "fmt"
- "reflect"
- "runtime"
- "slices"
- "strings"
-
- "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_cond"
- "github.com/go-spring/spring-core/util"
-)
-
-// BeanStatus represents the different lifecycle statuses of a bean.
-type BeanStatus int8
-
-const (
- 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.
-)
-
-// String returns a human-readable string of the bean status.
-func (status BeanStatus) String() string {
- switch status {
- 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 "unknown"
- }
-}
-
-// BeanMetadata holds the metadata information of a bean.
-type BeanMetadata struct {
- f *gs_arg.Callable
- init gs.BeanInitFunc
- destroy gs.BeanDestroyFunc
- dependsOn []gs.BeanSelector
- exports []reflect.Type
- conditions []gs.Condition
- status BeanStatus
- mocked bool
- fileLine string
- configuration *gs.Configuration
-}
-
-// Mocked returns true if the bean is mocked.
-func (d *BeanMetadata) Mocked() bool {
- return d.mocked
-}
-
-// 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)
-}
-
-// Init returns the initialization function of the bean.
-func (d *BeanMetadata) Init() gs.BeanInitFunc {
- return d.init
-}
-
-// Destroy returns the destruction function of the bean.
-func (d *BeanMetadata) Destroy() gs.BeanDestroyFunc {
- return d.destroy
-}
-
-// DependsOn returns the list of dependencies for the bean.
-func (d *BeanMetadata) DependsOn() []gs.BeanSelector {
- return d.dependsOn
-}
-
-// SetDependsOn sets the list of dependencies for the bean.
-func (d *BeanMetadata) SetDependsOn(selectors ...gs.BeanSelector) {
- d.dependsOn = append(d.dependsOn, selectors...)
-}
-
-// Exports returns the list of exported types for the bean.
-func (d *BeanMetadata) Exports() []reflect.Type {
- return d.exports
-}
-
-// Conditions returns the list of conditions for the bean.
-func (d *BeanMetadata) Conditions() []gs.Condition {
- return d.conditions
-}
-
-// SetCondition adds a condition to the list of conditions for the bean.
-func (d *BeanMetadata) SetCondition(conditions ...gs.Condition) {
- d.conditions = append(d.conditions, conditions...)
-}
-
-// Configuration returns the configuration parameters for the bean.
-func (d *BeanMetadata) Configuration() *gs.Configuration {
- return d.configuration
-}
-
-// SetConfiguration sets the configuration flag and parameters for the bean.
-func (d *BeanDefinition) SetConfiguration(c ...gs.Configuration) {
- var cfg gs.Configuration
- if len(c) > 0 {
- cfg = c[0]
- }
- d.configuration = &gs.Configuration{
- Includes: cfg.Includes,
- Excludes: cfg.Excludes,
- }
-}
-
-// SetCaller sets the caller for the bean.
-func (d *BeanMetadata) SetCaller(skip int) {
- _, file, line, _ := runtime.Caller(skip)
- d.SetFileLine(file, line)
-}
-
-// FileLine returns the file and line number for the bean.
-func (d *BeanMetadata) FileLine() string {
- return d.fileLine
-}
-
-// SetFileLine sets the file and line number for the bean.
-func (d *BeanMetadata) SetFileLine(file string, line int) {
- d.fileLine = fmt.Sprintf("%s:%d", file, line)
-}
-
-// 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.
-}
-
-// 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
-}
-
-// Interface returns the underlying value of the bean.
-func (d *BeanRuntime) Interface() any {
- return d.v.Interface()
-}
-
-// Callable returns the callable for the bean.
-func (d *BeanRuntime) Callable() *gs_arg.Callable {
- return nil
-}
-
-// 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 contains both metadata and runtime information of a bean.
-type BeanDefinition struct {
- *BeanMetadata
- *BeanRuntime
-}
-
-// makeBean creates a new bean definition.
-func makeBean(t reflect.Type, v reflect.Value, f *gs_arg.Callable, name string) *BeanDefinition {
- return &BeanDefinition{
- BeanMetadata: &BeanMetadata{
- f: f,
- status: StatusDefault,
- },
- BeanRuntime: &BeanRuntime{
- t: t,
- v: v,
- name: name,
- },
- }
-}
-
-// SetMock sets the mock object for the bean, replacing its runtime information.
-func (d *BeanDefinition) SetMock(obj any) {
- *d = BeanDefinition{
- BeanMetadata: &BeanMetadata{
- exports: d.exports,
- mocked: true,
- },
- BeanRuntime: &BeanRuntime{
- t: reflect.TypeOf(obj),
- v: reflect.ValueOf(obj),
- name: d.name,
- },
- }
-}
-
-// Callable returns the callable for the bean.
-func (d *BeanDefinition) Callable() *gs_arg.Callable {
- return d.f
-}
-
-// SetName sets the name of the bean.
-func (d *BeanDefinition) SetName(name string) {
- d.name = name
-}
-
-// Status returns the current status of the bean.
-func (d *BeanDefinition) Status() BeanStatus {
- return d.status
-}
-
-// SetStatus sets the current status of the bean.
-func (d *BeanDefinition) SetStatus(status BeanStatus) {
- d.status = status
-}
-
-// 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")
-}
-
-// 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")
-}
-
-// SetInitMethod sets the initialization function for the bean by method name.
-func (d *BeanDefinition) SetInitMethod(method string) {
- m, ok := d.t.MethodByName(method)
- if !ok {
- panic(fmt.Sprintf("method %s not found on type %s", method, d.t))
- }
- d.SetInit(m.Func.Interface())
-}
-
-// SetDestroyMethod sets the destruction function for the bean by method name.
-func (d *BeanDefinition) SetDestroyMethod(method string) {
- m, ok := d.t.MethodByName(method)
- if !ok {
- panic(fmt.Sprintf("method %s not found on type %s", method, d.t))
- }
- d.SetDestroy(m.Func.Interface())
-}
-
-// 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")
- }
- if !d.Type().Implements(t) {
- panic(fmt.Sprintf("doesn't implement interface %s", t))
- }
- if slices.Contains(d.exports, t) {
- continue
- }
- d.exports = append(d.exports, t)
- }
-}
-
-// OnProfiles sets the conditions for the bean based on the active profiles.
-func (d *BeanDefinition) OnProfiles(profiles string) {
- d.SetCondition(gs_cond.OnFunc(func(ctx gs.CondContext) (bool, error) {
- val := strings.TrimSpace(ctx.Prop("spring.profiles.active"))
- if val == "" {
- return false, nil
- }
- ss := strings.Split(strings.TrimSpace(profiles), ",")
- for s := range strings.SplitSeq(val, ",") {
- if slices.Contains(ss, s) {
- return true, nil
- }
- }
- return false, nil
- }))
-}
-
-// TypeAndName returns the type and name of the bean.
-func (d *BeanDefinition) TypeAndName() (reflect.Type, string) {
- return d.Type(), d.Name()
-}
-
-// String returns a string representation of the bean.
-func (d *BeanDefinition) String() string {
- return fmt.Sprintf("name=%s %s", d.name, d.fileLine)
-}
-
-// NewBean creates a new bean definition. When registering a normal function,
-// use reflect.ValueOf(fn) to avoid conflicts with constructors.
-func NewBean(objOrCtor any, ctorArgs ...gs.Arg) *gs.BeanDefinition {
-
- var f *gs_arg.Callable
- var v reflect.Value
- var fromValue bool
- var name string
- var cond gs.Condition
-
- switch i := objOrCtor.(type) {
- case reflect.Value:
- fromValue = true
- v = i
- default:
- v = reflect.ValueOf(i)
- }
-
- t := v.Type()
- if !util.IsBeanType(t) {
- panic("bean must be ref type")
- }
-
- // Ensure the value is valid and not nil
- if !v.IsValid() || v.IsNil() {
- panic("bean can't be nil")
- }
-
- // 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.Sprintf("constructor should be %s or %s", t1, t2))
- }
-
- // Bind the constructor arguments
- var err error
- f, err = gs_arg.NewCallable(objOrCtor, ctorArgs)
- if err != nil {
- panic(err)
- }
-
- 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) {
- v = v.Elem()
- }
-
- t = v.Type()
- if !util.IsBeanType(t) {
- panic("bean must be ref type")
- }
-
- // Extract function name for naming the bean
- fnPtr := reflect.ValueOf(objOrCtor).Pointer()
- fnInfo := runtime.FuncForPC(fnPtr)
- funcName := fnInfo.Name()
- name = funcName[strings.LastIndex(funcName, "/")+1:]
- name = name[strings.Index(name, ".")+1:]
- 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 {
- var s gs.BeanSelector = gs.BeanSelectorImpl{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.OnBeanSelector(s)
- }
- }
-
- // Extract the final type name for bean naming
- if name == "" {
- s := strings.Split(t.String(), ".")
- name = strings.TrimPrefix(s[len(s)-1], "*")
- }
-
- d := makeBean(t, v, f, name)
- if cond != nil {
- d.SetCondition(cond)
- }
- return gs.NewBeanDefinition(d)
-}
-
-
-
/*
- * 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
-
-import (
- "fmt"
- "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"
-)
-
-/********************************* OnFunc ************************************/
-
-// 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 func(ctx gs.CondContext) (bool, error)
-}
-
-// OnFunc creates a Conditional that evaluates using a custom function.
-func OnFunc(fn func(ctx gs.CondContext) (bool, error)) gs.Condition {
- return &onFunc{fn: fn}
-}
-
-// Matches checks if the condition is met according to the provided context.
-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
-}
-
-func (c *onFunc) String() string {
- _, _, fnName := util.FileLine(c.fn)
- return fmt.Sprintf("OnFunc(fn=%s)", fnName)
-}
-
-/******************************* 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 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 // The name of the property to check.
- matchIfMissing bool // Whether to match if the property is missing.
- havingValue any // The expected value or expression to match.
-}
-
-// 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
-}
-
-// Matches checks if the condition is met according to the provided context.
-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 the expected value is nil, the condition is always true.
- if c.havingValue == nil {
- return true, nil
- }
-
- havingValue := c.havingValue.(string)
-
- // Retrieve the property's value and compare it with the expected value.
- val := ctx.Prop(c.name)
- if !strings.HasPrefix(havingValue, "expr:") {
- return val == havingValue, nil
- }
-
- // Evaluate the expression and return the result.
- ok, err := EvalExpr(havingValue[5:], val)
- if err != nil {
- return false, errutil.WrapError(err, "condition matches error: %s", c)
- }
- return ok, nil
-}
-
-func (c *onProperty) String() string {
- var sb strings.Builder
- sb.WriteString("OnProperty(name=")
- sb.WriteString(c.name)
- if c.havingValue != nil {
- sb.WriteString(", havingValue=")
- sb.WriteString(c.havingValue.(string))
- }
- if c.matchIfMissing {
- sb.WriteString(", matchIfMissing")
- }
- sb.WriteString(")")
- return sb.String()
-}
-
-/*************************** OnMissingProperty *******************************/
-
-// 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.
-}
-
-// OnMissingProperty creates a condition that matches if the specified property is missing.
-func OnMissingProperty(name string) gs.Condition {
- return &onMissingProperty{name: name}
-}
-
-// Matches checks if the condition is met according to the provided context.
-func (c *onMissingProperty) Matches(ctx gs.CondContext) (bool, error) {
- return !ctx.Has(c.name), nil
-}
-
-func (c *onMissingProperty) String() string {
- return fmt.Sprintf("OnMissingProperty(name=%s)", c.name)
-}
-
-/********************************* OnBean ************************************/
-
-// 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.BeanSelector // The selector used to match beans in the context.
-}
-
-// OnBean creates a condition that evaluates to true if at least one bean
-// matches the specified type and name.
-func OnBean[T any](name ...string) gs.Condition {
- return &onBean{s: gs.BeanSelectorFor[T](name...)}
-}
-
-// OnBeanSelector creates a condition that evaluates to true if at least one
-// bean matches the provided selector.
-func OnBeanSelector(s gs.BeanSelector) gs.Condition {
- return &onBean{s: s}
-}
-
-// Matches checks if the condition is met according to the provided context.
-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 (c *onBean) String() string {
- return fmt.Sprintf("OnBean(selector=%s)", c.s)
-}
-
-/***************************** OnMissingBean *********************************/
-
-// 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.BeanSelector // The selector used to find beans.
-}
-
-// OnMissingBean creates a condition that evaluates to true if no beans match
-// the specified type and name.
-func OnMissingBean[T any](name ...string) gs.Condition {
- return &onMissingBean{s: gs.BeanSelectorFor[T](name...)}
-}
-
-// OnMissingBeanSelector creates a condition that evaluates to true if no beans
-// match the provided selector.
-func OnMissingBeanSelector(s gs.BeanSelector) gs.Condition {
- return &onMissingBean{s: s}
-}
-
-// Matches checks if the condition is met according to the provided context.
-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 len(beans) == 0, nil
-}
-
-func (c *onMissingBean) String() string {
- return fmt.Sprintf("OnMissingBean(selector=%s)", c.s)
-}
-
-/***************************** OnSingleBean **********************************/
-
-// 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.BeanSelector // The selector used to find beans.
-}
-
-// OnSingleBean creates a condition that evaluates to true if exactly one bean
-// matches the specified type and name.
-func OnSingleBean[T any](name ...string) gs.Condition {
- return &onSingleBean{s: gs.BeanSelectorFor[T](name...)}
-}
-
-// OnSingleBeanSelector creates a condition that evaluates to true if exactly
-// one bean matches the provided selector.
-func OnSingleBeanSelector(s gs.BeanSelector) gs.Condition {
- return &onSingleBean{s: s}
-}
-
-// Matches checks if the condition is met according to the provided context.
-func (c *onSingleBean) 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) == 1, nil
-}
-
-func (c *onSingleBean) String() string {
- return fmt.Sprintf("OnSingleBean(selector=%s)", c.s)
-}
-
-/***************************** OnExpression **********************************/
-
-// 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.
-}
-
-// 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}
-}
-
-// Matches checks if the condition is met according to the provided context.
-func (c *onExpression) Matches(ctx gs.CondContext) (bool, error) {
- err := util.UnimplementedMethod
- return false, errutil.WrapError(err, "condition matches error: %s", c)
-}
-
-func (c *onExpression) String() string {
- return fmt.Sprintf("OnExpression(expression=%s)", c.expression)
-}
-
-/********************************** Not ***************************************/
-
-// 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.
-}
-
-// Not creates a condition that inverts the result of the provided condition.
-func Not(c gs.Condition) gs.Condition {
- return &onNot{c: c}
-}
-
-// Matches checks if the condition is met according to the provided context.
-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)
- }
- return !ok, nil
-}
-
-func (c *onNot) String() string {
- return fmt.Sprintf("Not(%s)", c.c)
-}
-
-/********************************** Or ***************************************/
-
-// 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.
-}
-
-// 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 &onOr{conditions: conditions}
-}
-
-// Matches checks if the condition is met according to the provided context.
-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
-}
-
-func (g *onOr) String() string {
- return FormatGroup("Or", g.conditions)
-}
-
-/********************************* And ***************************************/
-
-// 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.
-}
-
-// 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}
-}
-
-// Matches checks if the condition is met according to the provided context.
-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
-}
-
-func (g *onAnd) String() string {
- return FormatGroup("And", g.conditions)
-}
-
-/********************************** None *************************************/
-
-// 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.
-}
-
-// 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}
-}
-
-// Matches checks if the condition is met according to the provided context.
-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
-}
-
-func (g *onNone) String() string {
- return FormatGroup("None", g.conditions)
-}
-
-/******************************* utilities ***********************************/
-
-// 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()
-}
-
-
-
/*
- * 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
-
-import (
- "fmt"
- "maps"
-
- "github.com/expr-lang/expr"
-)
-
-// funcMap stores registered functions that can be referenced in expressions.
-// These functions are available for use in all expressions evaluated by EvalExpr.
-var funcMap = map[string]any{}
-
-// RegisterExpressFunc registers a function under the given name, making it available
-// for use in expressions evaluated by EvalExpr. Functions must be registered before
-// they are referenced in any expression.
-func RegisterExpressFunc(name string, fn any) {
- funcMap[name] = fn
-}
-
-// EvalExpr evaluates a boolean expression using the provided value as the "$" variable.
-// `input` is a boolean expression string to evaluate, it must return a boolean result.
-// `val` is a string value accessible as "$" within the expression context.
-func EvalExpr(input string, val string) (bool, error) {
- env := map[string]any{"$": val}
- maps.Copy(env, funcMap)
- r, err := expr.Eval(input, env)
- if err != nil {
- return false, fmt.Errorf("eval %q returns error, %w", input, err)
- }
- ret, ok := r.(bool)
- if !ok {
- return false, fmt.Errorf("eval %q doesn't return bool value", input)
- }
- return ret, nil
-}
-
-
-
/*
- * 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_conf
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/go-spring/spring-core/conf"
-)
-
-// CommandArgsPrefix defines the environment variable name used to override
-// the default option prefix. This allows users to customize the prefix used
-// for command-line options if needed.
-const CommandArgsPrefix = "GS_ARGS_PREFIX"
-
-// 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 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.MutableProperties) 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
- }
-
- cmdArgs := os.Args[1:]
- n := len(cmdArgs)
- for i := range n {
- if cmdArgs[i] == option {
- if i+1 >= n {
- return fmt.Errorf("cmd option %s needs arg", option)
- }
- next := cmdArgs[i+1]
- ss := strings.SplitN(next, "=", 2)
- if len(ss) == 1 {
- ss = append(ss, "true")
- }
- if err := out.Set(ss[0], ss[1]); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-
-
/*
- * 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_conf
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/go-spring/spring-core/conf"
- "github.com/go-spring/spring-core/util/sysconf"
-)
-
-// osStat only for test.
-var osStat = os.Stat
-
-// PropertyCopier defines the interface for copying properties.
-type PropertyCopier interface {
- CopyTo(out *conf.MutableProperties) error
-}
-
-// NamedPropertyCopier defines the interface for copying properties with a name.
-type NamedPropertyCopier struct {
- PropertyCopier
- Name string
-}
-
-// NewNamedPropertyCopier creates a new instance of NamedPropertyCopier.
-func NewNamedPropertyCopier(name string, p PropertyCopier) *NamedPropertyCopier {
- return &NamedPropertyCopier{PropertyCopier: p, Name: name}
-}
-
-func (c *NamedPropertyCopier) CopyTo(out *conf.MutableProperties) error {
- if c.PropertyCopier != nil {
- return c.PropertyCopier.CopyTo(out)
- }
- return nil
-}
-
-/******************************** AppConfig **********************************/
-
-// AppConfig represents a layered application configuration.
-type AppConfig struct {
- LocalFile *PropertySources // Configuration sources from local files.
- RemoteFile *PropertySources // Configuration sources from remote files.
- RemoteProp conf.Properties // Remote properties.
- Environment *Environment // Environment variables as configuration source.
- CommandArgs *CommandArgs // Command line arguments as configuration source.
-}
-
-// NewAppConfig creates a new instance of AppConfig.
-func NewAppConfig() *AppConfig {
- return &AppConfig{
- LocalFile: NewPropertySources(ConfigTypeLocal, "app"),
- RemoteFile: NewPropertySources(ConfigTypeRemote, "app"),
- Environment: NewEnvironment(),
- CommandArgs: NewCommandArgs(),
- }
-}
-
-func merge(sources ...PropertyCopier) (conf.Properties, error) {
- out := conf.New()
- for _, s := range sources {
- if s != nil {
- if err := s.CopyTo(out); err != nil {
- return nil, err
- }
- }
- }
- return out, nil
-}
-
-// Refresh merges all layers of configurations into a read-only properties.
-func (c *AppConfig) Refresh() (conf.Properties, error) {
- p, err := merge(
- NewNamedPropertyCopier("sys", sysconf.Clone()),
- NewNamedPropertyCopier("env", c.Environment),
- NewNamedPropertyCopier("cmd", c.CommandArgs),
- )
- if err != nil {
- return nil, err
- }
-
- localFiles, err := c.LocalFile.loadFiles(p)
- if err != nil {
- return nil, err
- }
-
- remoteFiles, err := c.RemoteFile.loadFiles(p)
- if err != nil {
- return nil, err
- }
-
- var sources []PropertyCopier
- sources = append(sources, NewNamedPropertyCopier("sys", sysconf.Clone()))
- for _, file := range localFiles {
- sources = append(sources, file)
- }
- for _, file := range remoteFiles {
- sources = append(sources, file)
- }
- sources = append(sources, NewNamedPropertyCopier("remote", c.RemoteProp))
- sources = append(sources, NewNamedPropertyCopier("env", c.Environment))
- sources = append(sources, NewNamedPropertyCopier("cmd", c.CommandArgs))
-
- return merge(sources...)
-}
-
-/******************************** BootConfig *********************************/
-
-// BootConfig represents a layered boot configuration.
-type BootConfig struct {
- LocalFile *PropertySources // Configuration sources from local files.
- Environment *Environment // Environment variables as configuration source.
- CommandArgs *CommandArgs // Command line arguments as configuration source.
-}
-
-// NewBootConfig creates a new instance of BootConfig.
-func NewBootConfig() *BootConfig {
- return &BootConfig{
- LocalFile: NewPropertySources(ConfigTypeLocal, "boot"),
- Environment: NewEnvironment(),
- CommandArgs: NewCommandArgs(),
- }
-}
-
-// Refresh merges all layers of configurations into a read-only properties.
-func (c *BootConfig) Refresh() (conf.Properties, error) {
- p, err := merge(
- NewNamedPropertyCopier("sys", sysconf.Clone()),
- NewNamedPropertyCopier("env", c.Environment),
- NewNamedPropertyCopier("cmd", c.CommandArgs),
- )
- if err != nil {
- return nil, err
- }
-
- localFiles, err := c.LocalFile.loadFiles(p)
- if err != nil {
- return nil, err
- }
-
- var sources []PropertyCopier
- sources = append(sources, NewNamedPropertyCopier("sys", sysconf.Clone()))
- for _, file := range localFiles {
- sources = append(sources, file)
- }
- sources = append(sources, NewNamedPropertyCopier("env", c.Environment))
- sources = append(sources, NewNamedPropertyCopier("cmd", c.CommandArgs))
-
- return merge(sources...)
-}
-
-/****************************** PropertySources ******************************/
-
-// ConfigType defines the type of configuration: local or remote.
-type ConfigType string
-
-const (
- ConfigTypeLocal ConfigType = "local"
- ConfigTypeRemote ConfigType = "remote"
-)
-
-// PropertySources is a collection of configuration files.
-type PropertySources struct {
- configType ConfigType // Type of the configuration (local or remote).
- configName string // Name of the configuration.
- extraDirs []string // Extra directories to be included in the configuration.
- extraFiles []string // Extra files to be included in the configuration.
-}
-
-// NewPropertySources creates a new instance of PropertySources.
-func NewPropertySources(configType ConfigType, configName string) *PropertySources {
- return &PropertySources{
- configType: configType,
- configName: configName,
- }
-}
-
-// Reset resets all the extra files.
-func (p *PropertySources) Reset() {
- p.extraFiles = nil
- p.extraDirs = nil
-}
-
-// AddDir adds a or more than one extra directories.
-func (p *PropertySources) AddDir(dirs ...string) {
- for _, d := range dirs {
- info, err := osStat(d)
- if err != nil {
- if !os.IsNotExist(err) {
- panic(err)
- }
- continue
- }
- if !info.IsDir() {
- panic("should be a directory")
- }
- }
- p.extraDirs = append(p.extraDirs, dirs...)
-}
-
-// AddFile adds a or more than one extra files.
-func (p *PropertySources) AddFile(files ...string) {
- for _, f := range files {
- info, err := osStat(f)
- if err != nil {
- if !os.IsNotExist(err) {
- panic(err)
- }
- continue
- }
- if info.IsDir() {
- panic("should be a file")
- }
- }
- p.extraFiles = append(p.extraFiles, files...)
-}
-
-// getDefaultDir returns the default configuration directory based on the configuration type.
-func (p *PropertySources) getDefaultDir(resolver conf.Properties) (configDir string, err error) {
- switch p.configType {
- case ConfigTypeLocal:
- return resolver.Resolve("${spring.app.config-local.dir:=./conf}")
- case ConfigTypeRemote:
- return resolver.Resolve("${spring.app.config-remote.dir:=./conf/remote}")
- default:
- return "", fmt.Errorf("unknown config type: %s", p.configType)
- }
-}
-
-// getFiles returns the list of configuration files based on the configuration directory and active profiles.
-func (p *PropertySources) getFiles(dir string, resolver conf.Properties) (_ []string, err error) {
-
- files := []string{
- fmt.Sprintf("%s/%s.properties", dir, p.configName),
- fmt.Sprintf("%s/%s.yaml", dir, p.configName),
- fmt.Sprintf("%s/%s.toml", dir, p.configName),
- fmt.Sprintf("%s/%s.json", dir, p.configName),
- }
-
- activeProfiles, err := resolver.Resolve("${spring.profiles.active:=}")
- if err != nil {
- return nil, err
- }
-
- if activeProfiles = strings.TrimSpace(activeProfiles); activeProfiles != "" {
- for s := range strings.SplitSeq(activeProfiles, ",") {
- if s = strings.TrimSpace(s); s != "" {
- files = append(files, []string{
- fmt.Sprintf("%s/%s-%s.properties", dir, p.configName, s),
- fmt.Sprintf("%s/%s-%s.yaml", dir, p.configName, s),
- fmt.Sprintf("%s/%s-%s.toml", dir, p.configName, s),
- fmt.Sprintf("%s/%s-%s.json", dir, p.configName, s),
- }...)
- }
- }
- }
- return files, nil
-}
-
-// loadFiles loads all configuration files and returns them as a list of Properties.
-func (p *PropertySources) loadFiles(resolver conf.Properties) ([]PropertyCopier, error) {
-
- defaultDir, err := p.getDefaultDir(resolver)
- if err != nil {
- return nil, err
- }
- dirs := append([]string{defaultDir}, p.extraDirs...)
-
- var files []string
- for _, dir := range dirs {
- var temp []string
- temp, err = p.getFiles(dir, resolver)
- if err != nil {
- return nil, err
- }
- files = append(files, temp...)
- }
- files = append(files, p.extraFiles...)
-
- var ret []PropertyCopier
- for _, s := range files {
- filename, err := resolver.Resolve(s)
- if err != nil {
- return nil, err
- }
- c, err := conf.Load(filename)
- if err != nil {
- if os.IsNotExist(err) {
- continue
- }
- return nil, err
- }
- ret = append(ret, NewNamedPropertyCopier(filename, c))
- }
- return ret, nil
-}
-
-
-
/*
- * 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_conf
-
-import (
- "os"
- "strings"
-
- "github.com/go-spring/spring-core/conf"
-)
-
-// Environment represents the environment configuration.
-type Environment struct{}
-
-// NewEnvironment initializes a new instance of Environment.
-func NewEnvironment() *Environment {
- return &Environment{}
-}
-
-// CopyTo add environment variables that matches IncludeEnvPatterns and
-// exclude environment variables that matches ExcludeEnvPatterns.
-func (c *Environment) CopyTo(p *conf.MutableProperties) error {
- environ := os.Environ()
- if len(environ) == 0 {
- return nil
- }
- const prefix = "GS_"
- for _, env := range environ {
- ss := strings.SplitN(env, "=", 2)
- k, v := ss[0], ""
- if len(ss) > 1 {
- v = ss[1]
- }
- var propKey string
- if strings.HasPrefix(k, prefix) {
- propKey = strings.TrimPrefix(k, prefix)
- propKey = strings.ReplaceAll(propKey, "_", ".")
- propKey = strings.ToLower(propKey)
- } else {
- propKey = k
- }
- if err := p.Set(propKey, v); err != nil {
- return err
- }
- }
- return nil
-}
-
-
-
/*
- * 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
-
-import (
- "github.com/go-spring/spring-core/conf"
- "github.com/go-spring/spring-core/gs/internal/gs_core/injecting"
- "github.com/go-spring/spring-core/gs/internal/gs_core/resolving"
-)
-
-type Container struct {
- *resolving.Resolving
- *injecting.Injecting
-}
-
-// New creates a IoC container.
-func New() *Container {
- return &Container{
- Resolving: resolving.New(),
- }
-}
-
-// Refresh initializes and wires all beans in the container.
-func (c *Container) Refresh(p conf.Properties) error {
- if err := c.Resolving.Refresh(p); err != nil {
- return err
- }
- c.Injecting = injecting.New(p)
- if err := c.Injecting.Refresh(c.Beans()); err != nil {
- return err
- }
- c.Resolving = nil
- return nil
-}
-
-
-
/*
- * 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 injecting
-
-import (
- "bytes"
- "container/list"
- "errors"
- "fmt"
- "reflect"
- "slices"
- "sort"
- "strings"
- "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_bean"
- "github.com/go-spring/spring-core/gs/internal/gs_dync"
- "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"
- "github.com/spf13/cast"
-)
-
-// BeanRuntime defines an interface for runtime bean information.
-type BeanRuntime interface {
- Name() string
- Type() reflect.Type
- Value() reflect.Value
- Interface() any
- Callable() *gs_arg.Callable
- Status() gs_bean.BeanStatus
- String() string
-}
-
-// refreshState represents the state of a refresh operation.
-type refreshState int
-
-const (
- RefreshDefault = refreshState(iota) // Not refreshed yet
- Refreshing // Currently refreshing
- Refreshed // Successfully refreshed
-)
-
-// Injecting defines a bean injection container.
-type Injecting struct {
- p *gs_dync.Properties
- beansByName map[string][]BeanRuntime // 用于查找未导出接口
- beansByType map[reflect.Type][]BeanRuntime
- destroyers []func()
-}
-
-// New creates a new Injecting instance.
-func New(p conf.Properties) *Injecting {
- return &Injecting{
- p: gs_dync.New(p),
- }
-}
-
-// RefreshProperties refreshes the properties of the container.
-func (c *Injecting) RefreshProperties(p conf.Properties) error {
- return c.p.Refresh(p)
-}
-
-// Refresh refreshes the container with the given beans.
-func (c *Injecting) Refresh(beans []*gs_bean.BeanDefinition) (err error) {
- allowCircularReferences := cast.ToBool(c.p.Data().Get("spring.allow-circular-references"))
- forceAutowireIsNullable := cast.ToBool(c.p.Data().Get("spring.force-autowire-is-nullable"))
-
- // registers all beans
- c.beansByName = make(map[string][]BeanRuntime)
- c.beansByType = make(map[reflect.Type][]BeanRuntime)
- for _, b := range beans {
- 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 := NewStack()
- defer func() {
- if err != nil || len(stack.beans) > 0 {
- err = fmt.Errorf("%s ↩\n%s", err, stack.Path())
- syslog.Errorf("%s", err.Error())
- }
- }()
-
- r := &Injector{
- state: RefreshDefault,
- p: c.p,
- beansByName: c.beansByName,
- beansByType: c.beansByType,
- forceAutowireIsNullable: forceAutowireIsNullable,
- }
-
- // injects all beans
- r.state = Refreshing
- for _, b := range beans {
- if err = r.wireBean(b, stack); err != nil {
- return err
- }
- }
- r.state = Refreshed
-
- if allowCircularReferences {
- // processes the bean fields that are marked for lazy injection.
- for _, f := range stack.lazyFields {
- tag := strings.TrimSuffix(f.tag, ",lazy")
- if err = r.autowire(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("found circular autowire")
- }
-
- c.destroyers = stack.getSortedDestroyers()
-
- forceClean := cast.ToBool(c.p.Data().Get("spring.force-clean"))
- if !testing.Testing() || forceClean {
- if c.p.ObjectsCount() == 0 {
- c.p = nil
- }
- c.beansByName = nil
- c.beansByType = nil
- return nil
- }
-
- c.beansByName = make(map[string][]BeanRuntime)
- c.beansByType = make(map[reflect.Type][]BeanRuntime)
- for _, b := range beans {
- 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)
- }
- }
- return nil
-}
-
-// Wire injects dependencies into the given object.
-func (c *Injecting) Wire(obj any) error {
- r := &Injector{
- state: Refreshed,
- p: gs_dync.New(c.p.Data()),
- beansByName: c.beansByName,
- beansByType: c.beansByType,
- forceAutowireIsNullable: true,
- }
- t := reflect.TypeOf(obj)
- v := reflect.ValueOf(obj)
- return r.wireBeanValue(v, t, NewStack())
-}
-
-// Close closes the container and cleans up resources.
-func (c *Injecting) Close() {
- for _, f := range slices.Backward(c.destroyers) {
- f()
- }
-}
-
-type Injector struct {
- state refreshState
- p *gs_dync.Properties
- beansByName map[string][]BeanRuntime
- beansByType map[reflect.Type][]BeanRuntime
- forceAutowireIsNullable bool
-}
-
-// findBeans finds beans based on a given selector.
-func (c *Injector) findBeans(s gs.BeanSelector) []BeanRuntime {
- t, name := s.TypeAndName()
- var beans []BeanRuntime
- if t != nil {
- beans = c.beansByType[t]
- }
- if name != "" {
- var ret []BeanRuntime
- for _, b := range beans {
- if name == b.Name() {
- ret = append(ret, b)
- }
- }
- beans = ret
- }
- return beans
-}
-
-// 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)
- b.WriteString(tag.beanName)
- if tag.nullable {
- b.WriteString("?")
- }
- 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 {
- buf.WriteString(tag.String())
- if i < len(tags)-1 {
- buf.WriteByte(',')
- }
- }
- return buf.String()
-}
-
-// parseWireTag parses a wire tag string and returns a wireTag struct.
-func parseWireTag(str string) (tag WireTag) {
- if str != "" {
- if n := len(str) - 1; str[n] == '?' {
- tag.beanName = str[:n]
- tag.nullable = true
- } else {
- tag.beanName = str
- }
- }
- return
-}
-
-// getSingleBean retrieves the bean corresponding to the specified tag and assigns it to `v`.
-// `v` should be an uninitialized value.
-func (c *Injector) getBean(t reflect.Type, tag WireTag, stack *Stack) (BeanRuntime, 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 foundBeans []BeanRuntime
- // Iterate through all beans of the given type and match against the tag.
- for _, b := range c.beansByType[t] {
- if tag.beanName == "" || tag.beanName == b.Name() {
- foundBeans = append(foundBeans, b)
- }
- }
-
- // 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.Type().AssignableTo(t) {
- continue
- }
- if !slices.Contains(foundBeans, b) {
- foundBeans = append(foundBeans, b)
- syslog.Warnf("you should call Export() on %s", b)
- }
- }
- }
-
- // 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 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)
- }
-
- // Retrieve the single matching bean.
- b := foundBeans[0]
- if c.state == Refreshing {
- if err := c.wireBean(b.(*gs_bean.BeanDefinition), stack); err != nil {
- return nil, err
- }
- }
- return b, nil
-}
-
-// getMultiBeans collects beans into the given slice or map value `v`.
-// It supports dependency injection by resolving matching beans based on tags.
-func (c *Injector) getBeans(t reflect.Type, tags []WireTag, nullable bool, stack *Stack) ([]BeanRuntime, error) {
-
- et := t.Elem()
- if !util.IsBeanInjectionTarget(et) {
- return nil, fmt.Errorf("%s is not a valid receiver type", t.String())
- }
-
- beans := c.beansByType[et]
-
- // Process bean tags to filter and order beans
- if len(tags) > 0 {
- var (
- anyBeans []int
- afterAny []int
- beforeAny []int
- )
- foundAny := false
- for _, item := range tags {
-
- // 是否遇到了"无序"标记
- if item.beanName == "*" {
- if foundAny {
- return nil, fmt.Errorf("more than one * in collection %q", tags)
- }
- foundAny = true
- continue
- }
-
- var founds []int
- for i, b := range beans {
- if item.beanName == b.Name() {
- founds = append(founds, i)
- }
- }
- 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)
- }
-
- if foundAny {
- afterAny = append(afterAny, founds[0])
- } else {
- beforeAny = append(beforeAny, founds[0])
- }
- }
-
- if foundAny {
- temp := append(beforeAny, afterAny...)
- for i := range len(beans) {
- if slices.Contains(temp, i) {
- continue
- }
- anyBeans = append(anyBeans, i)
- }
- }
-
- n := len(beforeAny) + len(anyBeans) + len(afterAny)
- arr := make([]BeanRuntime, 0, n)
- for _, i := range beforeAny {
- arr = append(arr, beans[i])
- }
- for _, i := range anyBeans {
- arr = append(arr, beans[i])
- }
- for _, i := range afterAny {
- arr = append(arr, beans[i])
- }
-
- beans = arr
- }
-
- // Handle empty beans
- if len(beans) == 0 {
- if nullable {
- return nil, nil
- }
- return nil, fmt.Errorf("no beans collected for %q", toWireString(tags))
- }
-
- // Wire the beans based on the current state of the container
- if c.state == Refreshing {
- for _, b := range beans {
- if err := c.wireBean(b.(*gs_bean.BeanDefinition), stack); err != nil {
- return nil, err
- }
- }
- }
- return beans, nil
-}
-
-// autowire performs dependency injection by tag.
-func (c *Injector) autowire(v reflect.Value, str string, stack *Stack) error {
- str, err := c.p.Data().Resolve(str)
- if err != nil {
- return err
- }
- switch v.Kind() {
- case reflect.Map, reflect.Slice, reflect.Array:
- {
- var nullable bool
- var tags []WireTag
- if str != "" {
- nullable = true
- if str != "?" {
- for s := range strings.SplitSeq(str, ",") {
- g := parseWireTag(s)
- tags = append(tags, g)
- if !g.nullable {
- nullable = false
- }
- }
- }
- }
- if c.forceAutowireIsNullable {
- for i := range len(tags) {
- tags[i].nullable = true
- }
- nullable = true
- }
- beans, err := c.getBeans(v.Type(), tags, nullable, stack)
- if err != nil {
- return err
- }
- // 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:
- g := parseWireTag(str)
- if c.forceAutowireIsNullable {
- g.nullable = true
- }
- b, err := c.getBean(v.Type(), g, stack)
- if err != nil {
- return err
- }
- if b != nil {
- v.Set(b.Value())
- }
- return nil
- }
-}
-
-// 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 *Injector) wireBean(b *gs_bean.BeanDefinition, stack *Stack) error {
-
- haveDestroy := false
-
- // Ensure destroy functions are cleaned up in case of failure.
- defer func() {
- if haveDestroy {
- stack.popDestroyer()
- }
- }()
-
- // Record the destroy function for the bean, if it exists.
- if b.Destroy() != nil {
- haveDestroy = true
- stack.pushDestroyer(b)
- }
-
- stack.pushBean(b)
-
- // Detect circular dependency.
- if b.Status() == gs_bean.StatusCreating && b.Callable() != nil {
- if slices.Contains(stack.beans, b) {
- return errors.New("found circular autowire")
- }
- }
-
- // If the bean is already being created, return early.
- if b.Status() >= gs_bean.StatusCreating {
- stack.popBean()
- return nil
- }
-
- // Mark the bean as being created.
- b.SetStatus(gs_bean.StatusCreating)
-
- // Inject dependencies for the current bean.
- for _, s := range b.DependsOn() {
- beans := c.findBeans(s)
- for _, d := range beans {
- 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.StatusCreated)
-
- // Check if the bean has a value and wire it if it does.
- if v.IsValid() && !b.Mocked() {
-
- // Wire the value of the bean.
- err = c.wireBeanValue(v, v.Type(), 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)
- }
- }
- }
-
- // Mark the bean as wired and pop it from the stack.
- b.SetStatus(gs_bean.StatusWired)
- stack.popBean()
- return nil
-}
-
-// getBeanValue retrieves the value of a bean. If it is a constructor bean,
-// it executes the constructor and returns the result.
-func (c *Injector) getBeanValue(b BeanRuntime, stack *Stack) (reflect.Value, error) {
-
- // If the bean has no callable function, return its value directly.
- if b.Callable() == nil {
- return b.Value(), nil
- }
-
- // Call the bean's constructor and handle errors.
- out, err := b.Callable().Call(NewArgContext(c, stack))
- if err != nil {
- if c.forceAutowireIsNullable {
- syslog.Warnf("autowire error: %v", err)
- return reflect.Value{}, nil
- }
- return reflect.Value{}, err
- }
-
- if o := out[len(out)-1]; util.IsErrorType(o.Type()) {
- if i := o.Interface(); i != nil {
- if c.forceAutowireIsNullable {
- syslog.Warnf("autowire error: %v", err)
- return reflect.Value{}, nil
- }
- return reflect.Value{}, i.(error)
- }
- }
-
- // If the return value is of bean type, handle it accordingly.
- if val := out[0]; util.IsBeanType(val.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)
- } else {
- b.Value().Set(val)
- }
- } else {
- 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 v.Kind() == reflect.Interface {
- v = v.Elem()
- }
- return v, nil
-}
-
-// wireBeanValue binds properties and injects dependencies into the value v. v should already be initialized.
-func (c *Injector) wireBeanValue(v reflect.Value, t reflect.Type, stack *Stack) error {
-
- // Dereference pointer types and adjust the target type.
- if v.Kind() == reflect.Ptr {
- v = v.Elem()
- t = t.Elem()
- }
-
- // If v is not a struct type, no injection is needed.
- if v.Kind() != reflect.Struct {
- return nil
- }
-
- typeName := t.Name()
- 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)
-}
-
-// wireStruct performs dependency injection for a struct.
-func (c *Injector) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindParam, stack *Stack) error {
- // Loop through each field of the struct.
- for i := range t.NumField() {
- ft := t.Field(i)
- fv := v.Field(i)
-
- // If the field is unexported, try to patch it.
- if !fv.CanInterface() {
- fv = util.PatchValue(fv)
- }
-
- fieldPath := opt.Path + "." + ft.Name
-
- // 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 {
- if err := c.autowire(fv, tag, stack); err != nil {
- return fmt.Errorf("%q wired error: %w", fieldPath, err)
- }
- }
- continue
- }
-
- subParam := conf.BindParam{
- Key: opt.Key,
- 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 {
- // Recursively wire anonymous structs.
- err := c.wireStruct(fv, ft.Type, subParam, stack)
- if err != nil {
- return err
- }
- } else {
- // Refresh field value if needed.
- err := c.p.RefreshField(fv.Addr(), subParam)
- if err != nil {
- return err
- }
- }
- 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 {
- return err
- }
- }
- }
- return nil
-}
-
-// destroyer stores beans with destroy functions and their call order.
-type destroyer struct {
- current *gs_bean.BeanDefinition // The current bean being processed.
- depends []*gs_bean.BeanDefinition // Beans that must be destroyed before the current bean.
-}
-
-// after adds a bean to the later list, ensuring it is destroyed after the current bean.
-func (d *destroyer) isDependOn(b *gs_bean.BeanDefinition) bool {
- return slices.Contains(d.depends, b)
-}
-
-// after adds a bean to the later list, ensuring it is destroyed after the current bean.
-func (d *destroyer) dependOn(b *gs_bean.BeanDefinition) {
- if d.isDependOn(b) {
- return
- }
- d.depends = append(d.depends, b)
-}
-
-// 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.
-}
-
-// Stack tracks the injection path of beans and their destroyers.
-type Stack struct {
- beans []*gs_bean.BeanDefinition
- lazyFields []LazyField
- destroyers *list.List
- destroyerMap map[gs.BeanID]*destroyer
-}
-
-// NewStack creates a new Stack instance.
-func NewStack() *Stack {
- return &Stack{
- destroyers: list.New(),
- destroyerMap: make(map[gs.BeanID]*destroyer),
- }
-}
-
-// pushBean adds a bean to the injection path.
-func (s *Stack) pushBean(b *gs_bean.BeanDefinition) {
- syslog.Debugf("push %s %s", b, b.Status())
- s.beans = append(s.beans, b)
-}
-
-// popBean removes the last bean from the injection path.
-func (s *Stack) popBean() {
- n := len(s.beans)
- b := s.beans[n-1]
- s.beans[n-1] = nil
- s.beans = s.beans[:n-1]
- syslog.Debugf("pop %s %s", b, b.Status())
-}
-
-// Path returns the injection path as a string.
-func (s *Stack) Path() (path string) {
- if len(s.beans) == 0 {
- return ""
- }
- for _, b := range s.beans {
- path += fmt.Sprintf("=> %s ↩\n", b)
- }
- return path[:len(path)-1] // Remove the trailing newline.
-}
-
-// pushDestroyer tracks a bean with a destroy function, ensuring no duplicates.
-func (s *Stack) pushDestroyer(b *gs_bean.BeanDefinition) {
- beanID := gs.BeanID{Name: b.Name(), Type: b.Type()}
- d, ok := s.destroyerMap[beanID]
- if !ok {
- d = &destroyer{current: b}
- s.destroyerMap[beanID] = d
- }
- if i := s.destroyers.Back(); i != nil {
- d.dependOn(i.Value.(*gs_bean.BeanDefinition))
- }
- s.destroyers.PushBack(b)
-}
-
-// popDestroyer removes the last bean from the destroyer stack.
-func (s *Stack) popDestroyer() {
- s.destroyers.Remove(s.destroyers.Back())
-}
-
-// getBeforeDestroyers retrieves destroyers that should be processed before a given one for sorting purposes.
-func getBeforeDestroyers(destroyers *list.List, i any) *list.List {
- d := i.(*destroyer)
- result := list.New()
- for e := destroyers.Front(); e != nil; e = e.Next() {
- c := e.Value.(*destroyer)
- if d.isDependOn(c.current) {
- result.PushBack(c)
- }
- }
- return result
-}
-
-// getSortedDestroyers sorts beans with destroy functions by dependency order.
-func (s *Stack) getSortedDestroyers() []func() {
-
- destroy := func(v reflect.Value, fn any) func() {
- return func() {
- fnValue := reflect.ValueOf(fn)
- out := fnValue.Call([]reflect.Value{v})
- if len(out) > 0 && !out[0].IsNil() {
- syslog.Errorf("%s", out[0].Interface().(error).Error())
- }
- }
- }
-
- destroyers := list.New()
- for _, d := range s.destroyerMap {
- destroyers.PushBack(d)
- }
- // the injection process should first discover cyclic dependencies
- destroyers, _ = gs_util.TripleSort(destroyers, getBeforeDestroyers)
-
- 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
-}
-
-// ArgContext holds a Container and a Stack to manage dependency injection.
-type ArgContext struct {
- c *Injector
- stack *Stack
-}
-
-// NewArgContext creates a new ArgContext with a given Container and Stack.
-func NewArgContext(c *Injector, stack *Stack) *ArgContext {
- return &ArgContext{c: c, stack: stack}
-}
-
-func (a *ArgContext) Has(key string) bool {
- return a.c.p.Data().Has(key)
-}
-
-func (a *ArgContext) Prop(key string, def ...string) string {
- return a.c.p.Data().Get(key, def...)
-}
-
-func (a *ArgContext) Find(s gs.BeanSelector) ([]gs.CondBean, error) {
- beans := a.c.findBeans(s)
- var ret []gs.CondBean
- for _, bean := range beans {
- ret = append(ret, bean)
- }
- return ret, nil
-}
-
-// Check checks if a given condition matches the container.
-func (a *ArgContext) Check(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.autowire(v, tag, a.stack)
-}
-
-
-
/*
- * Copyright 2025 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 resolving
-
-import (
- "errors"
- "fmt"
- "reflect"
- "regexp"
- "slices"
-
- "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/internal/gs_cond"
- "github.com/go-spring/spring-core/util"
-)
-
-// RefreshState represents the current state of the container.
-type RefreshState int
-
-const (
- RefreshDefault = RefreshState(iota)
- Refreshing
- Refreshed
-)
-
-// BeanGroupFunc defines a function that dynamically registers beans
-// based on configuration properties.
-type BeanGroupFunc func(p conf.Properties) ([]*gs.BeanDefinition, error)
-
-// Resolving manages bean definitions, mocks, and dynamic bean registration functions.
-type Resolving struct {
- state RefreshState // Current refresh state
- mocks []gs.BeanMock // Registered mock beans
- beans []*gs_bean.BeanDefinition // Managed bean definitions
- funcs []BeanGroupFunc // Dynamic bean registration functions
-}
-
-// New creates an empty Resolving instance.
-func New() *Resolving {
- return &Resolving{}
-}
-
-// Beans returns all active bean definitions, excluding deleted ones.
-func (c *Resolving) Beans() []*gs_bean.BeanDefinition {
- var beans []*gs_bean.BeanDefinition
- for _, b := range c.beans {
- if b.Status() == gs_bean.StatusDeleted {
- continue
- }
- beans = append(beans, b)
- }
- return beans
-}
-
-// AddMock adds a mock bean to the container.
-func (c *Resolving) AddMock(mock gs.BeanMock) {
- c.mocks = append(c.mocks, mock)
-}
-
-// Object registers a pre-constructed instance as a bean.
-func (c *Resolving) Object(i any) *gs.RegisteredBean {
- b := gs_bean.NewBean(reflect.ValueOf(i))
- return c.Register(b).Caller(1)
-}
-
-// Provide registers a constructor function to create a bean.
-func (c *Resolving) Provide(ctor any, args ...gs.Arg) *gs.RegisteredBean {
- b := gs_bean.NewBean(ctor, args...)
- return c.Register(b).Caller(1)
-}
-
-// Register adds a bean definition to the container.
-func (c *Resolving) Register(b *gs.BeanDefinition) *gs.RegisteredBean {
- if c.state >= Refreshing {
- panic("container is refreshing or already refreshed")
- }
- bd := b.BeanRegistration().(*gs_bean.BeanDefinition)
- c.beans = append(c.beans, bd)
- return gs.NewRegisteredBean(bd)
-}
-
-// GroupRegister adds a function to dynamically register beans.
-func (c *Resolving) GroupRegister(fn BeanGroupFunc) {
- c.funcs = append(c.funcs, fn)
-}
-
-// Refresh performs the full initialization process of the container.
-// It transitions through several phases:
-// - Executes group functions to register additional beans.
-// - Scans configuration beans and registers their methods as beans.
-// - Applies mock beans to override specific targets.
-// - Resolves all beans based on their conditions.
-// - Validates that no duplicate beans exist.
-func (c *Resolving) Refresh(p conf.Properties) error {
- if c.state != RefreshDefault {
- return errors.New("container is already refreshing or refreshed")
- }
- c.state = Refreshing
-
- if err := c.applyGroupFuncs(p); err != nil {
- return err
- }
-
- if err := c.scanConfigurations(); err != nil {
- return err
- }
-
- if err := c.applyMocks(); err != nil {
- return err
- }
-
- if err := c.resolveBeans(p); err != nil {
- return err
- }
-
- if err := c.checkDuplicateBeans(); err != nil {
- return err
- }
-
- c.state = Refreshed
- return nil
-}
-
-// applyGroupFuncs executes registered group functions to add dynamic beans.
-func (c *Resolving) applyGroupFuncs(p conf.Properties) error {
- for _, fn := range c.funcs {
- beans, err := fn(p)
- if err != nil {
- return err
- }
- for _, b := range beans {
- d := b.BeanRegistration().(*gs_bean.BeanDefinition)
- c.beans = append(c.beans, d)
- }
- }
- return nil
-}
-
-// scanConfigurations processes configuration beans to register their methods as beans.
-func (c *Resolving) scanConfigurations() error {
- for _, b := range c.beans {
- if b.Configuration() == nil {
- continue
- }
- // Check if the configuration bean has a mock override
- var foundMocks []gs.BeanMock
- for _, mock := range c.mocks {
- t, s := mock.Target.TypeAndName()
- if s != "" && s != b.Name() {
- continue
- }
- if t != b.Type() {
- continue
- }
- foundMocks = append(foundMocks, mock)
- }
- if n := len(foundMocks); n > 1 {
- return fmt.Errorf("found duplicate mock bean for '%s'", b.Name())
- } else if n == 1 {
- b.SetMock(foundMocks[0].Object)
- continue
- }
- // Scan methods if no mock is applied
- beans, err := c.scanConfiguration(b)
- if err != nil {
- return err
- }
- c.beans = append(c.beans, beans...)
- }
- return nil
-}
-
-// scanConfiguration inspects the methods of a configuration bean, and for each
-// method that matches the include patterns and not the exclude patterns,
-// registers it as a bean. This enables dynamic bean registration based on method
-// naming conventions or regex.
-func (c *Resolving) scanConfiguration(bd *gs_bean.BeanDefinition) ([]*gs_bean.BeanDefinition, error) {
- var (
- includes []*regexp.Regexp
- excludes []*regexp.Regexp
- )
-
- param := bd.Configuration()
- ss := param.Includes
- if len(ss) == 0 {
- ss = []string{"New.*"}
- }
- for _, s := range ss {
- p, err := regexp.Compile(s)
- if err != nil {
- return nil, err
- }
- includes = append(includes, p)
- }
-
- ss = param.Excludes
- for _, s := range ss {
- p, err := regexp.Compile(s)
- if err != nil {
- return nil, err
- }
- excludes = append(excludes, p)
- }
-
- var ret []*gs_bean.BeanDefinition
- n := bd.Type().NumMethod()
- for i := range n {
- m := bd.Type().Method(i)
- skip := false
- for _, p := range excludes {
- if p.MatchString(m.Name) {
- skip = true
- break
- }
- }
- if skip {
- continue
- }
- for _, p := range includes {
- if !p.MatchString(m.Name) {
- continue
- }
- b := gs_bean.NewBean(m.Func.Interface(), gs.NewBeanDefinition(bd)).
- Name(bd.Name() + "_" + m.Name).
- Condition(gs_cond.OnBeanSelector(bd)).
- BeanRegistration().(*gs_bean.BeanDefinition)
- file, line, _ := util.FileLine(m.Func.Interface())
- b.SetFileLine(file, line)
- ret = append(ret, b)
- break
- }
- }
- return ret, nil
-}
-
-// isBeanMatched checks if a bean matches the target type and name selector.
-func isBeanMatched(t reflect.Type, s string, b *gs_bean.BeanDefinition) bool {
- if s != "" && s != b.Name() {
- return false
- }
- if t != nil && t != b.Type() {
- if !slices.Contains(b.Exports(), t) {
- return false
- }
- }
- return true
-}
-
-// applyMocks overrides target beans with registered mock objects.
-func (c *Resolving) applyMocks() error {
- for _, mock := range c.mocks {
- if err := c.applyMock(mock); err != nil {
- return err
- }
- }
- return nil
-}
-
-// applyMock applies a mock object to its target bean. It ensures that the mock
-// implements all the interfaces that the original bean exported. If multiple
-// matching beans are found, or if the mock doesn't implement required interfaces,
-// an error is returned.
-func (c *Resolving) applyMock(mock gs.BeanMock) error {
- var foundBeans []*gs_bean.BeanDefinition
- vt := reflect.TypeOf(mock.Object)
- t, s := mock.Target.TypeAndName()
-
- for _, b := range c.beans {
- if !isBeanMatched(t, s, b) {
- continue
- }
- // Verify mock implements all exported interfaces
- for _, et := range b.Exports() {
- if !vt.Implements(et) {
- return fmt.Errorf("found unimplemented interface")
- }
- }
- foundBeans = append(foundBeans, b)
- }
- if len(foundBeans) == 0 {
- return nil
- }
- if len(foundBeans) > 1 {
- return fmt.Errorf("found duplicate mocked beans")
- }
- foundBeans[0].SetMock(mock.Object)
- return nil
-}
-
-// resolveBeans evaluates conditions for all beans and marks inactive ones.
-func (c *Resolving) resolveBeans(p conf.Properties) error {
- ctx := &CondContext{p: p, c: c}
- for _, b := range c.beans {
- if err := ctx.resolveBean(b); err != nil {
- return err
- }
- }
- return nil
-}
-
-// checkDuplicateBeans ensures no duplicate type/name combinations exist.
-func (c *Resolving) checkDuplicateBeans() error {
- beansByID := make(map[gs.BeanID]*gs_bean.BeanDefinition)
- for _, b := range c.beans {
- if b.Status() == gs_bean.StatusDeleted {
- continue
- }
- for _, t := range append(b.Exports(), b.Type()) {
- beanID := gs.BeanID{Name: b.Name(), Type: t}
- if d, ok := beansByID[beanID]; ok {
- return fmt.Errorf("found duplicate beans [%s] [%s]", b, d)
- }
- beansByID[beanID] = b
- }
- }
- return nil
-}
-
-// CondContext provides condition evaluation context during resolution.
-type CondContext struct {
- c *Resolving
- p conf.Properties
-}
-
-// resolveBean evaluates a bean's conditions, updating its status accordingly.
-// If any condition fails, the bean is marked as deleted.
-func (c *CondContext) resolveBean(b *gs_bean.BeanDefinition) error {
- if b.Status() >= gs_bean.StatusResolving {
- return 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.StatusDeleted)
- return nil
- }
- }
- b.SetStatus(gs_bean.StatusResolved)
- return nil
-}
-
-// Has checks if a configuration property exists.
-func (c *CondContext) Has(key string) bool {
- return c.p.Has(key)
-}
-
-// Prop retrieves a configuration property with optional default value.
-func (c *CondContext) Prop(key string, def ...string) string {
- return c.p.Get(key, def...)
-}
-
-// Find returns beans matching the selector after resolving their conditions.
-func (c *CondContext) Find(s gs.BeanSelector) ([]gs.CondBean, error) {
- var found []gs.CondBean
- t, name := s.TypeAndName()
- for _, b := range c.c.beans {
- if b.Status() == gs_bean.StatusResolving || b.Status() == gs_bean.StatusDeleted {
- continue
- }
- if !isBeanMatched(t, name, b) {
- continue
- }
- if err := c.resolveBean(b); err != nil {
- return nil, err
- }
- if b.Status() == gs_bean.StatusDeleted {
- continue
- }
- found = append(found, b)
- }
- return found, nil
-}
-
-
-
/*
- * 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_dync
-
-import (
- "encoding/json"
- "reflect"
- "strings"
- "sync"
- "sync/atomic"
-
- "github.com/go-spring/spring-core/conf"
- "github.com/go-spring/spring-core/util"
-)
-
-// refreshable represents an object that can be dynamically refreshed.
-type refreshable interface {
- onRefresh(prop conf.Properties, param conf.BindParam) error
-}
-
-// Listener holds a channel to receive notifications.
-type Listener struct {
- C chan struct{}
-}
-
-// listeners maintains a collection of listeners that can be notified on value updates.
-type listeners struct {
- m sync.Mutex
- a []*Listener
-}
-
-// NewListener creates and registers a new listener.
-func (r *listeners) NewListener() *Listener {
- r.m.Lock()
- defer r.m.Unlock()
- l := &Listener{C: make(chan struct{})}
- r.a = append(r.a, l)
- return l
-}
-
-// notifyAll sends a notification signal to all registered listeners.
-func (r *listeners) notifyAll() {
- r.m.Lock()
- defer r.m.Unlock()
- for _, l := range r.a {
- select {
- case l.C <- struct{}{}:
- default:
- }
- }
-}
-
-// Value represents a thread-safe object that can dynamically refresh its value.
-type Value[T any] struct {
- listeners
- v atomic.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 {
- v, ok := r.v.Load().(T)
- if !ok {
- var zero T
- return zero
- }
- return v
-}
-
-// onRefresh updates the stored value with new properties and notifies listeners.
-func (r *Value[T]) onRefresh(prop conf.Properties, param conf.BindParam) error {
- t := reflect.TypeFor[T]()
- v := reflect.New(t).Elem()
- err := conf.BindValue(prop, v, t, param, nil)
- if err != nil {
- return err
- }
- r.v.Store(v.Interface())
- r.notifyAll()
- return nil
-}
-
-// MarshalJSON serializes the stored value as JSON.
-func (r *Value[T]) MarshalJSON() ([]byte, error) {
- return json.Marshal(r.v.Load())
-}
-
-// refreshObject represents an object bound to dynamic properties that can be refreshed.
-type refreshObject struct {
- target refreshable // The refreshable object.
- param conf.BindParam // Parameters used for refreshing.
-}
-
-// Properties manages dynamic properties and refreshable objects.
-type Properties struct {
- prop conf.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 creates and returns a new Properties instance.
-func New(p conf.Properties) *Properties {
- return &Properties{
- prop: p,
- }
-}
-
-// Data returns the current properties.
-func (p *Properties) Data() conf.Properties {
- p.lock.RLock()
- defer p.lock.RUnlock()
- return p.prop
-}
-
-// 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 updates the properties and refreshes all bound objects as necessary.
-func (p *Properties) Refresh(prop conf.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
-
- oldKeys := make(map[string]struct{})
- for _, k := range old.Keys() {
- oldKeys[k] = struct{}{}
- }
-
- changes := make(map[string]struct{})
- for _, k := range prop.Keys() {
- if _, ok := oldKeys[k]; ok {
- delete(oldKeys, k)
- if old.Get(k) == prop.Get(k) {
- continue
- }
- }
- changes[k] = struct{}{}
- }
- for k := range oldKeys {
- changes[k] = struct{}{}
- }
-
- keys := util.OrderedMapKeys(changes)
- return p.refreshKeys(keys)
-}
-
-// refreshKeys refreshes objects bound by the specified keys.
-func (p *Properties) refreshKeys(keys []string) (err error) {
- 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) { // Check if the key starts with the parameter key.
- continue
- }
- if len(s) == 0 || s[0] == '.' || s[0] == '[' {
- if _, ok := updateIndexes[index]; !ok {
- updateIndexes[index] = o
- }
- }
- }
- }
-
- // Sort and collect objects that need updating.
- updateObjects := make([]*refreshObject, 0, len(updateIndexes))
- {
- ints := util.OrderedMapKeys(updateIndexes)
- for _, k := range ints {
- updateObjects = append(updateObjects, updateIndexes[k])
- }
- }
-
- if len(updateObjects) == 0 {
- return nil
- }
- return p.refreshObjects(updateObjects)
-}
-
-// Errors represents a collection of errors.
-type Errors struct {
- arr []error
-}
-
-// Len returns the number of errors.
-func (e *Errors) Len() int {
- return len(e.arr)
-}
-
-// 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 concatenates all errors into a single string.
-func (e *Errors) Error() string {
- var sb strings.Builder
- for i, err := range e.arr {
- sb.WriteString(err.Error())
- if i < len(e.arr)-1 {
- sb.WriteString("; ")
- }
- }
- return sb.String()
-}
-
-// refreshObjects refreshes all provided objects and aggregates errors.
-func (p *Properties) refreshObjects(objects []*refreshObject) error {
- ret := &Errors{}
- for _, obj := range objects {
- err := obj.target.onRefresh(p.prop, obj.param)
- ret.Append(err)
- }
- if ret.Len() == 0 {
- return nil
- }
- return ret
-}
-
-// filter is used to selectively refresh objects and fields.
-type filter struct {
- *Properties
-}
-
-// Do attempts to refresh a single object if it implements the [refreshable] interface.
-func (f *filter) Do(i any, param conf.BindParam) (bool, error) {
- v, ok := i.(refreshable)
- if !ok {
- return false, nil
- }
- f.objects = append(f.objects, &refreshObject{
- target: v,
- param: param,
- })
- return true, v.onRefresh(f.prop, param)
-}
-
-// RefreshField refreshes a field of a bean, optionally registering it as refreshable.
-func (p *Properties) RefreshField(v reflect.Value, param conf.BindParam) error {
- p.lock.Lock()
- defer p.lock.Unlock()
- f := &filter{Properties: p}
- if v.Kind() == reflect.Ptr {
- ok, err := f.Do(v.Interface(), param)
- if err != nil {
- return err
- }
- if ok {
- return nil
- }
- }
- return conf.BindValue(p.prop, v.Elem(), v.Elem().Type(), param, f)
-}
-
-
-
/*
- * 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 any) *list.List
-
-// 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 any) *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 any, 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 := fn(sorting, current)
-
- // 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
-}
-
-
-
/*
- * Copyright 2025 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
-
-import (
- "net/http"
- "net/http/pprof"
-)
-
-func init() {
- // Registers a SimplePProfServer object to the container.
- Provide(
- NewSimplePProfServer,
- TagArg("${pprof.server.addr:=0.0.0.0:9981}"),
- ).Condition(
- OnProperty(EnableServersProp).HavingValue("true").MatchIfMissing(),
- OnProperty(EnableSimplePProfServerProp).HavingValue("true").MatchIfMissing(),
- ).AsServer()
-}
-
-// SimplePProfServer is a simple pprof server.
-type SimplePProfServer struct {
- *SimpleHttpServer
-}
-
-// NewSimplePProfServer creates a new SimplePProfServer.
-func NewSimplePProfServer(addr string) *SimplePProfServer {
- mux := http.NewServeMux()
- mux.HandleFunc("GET /debug/pprof/", pprof.Index)
- mux.HandleFunc("GET /debug/pprof/cmdline", pprof.Cmdline)
- mux.HandleFunc("GET /debug/pprof/profile", pprof.Profile)
- mux.HandleFunc("GET /debug/pprof/symbol", pprof.Symbol)
- mux.HandleFunc("GET /debug/pprof/trace", pprof.Trace)
- return &SimplePProfServer{
- SimpleHttpServer: NewSimpleHttpServer(mux, SetHttpServerAddr(addr)),
- }
-}
-
-
-
/*
- * Copyright 2025 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
-
-import (
- "strconv"
-
- "github.com/go-spring/spring-core/util/sysconf"
-)
-
-const (
- AllowCircularReferencesProp = "spring.allow-circular-references"
- ForceAutowireIsNullableProp = "spring.force-autowire-is-nullable"
- ActiveProfilesProp = "spring.profiles.active"
- EnableJobsProp = "spring.app.enable-jobs"
- EnableServersProp = "spring.app.enable-servers"
- EnableSimpleHttpServerProp = "spring.enable.simple-http-server"
- EnableSimplePProfServerProp = "spring.enable.simple-pprof-server"
-)
-
-// AllowCircularReferences enables or disables circular references between beans.
-func AllowCircularReferences(enable bool) {
- sysconf.Set(AllowCircularReferencesProp, strconv.FormatBool(enable))
-}
-
-// ForceAutowireIsNullable forces autowire to be nullable.
-func ForceAutowireIsNullable(enable bool) {
- sysconf.Set(ForceAutowireIsNullableProp, strconv.FormatBool(enable))
-}
-
-// SetActiveProfiles sets the active profiles for the app.
-func SetActiveProfiles(profiles string) {
- sysconf.Set(ActiveProfilesProp, profiles)
-}
-
-// EnableJobs enables or disables the app jobs.
-func EnableJobs(enable bool) {
- sysconf.Set(EnableJobsProp, strconv.FormatBool(enable))
-}
-
-// EnableServers enables or disables the app servers.
-func EnableServers(enable bool) {
- sysconf.Set(EnableServersProp, strconv.FormatBool(enable))
-}
-
-// EnableSimpleHttpServer enables or disables the simple HTTP server.
-func EnableSimpleHttpServer(enable bool) {
- sysconf.Set(EnableSimpleHttpServerProp, strconv.FormatBool(enable))
-}
-
-// EnableSimplePProfServer enables or disables the simple pprof server.
-func EnableSimplePProfServer(enable bool) {
- sysconf.Set(EnableSimplePProfServerProp, strconv.FormatBool(enable))
-}
-
-
-
/*
- * 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
-
-import (
- "fmt"
-)
-
-// LineBreak defines the separator used between errors with hierarchical relationships.
-var LineBreak = " << "
-
-// WrapError wraps an existing error, creating a new error with hierarchical relationships.
-func WrapError(err error, format string, args ...any) error {
- msg := fmt.Sprintf(format, args...)
- return fmt.Errorf("%s"+LineBreak+"%w", msg, err)
-}
-
-
-
/*
- * 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 util
-
-import (
- "fmt"
- "reflect"
-
- "github.com/spf13/cast"
-)
-
-// 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]any) map[string]string {
- result := make(map[string]string)
- for key, val := range m {
- FlattenValue(key, val, result)
- }
- return result
-}
-
-// FlattenValue flattens a single value (which can be a map, array, slice,
-// or other types) into the result map.
-func FlattenValue(key string, val any, result map[string]string) {
- if val == nil {
- return
- }
- switch v := reflect.ValueOf(val); v.Kind() {
- case reflect.Map:
- if v.Len() == 0 {
- result[key] = ""
- return
- }
- iter := v.MapRange()
- for iter.Next() {
- mapKey := cast.ToString(iter.Key().Interface())
- mapValue := iter.Value().Interface()
- FlattenValue(key+"."+mapKey, mapValue, result)
- }
- case reflect.Array, reflect.Slice:
- if v.Len() == 0 {
- result[key] = ""
- return
- }
- for i := range v.Len() {
- 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:
- result[key] = cast.ToString(val)
- }
-}
-
-
-
/*
- * 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 provides a safe way to execute goroutines with built-in panic recovery.
-
-In practice, goroutines may panic due to issues like nil pointer dereference or out-of-bounds access.
-However, these panics can be recovered. This package offers a wrapper to safely run goroutines,
-ensuring that any panic is caught and passed to a user-defined `OnPanic` handler.
-
-The `OnPanic` function allows developers to log the panic, report metrics, or perform other
-custom recovery logic, making it easier to manage and observe unexpected failures in concurrent code.
-*/
-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(ctx context.Context, r any) {
- 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(ctx, 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(context.Background(), 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(ctx, r)
- }
- s.err = errors.New("panic occurred")
- }
- }()
- s.val, s.err = f(ctx)
- }()
- return s
-}
-
-
-
/*
- * Copyright 2025 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 util
-
-import (
- "container/list"
-)
-
-// ListOf creates a list of the given items.
-func ListOf[T any](a ...T) *list.List {
- l := list.New()
- for _, i := range a {
- l.PushBack(i)
- }
- return l
-}
-
-// AllOfList returns a slice of all items in the given list.
-func AllOfList[T any](l *list.List) []T {
- if l == nil {
- return nil
- }
- if l.Len() == 0 {
- return nil
- }
- ret := make([]T, 0, l.Len())
- for e := l.Front(); e != nil; e = e.Next() {
- ret = append(ret, e.Value.(T))
- }
- return ret
-}
-
-
-
/*
- * Copyright 2025 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 util
-
-import (
- "cmp"
- "slices"
-)
-
-// MapKeys returns the keys of the map m.
-func MapKeys[M ~map[K]V, K comparable, V any](m M) []K {
- r := make([]K, 0, len(m))
- for k := range m {
- r = append(r, k)
- }
- return r
-}
-
-// OrderedMapKeys returns the keys of the map m in sorted order.
-func OrderedMapKeys[M ~map[K]V, K cmp.Ordered, V any](m M) []K {
- r := MapKeys(m)
- slices.Sort(r)
- return r
-}
-
-
-
/*
- * 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 sysconf provides a unified configuration container for the Go programming language.
-
-In the Go programming language, unlike many other languages,
-the standard library lacks a unified and general-purpose configuration container.
-To address this gap, go-spring introduces a powerful configuration system that supports
-layered configuration management and flexible injection.
-
-So sysconf serves as the fallback configuration container within an application,
-acting as the lowest-level foundation of the configuration system.
-It can be used independently or as a lightweight alternative or supplement to other
-configuration sources such as environment variables, command-line arguments, or configuration files.
-*/
-package sysconf
-
-import (
- "sync"
-
- "github.com/go-spring/spring-core/conf"
- "github.com/go-spring/spring-core/util/syslog"
-)
-
-var (
- prop = conf.New()
- lock sync.Mutex
-)
-
-// Has returns whether the key exists.
-func Has(key string) bool {
- lock.Lock()
- defer lock.Unlock()
- return prop.Has(key)
-}
-
-// Get returns the property of the key.
-func Get(key string) string {
- lock.Lock()
- defer lock.Unlock()
- return prop.Get(key)
-}
-
-// Set sets the property of the key.
-func Set(key string, val string) {
- lock.Lock()
- defer lock.Unlock()
- if err := prop.Set(key, val); err != nil {
- syslog.Errorf("failed to set property key=%s, err=%v", key, err)
- }
-}
-
-// Clear clears all properties.
-func Clear() {
- lock.Lock()
- defer lock.Unlock()
- prop = conf.New()
-}
-
-// Clone copies all properties into another properties.
-func Clone() *conf.MutableProperties {
- lock.Lock()
- defer lock.Unlock()
- p := conf.New()
- err := prop.CopyTo(p)
- _ = err // should no error
- return p
-}
-
-
-
/*
- * 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 provides simplified logging utilities for tracking the execution flow
-of the go-spring framework. It is designed to offer a more convenient interface than
-the standard library's slog package, whose Info, Warn, and related methods can be
-cumbersome to use. Logs produced by this package are typically output to the console.
-*/
-package syslog
-
-import (
- "context"
- "fmt"
- "log"
- "log/slog"
- "os"
- "runtime"
- "time"
-)
-
-func init() {
- log.SetOutput(os.Stdout)
- 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
-}
-
-
-
/*
- * 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 util
-
-import (
- "reflect"
-)
-
-// errorType is the [reflect.Type] of the error interface.
-var errorType = reflect.TypeFor[error]()
-
-// 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 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 true if the provided function type t has no return values.
-func ReturnNothing(t reflect.Type) bool {
- return t.NumOut() == 0
-}
-
-// 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))
-}
-
-// 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 {
- if !IsFuncType(t) {
- return false
- }
- switch t.NumOut() {
- case 1:
- return !IsErrorType(t.Out(0))
- case 2:
- return IsErrorType(t.Out(1))
- default:
- return false
- }
-}
-
-// 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.Float32, reflect.Float64:
- return true
- case reflect.String:
- return true
- case reflect.Bool:
- return true
- default:
- return false
- }
-}
-
-// 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:
- t = t.Elem() // for collection types, check the element type
- default:
- // do nothing
- }
- return IsPrimitiveValueType(t) || t.Kind() == reflect.Struct
-}
-
-// 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:
- return true
- case reflect.Ptr:
- return t.Elem().Kind() == reflect.Struct
- default:
- return false
- }
-}
-
-// 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:
- t = t.Elem() // for collection types, check the element type
- default:
- // do nothing
- }
- return IsBeanType(t)
-}
-
-
-
/*
- * 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 util
-
-import (
- "reflect"
- "runtime"
- "strings"
- "unsafe"
-)
-
-const (
- flagStickyRO = 1 << 5
- flagEmbedRO = 1 << 6
- flagRO = flagStickyRO | flagEmbedRO
-)
-
-// 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")
- ptrFlag := (*uintptr)(unsafe.Pointer(flag.UnsafeAddr()))
- *ptrFlag = *ptrFlag &^ flagRO
- return v
-}
-
-// FuncName returns the function name for a given function.
-func FuncName(fn any) string {
- _, _, fnName := FileLine(fn)
- return fnName
-}
-
-// 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 any) (file string, line int, fnName string) {
-
- fnPtr := reflect.ValueOf(fn).Pointer()
- fnInfo := runtime.FuncForPC(fnPtr)
- file, line = fnInfo.FileLine(fnPtr)
-
- s := fnInfo.Name()
- i := strings.LastIndex(s, "/")
- if i > 0 {
- s = s[i+1:]
- }
-
- // method values are printed as "T.m-fm"
- s = strings.TrimRight(s, "-fm")
- return file, line, s
-}
-
-
-
-
-
-
diff --git a/gs/examples/bookman/main.go b/gs/examples/bookman/main.go
index 4e039397..e01c2ccd 100644
--- a/gs/examples/bookman/main.go
+++ b/gs/examples/bookman/main.go
@@ -54,7 +54,10 @@ func runTest(ctx context.Context) error {
if err != nil {
panic(err)
}
- defer resp.Body.Close()
+ defer func() {
+ err = resp.Body.Close()
+ _ = err
+ }()
fmt.Print(string(b))
time.Sleep(time.Millisecond * 400)
})
diff --git a/gs/examples/bookman/run.sh b/gs/examples/bookman/run.sh
deleted file mode 100644
index 45e07632..00000000
--- a/gs/examples/bookman/run.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-export GOCOVERDIR=.cover
-rm -rf ./.cover/*
-go run -race -cover -covermode=atomic main.go
-go tool covdata textfmt -i=.cover -o ./.cover/cover.txt
-go tool cover -html=./.cover/cover.txt -o ./.cover/cover.html
\ No newline at end of file
diff --git a/gs/examples/bookman/src/dao/book_dao/book_dao.go b/gs/examples/bookman/src/dao/book_dao/book_dao.go
index 91f0225d..69007707 100644
--- a/gs/examples/bookman/src/dao/book_dao/book_dao.go
+++ b/gs/examples/bookman/src/dao/book_dao/book_dao.go
@@ -59,7 +59,8 @@ func (dao *BookDao) ListBooks() ([]Book, error) {
// GetBook retrieves a book by its ISBN.
func (dao *BookDao) GetBook(isbn string) (Book, error) {
- r, _ := dao.Store[isbn]
+ r, ok := dao.Store[isbn]
+ _ = ok
return r, nil
}
diff --git a/gs/internal/gs_arg/arg.go b/gs/internal/gs_arg/arg.go
index e07d57d4..8920d73a 100644
--- a/gs/internal/gs_arg/arg.go
+++ b/gs/internal/gs_arg/arg.go
@@ -78,7 +78,7 @@ func Index(n int, arg gs.Arg) gs.Arg {
// GetArgValue panics if called directly. IndexArg must be processed by ArgList.
func (arg IndexArg) GetArgValue(ctx gs.ArgContext, t reflect.Type) (reflect.Value, error) {
- panic(util.UnimplementedMethod)
+ panic(util.ErrUnimplementedMethod)
}
// ValueArg represents a fixed-value argument.
diff --git a/gs/internal/gs_cond/cond.go b/gs/internal/gs_cond/cond.go
index f275112a..cfd60f3a 100755
--- a/gs/internal/gs_cond/cond.go
+++ b/gs/internal/gs_cond/cond.go
@@ -269,7 +269,7 @@ func OnExpression(expression string) gs.Condition {
// Matches checks if the condition is met according to the provided context.
func (c *onExpression) Matches(ctx gs.CondContext) (bool, error) {
- err := util.UnimplementedMethod
+ err := util.ErrUnimplementedMethod
return false, errutil.WrapError(err, "condition matches error: %s", c)
}
diff --git a/gs/internal/gs_cond/cond_test.go b/gs/internal/gs_cond/cond_test.go
index 67d06ebe..3bd3cecc 100644
--- a/gs/internal/gs_cond/cond_test.go
+++ b/gs/internal/gs_cond/cond_test.go
@@ -337,7 +337,7 @@ func TestOnExpression(t *testing.T) {
ctx := NewMockCondContext(ctrl)
cond := OnExpression("1+1==2")
_, err := cond.Matches(ctx)
- assert.True(t, errors.Is(err, util.UnimplementedMethod))
+ assert.True(t, errors.Is(err, util.ErrUnimplementedMethod))
}
func TestNot(t *testing.T) {
diff --git a/gs/internal/gs_conf/conf.go b/gs/internal/gs_conf/conf.go
index db078148..fa19cacc 100644
--- a/gs/internal/gs_conf/conf.go
+++ b/gs/internal/gs_conf/conf.go
@@ -107,12 +107,8 @@ func (c *AppConfig) Refresh() (conf.Properties, error) {
var sources []PropertyCopier
sources = append(sources, NewNamedPropertyCopier("sys", sysconf.Clone()))
- for _, file := range localFiles {
- sources = append(sources, file)
- }
- for _, file := range remoteFiles {
- sources = append(sources, file)
- }
+ sources = append(sources, localFiles...)
+ sources = append(sources, remoteFiles...)
sources = append(sources, NewNamedPropertyCopier("remote", c.RemoteProp))
sources = append(sources, NewNamedPropertyCopier("env", c.Environment))
sources = append(sources, NewNamedPropertyCopier("cmd", c.CommandArgs))
@@ -156,9 +152,7 @@ func (c *BootConfig) Refresh() (conf.Properties, error) {
var sources []PropertyCopier
sources = append(sources, NewNamedPropertyCopier("sys", sysconf.Clone()))
- for _, file := range localFiles {
- sources = append(sources, file)
- }
+ sources = append(sources, localFiles...)
sources = append(sources, NewNamedPropertyCopier("env", c.Environment))
sources = append(sources, NewNamedPropertyCopier("cmd", c.CommandArgs))
diff --git a/gs/internal/gs_core/injecting/injecting_test.go b/gs/internal/gs_core/injecting/injecting_test.go
index 3ece3059..c58ac9c8 100644
--- a/gs/internal/gs_core/injecting/injecting_test.go
+++ b/gs/internal/gs_core/injecting/injecting_test.go
@@ -211,6 +211,7 @@ type LazyA struct {
}
type LazyB struct {
+ // nolint
dummy int `value:"${dummy:=9}"`
}
@@ -954,6 +955,7 @@ func TestForceClean(t *testing.T) {
assert.Nil(t, r.beansByType)
runtime.GC()
+ time.Sleep(100 * time.Millisecond)
assert.That(t, release).Equal(map[string]struct{}{
"biz": {},
"sys": {},
diff --git a/gs/internal/gs_dync/dync_test.go b/gs/internal/gs_dync/dync_test.go
index 296a974d..dc2b737d 100644
--- a/gs/internal/gs_dync/dync_test.go
+++ b/gs/internal/gs_dync/dync_test.go
@@ -147,6 +147,7 @@ func TestDync(t *testing.T) {
"config.s4.value": "123",
})
err = p.Refresh(prop)
+ assert.Nil(t, err)
assert.That(t, p.ObjectsCount()).Equal(2)
assert.That(t, cfg.S1.Value.Value()).Equal(99)
assert.That(t, cfg.S2.Value.Value()).Equal(456)
@@ -157,6 +158,7 @@ func TestDync(t *testing.T) {
"config.s3.value": "xyz",
})
err = p.Refresh(prop)
+ assert.Nil(t, err)
assert.That(t, p.ObjectsCount()).Equal(2)
assert.That(t, cfg.S1.Value.Value()).Equal(99)
assert.That(t, cfg.S2.Value.Value()).Equal(456)
diff --git a/util/error.go b/util/error.go
index 18784932..7e56a850 100644
--- a/util/error.go
+++ b/util/error.go
@@ -20,8 +20,8 @@ import (
"errors"
)
-// ForbiddenMethod throws this error when calling a method is prohibited.
-var ForbiddenMethod = errors.New("forbidden method")
+// ErrForbiddenMethod throws this error when calling a method is prohibited.
+var ErrForbiddenMethod = errors.New("forbidden method")
-// UnimplementedMethod throws this error when calling an unimplemented method.
-var UnimplementedMethod = errors.New("unimplemented method")
+// ErrUnimplementedMethod throws this error when calling an unimplemented method.
+var ErrUnimplementedMethod = errors.New("unimplemented method")