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:
2026-09-03 15:32:07 +08:00
parent 9019798382
commit 41df889a1a
24 changed files with 1356 additions and 214 deletions
+141 -78
View File
@@ -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
}