mirror of
https://github.com/harshabose/socket-comm.git
synced 2025-10-05 15:46:52 +08:00
181 lines
5.0 KiB
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
|
|
}
|