Files
showbridge-go/internal/module/nats-client.go
T

155 lines
3.3 KiB
Go

package module
import (
"context"
"errors"
"log/slog"
"sync"
"github.com/google/jsonschema-go/jsonschema"
"github.com/jwetzell/showbridge-go/internal/common"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/nats-io/nats.go"
)
func init() {
RegisterModule(ModuleRegistration{
Type: "nats.client",
Title: "NATS Client",
ParamsSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"url": {
Title: "NATS Server URL",
Type: "string",
},
"subject": {
Title: "Subject",
Type: "string",
},
},
Required: []string{"url", "subject"},
AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}},
},
New: func(config config.ModuleConfig) (common.Module, error) {
params := config.Params
urlString, err := params.GetString("url")
if err != nil {
return nil, errors.New("nats.client url error: " + err.Error())
}
subjectString, err := params.GetString("subject")
if err != nil {
return nil, errors.New("nats.client subject error: " + err.Error())
}
return &NATSClient{config: config, URL: urlString, Subject: subjectString, logger: CreateLogger(config)}, nil
},
})
}
type NATSClient struct {
config config.ModuleConfig
ctx context.Context
inputHandler common.InputHandler
URL string
Subject string
client *nats.Conn
logger *slog.Logger
cancel context.CancelFunc
sub *nats.Subscription
subMu sync.Mutex
clientMu sync.Mutex
}
func (nc *NATSClient) Id() string {
return nc.config.Id
}
func (nc *NATSClient) Type() string {
return nc.config.Type
}
func (nc *NATSClient) Start(ctx context.Context, inputHandler common.InputHandler) error {
nc.logger.Debug("running")
nc.inputHandler = inputHandler
moduleContext, cancel := context.WithCancel(ctx)
nc.ctx = moduleContext
nc.cancel = cancel
client, err := nats.Connect(nc.URL, nats.RetryOnFailedConnect(true))
if err != nil {
return err
}
nc.clientMu.Lock()
nc.client = client
nc.clientMu.Unlock()
sub, err := nc.client.Subscribe(nc.Subject, func(msg *nats.Msg) {
if nc.inputHandler != nil {
nc.inputHandler(nc.ctx, nc.Id(), msg)
}
})
if err != nil {
return err
}
nc.subMu.Lock()
nc.sub = sub
nc.subMu.Unlock()
<-nc.ctx.Done()
nc.logger.Debug("done")
return nil
}
func (nc *NATSClient) Publish(ctx context.Context, topic string, payload any) error {
payloadBytes, ok := common.GetAnyAsByteSlice(payload)
if !ok {
payloadString, ok := common.GetAnyAs[string](payload)
if !ok {
return errors.New("nats.client is only able to publish bytes or string")
}
payloadBytes = []byte(payloadString)
}
nc.clientMu.Lock()
defer nc.clientMu.Unlock()
if nc.client == nil {
return errors.New("nats.client client is not setup")
}
if !nc.client.IsConnected() {
return errors.New("nats.client is not connected")
}
err := nc.client.Publish(topic, payloadBytes)
return err
}
func (nc *NATSClient) Stop() {
if nc.cancel != nil {
defer nc.cancel()
}
nc.subMu.Lock()
defer nc.subMu.Unlock()
if nc.sub != nil {
nc.sub.Unsubscribe()
}
nc.clientMu.Lock()
defer nc.clientMu.Unlock()
if nc.client != nil {
nc.client.Drain()
// TODO(jwetzell): setup closed callback to get when client is fully closed
nc.client.Close()
}
}