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:
@@ -3,12 +3,16 @@ package fun
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func isPrivate(value string) bool {
|
||||
if value == "" {
|
||||
return false // 匿名类型名:交由各处的具名校验给出明确报错,不在此越界 panic
|
||||
}
|
||||
return !unicode.IsUpper([]rune(value)[0])
|
||||
}
|
||||
|
||||
@@ -36,6 +40,9 @@ func checkType(t reflect.Type) {
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
if t.Name() == "" {
|
||||
panic("fun: anonymous struct types are not supported, define a named type")
|
||||
}
|
||||
if t.NumField() == 0 {
|
||||
panic("fun: " + t.Name() + " must have at least one field")
|
||||
}
|
||||
@@ -49,7 +56,11 @@ func checkType(t reflect.Type) {
|
||||
case reflect.Slice:
|
||||
checkType(t.Elem())
|
||||
default:
|
||||
panic("fun:Unsupported types " + t.Name())
|
||||
name := t.Name()
|
||||
if name == "" {
|
||||
name = t.String() // 匿名/内建组合类型(如 interface{})给出可读的名字
|
||||
}
|
||||
panic("fun:Unsupported types " + name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +133,9 @@ func checkDto(dtoType reflect.Type, dtoMap any, methodName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkEnumValue 运行时校验枚举值是否在范围内
|
||||
// checkEnumValue 运行时校验枚举值是否在范围内。
|
||||
// 必须在原始数值上先判范围再转 uint8:否则 256/512 等越界值先被截断成
|
||||
// 合法小值(256→0),绕过范围检查后静默落到错误的枚举项上
|
||||
func checkEnumValue(t reflect.Type, value any, name string) error {
|
||||
var max uint8
|
||||
enumValue := reflect.New(t).Elem()
|
||||
@@ -131,35 +144,67 @@ func checkEnumValue(t reflect.Type, value any, name string) error {
|
||||
} else {
|
||||
max = uint8(len(enumValue.Interface().(enum).Names()))
|
||||
}
|
||||
outOfRange := callError(errors.New("Fun:" + name + " Dto value out of range"))
|
||||
var num uint8
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
if v < 0 || v != math.Trunc(v) || v >= float64(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case float32:
|
||||
num = uint8(v)
|
||||
f := float64(v)
|
||||
if f < 0 || f != math.Trunc(f) || f >= float64(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(f)
|
||||
case uint8:
|
||||
num = v
|
||||
case uint16:
|
||||
if v >= uint16(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case uint32:
|
||||
if v >= uint32(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case uint64:
|
||||
if v >= uint64(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case int:
|
||||
if v < 0 || v >= int(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case int8:
|
||||
if v < 0 || int(v) >= int(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case int16:
|
||||
if v < 0 || int(v) >= int(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case int32:
|
||||
if v < 0 || int64(v) >= int64(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
case int64:
|
||||
if v < 0 || v >= int64(max) {
|
||||
return outOfRange
|
||||
}
|
||||
num = uint8(v)
|
||||
default:
|
||||
return callError(errors.New("Fun:" + name + " Dto enum value type is not supported"))
|
||||
}
|
||||
if num >= max {
|
||||
return callError(errors.New("Fun:" + name + " Dto value out of range"))
|
||||
return outOfRange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user