package discovery import ( "context" "encoding/json" "strings" "sync" "github.com/google/uuid" msg "github.com/tech/sendico/pkg/messaging" mb "github.com/tech/sendico/pkg/messaging/broker" cons "github.com/tech/sendico/pkg/messaging/consumer" me "github.com/tech/sendico/pkg/messaging/envelope" msgproducer "github.com/tech/sendico/pkg/messaging/producer" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "go.uber.org/zap" ) type Client struct { logger mlogger.Logger producer msg.Producer consumer msg.Consumer sender string mu sync.Mutex pending map[string]chan LookupResponse } func NewClient(logger mlogger.Logger, msgBroker mb.Broker, producer msg.Producer, sender string) (*Client, error) { if msgBroker == nil { return nil, merrors.InvalidArgument("discovery client: broker is nil") } if logger == nil { logger = zap.NewNop() } logger = logger.Named("discovery_client") if producer == nil { producer = msgproducer.NewProducer(logger, msgBroker) } sender = strings.TrimSpace(sender) if sender == "" { sender = "discovery_client" } consumer, err := cons.NewConsumer(logger, msgBroker, LookupResponseEvent()) if err != nil { return nil, err } client := &Client{ logger: logger, producer: producer, consumer: consumer, sender: sender, pending: map[string]chan LookupResponse{}, } go func() { if err := consumer.ConsumeMessages(client.handleLookupResponse); err != nil { client.logger.Warn("Discovery lookup consumer stopped", zap.String("event", LookupResponseEvent().ToString()), zap.Error(err)) } }() return client, nil } func (c *Client) Close() { if c == nil { return } if c.consumer != nil { c.consumer.Close() } c.mu.Lock() for key, ch := range c.pending { close(ch) delete(c.pending, key) } c.mu.Unlock() } func (c *Client) Lookup(ctx context.Context) (LookupResponse, error) { if c == nil || c.producer == nil { return LookupResponse{}, merrors.Internal("discovery client: producer not configured") } requestID := uuid.NewString() ch := make(chan LookupResponse, 1) c.mu.Lock() c.pending[requestID] = ch c.mu.Unlock() defer func() { c.mu.Lock() delete(c.pending, requestID) c.mu.Unlock() }() req := LookupRequest{RequestID: requestID} if err := c.producer.SendMessage(NewLookupRequestEnvelope(c.sender, req)); err != nil { return LookupResponse{}, err } select { case <-ctx.Done(): return LookupResponse{}, ctx.Err() case resp := <-ch: return resp, nil } } func (c *Client) handleLookupResponse(_ context.Context, env me.Envelope) error { var payload LookupResponse if err := json.Unmarshal(env.GetData(), &payload); err != nil { fields := append(envelopeFields(env), zap.Int("data_len", len(env.GetData())), zap.Error(err)) c.logWarn("Failed to decode discovery lookup response", fields...) return err } requestID := strings.TrimSpace(payload.RequestID) if requestID == "" { return nil } c.mu.Lock() ch := c.pending[requestID] c.mu.Unlock() if ch != nil { ch <- payload } return nil } func (c *Client) logWarn(message string, fields ...zap.Field) { if c == nil { return } c.logger.Warn(message, fields...) }