Files
socket-comm/pkg/message/message.go

181 lines
5.0 KiB
Go

// Package message provides a type-safe, extensible message system for WebSocket communication.
// It implements a nested message structure that allows for message interception and transformation
// through an interceptor chain pattern.
package message
import (
"encoding/json"
"fmt"
)
// Type aliases for improved readability and type safety
type (
// Protocol identifies the message type or format
Protocol string
// Payload contains the serialized data of the message
Payload json.RawMessage
// Sender identifies the source of the message
Sender string
// Receiver identifies the intended recipient of the message
Receiver string
// Version specifies the message protocol version
Version string
)
// Protocol constants
const (
// NoneProtocol indicates no nested message exists
NoneProtocol Protocol = "none"
// Version1 is the current message protocol version
Version1 Version = "v1.0"
// UnknownSender senderID initialising
UnknownSender Sender = "unknown.sender"
// UnknownReceiver receiverID initialising
UnknownReceiver Receiver = "unknown.receiver"
)
type Marshallable interface {
// Marshal serializes the message to JSON format
Marshal() ([]byte, error)
}
type Unmarshallable interface {
// Unmarshal deserializes the message from JSON format
Unmarshal([]byte) error
}
func (p Payload) Marshal() ([]byte, error) {
return p, nil
}
// Message defines the interface that all message types must implement.
// It provides methods for protocol identification, serialization, and
// message nesting/unwrapping.
type Message interface {
// GetProtocol returns the protocol identifier for this message
GetProtocol() Protocol
GetNextPayload() (Payload, error)
GetNextProtocol() Protocol
GetCurrentHeader() Header
// GetNext retrieves the next message in the chain, if one exists
// Returns nil, nil if there is no next message
GetNext(Registry) (Message, error)
Marshallable
Unmarshallable
}
// Header contains common metadata for all messages
type Header struct {
Sender Sender `json:"sender"` // Sender identifies the message source
Receiver Receiver `json:"receiver"` // Receiver identifies the intended recipient
Version Version `json:"version"` // Version specifies the protocol version
}
// NewV1Header creates a new header with Version1
// This is a convenience constructor for common header creation
func NewV1Header(sender Sender, receiver Receiver) Header {
return Header{
Sender: sender,
Receiver: receiver,
Version: Version1,
}
}
// BaseMessage provides a foundation for all message types.
// It implements the Message interface and manages message nesting.
// Custom message types should embed this struct to inherit its functionality.
type BaseMessage struct {
// CURRENT MESSAGE PROCESSOR
CurrentProtocol Protocol `json:"protocol"` // CurrentProtocol identifies this message's type
CurrentHeader Header `json:"header"` // CurrentHeader contains metadata for this message
// CURRENT OTHER FIELDS...
// NEXT MESSAGE PROCESSOR
NextPayload Payload `json:"next,omitempty"` // NextPayload contains the serialized next message in the chain
NextProtocol Protocol `json:"next_protocol"` // NextProtocol identifies the type of the next message. NoneProtocol indicates end of chain
}
// GetProtocol returns this message's protocol identifier
func (m *BaseMessage) GetProtocol() Protocol {
return m.CurrentProtocol
}
func (m *BaseMessage) GetNextPayload() (Payload, error) {
if m.NextProtocol == NoneProtocol {
return nil, nil
}
if m.NextPayload == nil {
return nil, ErrNoPayload
}
return m.NextPayload, nil
}
func (m *BaseMessage) GetNextProtocol() Protocol {
return m.NextProtocol
}
func (m *BaseMessage) GetCurrentHeader() Header {
return m.CurrentHeader
}
// GetNext retrieves the next message in the chain, if one exists.
// Returns nil, nil if NextProtocol is NoneProtocol.
// Uses the provided Registry to create and unmarshal the next message.
func (m *BaseMessage) GetNext(registry Registry) (Message, error) {
if m.NextProtocol == NoneProtocol {
return nil, nil
}
if m.NextPayload == nil {
return nil, ErrNoPayload
}
return registry.Unmarshal(m.NextProtocol, m.NextPayload)
}
// Marshal serializes the message to JSON format
func (m *BaseMessage) Marshal() ([]byte, error) {
return json.Marshal(m)
}
// Unmarshal deserializes the message from JSON format
func (m *BaseMessage) Unmarshal(data []byte) error {
return json.Unmarshal(data, m)
}
func NewBaseMessage(nextProtocol Protocol, nextPayload Marshallable, msg Message) (BaseMessage, error) {
var inner json.RawMessage = nil
if nextPayload != nil {
if nextProtocol == NoneProtocol {
return BaseMessage{}, fmt.Errorf("nextPayload was empty, but protocol was not - protocol: %s", nextProtocol)
}
_inner, err := nextPayload.Marshal()
if err != nil {
return BaseMessage{}, err
}
inner = _inner
}
return BaseMessage{
CurrentProtocol: msg.GetProtocol(),
CurrentHeader: NewV1Header(UnknownSender, UnknownReceiver),
NextPayload: Payload(inner),
NextProtocol: nextProtocol,
}, nil
}