Files
fun/box.go
T
chiyi 4bf991b3e7 fun: HTTP RPC framework with DI, guards, streams and codegen
- Service binding via reflection with 4 method signatures
- Dependency injection (Wired/auto tags) and guards
- Streamable HTTP (NDJSON) streaming responses
- Go/TypeScript client code generation (self-contained, with interceptors)
- Enum support, structured Result, logging
- 16 regression tests covering all fixed bugs
2026-08-16 23:51:58 +08:00

160 lines
4.7 KiB
Go

package fun
import (
"reflect"
)
// Wired 创建并注册一个依赖实例;auto 标签字段递归注入依赖;存在 New() 则调用
func Wired[T any]() *T {
t := reflect.TypeFor[T]()
data := new(T)
if t.Kind() != reflect.Struct {
panic("Fun: " + t.Name() + " It must be a structure")
}
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")
}
}
f := GetFun()
if box, isWired := f.boxes.Load(reflect.TypeFor[*T]()); isWired {
return box.(reflect.Value).Interface().(*T)
}
v := reflect.ValueOf(data)
f.boxes.Store(reflect.TypeFor[*T](), v)
boxList := map[reflect.Type]bool{}
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))
}
}
}
newMethod := v.MethodByName("New")
if newMethod.IsValid() {
newMethod.Call(nil)
}
return data
}
// autowired 递归创建依赖实例并注入 auto 标签字段
func (f *Fun) autowired(fieldValue reflect.Value) {
instance := reflect.New(fieldValue.Type().Elem())
f.boxes.Store(fieldValue.Type(), 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))
}
}
}
newMethod := instance.MethodByName("New")
if newMethod.IsValid() {
newMethod.Call(nil)
}
}
// checkBox 校验 auto 注入字段:必须是指针+struct、非匿名、非私有;New() 必须无参无返回值
func checkBox(s reflect.StructField, boxList map[reflect.Type]bool) {
if _, ok := boxList[s.Type]; ok {
return
}
boxList[s.Type] = true
if s.Anonymous {
panic("Fun:" + s.Name + " cannot be Anonymous")
}
if s.Type.Kind() != reflect.Ptr || s.Type.Elem().Kind() != reflect.Struct {
panic("Fun:" + s.Name + " Must be a pointer and a struct")
}
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)
}
}
}
// boxWired 注册期预初始化服务结构体字段中的 Box 依赖
func boxWired(service any, f *Fun) {
serviceInstance := reflect.New(reflect.TypeOf(service).Elem()).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)
}
}
}
}
// serviceWired 每请求把 Ctx 与 Box 依赖注入到新创建的服务实例
func (f *Fun) serviceWired(serviceInstance reflect.Value, ctx *Ctx) {
for i := 0; i < serviceInstance.NumField(); i++ {
field := serviceInstance.Field(i)
if !field.CanSet() {
continue
}
if field.Type() == ctxType {
field.Set(reflect.ValueOf(*ctx))
} else if dependency, ok := f.boxes.Load(field.Type()); ok {
field.Set(dependency.(reflect.Value))
}
}
}
// checkGuard 校验 Guard 类型:必须是指向结构体的指针
func checkGuard(guard Guard) {
t := reflect.TypeOf(guard)
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
panic("Fun: guard must be a pointer to a struct")
}
if isPrivate(t.Elem().Name()) {
panic("Fun:" + t.Elem().Name() + " cannot be Private")
}
}
// serviceGuardWired 创建 Guard 实例并注入 Box 依赖,返回 guard 引用
func serviceGuardWired(guard Guard, f *Fun) *any {
t := reflect.TypeOf(guard).Elem()
guardInstance := reflect.New(t).Elem()
for i := 0; i < guardInstance.NumField(); i++ {
field := guardInstance.Field(i)
if !field.CanSet() {
continue
}
if dependency, ok := f.boxes.Load(field.Type()); ok {
field.Set(dependency.(reflect.Value))
} else {
f.autowired(field)
}
}
g := guardInstance.Addr().Interface()
return &g
}