feat: add TypedMessageHandler + generic Decode[T] helper
- handler: add OnTypedMessage() for typed NATS message callbacks Avoids double-decode (msgpack→map→typed) by skipping map step - handler: refactor wrapHandler into wrapTypedHandler + wrapMapHandler - natsutil: add generic Decode[T](data) for direct msgpack→struct decode - tests: add typed handler tests + benchmark (11 tests pass)
This commit is contained in:
@@ -73,6 +73,19 @@ func TestCallbackRegistration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypedMessageRegistration(t *testing.T) {
|
||||
cfg := config.Load()
|
||||
h := New("ai.test", cfg)
|
||||
|
||||
h.OnTypedMessage(func(ctx context.Context, msg *nats.Msg) (any, error) {
|
||||
return map[string]any{"ok": true}, nil
|
||||
})
|
||||
|
||||
if h.onTypedMessage == nil {
|
||||
t.Error("onTypedMessage should not be nil after registration")
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// wrapHandler dispatch tests (unit test the message decode + dispatch logic)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
@@ -173,6 +186,71 @@ func TestWrapHandler_NilResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// wrapHandler dispatch tests — typed handler path
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestWrapTypedHandler_ValidMessage(t *testing.T) {
|
||||
cfg := config.Load()
|
||||
h := New("ai.test", cfg)
|
||||
|
||||
type testReq struct {
|
||||
RequestID string `msgpack:"request_id"`
|
||||
Message string `msgpack:"message"`
|
||||
}
|
||||
|
||||
var received testReq
|
||||
h.OnTypedMessage(func(ctx context.Context, msg *nats.Msg) (any, error) {
|
||||
if err := msgpack.Unmarshal(msg.Data, &received); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]any{"status": "ok"}, nil
|
||||
})
|
||||
|
||||
encoded, _ := msgpack.Marshal(map[string]any{
|
||||
"request_id": "typed-001",
|
||||
"message": "hello typed",
|
||||
})
|
||||
|
||||
handler := h.wrapHandler(context.Background())
|
||||
handler(&nats.Msg{Subject: "ai.test", Data: encoded})
|
||||
|
||||
if received.RequestID != "typed-001" {
|
||||
t.Errorf("RequestID = %q", received.RequestID)
|
||||
}
|
||||
if received.Message != "hello typed" {
|
||||
t.Errorf("Message = %q", received.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapTypedHandler_Error(t *testing.T) {
|
||||
cfg := config.Load()
|
||||
h := New("ai.test", cfg)
|
||||
|
||||
h.OnTypedMessage(func(ctx context.Context, msg *nats.Msg) (any, error) {
|
||||
return nil, context.DeadlineExceeded
|
||||
})
|
||||
|
||||
encoded, _ := msgpack.Marshal(map[string]any{"key": "val"})
|
||||
handler := h.wrapHandler(context.Background())
|
||||
|
||||
// Should not panic.
|
||||
handler(&nats.Msg{Subject: "ai.test", Data: encoded})
|
||||
}
|
||||
|
||||
func TestWrapTypedHandler_NilResponse(t *testing.T) {
|
||||
cfg := config.Load()
|
||||
h := New("ai.test", cfg)
|
||||
|
||||
h.OnTypedMessage(func(ctx context.Context, msg *nats.Msg) (any, error) {
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
encoded, _ := msgpack.Marshal(map[string]any{"x": 1})
|
||||
handler := h.wrapHandler(context.Background())
|
||||
handler(&nats.Msg{Subject: "ai.test", Data: encoded})
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Benchmark: message decode + dispatch overhead
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
@@ -199,3 +277,35 @@ func BenchmarkWrapHandler(b *testing.B) {
|
||||
handler(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWrapTypedHandler(b *testing.B) {
|
||||
type benchReq struct {
|
||||
RequestID string `msgpack:"request_id"`
|
||||
Message string `msgpack:"message"`
|
||||
Premium bool `msgpack:"premium"`
|
||||
TopK int `msgpack:"top_k"`
|
||||
}
|
||||
|
||||
cfg := config.Load()
|
||||
h := New("ai.test", cfg)
|
||||
h.OnTypedMessage(func(ctx context.Context, msg *nats.Msg) (any, error) {
|
||||
var req benchReq
|
||||
msgpack.Unmarshal(msg.Data, &req)
|
||||
return map[string]any{"ok": true}, nil
|
||||
})
|
||||
|
||||
payload := map[string]any{
|
||||
"request_id": "bench-001",
|
||||
"message": "What is the capital of France?",
|
||||
"premium": true,
|
||||
"top_k": 10,
|
||||
}
|
||||
encoded, _ := msgpack.Marshal(payload)
|
||||
handler := h.wrapHandler(context.Background())
|
||||
msg := &nats.Msg{Subject: "ai.test", Data: encoded}
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
handler(msg)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user