v1.4.0: generic Stream[T] — typed stream messages, drop (T, *Stream, error)
- Stream[T]: Send(v T) type-safe; message type recovered from the instantiated Send signature (reflect has no type-args API) - method signatures reduced to (error), (T, error), (*Stream[T], error); the (T, *Stream, error) first-message form is gone — send it via Send - return T and stream message T now run checkType at bind time (Stream[any] remains the explicit untyped escape hatch) - generated clients: TS onMessage: (data: T); Go <-chan T - fix vacuous panic assertions (fmt.Sprint(nil) -> "<nil>") in hardening/route test catch helpers Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
+56
-3
@@ -185,7 +185,11 @@ func TestStartedGuardAndShutdown(t *testing.T) {
|
||||
}
|
||||
|
||||
catch := func(fn func()) (msg string) {
|
||||
defer func() { msg = fmt.Sprint(recover()) }()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
msg = fmt.Sprint(r)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
return ""
|
||||
}
|
||||
@@ -248,8 +252,8 @@ func (boomMarshaler) MarshalJSON() ([]byte, error) { panic("boom-json") }
|
||||
|
||||
type PanicStreamSvc struct{}
|
||||
|
||||
func (s *PanicStreamSvc) Go() (*Stream, error) {
|
||||
st := &Stream{}
|
||||
func (s *PanicStreamSvc) Go() (*Stream[any], error) {
|
||||
st := &Stream[any]{}
|
||||
go func() {
|
||||
_ = st.Send(boomMarshaler{})
|
||||
st.Close()
|
||||
@@ -341,3 +345,52 @@ if (!requestBody.includes('"id":42')) throw new Error("small int broken: " + req
|
||||
t.Fatalf("bigint round trip failed: %v\n%s", err, output)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 返回类型 T 与 DTO 同受类型系统约束(含 Stream[T] 的消息类型) ----
|
||||
|
||||
type BadRetOnly struct{}
|
||||
|
||||
func (*BadRetOnly) Ok() (string, error) { return "ok", nil }
|
||||
|
||||
type mapRetSvc struct{}
|
||||
|
||||
func (*mapRetSvc) M() (map[string]any, error) { return nil, nil }
|
||||
|
||||
type anyRetSvc struct{}
|
||||
|
||||
func (*anyRetSvc) M() (any, error) { return nil, nil }
|
||||
|
||||
type badStreamSvc struct{}
|
||||
|
||||
func (*badStreamSvc) M() (*Stream[map[string]any], error) { return nil, nil }
|
||||
|
||||
type anyStreamSvc struct{}
|
||||
|
||||
func (*anyStreamSvc) M() (*Stream[any], error) { return &Stream[any]{}, nil }
|
||||
|
||||
func TestReturnTypeCheckedAtBind(t *testing.T) {
|
||||
panics := func(fn func()) (p bool) {
|
||||
defer func() { p = recover() != nil }()
|
||||
fn()
|
||||
return false
|
||||
}
|
||||
if panics(func() { _ = New().BindService(&BadRetOnly{}) }) {
|
||||
t.Fatal("normal service must not panic")
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
svc any
|
||||
}{
|
||||
{"map return", &mapRetSvc{}},
|
||||
{"any return", &anyRetSvc{}},
|
||||
{"Stream[map]", &badStreamSvc{}},
|
||||
} {
|
||||
if !panics(func() { _ = New().BindService(tc.svc) }) {
|
||||
t.Fatalf("%s must panic at bind", tc.name)
|
||||
}
|
||||
}
|
||||
// Stream[any] 显式任意消息流:放行
|
||||
if panics(func() { _ = New().BindService(&anyStreamSvc{}) }) {
|
||||
t.Fatal("Stream[any] should pass")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user