v2.0.0: error-channel APIs, correctness fixes, and server hardening
BREAKING CHANGES: - Guard interface: Guard(ctx Ctx) error; returning error short-circuits (subsequent guards and the method no longer execute) - Wired[T]() (*T, error); New() may return error; failed wiring is sticky - BindService/BindGuard/BindRoute return error; routes accept guards (guards receive merged query/form params as Ctx.State) - registration panics after Start; Ctx.Ip honors X-Forwarded-For/X-Real-IP - internal errors sanitized to fixed client messages FIXES: - int64 precision loss: /cell data decoded from raw JSON bytes and responses serialized with json.Number (no float64 round-trip) - enum values range-checked before uint8 conversion (256 no longer truncates to 0 and slips through) - logger: files failing name parsing are no longer deleted; log channel never blocks request goroutines; ConfigLogger is race-free; logWriterWorker unlock bug fixed - stream writer panics recovered (process no longer crashes); streamDone closed exactly once - generated Go client emits definitions for pointer-to-enum/struct fields - anonymous structs rejected at registration; isPrivate safe on empty names - removed dead Result.Id and RequestInfo.Type ADDITIONS: - graceful shutdown (Shutdown), StartOn, server timeouts by default (Read 60s/Idle 120s/Write off), SetTimeouts/SetMaxConcurrency - big-int-safe JSON in the generated TS client (>2^53 as BigInt) - example/ demo services and cmd/genexample artifact generator
This commit is contained in:
@@ -1,119 +1,171 @@
|
||||
package fun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Wired 创建并注册一个依赖实例;auto 标签字段递归注入依赖;存在 New() 则调用
|
||||
func Wired[T any]() *T {
|
||||
// boxEntry 依赖容器条目:装配完成的单例,或粘性初始化错误。
|
||||
// 装配失败的类型记录错误后不再重试,也不把半初始化实例暴露给后续装配
|
||||
type boxEntry struct {
|
||||
val reflect.Value
|
||||
err error
|
||||
}
|
||||
|
||||
// Wired 创建并注册一个依赖实例;auto 标签字段递归注入依赖,存在 New() 则调用。
|
||||
// New 支持 () 与 () error 两种签名,返回非 nil error 即装配失败。
|
||||
// 失败以 error 返回;同类型再次 Wired 返回同一错误(粘性),避免启动期反复重连
|
||||
func Wired[T any]() (*T, error) {
|
||||
t := reflect.TypeFor[T]()
|
||||
data := new(T)
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic("Fun: " + t.Name() + " It must be a structure")
|
||||
}
|
||||
if t.Name() == "" {
|
||||
panic("Fun: Wired requires a named struct type")
|
||||
}
|
||||
if isPrivate(t.Name()) {
|
||||
panic("Fun:" + t.Name() + " cannot be Private")
|
||||
}
|
||||
if newMethod, found := t.MethodByName("New"); found {
|
||||
if newMethod.Type.NumIn() != 1 || newMethod.Type.NumOut() != 0 {
|
||||
panic("Fun:" + t.Name() + " New method must have no parameters and no return values")
|
||||
}
|
||||
}
|
||||
pt := reflect.TypeFor[*T]()
|
||||
checkNewSignature(pt, t.Name())
|
||||
f := GetFun()
|
||||
if box, isWired := f.boxes.Load(reflect.TypeFor[*T]()); isWired {
|
||||
return box.(reflect.Value).Interface().(*T)
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if entry, ok := f.boxes.Load(pt); ok {
|
||||
e := entry.(boxEntry)
|
||||
if e.err != nil {
|
||||
return nil, e.err
|
||||
}
|
||||
return e.val.Interface().(*T), nil
|
||||
}
|
||||
data := new(T)
|
||||
v := reflect.ValueOf(data)
|
||||
f.boxes.Store(reflect.TypeFor[*T](), v)
|
||||
boxList := map[reflect.Type]bool{}
|
||||
// 先入容器再装配:循环依赖(A→B→A)靠占位引用解开;
|
||||
// 失败时下面覆盖为粘性错误,容器中不留可用半成品
|
||||
f.boxes.Store(pt, boxEntry{val: v})
|
||||
if err := f.wireStruct(v.Elem()); err != nil {
|
||||
f.boxes.Store(pt, boxEntry{err: err})
|
||||
return nil, err
|
||||
}
|
||||
if err := callNewIfPresent(v); err != nil {
|
||||
f.boxes.Store(pt, boxEntry{err: err})
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// wireStruct 注入 auto 标签字段;依赖缺失时递归装配(须持有 f.mu)
|
||||
func (f *Fun) wireStruct(structValue reflect.Value) error {
|
||||
t := structValue.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
c := t.Field(i)
|
||||
fieldTag := newTag(c.Tag)
|
||||
if _, isAuto := fieldTag.getTag("auto"); isAuto {
|
||||
if dependency, loaded := f.boxes.Load(c.Type); loaded {
|
||||
v.Elem().Field(i).Set(dependency.(reflect.Value))
|
||||
} else {
|
||||
checkBox(c, boxList)
|
||||
f.autowired(v.Elem().Field(i))
|
||||
if _, isAuto := newTag(c.Tag).getTag("auto"); !isAuto {
|
||||
continue
|
||||
}
|
||||
if c.Anonymous {
|
||||
panic("Fun:" + c.Name + " cannot be Anonymous")
|
||||
}
|
||||
if entry, loaded := f.boxes.Load(c.Type); loaded {
|
||||
e := entry.(boxEntry)
|
||||
if e.err != nil {
|
||||
return e.err
|
||||
}
|
||||
structValue.Field(i).Set(e.val)
|
||||
continue
|
||||
}
|
||||
if err := f.autowired(structValue.Field(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
newMethod := v.MethodByName("New")
|
||||
if newMethod.IsValid() {
|
||||
newMethod.Call(nil)
|
||||
}
|
||||
return data
|
||||
return nil
|
||||
}
|
||||
|
||||
// autowired 递归创建依赖实例并注入 auto 标签字段
|
||||
func (f *Fun) autowired(fieldValue reflect.Value) {
|
||||
instance := reflect.New(fieldValue.Type().Elem())
|
||||
f.boxes.Store(fieldValue.Type(), instance)
|
||||
// autowired 递归创建依赖实例并注入 auto 字段(须持有 f.mu)。
|
||||
// 实例先入容器再装配字段,供循环依赖拿到占位引用;失败时覆盖为粘性错误
|
||||
func (f *Fun) autowired(fieldValue reflect.Value) error {
|
||||
if fieldValue.Kind() != reflect.Ptr || fieldValue.Type().Elem().Kind() != reflect.Struct {
|
||||
panic("Fun: auto field " + fieldValue.Type().String() + " must be a pointer to a struct")
|
||||
}
|
||||
if isPrivate(fieldValue.Type().Elem().Name()) {
|
||||
panic("Fun:" + fieldValue.Type().Elem().Name() + " cannot be Private")
|
||||
}
|
||||
pt := fieldValue.Type()
|
||||
checkNewSignature(pt, pt.Elem().Name())
|
||||
instance := reflect.New(pt.Elem())
|
||||
f.boxes.Store(pt, boxEntry{val: instance})
|
||||
fieldValue.Set(instance)
|
||||
structValue := instance.Elem()
|
||||
for i := 0; i < structValue.NumField(); i++ {
|
||||
structField := structValue.Type().Field(i)
|
||||
fieldTag := newTag(structField.Tag)
|
||||
if _, isAuto := fieldTag.getTag("auto"); isAuto {
|
||||
if dependency, loaded := f.boxes.Load(structField.Type); loaded {
|
||||
structValue.Field(i).Set(dependency.(reflect.Value))
|
||||
} else {
|
||||
f.autowired(structValue.Field(i))
|
||||
}
|
||||
}
|
||||
if err := f.wireStruct(instance.Elem()); err != nil {
|
||||
f.boxes.Store(pt, boxEntry{err: err})
|
||||
return err
|
||||
}
|
||||
newMethod := instance.MethodByName("New")
|
||||
if newMethod.IsValid() {
|
||||
newMethod.Call(nil)
|
||||
if err := callNewIfPresent(instance); err != nil {
|
||||
f.boxes.Store(pt, boxEntry{err: err})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkBox 校验 auto 注入字段:必须是指针+struct、非匿名、非私有;New() 必须无参无返回值
|
||||
func checkBox(s reflect.StructField, boxList map[reflect.Type]bool) {
|
||||
if _, ok := boxList[s.Type]; ok {
|
||||
// checkNewSignature 校验 New 方法签名:无参数,返回 () 或 (error)。
|
||||
// 在指针类型上查找,兼容值接收器与指针接收器两种定义
|
||||
func checkNewSignature(pt reflect.Type, name string) {
|
||||
m, found := pt.MethodByName("New")
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
boxList[s.Type] = true
|
||||
if s.Anonymous {
|
||||
panic("Fun:" + s.Name + " cannot be Anonymous")
|
||||
mt := m.Type
|
||||
if mt.NumIn() != 1 { // 仅接收者
|
||||
panic("Fun:" + name + " New method must have no parameters")
|
||||
}
|
||||
if s.Type.Kind() != reflect.Ptr || s.Type.Elem().Kind() != reflect.Struct {
|
||||
panic("Fun:" + s.Name + " Must be a pointer and a struct")
|
||||
if mt.NumOut() == 0 {
|
||||
return
|
||||
}
|
||||
if isPrivate(s.Name) {
|
||||
panic("Fun:" + s.Name + " cannot be Private")
|
||||
}
|
||||
if newMethod, found := s.Type.MethodByName("New"); found {
|
||||
if newMethod.Type.NumIn() != 1 || newMethod.Type.NumOut() != 0 {
|
||||
panic("Fun:" + s.Name + " New method must have no parameters and no return values")
|
||||
}
|
||||
}
|
||||
for i := 0; i < s.Type.Elem().NumField(); i++ {
|
||||
f := s.Type.Elem().Field(i)
|
||||
fieldTag := newTag(f.Tag)
|
||||
if _, isAuto := fieldTag.getTag("auto"); isAuto {
|
||||
checkBox(f, boxList)
|
||||
}
|
||||
if mt.NumOut() == 1 && mt.Out(0) == errorType {
|
||||
return
|
||||
}
|
||||
panic("Fun:" + name + " New method must return nothing or error")
|
||||
}
|
||||
|
||||
// boxWired 注册期预初始化服务结构体字段中的 Box 依赖
|
||||
func boxWired(service any, f *Fun) {
|
||||
serviceInstance := reflect.New(reflect.TypeOf(service).Elem()).Elem()
|
||||
// callNewIfPresent 调用指针上的 New()(若存在),支持 () 与 () error 两种签名
|
||||
func callNewIfPresent(ptr reflect.Value) error {
|
||||
m := ptr.MethodByName("New")
|
||||
if !m.IsValid() {
|
||||
return nil
|
||||
}
|
||||
out := m.Call(nil)
|
||||
if len(out) == 1 {
|
||||
if err, ok := out[0].Interface().(error); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// boxWired 注册期预初始化服务结构体字段中的 Box 依赖(须持有 f.mu)。
|
||||
// 字段对应类型装配失败时向上返回 error
|
||||
func boxWired(service any, f *Fun) error {
|
||||
svcType := reflect.TypeOf(service).Elem()
|
||||
serviceInstance := reflect.New(svcType).Elem()
|
||||
for i := 0; i < serviceInstance.NumField(); i++ {
|
||||
field := serviceInstance.Field(i)
|
||||
if field.Type() == ctxType {
|
||||
continue
|
||||
}
|
||||
if field.Type().Kind() == reflect.Ptr && field.Type().Elem().Kind() == reflect.Struct {
|
||||
if _, isWired := f.boxes.Load(field.Type()); !isWired {
|
||||
f.autowired(field)
|
||||
if entry, isWired := f.boxes.Load(field.Type()); isWired {
|
||||
if e := entry.(boxEntry); e.err != nil {
|
||||
return e.err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := f.autowired(field); err != nil {
|
||||
return fmt.Errorf("fun: wire %s.%s: %w", svcType.Name(), svcType.Field(i).Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serviceWired 每请求把 Ctx 与 Box 依赖注入到新创建的服务实例
|
||||
// serviceWired 每请求把 Ctx 与 Box 依赖注入到新创建的服务实例(只读容器,无锁)
|
||||
func (f *Fun) serviceWired(serviceInstance reflect.Value, ctx *Ctx) {
|
||||
for i := 0; i < serviceInstance.NumField(); i++ {
|
||||
field := serviceInstance.Field(i)
|
||||
@@ -123,7 +175,9 @@ func (f *Fun) serviceWired(serviceInstance reflect.Value, ctx *Ctx) {
|
||||
if field.Type() == ctxType {
|
||||
field.Set(reflect.ValueOf(*ctx))
|
||||
} else if dependency, ok := f.boxes.Load(field.Type()); ok {
|
||||
field.Set(dependency.(reflect.Value))
|
||||
if e := dependency.(boxEntry); e.err == nil {
|
||||
field.Set(e.val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,8 +193,8 @@ func checkGuard(guard Guard) {
|
||||
}
|
||||
}
|
||||
|
||||
// serviceGuardWired 创建 Guard 实例并注入 Box 依赖,返回 guard 引用
|
||||
func serviceGuardWired(guard Guard, f *Fun) *any {
|
||||
// serviceGuardWired 创建 Guard 实例并注入 Box 依赖,返回 guard 引用(须持有 f.mu)
|
||||
func serviceGuardWired(guard Guard, f *Fun) (*any, error) {
|
||||
t := reflect.TypeOf(guard).Elem()
|
||||
guardInstance := reflect.New(t).Elem()
|
||||
for i := 0; i < guardInstance.NumField(); i++ {
|
||||
@@ -148,12 +202,21 @@ func serviceGuardWired(guard Guard, f *Fun) *any {
|
||||
if !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
if dependency, ok := f.boxes.Load(field.Type()); ok {
|
||||
field.Set(dependency.(reflect.Value))
|
||||
} else {
|
||||
f.autowired(field)
|
||||
if field.Type() == ctxType {
|
||||
continue
|
||||
}
|
||||
if entry, ok := f.boxes.Load(field.Type()); ok {
|
||||
e := entry.(boxEntry)
|
||||
if e.err != nil {
|
||||
return nil, e.err
|
||||
}
|
||||
field.Set(e.val)
|
||||
} else if field.Kind() == reflect.Ptr && field.Type().Elem().Kind() == reflect.Struct {
|
||||
if err := f.autowired(field); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
g := guardInstance.Addr().Interface()
|
||||
return &g
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user