2024-11-15 15:37:28 +08:00
|
|
|
package rabbitmq_amqp
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2025-01-30 18:29:44 +08:00
|
|
|
"github.com/Azure/go-amqp"
|
2024-11-15 15:37:28 +08:00
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
// TargetAddress is an interface that represents an address that can be used to send messages to.
|
|
|
|
|
// It can be either a Queue or an Exchange with a routing key.
|
|
|
|
|
type TargetAddress interface {
|
|
|
|
|
toAddress() (string, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// QueueAddress represents the address of a queue.
|
|
|
|
|
type QueueAddress struct {
|
|
|
|
|
Queue string // The name of the queue
|
|
|
|
|
Parameters string // Additional parameters not related to the queue. Most of the time it is empty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (qas *QueueAddress) toAddress() (string, error) {
|
|
|
|
|
q := &qas.Queue
|
|
|
|
|
if isStringNilOrEmpty(&qas.Queue) {
|
|
|
|
|
q = nil
|
|
|
|
|
}
|
|
|
|
|
return queueAddress(q)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ExchangeAddress represents the address of an exchange with a routing key.
|
|
|
|
|
type ExchangeAddress struct {
|
|
|
|
|
Exchange string // The name of the exchange
|
|
|
|
|
Key string // The routing key. Can be empty
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (eas *ExchangeAddress) toAddress() (string, error) {
|
|
|
|
|
ex := &eas.Exchange
|
|
|
|
|
if isStringNilOrEmpty(&eas.Exchange) {
|
|
|
|
|
ex = nil
|
|
|
|
|
}
|
|
|
|
|
k := &eas.Key
|
|
|
|
|
if isStringNilOrEmpty(&eas.Key) {
|
|
|
|
|
k = nil
|
|
|
|
|
}
|
|
|
|
|
return exchangeAddress(ex, k)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MessageToAddressHelper sets the To property of the message to the address of the target.
|
|
|
|
|
// The target must be a QueueAddress or an ExchangeAddress.
|
2025-02-14 18:08:09 +08:00
|
|
|
// Note: The field msgRef.Properties.To will be overwritten if it is already set.
|
2025-01-30 18:29:44 +08:00
|
|
|
func MessageToAddressHelper(msgRef *amqp.Message, target TargetAddress) error {
|
|
|
|
|
if target == nil {
|
|
|
|
|
return errors.New("target cannot be nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
address, err := target.toAddress()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if msgRef.Properties == nil {
|
|
|
|
|
msgRef.Properties = &amqp.MessageProperties{}
|
|
|
|
|
}
|
|
|
|
|
msgRef.Properties.To = &address
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-14 18:08:09 +08:00
|
|
|
// NewMessageToAddress creates a new message with the given payload and sets the To property to the address of the target.
|
|
|
|
|
// The target must be a QueueAddress or an ExchangeAddress.
|
|
|
|
|
// This function is a helper that combines NewMessage and MessageToAddressHelper.
|
|
|
|
|
func NewMessageToAddress(msg []byte, target TargetAddress) (*amqp.Message, error) {
|
|
|
|
|
message := amqp.NewMessage(msg)
|
|
|
|
|
err := MessageToAddressHelper(message, target)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return message, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
// address Creates the address for the exchange or queue following the RabbitMQ conventions.
|
2024-11-21 17:34:08 +08:00
|
|
|
// see: https://www.rabbitmq.com/docs/next/amqp#address-v2
|
2025-01-30 18:29:44 +08:00
|
|
|
func address(exchange, key, queue *string, urlParameters *string) (string, error) {
|
2024-11-21 17:34:08 +08:00
|
|
|
if exchange == nil && queue == nil {
|
2024-11-15 15:37:28 +08:00
|
|
|
return "", errors.New("exchange or queue must be set")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
urlAppend := ""
|
2024-11-21 17:34:08 +08:00
|
|
|
if !isStringNilOrEmpty(urlParameters) {
|
|
|
|
|
urlAppend = *urlParameters
|
2024-11-15 15:37:28 +08:00
|
|
|
}
|
2024-11-21 17:34:08 +08:00
|
|
|
if !isStringNilOrEmpty(exchange) && !isStringNilOrEmpty(queue) {
|
2024-11-15 15:37:28 +08:00
|
|
|
return "", errors.New("exchange and queue cannot be set together")
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-21 17:34:08 +08:00
|
|
|
if !isStringNilOrEmpty(exchange) {
|
|
|
|
|
if !isStringNilOrEmpty(key) {
|
|
|
|
|
return "/" + exchanges + "/" + encodePathSegments(*exchange) + "/" + encodePathSegments(*key) + urlAppend, nil
|
2024-11-15 15:37:28 +08:00
|
|
|
}
|
2024-11-21 17:34:08 +08:00
|
|
|
return "/" + exchanges + "/" + encodePathSegments(*exchange) + urlAppend, nil
|
2024-11-15 15:37:28 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-21 17:34:08 +08:00
|
|
|
if queue == nil {
|
2024-11-15 15:37:28 +08:00
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-21 17:34:08 +08:00
|
|
|
if isStringNilOrEmpty(queue) {
|
2024-11-15 15:37:28 +08:00
|
|
|
return "", errors.New("queue must be set")
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-21 17:34:08 +08:00
|
|
|
return "/" + queues + "/" + encodePathSegments(*queue) + urlAppend, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
// exchangeAddress Creates the address for the exchange
|
|
|
|
|
// See address for more information
|
|
|
|
|
func exchangeAddress(exchange, key *string) (string, error) {
|
|
|
|
|
return address(exchange, key, nil, nil)
|
2024-11-21 17:34:08 +08:00
|
|
|
}
|
|
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
// queueAddress Creates the address for the queue.
|
|
|
|
|
// See address for more information
|
|
|
|
|
func queueAddress(queue *string) (string, error) {
|
|
|
|
|
return address(nil, nil, queue, nil)
|
2024-11-21 17:34:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PurgeQueueAddress Creates the address for purging the queue.
|
2025-01-30 18:29:44 +08:00
|
|
|
// See address for more information
|
|
|
|
|
func purgeQueueAddress(queue *string) (string, error) {
|
2024-11-21 17:34:08 +08:00
|
|
|
parameter := "/messages"
|
2025-01-30 18:29:44 +08:00
|
|
|
return address(nil, nil, queue, ¶meter)
|
2024-11-15 15:37:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// encodePathSegments takes a string and returns its percent-encoded representation.
|
|
|
|
|
func encodePathSegments(input string) string {
|
|
|
|
|
var encoded strings.Builder
|
|
|
|
|
|
|
|
|
|
// Iterate over each character in the input string
|
|
|
|
|
for _, char := range input {
|
|
|
|
|
// Check if the character is an unreserved character (i.e., it doesn't need encoding)
|
|
|
|
|
if isUnreserved(char) {
|
|
|
|
|
encoded.WriteRune(char) // Append as is
|
|
|
|
|
} else {
|
|
|
|
|
// Encode character To %HH format
|
|
|
|
|
encoded.WriteString(fmt.Sprintf("%%%02X", char))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return encoded.String()
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 22:26:12 +08:00
|
|
|
//// Decode takes a percent-encoded string and returns its decoded representation.
|
|
|
|
|
//func decode(input string) (string, error) {
|
|
|
|
|
// // Use url.QueryUnescape which properly decodes percent-encoded strings
|
|
|
|
|
// decoded, err := url.QueryUnescape(input)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return "", err
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// return decoded, nil
|
|
|
|
|
//}
|
2024-11-15 15:37:28 +08:00
|
|
|
|
|
|
|
|
// isUnreserved checks if a character is an unreserved character in percent encoding
|
|
|
|
|
// Unreserved characters are: A-Z, a-z, 0-9, -, ., _, ~
|
|
|
|
|
func isUnreserved(char rune) bool {
|
|
|
|
|
return (char >= 'A' && char <= 'Z') ||
|
|
|
|
|
(char >= 'a' && char <= 'z') ||
|
|
|
|
|
(char >= '0' && char <= '9') ||
|
|
|
|
|
char == '-' || char == '.' || char == '_' || char == '~'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func bindingPath() string {
|
|
|
|
|
return "/" + bindings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func bindingPathWithExchangeQueueKey(toQueue bool, sourceName, destinationName, key string) string {
|
|
|
|
|
sourceNameEncoded := encodePathSegments(sourceName)
|
|
|
|
|
destinationNameEncoded := encodePathSegments(destinationName)
|
|
|
|
|
keyEncoded := encodePathSegments(key)
|
|
|
|
|
destinationType := "dste"
|
|
|
|
|
if toQueue {
|
|
|
|
|
destinationType = "dstq"
|
|
|
|
|
}
|
|
|
|
|
format := "/%s/src=%s;%s=%s;key=%s;args="
|
|
|
|
|
return fmt.Sprintf(format, bindings, sourceNameEncoded, destinationType, destinationNameEncoded, keyEncoded)
|
2025-01-16 22:26:12 +08:00
|
|
|
}
|
2024-11-15 15:37:28 +08:00
|
|
|
|
2025-01-30 18:29:44 +08:00
|
|
|
func validateAddress(address string) error {
|
|
|
|
|
if strings.HasPrefix(address, fmt.Sprintf("/%s/", exchanges)) || strings.HasPrefix(address, fmt.Sprintf("/%s/", queues)) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("invalid destination address, the address should start with /%s/ or/%s/ ", exchanges, queues)
|
2024-11-15 15:37:28 +08:00
|
|
|
}
|