package events import ( "context" "encoding/json" "strings" "time" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "go.uber.org/zap" ) type parserService struct { logger mlogger.Logger } // New creates event parser/payload builder service. func New(logger mlogger.Logger) Service { if logger == nil { logger = zap.NewNop() } return &parserService{logger: logger.Named("events")} } func (s *parserService) Parse(data []byte) (*Envelope, error) { if len(data) == 0 { return nil, merrors.InvalidArgument("event payload is empty", "data") } var envelope Envelope if err := json.Unmarshal(data, &envelope); err != nil { return nil, merrors.InvalidArgumentWrap(err, "event payload is not valid JSON", "data") } if strings.TrimSpace(envelope.EventID) == "" { return nil, merrors.InvalidArgument("event_id is required", "event_id") } if strings.TrimSpace(envelope.Type) == "" { return nil, merrors.InvalidArgument("type is required", "type") } if strings.TrimSpace(envelope.ClientID) == "" { return nil, merrors.InvalidArgument("client_id is required", "client_id") } if envelope.OccurredAt.IsZero() { return nil, merrors.InvalidArgument("occurred_at is required", "occurred_at") } if len(envelope.Data) == 0 { envelope.Data = []byte("{}") } envelope.EventID = strings.TrimSpace(envelope.EventID) envelope.Type = strings.TrimSpace(envelope.Type) envelope.ClientID = strings.TrimSpace(envelope.ClientID) envelope.OccurredAt = envelope.OccurredAt.UTC() if !envelope.PublishedAt.IsZero() { envelope.PublishedAt = envelope.PublishedAt.UTC() } return &envelope, nil } func (s *parserService) BuildPayload(_ context.Context, envelope *Envelope) ([]byte, error) { if envelope == nil { return nil, merrors.InvalidArgument("event envelope is required", "envelope") } payload := Payload{ EventID: envelope.EventID, Type: envelope.Type, ClientID: envelope.ClientID, OccurredAt: envelope.OccurredAt.UTC().Format(time.RFC3339Nano), Data: envelope.Data, } if !envelope.PublishedAt.IsZero() { payload.PublishedAt = envelope.PublishedAt.UTC().Format(time.RFC3339Nano) } data, err := json.Marshal(payload) if err != nil { s.logger.Warn("Failed to marshal callback payload", zap.Error(err), zap.String("event_id", envelope.EventID)) return nil, merrors.InternalWrap(err, "failed to marshal callback payload") } return data, nil }