feat!: replace msgpack with protobuf for all NATS messages
Some checks failed
CI / Lint (push) Failing after 3m2s
CI / Test (push) Successful in 3m44s
CI / Release (push) Has been skipped
CI / Notify Downstream (chat-handler) (push) Has been skipped
CI / Notify Downstream (pipeline-bridge) (push) Has been skipped
CI / Notify Downstream (stt-module) (push) Has been skipped
CI / Notify Downstream (tts-module) (push) Has been skipped
CI / Notify Downstream (voice-assistant) (push) Has been skipped
CI / Notify (push) Successful in 1s
Some checks failed
CI / Lint (push) Failing after 3m2s
CI / Test (push) Successful in 3m44s
CI / Release (push) Has been skipped
CI / Notify Downstream (chat-handler) (push) Has been skipped
CI / Notify Downstream (pipeline-bridge) (push) Has been skipped
CI / Notify Downstream (stt-module) (push) Has been skipped
CI / Notify Downstream (tts-module) (push) Has been skipped
CI / Notify Downstream (voice-assistant) (push) Has been skipped
CI / Notify (push) Successful in 1s
BREAKING CHANGE: All NATS message serialization now uses Protocol Buffers. - Added proto/messages/v1/messages.proto with 22 message types - Generated Go code at gen/messagespb/ - messages/ package now exports type aliases to proto types - natsutil.Publish/Request/Decode use proto.Marshal/Unmarshal - Removed legacy MessageHandler, OnMessage, wrapMapHandler - TypedMessageHandler now returns (proto.Message, error) - EffectiveQuery is now a free function: messages.EffectiveQuery(req) - Removed msgpack dependency entirely
This commit is contained in:
@@ -1,70 +1,70 @@
|
||||
// Package natsutil provides a NATS/JetStream client with msgpack serialization.
|
||||
// Package natsutil provides a NATS/JetStream client with protobuf serialization.
|
||||
package natsutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"github.com/nats-io/nats.go"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Client wraps a NATS connection with msgpack helpers.
|
||||
// Client wraps a NATS connection with protobuf helpers.
|
||||
type Client struct {
|
||||
nc *nats.Conn
|
||||
js nats.JetStreamContext
|
||||
subs []*nats.Subscription
|
||||
url string
|
||||
opts []nats.Option
|
||||
nc *nats.Conn
|
||||
js nats.JetStreamContext
|
||||
subs []*nats.Subscription
|
||||
url string
|
||||
opts []nats.Option
|
||||
}
|
||||
|
||||
// New creates a NATS client configured to connect to the given URL.
|
||||
// Optional NATS options (e.g. credentials) can be appended.
|
||||
func New(url string, opts ...nats.Option) *Client {
|
||||
defaults := []nats.Option{
|
||||
nats.ReconnectWait(2 * time.Second),
|
||||
nats.MaxReconnects(-1),
|
||||
nats.DisconnectErrHandler(func(_ *nats.Conn, err error) {
|
||||
slog.Warn("NATS disconnected", "error", err)
|
||||
}),
|
||||
nats.ReconnectHandler(func(_ *nats.Conn) {
|
||||
slog.Info("NATS reconnected")
|
||||
}),
|
||||
}
|
||||
return &Client{
|
||||
url: url,
|
||||
opts: append(defaults, opts...),
|
||||
}
|
||||
defaults := []nats.Option{
|
||||
nats.ReconnectWait(2 * time.Second),
|
||||
nats.MaxReconnects(-1),
|
||||
nats.DisconnectErrHandler(func(_ *nats.Conn, err error) {
|
||||
slog.Warn("NATS disconnected", "error", err)
|
||||
}),
|
||||
nats.ReconnectHandler(func(_ *nats.Conn) {
|
||||
slog.Info("NATS reconnected")
|
||||
}),
|
||||
}
|
||||
return &Client{
|
||||
url: url,
|
||||
opts: append(defaults, opts...),
|
||||
}
|
||||
}
|
||||
|
||||
// Connect establishes the NATS connection and JetStream context.
|
||||
func (c *Client) Connect() error {
|
||||
nc, err := nats.Connect(c.url, c.opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nats connect: %w", err)
|
||||
}
|
||||
js, err := nc.JetStream()
|
||||
if err != nil {
|
||||
nc.Close()
|
||||
return fmt.Errorf("jetstream: %w", err)
|
||||
}
|
||||
c.nc = nc
|
||||
c.js = js
|
||||
slog.Info("connected to NATS", "url", c.url)
|
||||
return nil
|
||||
nc, err := nats.Connect(c.url, c.opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nats connect: %w", err)
|
||||
}
|
||||
js, err := nc.JetStream()
|
||||
if err != nil {
|
||||
nc.Close()
|
||||
return fmt.Errorf("jetstream: %w", err)
|
||||
}
|
||||
c.nc = nc
|
||||
c.js = js
|
||||
slog.Info("connected to NATS", "url", c.url)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close drains subscriptions and closes the connection.
|
||||
func (c *Client) Close() {
|
||||
if c.nc == nil {
|
||||
return
|
||||
}
|
||||
for _, sub := range c.subs {
|
||||
_ = sub.Drain()
|
||||
}
|
||||
c.nc.Close()
|
||||
slog.Info("NATS connection closed")
|
||||
if c.nc == nil {
|
||||
return
|
||||
}
|
||||
for _, sub := range c.subs {
|
||||
_ = sub.Drain()
|
||||
}
|
||||
c.nc.Close()
|
||||
slog.Info("NATS connection closed")
|
||||
}
|
||||
|
||||
// Conn returns the underlying *nats.Conn.
|
||||
@@ -75,68 +75,56 @@ func (c *Client) JS() nats.JetStreamContext { return c.js }
|
||||
|
||||
// IsConnected returns true if the NATS connection is active.
|
||||
func (c *Client) IsConnected() bool {
|
||||
return c.nc != nil && c.nc.IsConnected()
|
||||
return c.nc != nil && c.nc.IsConnected()
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a subject with an optional queue group.
|
||||
// The handler receives the raw *nats.Msg.
|
||||
func (c *Client) Subscribe(subject string, handler nats.MsgHandler, queue string) error {
|
||||
var sub *nats.Subscription
|
||||
var err error
|
||||
if queue != "" {
|
||||
sub, err = c.nc.QueueSubscribe(subject, queue, handler)
|
||||
slog.Info("subscribed", "subject", subject, "queue", queue)
|
||||
} else {
|
||||
sub, err = c.nc.Subscribe(subject, handler)
|
||||
slog.Info("subscribed", "subject", subject)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("subscribe %s: %w", subject, err)
|
||||
}
|
||||
c.subs = append(c.subs, sub)
|
||||
return nil
|
||||
var sub *nats.Subscription
|
||||
var err error
|
||||
if queue != "" {
|
||||
sub, err = c.nc.QueueSubscribe(subject, queue, handler)
|
||||
slog.Info("subscribed", "subject", subject, "queue", queue)
|
||||
} else {
|
||||
sub, err = c.nc.Subscribe(subject, handler)
|
||||
slog.Info("subscribed", "subject", subject)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("subscribe %s: %w", subject, err)
|
||||
}
|
||||
c.subs = append(c.subs, sub)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Publish encodes data as msgpack and publishes to the subject.
|
||||
func (c *Client) Publish(subject string, data any) error {
|
||||
payload, err := msgpack.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("msgpack marshal: %w", err)
|
||||
}
|
||||
return c.nc.Publish(subject, payload)
|
||||
// Publish encodes data as protobuf and publishes to the subject.
|
||||
func (c *Client) Publish(subject string, data proto.Message) error {
|
||||
payload, err := proto.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proto marshal: %w", err)
|
||||
}
|
||||
return c.nc.Publish(subject, payload)
|
||||
}
|
||||
|
||||
// Request sends a msgpack-encoded request and decodes the response into result.
|
||||
func (c *Client) Request(subject string, data any, result any, timeout time.Duration) error {
|
||||
payload, err := msgpack.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("msgpack marshal: %w", err)
|
||||
}
|
||||
msg, err := c.nc.Request(subject, payload, timeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nats request: %w", err)
|
||||
}
|
||||
return msgpack.Unmarshal(msg.Data, result)
|
||||
// PublishRaw publishes pre-encoded bytes to the subject.
|
||||
func (c *Client) PublishRaw(subject string, data []byte) error {
|
||||
return c.nc.Publish(subject, data)
|
||||
}
|
||||
|
||||
// DecodeMsgpack decodes msgpack-encoded NATS message data into dest.
|
||||
func DecodeMsgpack(msg *nats.Msg, dest any) error {
|
||||
return msgpack.Unmarshal(msg.Data, dest)
|
||||
// Request sends a protobuf-encoded request and decodes the response into result.
|
||||
func (c *Client) Request(subject string, data proto.Message, result proto.Message, timeout time.Duration) error {
|
||||
payload, err := proto.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("proto marshal: %w", err)
|
||||
}
|
||||
msg, err := c.nc.Request(subject, payload, timeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nats request: %w", err)
|
||||
}
|
||||
return proto.Unmarshal(msg.Data, result)
|
||||
}
|
||||
|
||||
// Decode is a generic helper that unmarshals msgpack bytes into T.
|
||||
// Usage: req, err := natsutil.Decode[messages.ChatRequest](msg.Data)
|
||||
func Decode[T any](data []byte) (T, error) {
|
||||
var v T
|
||||
err := msgpack.Unmarshal(data, &v)
|
||||
return v, err
|
||||
}
|
||||
|
||||
// DecodeMsgpackMap decodes msgpack data into a generic map.
|
||||
func DecodeMsgpackMap(data []byte) (map[string]any, error) {
|
||||
var m map[string]any
|
||||
if err := msgpack.Unmarshal(data, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
// Decode unmarshals protobuf bytes into dest.
|
||||
func Decode(data []byte, dest proto.Message) error {
|
||||
return proto.Unmarshal(data, dest)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user