Refactor the API interfaces (#21)
* Refactor the API * Use the interfaces to define the targets for queues, exchanges and bindings * Implement the producer message target-based --------- Signed-off-by: Gabriele Santomaggio <G.santomaggio@gmail.com>
This commit is contained in:
parent
bfc8b02de9
commit
89c4dd74a4
|
|
@ -10,4 +10,10 @@ This library is in early stages of development. It is meant to be used with Rabb
|
|||
|
||||
## Getting Started
|
||||
|
||||
You can find an example in: `examples/getting_started`
|
||||
You can find an example in: `docs/examples/getting_started`
|
||||
|
||||
## Examples
|
||||
|
||||
You can find more examples in: `docs/examples`
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func main() {
|
|||
// Create the management interface for the connection
|
||||
// so we can declare exchanges, queues, and bindings
|
||||
management := amqpConnection.Management()
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &rabbitmq_amqp.ExchangeSpecification{
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &rabbitmq_amqp.TopicExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -46,9 +46,8 @@ func main() {
|
|||
}
|
||||
|
||||
// Declare a Quorum queue
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &rabbitmq_amqp.QueueSpecification{
|
||||
Name: queueName,
|
||||
QueueType: rabbitmq_amqp.QueueType{Type: rabbitmq_amqp.Quorum},
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &rabbitmq_amqp.QuorumQueueSpecification{
|
||||
Name: queueName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -57,7 +56,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Bind the queue to the exchange
|
||||
bindingPath, err := management.Bind(context.TODO(), &rabbitmq_amqp.BindingSpecification{
|
||||
bindingPath, err := management.Bind(context.TODO(), &rabbitmq_amqp.ExchangeToQueueBindingSpecification{
|
||||
SourceExchange: exchangeName,
|
||||
DestinationQueue: queueName,
|
||||
BindingKey: routingKey,
|
||||
|
|
@ -70,8 +69,10 @@ func main() {
|
|||
|
||||
// Create a consumer to receive messages from the queue
|
||||
// you need to build the address of the queue, but you can use the helper function
|
||||
addrQueue, _ := rabbitmq_amqp.QueueAddress(&queueName)
|
||||
consumer, err := amqpConnection.Consumer(context.Background(), addrQueue, "getting-started-consumer")
|
||||
|
||||
consumer, err := amqpConnection.NewConsumer(context.Background(), &rabbitmq_amqp.QueueAddress{
|
||||
Queue: queueName,
|
||||
}, "getting-started-consumer")
|
||||
if err != nil {
|
||||
rabbitmq_amqp.Error("Error creating consumer", err)
|
||||
return
|
||||
|
|
@ -105,8 +106,10 @@ func main() {
|
|||
}
|
||||
}(consumerContext)
|
||||
|
||||
addr, _ := rabbitmq_amqp.ExchangeAddress(&exchangeName, &routingKey)
|
||||
publisher, err := amqpConnection.Publisher(context.Background(), addr, "getting-started-publisher")
|
||||
publisher, err := amqpConnection.NewPublisher(context.Background(), &rabbitmq_amqp.ExchangeAddress{
|
||||
Exchange: exchangeName,
|
||||
Key: routingKey,
|
||||
}, "getting-started-publisher")
|
||||
if err != nil {
|
||||
rabbitmq_amqp.Error("Error creating publisher", err)
|
||||
return
|
||||
|
|
@ -151,10 +154,12 @@ func main() {
|
|||
err = consumer.Close(context.Background())
|
||||
if err != nil {
|
||||
rabbitmq_amqp.Error("[Consumer]", err)
|
||||
return
|
||||
}
|
||||
// Close the publisher
|
||||
err = publisher.Close(context.Background())
|
||||
if err != nil {
|
||||
rabbitmq_amqp.Error("[Publisher]", err)
|
||||
return
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
|
|
@ -5,18 +5,18 @@ go 1.22.0
|
|||
require (
|
||||
github.com/Azure/go-amqp v1.4.0-beta.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/onsi/ginkgo/v2 v2.20.2
|
||||
github.com/onsi/gomega v1.34.2
|
||||
github.com/onsi/ginkgo/v2 v2.22.1
|
||||
github.com/onsi/gomega v1.36.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
20
go.sum
20
go.sum
|
|
@ -10,14 +10,14 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
|
|||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
|
||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM=
|
||||
github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
|
|
@ -28,10 +28,10 @@ golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
|||
|
|
@ -3,12 +3,71 @@ package rabbitmq_amqp
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Azure/go-amqp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Address Creates the address for the exchange or queue following the RabbitMQ conventions.
|
||||
// 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.
|
||||
// Note: The field To will be overwritten if it is already set.
|
||||
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
|
||||
}
|
||||
|
||||
// address Creates the address for the exchange or queue following the RabbitMQ conventions.
|
||||
// see: https://www.rabbitmq.com/docs/next/amqp#address-v2
|
||||
func Address(exchange, key, queue *string, urlParameters *string) (string, error) {
|
||||
func address(exchange, key, queue *string, urlParameters *string) (string, error) {
|
||||
if exchange == nil && queue == nil {
|
||||
return "", errors.New("exchange or queue must be set")
|
||||
}
|
||||
|
|
@ -39,23 +98,23 @@ func Address(exchange, key, queue *string, urlParameters *string) (string, error
|
|||
return "/" + queues + "/" + encodePathSegments(*queue) + urlAppend, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// QueueAddress Creates the address for the queue.
|
||||
// See Address for more information
|
||||
func QueueAddress(queue *string) (string, error) {
|
||||
return Address(nil, nil, queue, nil)
|
||||
// queueAddress Creates the address for the queue.
|
||||
// See address for more information
|
||||
func queueAddress(queue *string) (string, error) {
|
||||
return address(nil, nil, queue, nil)
|
||||
}
|
||||
|
||||
// PurgeQueueAddress Creates the address for purging the queue.
|
||||
// See Address for more information
|
||||
func PurgeQueueAddress(queue *string) (string, error) {
|
||||
// See address for more information
|
||||
func purgeQueueAddress(queue *string) (string, error) {
|
||||
parameter := "/messages"
|
||||
return Address(nil, nil, queue, ¶meter)
|
||||
return address(nil, nil, queue, ¶meter)
|
||||
}
|
||||
|
||||
// encodePathSegments takes a string and returns its percent-encoded representation.
|
||||
|
|
@ -112,6 +171,9 @@ func bindingPathWithExchangeQueueKey(toQueue bool, sourceName, destinationName,
|
|||
return fmt.Sprintf(format, bindings, sourceNameEncoded, destinationType, destinationNameEncoded, keyEncoded)
|
||||
}
|
||||
|
||||
func validateAddress(address string) bool {
|
||||
return strings.HasPrefix(address, fmt.Sprintf("/%s/", exchanges)) || strings.HasPrefix(address, fmt.Sprintf("/%s/", queues))
|
||||
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)
|
||||
}
|
||||
|
|
@ -5,18 +5,18 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Address builder test ", func() {
|
||||
var _ = Describe("address builder test ", func() {
|
||||
It("With exchange, queue and key should raise and error", func() {
|
||||
queue := "my_queue"
|
||||
exchange := "my_exchange"
|
||||
|
||||
_, err := Address(&exchange, nil, &queue, nil)
|
||||
_, err := address(&exchange, nil, &queue, nil)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(Equal("exchange and queue cannot be set together"))
|
||||
})
|
||||
|
||||
It("Without exchange and queue should raise and error", func() {
|
||||
_, err := Address(nil, nil, nil, nil)
|
||||
_, err := address(nil, nil, nil, nil)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(Equal("exchange or queue must be set"))
|
||||
})
|
||||
|
|
@ -25,14 +25,14 @@ var _ = Describe("Address builder test ", func() {
|
|||
exchange := "my_exchange"
|
||||
key := "my_key"
|
||||
|
||||
address, err := Address(&exchange, &key, nil, nil)
|
||||
address, err := address(&exchange, &key, nil, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(address).To(Equal("/exchanges/my_exchange/my_key"))
|
||||
})
|
||||
|
||||
It("With exchange should return address", func() {
|
||||
exchange := "my_exchange"
|
||||
address, err := Address(&exchange, nil, nil, nil)
|
||||
address, err := address(&exchange, nil, nil, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(address).To(Equal("/exchanges/my_exchange"))
|
||||
})
|
||||
|
|
@ -42,21 +42,21 @@ var _ = Describe("Address builder test ", func() {
|
|||
exchange := "my_ exchange/()"
|
||||
key := "my_key "
|
||||
|
||||
address, err := Address(&exchange, &key, nil, nil)
|
||||
address, err := address(&exchange, &key, nil, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(address).To(Equal("/exchanges/my_%20exchange%2F%28%29/my_key%20"))
|
||||
})
|
||||
|
||||
It("With queue should return address", func() {
|
||||
queue := "my_queue>"
|
||||
address, err := Address(nil, nil, &queue, nil)
|
||||
address, err := address(nil, nil, &queue, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(address).To(Equal("/queues/my_queue%3E"))
|
||||
})
|
||||
|
||||
It("With queue and urlParameters should return address", func() {
|
||||
queue := "my_queue"
|
||||
address, err := PurgeQueueAddress(&queue)
|
||||
address, err := purgeQueueAddress(&queue)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(address).To(Equal("/queues/my_queue/messages"))
|
||||
})
|
||||
|
|
@ -2,6 +2,7 @@ package rabbitmq_amqp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/Azure/go-amqp"
|
||||
)
|
||||
|
||||
|
|
@ -31,18 +32,9 @@ func (b *AMQPBinding) SourceExchange(sourceName string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *AMQPBinding) DestinationExchange(destinationName string) {
|
||||
if len(destinationName) > 0 {
|
||||
b.destinationName = destinationName
|
||||
b.toQueue = false
|
||||
}
|
||||
}
|
||||
|
||||
func (b *AMQPBinding) DestinationQueue(queueName string) {
|
||||
if len(queueName) > 0 {
|
||||
b.destinationName = queueName
|
||||
b.toQueue = true
|
||||
}
|
||||
func (b *AMQPBinding) Destination(name string, isQueue bool) {
|
||||
b.destinationName = name
|
||||
b.toQueue = isQueue
|
||||
}
|
||||
|
||||
// Bind creates a binding between an exchange and a queue or exchange
|
||||
|
|
@ -50,11 +42,20 @@ func (b *AMQPBinding) DestinationQueue(queueName string) {
|
|||
// Returns the binding path that can be used to unbind the binding.
|
||||
// Given a virtual host, the binding path is unique.
|
||||
func (b *AMQPBinding) Bind(ctx context.Context) (string, error) {
|
||||
destination := "destination_queue"
|
||||
if !b.toQueue {
|
||||
destination = "destination_exchange"
|
||||
}
|
||||
|
||||
if len(b.sourceName) == 0 || len(b.destinationName) == 0 {
|
||||
return "", errors.New("source and destination names are required")
|
||||
}
|
||||
|
||||
path := bindingPath()
|
||||
kv := make(map[string]any)
|
||||
kv["binding_key"] = b.bindingKey
|
||||
kv["source"] = b.sourceName
|
||||
kv["destination_queue"] = b.destinationName
|
||||
kv[destination] = b.destinationName
|
||||
kv["arguments"] = make(map[string]any)
|
||||
_, err := b.management.Request(ctx, kv, path, commandPost, []int{responseCode204})
|
||||
bindingPathWithExchangeQueueKey := bindingPathWithExchangeQueueKey(b.toQueue, b.sourceName, b.destinationName, b.bindingKey)
|
||||
|
|
|
|||
|
|
@ -23,20 +23,20 @@ var _ = Describe("AMQP Bindings test ", func() {
|
|||
It("AMQP Bindings between Exchange and Queue Should succeed", func() {
|
||||
const exchangeName = "Exchange_AMQP Bindings between Exchange and Queue should uccess"
|
||||
const queueName = "Queue_AMQP Bindings between Exchange and Queue should succeed"
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &ExchangeSpecification{
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &TopicExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exchangeInfo).NotTo(BeNil())
|
||||
Expect(exchangeInfo.Name()).To(Equal(exchangeName))
|
||||
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||
Name: queueName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queueInfo).NotTo(BeNil())
|
||||
Expect(queueInfo.Name()).To(Equal(queueName))
|
||||
bindingPath, err := management.Bind(context.TODO(), &BindingSpecification{
|
||||
bindingPath, err := management.Bind(context.TODO(), &ExchangeToQueueBindingSpecification{
|
||||
SourceExchange: exchangeName,
|
||||
DestinationQueue: queueName,
|
||||
BindingKey: "routing-key",
|
||||
|
|
@ -49,4 +49,68 @@ var _ = Describe("AMQP Bindings test ", func() {
|
|||
err = management.DeleteQueue(context.TODO(), queueName)
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Bindings between Exchange and Exchange Should succeed", func() {
|
||||
var exchangeName = generateName("Exchange_AMQP Bindings between Exchange and Exchange should succeed")
|
||||
var exchangeName2 = generateName("Exchange_AMQP Bindings between Exchange and Exchange should succeed 2")
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &TopicExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exchangeInfo).NotTo(BeNil())
|
||||
Expect(exchangeInfo.Name()).To(Equal(exchangeName))
|
||||
|
||||
exchangeInfo2, err := management.DeclareExchange(context.TODO(), &TopicExchangeSpecification{
|
||||
Name: exchangeName2})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exchangeInfo2).NotTo(BeNil())
|
||||
Expect(exchangeInfo2.Name()).To(Equal(exchangeName2))
|
||||
|
||||
bindingPath, err := management.Bind(context.TODO(), &ExchangeToExchangeBindingSpecification{
|
||||
SourceExchange: exchangeName,
|
||||
DestinationExchange: exchangeName2,
|
||||
})
|
||||
|
||||
Expect(err).To(BeNil())
|
||||
Expect(management.Unbind(context.TODO(), bindingPath)).To(BeNil())
|
||||
Expect(management.DeleteExchange(context.TODO(), exchangeName)).To(BeNil())
|
||||
Expect(management.DeleteExchange(context.TODO(), exchangeName2)).To(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Bindings should fail if source or destinations are empty", func() {
|
||||
|
||||
_, err := management.Bind(context.TODO(), &ExchangeToExchangeBindingSpecification{
|
||||
SourceExchange: "",
|
||||
DestinationExchange: "destination",
|
||||
})
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("source and destination names are required"))
|
||||
|
||||
_, err = management.Bind(context.TODO(), &ExchangeToExchangeBindingSpecification{
|
||||
SourceExchange: "source",
|
||||
DestinationExchange: "",
|
||||
})
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("source and destination names are required"))
|
||||
|
||||
_, err = management.Bind(context.TODO(), &ExchangeToQueueBindingSpecification{
|
||||
SourceExchange: "",
|
||||
DestinationQueue: "destination",
|
||||
})
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("source and destination names are required"))
|
||||
|
||||
_, err = management.Bind(context.TODO(), &ExchangeToQueueBindingSpecification{
|
||||
SourceExchange: "source",
|
||||
DestinationQueue: "",
|
||||
})
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("source and destination names are required"))
|
||||
})
|
||||
|
||||
It("AMQP Bindings should fail specification is nil", func() {
|
||||
_, err := management.Bind(context.TODO(), nil)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("binding specification cannot be nil"))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,20 +22,40 @@ type AmqpConnection struct {
|
|||
session *amqp.Session
|
||||
}
|
||||
|
||||
func (a *AmqpConnection) Publisher(ctx context.Context, destinationAdd string, linkName string) (*Publisher, error) {
|
||||
if !validateAddress(destinationAdd) {
|
||||
return nil, fmt.Errorf("invalid destination address, the address should start with /%s/ or/%s/ ", exchanges, queues)
|
||||
// NewPublisher creates a new Publisher that sends messages to the provided destination.
|
||||
// The destination is a TargetAddress that can be a Queue or an Exchange with a routing key.
|
||||
// See QueueAddress and ExchangeAddress for more information.
|
||||
func (a *AmqpConnection) NewPublisher(ctx context.Context, destination TargetAddress, linkName string) (*Publisher, error) {
|
||||
destinationAdd := ""
|
||||
err := error(nil)
|
||||
if destination != nil {
|
||||
destinationAdd, err = destination.toAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = validateAddress(destinationAdd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
sender, err := a.session.NewSender(ctx, destinationAdd, createSenderLinkOptions(destinationAdd, linkName, AtLeastOnce))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPublisher(sender), nil
|
||||
return newPublisher(sender, destinationAdd != ""), nil
|
||||
}
|
||||
|
||||
func (a *AmqpConnection) Consumer(ctx context.Context, destinationAdd string, linkName string) (*Consumer, error) {
|
||||
if !validateAddress(destinationAdd) {
|
||||
return nil, fmt.Errorf("invalid destination address, the address should start with /%s/ or/%s/ ", exchanges, queues)
|
||||
// NewConsumer creates a new Consumer that listens to the provided destination. Destination is a QueueAddress.
|
||||
func (a *AmqpConnection) NewConsumer(ctx context.Context, destination *QueueAddress, linkName string) (*Consumer, error) {
|
||||
destinationAdd, err := destination.toAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = validateAddress(destinationAdd)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
receiver, err := a.session.NewReceiver(ctx, destinationAdd, createReceiverLinkOptions(destinationAdd, linkName, AtLeastOnce))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,46 +9,41 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var _ = Describe("Consumer tests", func() {
|
||||
var _ = Describe("NewConsumer tests", func() {
|
||||
|
||||
It("AMQP Consumer should fail due to context cancellation", func() {
|
||||
qName := generateNameWithDateTime("AMQP Consumer should fail due to context cancellation")
|
||||
It("AMQP NewConsumer should fail due to context cancellation", func() {
|
||||
qName := generateNameWithDateTime("AMQP NewConsumer should fail due to context cancellation")
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
addr, _ := QueueAddress(&qName)
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||
Name: qName,
|
||||
IsAutoDelete: false,
|
||||
IsExclusive: false,
|
||||
QueueType: QueueType{Quorum},
|
||||
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: qName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queue).NotTo(BeNil())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
|
||||
cancel()
|
||||
_, err = connection.Consumer(ctx, addr, "test")
|
||||
_, err = connection.NewConsumer(ctx, &QueueAddress{
|
||||
Queue: qName,
|
||||
}, "test")
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("context canceled"))
|
||||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Consumer should ack and empty the queue", func() {
|
||||
qName := generateNameWithDateTime("AMQP Consumer should ack and empty the queue")
|
||||
It("AMQP NewConsumer should ack and empty the queue", func() {
|
||||
qName := generateNameWithDateTime("AMQP NewConsumer should ack and empty the queue")
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||
Name: qName,
|
||||
IsAutoDelete: false,
|
||||
IsExclusive: false,
|
||||
QueueType: QueueType{Quorum},
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: qName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queue).NotTo(BeNil())
|
||||
publishMessages(qName, 10)
|
||||
addr, _ := QueueAddress(&qName)
|
||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
||||
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(consumer).NotTo(BeNil())
|
||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||
|
|
@ -66,22 +61,18 @@ var _ = Describe("Consumer tests", func() {
|
|||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Consumer should requeue the message to the queue", func() {
|
||||
It("AMQP NewConsumer should requeue the message to the queue", func() {
|
||||
|
||||
qName := generateNameWithDateTime("AMQP Consumer should requeue the message to the queue")
|
||||
qName := generateNameWithDateTime("AMQP NewConsumer should requeue the message to the queue")
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||
Name: qName,
|
||||
IsAutoDelete: false,
|
||||
IsExclusive: false,
|
||||
QueueType: QueueType{Quorum},
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: qName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queue).NotTo(BeNil())
|
||||
publishMessages(qName, 1)
|
||||
addr, _ := QueueAddress(&qName)
|
||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
||||
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(consumer).NotTo(BeNil())
|
||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||
|
|
@ -97,22 +88,18 @@ var _ = Describe("Consumer tests", func() {
|
|||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Consumer should requeue the message to the queue with annotations", func() {
|
||||
It("AMQP NewConsumer should requeue the message to the queue with annotations", func() {
|
||||
|
||||
qName := generateNameWithDateTime("AMQP Consumer should requeue the message to the queue with annotations")
|
||||
qName := generateNameWithDateTime("AMQP NewConsumer should requeue the message to the queue with annotations")
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||
Name: qName,
|
||||
IsAutoDelete: false,
|
||||
IsExclusive: false,
|
||||
QueueType: QueueType{Quorum},
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: qName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queue).NotTo(BeNil())
|
||||
publishMessages(qName, 1)
|
||||
addr, _ := QueueAddress(&qName)
|
||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
||||
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(consumer).NotTo(BeNil())
|
||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||
|
|
@ -136,22 +123,18 @@ var _ = Describe("Consumer tests", func() {
|
|||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Consumer should discard the message to the queue with and without annotations", func() {
|
||||
It("AMQP NewConsumer should discard the message to the queue with and without annotations", func() {
|
||||
// TODO: Implement this test with a dead letter queue to test the discard feature
|
||||
qName := generateNameWithDateTime("AMQP Consumer should discard the message to the queue with and without annotations")
|
||||
qName := generateNameWithDateTime("AMQP NewConsumer should discard the message to the queue with and without annotations")
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||
Name: qName,
|
||||
IsAutoDelete: false,
|
||||
IsExclusive: false,
|
||||
QueueType: QueueType{Quorum},
|
||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: qName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queue).NotTo(BeNil())
|
||||
publishMessages(qName, 2)
|
||||
addr, _ := QueueAddress(&qName)
|
||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
||||
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(consumer).NotTo(BeNil())
|
||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package rabbitmq_amqp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/Azure/go-amqp"
|
||||
)
|
||||
|
||||
|
|
@ -34,7 +35,11 @@ func newAmqpExchange(management *AmqpManagement, name string) *AmqpExchange {
|
|||
}
|
||||
|
||||
func (e *AmqpExchange) Declare(ctx context.Context) (*AmqpExchangeInfo, error) {
|
||||
path, err := ExchangeAddress(&e.name, nil)
|
||||
if len(e.name) == 0 {
|
||||
return nil, errors.New("exchange name cannot be empty")
|
||||
}
|
||||
|
||||
path, err := exchangeAddress(&e.name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -59,7 +64,7 @@ func (e *AmqpExchange) IsAutoDelete() bool {
|
|||
}
|
||||
|
||||
func (e *AmqpExchange) Delete(ctx context.Context) error {
|
||||
path, err := ExchangeAddress(&e.name, nil)
|
||||
path, err := exchangeAddress(&e.name, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ var _ = Describe("AMQP Exchange test ", func() {
|
|||
|
||||
It("AMQP Exchange Declare with Default and Delete should succeed", func() {
|
||||
const exchangeName = "AMQP Exchange Declare and Delete with Default should succeed"
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &ExchangeSpecification{
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &DirectExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
|
|
@ -35,9 +35,8 @@ var _ = Describe("AMQP Exchange test ", func() {
|
|||
|
||||
It("AMQP Exchange Declare with Topic and Delete should succeed", func() {
|
||||
const exchangeName = "AMQP Exchange Declare with Topic and Delete should succeed"
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &ExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
ExchangeType: ExchangeType{Topic},
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &TopicExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exchangeInfo).NotTo(BeNil())
|
||||
|
|
@ -48,10 +47,8 @@ var _ = Describe("AMQP Exchange test ", func() {
|
|||
|
||||
It("AMQP Exchange Declare with FanOut and Delete should succeed", func() {
|
||||
const exchangeName = "AMQP Exchange Declare with FanOut and Delete should succeed"
|
||||
//exchangeSpec := management.Exchange(exchangeName).ExchangeType(ExchangeType{FanOut})
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &ExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
ExchangeType: ExchangeType{FanOut},
|
||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &FanOutExchangeSpecification{
|
||||
Name: exchangeName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exchangeInfo).NotTo(BeNil())
|
||||
|
|
@ -59,4 +56,19 @@ var _ = Describe("AMQP Exchange test ", func() {
|
|||
err = management.DeleteExchange(context.TODO(), exchangeName)
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Exchange should fail when specification is nil", func() {
|
||||
_, err := management.DeclareExchange(context.TODO(), nil)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("exchange specification cannot be nil"))
|
||||
})
|
||||
|
||||
It("AMQP Exchange should fail when name is empty", func() {
|
||||
_, err := management.DeclareExchange(context.TODO(), &TopicExchangeSpecification{
|
||||
Name: "",
|
||||
IsAutoDelete: false,
|
||||
})
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("exchange name cannot be empty"))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -170,23 +170,17 @@ func (a *AmqpManagement) request(ctx context.Context, id string, body any, path
|
|||
return make(map[string]any), nil
|
||||
}
|
||||
|
||||
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification *QueueSpecification) (*AmqpQueueInfo, error) {
|
||||
var amqpQueue *AmqpQueue
|
||||
|
||||
if specification == nil || len(specification.Name) <= 0 {
|
||||
// If the specification is nil or the name is empty, then we create a new queue
|
||||
// with a random name with generateNameWithDefaultPrefix()
|
||||
amqpQueue = newAmqpQueue(a, "")
|
||||
} else {
|
||||
amqpQueue = newAmqpQueue(a, specification.Name)
|
||||
amqpQueue.AutoDelete(specification.IsAutoDelete)
|
||||
amqpQueue.Exclusive(specification.IsExclusive)
|
||||
amqpQueue.MaxLengthBytes(specification.MaxLengthBytes)
|
||||
amqpQueue.DeadLetterExchange(specification.DeadLetterExchange)
|
||||
amqpQueue.DeadLetterRoutingKey(specification.DeadLetterRoutingKey)
|
||||
amqpQueue.QueueType(specification.QueueType)
|
||||
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification QueueSpecification) (*AmqpQueueInfo, error) {
|
||||
if specification == nil {
|
||||
return nil, fmt.Errorf("queue specification cannot be nil. You need to provide a valid QueueSpecification")
|
||||
}
|
||||
|
||||
amqpQueue := newAmqpQueue(a, specification.name())
|
||||
amqpQueue.AutoDelete(specification.isAutoDelete())
|
||||
amqpQueue.Exclusive(specification.isExclusive())
|
||||
amqpQueue.QueueType(specification.queueType())
|
||||
amqpQueue.SetArguments(specification.buildArguments())
|
||||
|
||||
return amqpQueue.Declare(ctx)
|
||||
}
|
||||
|
||||
|
|
@ -195,14 +189,14 @@ func (a *AmqpManagement) DeleteQueue(ctx context.Context, name string) error {
|
|||
return q.Delete(ctx)
|
||||
}
|
||||
|
||||
func (a *AmqpManagement) DeclareExchange(ctx context.Context, exchangeSpecification *ExchangeSpecification) (*AmqpExchangeInfo, error) {
|
||||
func (a *AmqpManagement) DeclareExchange(ctx context.Context, exchangeSpecification ExchangeSpecification) (*AmqpExchangeInfo, error) {
|
||||
if exchangeSpecification == nil {
|
||||
return nil, fmt.Errorf("exchangeSpecification is nil")
|
||||
return nil, errors.New("exchange specification cannot be nil. You need to provide a valid ExchangeSpecification")
|
||||
}
|
||||
|
||||
exchange := newAmqpExchange(a, exchangeSpecification.Name)
|
||||
exchange.AutoDelete(exchangeSpecification.IsAutoDelete)
|
||||
exchange.ExchangeType(exchangeSpecification.ExchangeType)
|
||||
exchange := newAmqpExchange(a, exchangeSpecification.name())
|
||||
exchange.AutoDelete(exchangeSpecification.isAutoDelete())
|
||||
exchange.ExchangeType(exchangeSpecification.exchangeType())
|
||||
return exchange.Declare(ctx)
|
||||
}
|
||||
|
||||
|
|
@ -211,12 +205,15 @@ func (a *AmqpManagement) DeleteExchange(ctx context.Context, name string) error
|
|||
return e.Delete(ctx)
|
||||
}
|
||||
|
||||
func (a *AmqpManagement) Bind(ctx context.Context, bindingSpecification *BindingSpecification) (string, error) {
|
||||
func (a *AmqpManagement) Bind(ctx context.Context, bindingSpecification BindingSpecification) (string, error) {
|
||||
if bindingSpecification == nil {
|
||||
return "", fmt.Errorf("binding specification cannot be nil. You need to provide a valid BindingSpecification")
|
||||
}
|
||||
|
||||
bind := newAMQPBinding(a)
|
||||
bind.SourceExchange(bindingSpecification.SourceExchange)
|
||||
bind.DestinationQueue(bindingSpecification.DestinationQueue)
|
||||
bind.DestinationExchange(bindingSpecification.DestinationExchange)
|
||||
bind.BindingKey(bindingSpecification.BindingKey)
|
||||
bind.SourceExchange(bindingSpecification.sourceExchange())
|
||||
bind.Destination(bindingSpecification.destination(), bindingSpecification.isDestinationQueue())
|
||||
bind.BindingKey(bindingSpecification.bindingKey())
|
||||
return bind.Bind(ctx)
|
||||
|
||||
}
|
||||
|
|
@ -225,7 +222,7 @@ func (a *AmqpManagement) Unbind(ctx context.Context, bindingPath string) error {
|
|||
return bind.Unbind(ctx, bindingPath)
|
||||
}
|
||||
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*AmqpQueueInfo, error) {
|
||||
path, err := QueueAddress(&queueName)
|
||||
path, err := queueAddress(&queueName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package rabbitmq_amqp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Azure/go-amqp"
|
||||
)
|
||||
|
||||
|
|
@ -10,23 +11,63 @@ type PublishResult struct {
|
|||
Message *amqp.Message
|
||||
}
|
||||
|
||||
// Publisher is a publisher that sends messages to a specific destination address.
|
||||
type Publisher struct {
|
||||
sender *amqp.Sender
|
||||
sender *amqp.Sender
|
||||
staticTargetAddress bool
|
||||
}
|
||||
|
||||
func newPublisher(sender *amqp.Sender) *Publisher {
|
||||
return &Publisher{sender: sender}
|
||||
func newPublisher(sender *amqp.Sender, staticTargetAddress bool) *Publisher {
|
||||
return &Publisher{sender: sender, staticTargetAddress: staticTargetAddress}
|
||||
}
|
||||
|
||||
// Publish sends a message to the destination address.
|
||||
// The message is sent to the destination address and the outcome of the operation is returned.
|
||||
// The outcome is a DeliveryState that indicates if the message was accepted or rejected.
|
||||
// RabbitMQ supports the following DeliveryState types:
|
||||
// - StateAccepted
|
||||
// - StateReleased
|
||||
// - StateRejected
|
||||
// See: https://www.rabbitmq.com/docs/next/amqp#outcomes for more information.
|
||||
/*
|
||||
Publish sends a message to the destination address that can be decided during the creation of the publisher or at the time of sending the message.
|
||||
|
||||
The message is sent and the outcome of the operation is returned.
|
||||
The outcome is a DeliveryState that indicates if the message was accepted or rejected.
|
||||
RabbitMQ supports the following DeliveryState types:
|
||||
|
||||
- StateAccepted
|
||||
- StateReleased
|
||||
- StateRejected
|
||||
See: https://www.rabbitmq.com/docs/next/amqp#outcomes for more information.
|
||||
|
||||
Note: If the destination address is not defined during the creation, the message must have a TO property set.
|
||||
You can use the helper "MessageToAddressHelper" to create the destination address.
|
||||
See the examples:
|
||||
Create a new publisher that sends messages to a specific destination address:
|
||||
<code>
|
||||
|
||||
publisher, err := amqpConnection.NewPublisher(context.Background(), &rabbitmq_amqp.ExchangeAddress{
|
||||
Exchange: "myExchangeName",
|
||||
Key: "myRoutingKey",
|
||||
}
|
||||
|
||||
.. publisher.Publish(context.Background(), amqp.NewMessage([]byte("Hello, World!")))
|
||||
|
||||
</code>
|
||||
Create a new publisher that sends messages based on message destination address:
|
||||
<code>
|
||||
|
||||
publisher, err := connection.NewPublisher(context.Background(), nil, "test")
|
||||
msg := amqp.NewMessage([]byte("hello"))
|
||||
..:= MessageToAddressHelper(msg, &QueueAddress{Queue: "myQueueName"})
|
||||
..:= publisher.Publish(context.Background(), msg)
|
||||
|
||||
</code>
|
||||
*/
|
||||
func (m *Publisher) Publish(ctx context.Context, message *amqp.Message) (*PublishResult, error) {
|
||||
if !m.staticTargetAddress {
|
||||
if message.Properties == nil || message.Properties.To == nil {
|
||||
return nil, fmt.Errorf("message properties TO is required to send a message to a dynamic target address")
|
||||
}
|
||||
|
||||
err := validateAddress(*message.Properties.To)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
r, err := m.sender.SendWithReceipt(ctx, message, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -8,18 +8,17 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("AMQP publisher ", func() {
|
||||
It("Send a message to a queue with a Message Target Publisher", func() {
|
||||
qName := generateNameWithDateTime("Send a message to a queue with a Message Target Publisher")
|
||||
It("Send a message to a queue with a Message Target NewPublisher", func() {
|
||||
qName := generateNameWithDateTime("Send a message to a queue with a Message Target NewPublisher")
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: qName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queueInfo).NotTo(BeNil())
|
||||
dest, _ := QueueAddress(&qName)
|
||||
publisher, err := connection.Publisher(context.Background(), dest, "test")
|
||||
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publisher).NotTo(BeNil())
|
||||
Expect(publisher).To(BeAssignableToTypeOf(&Publisher{}))
|
||||
|
|
@ -36,48 +35,34 @@ var _ = Describe("AMQP publisher ", func() {
|
|||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||
})
|
||||
|
||||
It("Publisher should fail to a not existing exchange", func() {
|
||||
It("NewPublisher should fail to a not existing exchange", func() {
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
exchangeName := "Nope"
|
||||
addr, err := ExchangeAddress(&exchangeName, nil)
|
||||
Expect(err).To(BeNil())
|
||||
publisher, err := connection.Publisher(context.Background(), addr, "test")
|
||||
publisher, err := connection.NewPublisher(context.Background(), &ExchangeAddress{Exchange: exchangeName}, "test")
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(publisher).To(BeNil())
|
||||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("Publisher should fail if the destination address does not start in the correct way", func() {
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
destinationAddress := "this is not valid since does not start with exchanges or queues"
|
||||
Expect(err).To(BeNil())
|
||||
publisher, err := connection.Publisher(context.Background(), destinationAddress, "test")
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(publisher).To(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("invalid destination address"))
|
||||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("publishResult should released to a not existing routing key", func() {
|
||||
eName := generateNameWithDateTime("publishResult should released to a not existing routing key")
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
exchange, err := connection.Management().DeclareExchange(context.Background(), &ExchangeSpecification{
|
||||
exchange, err := connection.Management().DeclareExchange(context.Background(), &TopicExchangeSpecification{
|
||||
Name: eName,
|
||||
IsAutoDelete: false,
|
||||
ExchangeType: ExchangeType{Type: Topic},
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(exchange).NotTo(BeNil())
|
||||
routingKeyNope := "I don't exist"
|
||||
addr, err := ExchangeAddress(&eName, &routingKeyNope)
|
||||
Expect(err).To(BeNil())
|
||||
publisher, err := connection.Publisher(context.Background(), addr, "test")
|
||||
publisher, err := connection.NewPublisher(context.Background(), &ExchangeAddress{
|
||||
Exchange: eName,
|
||||
Key: routingKeyNope,
|
||||
}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publisher).NotTo(BeNil())
|
||||
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
||||
|
|
@ -93,12 +78,11 @@ var _ = Describe("AMQP publisher ", func() {
|
|||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
_, err = connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
||||
_, err = connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: qName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
dest, _ := QueueAddress(&qName)
|
||||
publisher, err := connection.Publisher(context.Background(), dest, "test")
|
||||
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publisher).NotTo(BeNil())
|
||||
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
||||
|
|
@ -110,4 +94,104 @@ var _ = Describe("AMQP publisher ", func() {
|
|||
Expect(err).NotTo(BeNil())
|
||||
Expect(connection.Close(context.Background()))
|
||||
})
|
||||
|
||||
It("Multi Targets Publisher should fail with StateReleased when the destination does not exist", func() {
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
publisher, err := connection.NewPublisher(context.Background(), nil, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publisher).NotTo(BeNil())
|
||||
qName := generateNameWithDateTime("Targets Publisher should fail when the destination does not exist")
|
||||
msg := amqp.NewMessage([]byte("hello"))
|
||||
Expect(MessageToAddressHelper(msg, &QueueAddress{Queue: qName})).To(BeNil())
|
||||
|
||||
publishResult, err := publisher.Publish(context.Background(), msg)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publishResult).NotTo(BeNil())
|
||||
Expect(publishResult.Outcome).To(Equal(&amqp.StateReleased{}))
|
||||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("Multi Targets Publisher should success with StateReceived when the destination exists", func() {
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
Expect(err).To(BeNil())
|
||||
publisher, err := connection.NewPublisher(context.Background(), nil, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publisher).NotTo(BeNil())
|
||||
name := generateNameWithDateTime("Targets Publisher should success with StateReceived when the destination exists")
|
||||
_, err = connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||
Name: name,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
// as first message is sent to a queue, the outcome should be StateReceived
|
||||
// since the message was accepted by the existing queue
|
||||
msg := amqp.NewMessage([]byte("hello"))
|
||||
Expect(MessageToAddressHelper(msg, &QueueAddress{Queue: name})).To(BeNil())
|
||||
|
||||
publishResult, err := publisher.Publish(context.Background(), msg)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publishResult).NotTo(BeNil())
|
||||
Expect(publishResult.Outcome).To(Equal(&amqp.StateAccepted{}))
|
||||
|
||||
_, err = connection.Management().DeclareExchange(context.Background(), &TopicExchangeSpecification{
|
||||
Name: name,
|
||||
IsAutoDelete: false,
|
||||
})
|
||||
msg = amqp.NewMessage([]byte("hello"))
|
||||
Expect(MessageToAddressHelper(msg, &ExchangeAddress{Exchange: name})).To(BeNil())
|
||||
// the status should be StateReleased since the exchange does not have any binding
|
||||
publishResult, err = publisher.Publish(context.Background(), msg)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publishResult).NotTo(BeNil())
|
||||
Expect(publishResult.Outcome).To(Equal(&amqp.StateReleased{}))
|
||||
|
||||
// Create the binding between the exchange and the queue
|
||||
_, err = connection.Management().Bind(context.Background(), &ExchangeToQueueBindingSpecification{
|
||||
SourceExchange: name,
|
||||
DestinationQueue: name,
|
||||
BindingKey: "#",
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
// the status should be StateAccepted since the exchange has a binding
|
||||
msg = amqp.NewMessage([]byte("hello"))
|
||||
Expect(MessageToAddressHelper(msg, &ExchangeAddress{Exchange: name})).To(BeNil())
|
||||
publishResult, err = publisher.Publish(context.Background(), msg)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publishResult).NotTo(BeNil())
|
||||
Expect(publishResult.Outcome).To(Equal(&amqp.StateAccepted{}))
|
||||
Expect(connection.Management().DeleteQueue(context.Background(), name)).To(BeNil())
|
||||
Expect(connection.Management().DeleteExchange(context.Background(), name)).To(BeNil())
|
||||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
|
||||
It("Multi Targets Publisher should fail it TO is not set or not valid", func() {
|
||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(connection).NotTo(BeNil())
|
||||
publisher, err := connection.NewPublisher(context.Background(), nil, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publisher).NotTo(BeNil())
|
||||
msg := amqp.NewMessage([]byte("hello"))
|
||||
// the message should fail since the TO property is not set
|
||||
publishResult, err := publisher.Publish(context.Background(), msg)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("message properties TO is required"))
|
||||
Expect(publishResult).To(BeNil())
|
||||
|
||||
invalid := "invalid"
|
||||
// the message should fail since the TO property is not valid
|
||||
msg.Properties = &amqp.MessageProperties{
|
||||
To: &invalid,
|
||||
}
|
||||
|
||||
publishResult, err = publisher.Publish(context.Background(), msg)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("invalid destination address"))
|
||||
Expect(publishResult).To(BeNil())
|
||||
|
||||
Expect(connection.Close(context.Background())).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package rabbitmq_amqp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/Azure/go-amqp"
|
||||
)
|
||||
|
|
@ -70,22 +71,8 @@ type AmqpQueue struct {
|
|||
name string
|
||||
}
|
||||
|
||||
func (a *AmqpQueue) DeadLetterExchange(dlx string) {
|
||||
if len(dlx) != 0 {
|
||||
a.arguments["x-dead-letter-exchange"] = dlx
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AmqpQueue) DeadLetterRoutingKey(dlrk string) {
|
||||
if len(dlrk) != 0 {
|
||||
a.arguments["x-dead-letter-routing-key"] = dlrk
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AmqpQueue) MaxLengthBytes(length int64) {
|
||||
if length != 0 {
|
||||
a.arguments["max-length-bytes"] = length
|
||||
}
|
||||
func (a *AmqpQueue) SetArguments(arguments map[string]any) {
|
||||
a.arguments = arguments
|
||||
}
|
||||
|
||||
func (a *AmqpQueue) QueueType(queueType QueueType) {
|
||||
|
|
@ -94,13 +81,6 @@ func (a *AmqpQueue) QueueType(queueType QueueType) {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *AmqpQueue) GetQueueType() TQueueType {
|
||||
if a.arguments["x-queue-type"] == nil {
|
||||
return Classic
|
||||
}
|
||||
return TQueueType(a.arguments["x-queue-type"].(string))
|
||||
}
|
||||
|
||||
func (a *AmqpQueue) Exclusive(isExclusive bool) {
|
||||
a.isExclusive = isExclusive
|
||||
}
|
||||
|
|
@ -124,32 +104,40 @@ func newAmqpQueue(management *AmqpManagement, queueName string) *AmqpQueue {
|
|||
}
|
||||
|
||||
func (a *AmqpQueue) validate() error {
|
||||
if a.arguments["max-length-bytes"] != nil {
|
||||
err := validatePositive("max length", a.arguments["max-length-bytes"].(int64))
|
||||
if a.arguments["x-max-length-bytes"] != nil {
|
||||
err := validatePositive("max length", a.arguments["x-max-length-bytes"].(int64))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if a.arguments["x-max-length"] != nil {
|
||||
err := validatePositive("max length", a.arguments["x-max-length"].(int64))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if a.arguments["x-max-priority"] != nil {
|
||||
err := validatePositive("max priority", a.arguments["x-max-priority"].(int64))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AmqpQueue) Declare(ctx context.Context) (*AmqpQueueInfo, error) {
|
||||
if Quorum == a.GetQueueType() ||
|
||||
Stream == a.GetQueueType() {
|
||||
// mandatory arguments for quorum queues and streams
|
||||
a.Exclusive(false)
|
||||
a.AutoDelete(false)
|
||||
}
|
||||
|
||||
if err := a.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if a.name == "" {
|
||||
a.name = generateNameWithDefaultPrefix()
|
||||
return nil, errors.New("queue name is required")
|
||||
}
|
||||
|
||||
path, err := QueueAddress(&a.name)
|
||||
path, err := queueAddress(&a.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -166,7 +154,7 @@ func (a *AmqpQueue) Declare(ctx context.Context) (*AmqpQueueInfo, error) {
|
|||
}
|
||||
|
||||
func (a *AmqpQueue) Delete(ctx context.Context) error {
|
||||
path, err := QueueAddress(&a.name)
|
||||
path, err := queueAddress(&a.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -175,7 +163,7 @@ func (a *AmqpQueue) Delete(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (a *AmqpQueue) Purge(ctx context.Context) (int, error) {
|
||||
path, err := PurgeQueueAddress(&a.name)
|
||||
path, err := purgeQueueAddress(&a.name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
})
|
||||
|
||||
It("AMQP Queue Declare With Response and Get/Delete should succeed", func() {
|
||||
const queueName = "AMQP Queue Declare With Response and Delete should succeed"
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
var queueName = generateName("AMQP Queue Declare With Response and Delete should succeed")
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||
Name: queueName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
|
|
@ -35,7 +35,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
Expect(queueInfo.IsDurable()).To(BeTrue())
|
||||
Expect(queueInfo.IsAutoDelete()).To(BeFalse())
|
||||
Expect(queueInfo.IsExclusive()).To(BeFalse())
|
||||
Expect(queueInfo.Type()).To(Equal(Classic))
|
||||
Expect(queueInfo.Type()).To(Equal(Quorum))
|
||||
|
||||
// validate GET (query queue info)
|
||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||
|
|
@ -46,16 +46,22 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
})
|
||||
|
||||
It("AMQP Queue Declare With Parameters and Get/Delete should succeed", func() {
|
||||
const queueName = "AMQP Queue Declare With Parameters and Delete should succeed"
|
||||
var queueName = generateName("AMQP Queue Declare With Parameters and Delete should succeed")
|
||||
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &ClassicQueueSpecification{
|
||||
Name: queueName,
|
||||
IsAutoDelete: true,
|
||||
IsExclusive: true,
|
||||
QueueType: QueueType{Classic},
|
||||
MaxLengthBytes: CapacityGB(1),
|
||||
AutoExpire: 1000,
|
||||
MessageTTL: 1000,
|
||||
OverflowStrategy: &DropHeadOverflowStrategy{},
|
||||
SingleActiveConsumer: true,
|
||||
DeadLetterExchange: "dead-letter-exchange",
|
||||
DeadLetterRoutingKey: "dead-letter-routing-key",
|
||||
MaxLength: 9_000,
|
||||
MaxLengthBytes: CapacityGB(1),
|
||||
MaxPriority: 2,
|
||||
LeaderLocator: &BalancedLeaderLocator{},
|
||||
})
|
||||
|
||||
Expect(err).To(BeNil())
|
||||
|
|
@ -70,7 +76,14 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-dead-letter-exchange", "dead-letter-exchange"))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-dead-letter-routing-key", "dead-letter-routing-key"))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("max-length-bytes", int64(1000000000)))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-max-length-bytes", int64(1000000000)))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-max-length", int64(9000)))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-message-ttl", int64(1000)))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-single-active-consumer", true))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-overflow", "drop-head"))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-expires", int64(1000)))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-max-priority", int64(2)))
|
||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-queue-leader-locator", "random"))
|
||||
|
||||
// validate GET (query queue info)
|
||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||
|
|
@ -82,14 +95,11 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
})
|
||||
|
||||
It("AMQP Declare Quorum Queue and Get/Delete should succeed", func() {
|
||||
const queueName = "AMQP Declare Quorum Queue and Delete should succeed"
|
||||
var queueName = generateName("AMQP Declare Quorum Queue and Delete should succeed")
|
||||
// Quorum queue will ignore Exclusive and AutoDelete settings
|
||||
// since they are not supported by quorum queues
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
Name: queueName,
|
||||
IsAutoDelete: true,
|
||||
IsExclusive: true,
|
||||
QueueType: QueueType{Quorum},
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||
Name: queueName,
|
||||
})
|
||||
|
||||
Expect(err).To(BeNil())
|
||||
|
|
@ -113,12 +123,10 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
// Stream queue will ignore Exclusive and AutoDelete settings
|
||||
// since they are not supported by quorum queues
|
||||
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &ClassicQueueSpecification{
|
||||
Name: queueName,
|
||||
IsAutoDelete: true,
|
||||
IsExclusive: true,
|
||||
QueueType: QueueType{Stream},
|
||||
})
|
||||
IsAutoDelete: false,
|
||||
IsExclusive: false})
|
||||
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queueInfo).NotTo(BeNil())
|
||||
|
|
@ -126,7 +134,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
Expect(queueInfo.IsDurable()).To(BeTrue())
|
||||
Expect(queueInfo.IsAutoDelete()).To(BeFalse())
|
||||
Expect(queueInfo.IsExclusive()).To(BeFalse())
|
||||
Expect(queueInfo.Type()).To(Equal(Stream))
|
||||
Expect(queueInfo.Type()).To(Equal(Classic))
|
||||
// validate GET (query queue info)
|
||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||
Expect(queueInfoReceived).To(Equal(queueInfo))
|
||||
|
|
@ -136,29 +144,18 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
|
||||
})
|
||||
|
||||
It("AMQP Declare Queue with invalid type should fail", func() {
|
||||
const queueName = "AMQP Declare Queue with invalid type should fail"
|
||||
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
Name: queueName,
|
||||
QueueType: QueueType{Type: "invalid"},
|
||||
})
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
|
||||
It("AMQP Declare Queue should fail with Precondition fail", func() {
|
||||
// The first queue is declared as Classic, and it should succeed
|
||||
// The second queue is declared as Quorum, and it should fail since it is already declared as Classic
|
||||
queueName := generateName("AMQP Declare Queue should fail with Precondition fail")
|
||||
|
||||
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
Name: queueName,
|
||||
QueueType: QueueType{Classic},
|
||||
//queueName := generateName("AMQP Declare Queue should fail with Precondition fail")
|
||||
queueName := "ab"
|
||||
_, err := management.DeclareQueue(context.TODO(), &ClassicQueueSpecification{
|
||||
Name: queueName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
_, err = management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
Name: queueName,
|
||||
QueueType: QueueType{Quorum},
|
||||
_, err = management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||
Name: queueName,
|
||||
})
|
||||
|
||||
Expect(err).NotTo(BeNil())
|
||||
|
|
@ -169,7 +166,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
|
||||
It("AMQP Declare Queue should fail during validation", func() {
|
||||
queueName := generateName("AMQP Declare Queue should fail during validation")
|
||||
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
_, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||
Name: queueName,
|
||||
MaxLengthBytes: -1,
|
||||
})
|
||||
|
|
@ -178,8 +175,18 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("AMQP Declare Queue should fail if queue specification is nil", func() {
|
||||
_, err := management.DeclareQueue(context.TODO(), nil)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("queue specification cannot be nil"))
|
||||
})
|
||||
|
||||
It("AMQP Declare Queue should create client name queue", func() {
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), nil)
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &AutoGeneratedQueueSpecification{
|
||||
IsAutoDelete: true,
|
||||
IsExclusive: false,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
Expect(queueInfo).NotTo(BeNil())
|
||||
Expect(queueInfo.Name()).To(ContainSubstring("client.gen-"))
|
||||
|
|
@ -189,7 +196,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
|||
|
||||
It("AMQP Purge Queue should succeed and return the number of messages purged", func() {
|
||||
queueName := generateName("AMQP Purge Queue should succeed and return the number of messages purged")
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||
Name: queueName,
|
||||
})
|
||||
Expect(err).To(BeNil())
|
||||
|
|
@ -213,10 +220,7 @@ func publishMessages(queueName string, count int) {
|
|||
conn, err := Dial(context.TODO(), []string{"amqp://guest:guest@localhost"}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
address, err := QueueAddress(&queueName)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
publisher, err := conn.Publisher(context.TODO(), address, "test")
|
||||
publisher, err := conn.NewPublisher(context.TODO(), &QueueAddress{Queue: queueName}, "test")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(publisher).NotTo(BeNil())
|
||||
|
||||
|
|
|
|||
|
|
@ -16,17 +16,264 @@ func (e QueueType) String() string {
|
|||
return string(e.Type)
|
||||
}
|
||||
|
||||
// QueueSpecification represents the specification of a queue
|
||||
type QueueSpecification struct {
|
||||
type QueueSpecification interface {
|
||||
name() string
|
||||
isAutoDelete() bool
|
||||
isExclusive() bool
|
||||
queueType() QueueType
|
||||
buildArguments() map[string]any
|
||||
}
|
||||
|
||||
// QuorumQueueSpecification represents the specification of the quorum queue
|
||||
|
||||
type OverflowStrategy interface {
|
||||
overflowStrategy() string
|
||||
}
|
||||
|
||||
type DropHeadOverflowStrategy struct {
|
||||
}
|
||||
|
||||
func (d *DropHeadOverflowStrategy) overflowStrategy() string {
|
||||
return "drop-head"
|
||||
}
|
||||
|
||||
type RejectPublishOverflowStrategy struct {
|
||||
}
|
||||
|
||||
func (r *RejectPublishOverflowStrategy) overflowStrategy() string {
|
||||
return "reject-publish"
|
||||
}
|
||||
|
||||
type RejectPublishDlxOverflowStrategy struct {
|
||||
}
|
||||
|
||||
func (r *RejectPublishDlxOverflowStrategy) overflowStrategy() string {
|
||||
return "reject-publish-dlx"
|
||||
}
|
||||
|
||||
type LeaderLocator interface {
|
||||
leaderLocator() string
|
||||
}
|
||||
|
||||
type BalancedLeaderLocator struct {
|
||||
}
|
||||
|
||||
func (r *BalancedLeaderLocator) leaderLocator() string {
|
||||
return "random"
|
||||
}
|
||||
|
||||
type ClientLocalLeaderLocator struct {
|
||||
}
|
||||
|
||||
func (r *ClientLocalLeaderLocator) leaderLocator() string {
|
||||
return "client-local"
|
||||
}
|
||||
|
||||
type QuorumQueueSpecification struct {
|
||||
Name string
|
||||
AutoExpire int64
|
||||
MessageTTL int64
|
||||
OverflowStrategy OverflowStrategy
|
||||
SingleActiveConsumer bool
|
||||
DeadLetterExchange string
|
||||
DeadLetterRoutingKey string
|
||||
MaxLength int64
|
||||
MaxLengthBytes int64
|
||||
DeliveryLimit int64
|
||||
TargetClusterSize int64
|
||||
LeaderLocator LeaderLocator
|
||||
}
|
||||
|
||||
func (q *QuorumQueueSpecification) name() string {
|
||||
return q.Name
|
||||
}
|
||||
|
||||
func (q *QuorumQueueSpecification) isAutoDelete() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (q *QuorumQueueSpecification) isExclusive() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (q *QuorumQueueSpecification) queueType() QueueType {
|
||||
return QueueType{Type: Quorum}
|
||||
}
|
||||
|
||||
func (q *QuorumQueueSpecification) buildArguments() map[string]any {
|
||||
result := map[string]any{}
|
||||
if q.MaxLengthBytes != 0 {
|
||||
result["x-max-length-bytes"] = q.MaxLengthBytes
|
||||
}
|
||||
|
||||
if len(q.DeadLetterExchange) != 0 {
|
||||
result["x-dead-letter-exchange"] = q.DeadLetterExchange
|
||||
}
|
||||
|
||||
if len(q.DeadLetterRoutingKey) != 0 {
|
||||
result["x-dead-letter-routing-key"] = q.DeadLetterRoutingKey
|
||||
}
|
||||
|
||||
if q.AutoExpire != 0 {
|
||||
result["x-expires"] = q.AutoExpire
|
||||
}
|
||||
|
||||
if q.MessageTTL != 0 {
|
||||
result["x-message-ttl"] = q.MessageTTL
|
||||
}
|
||||
|
||||
if q.OverflowStrategy != nil {
|
||||
result["x-overflow"] = q.OverflowStrategy.overflowStrategy()
|
||||
}
|
||||
|
||||
if q.SingleActiveConsumer {
|
||||
result["x-single-active-consumer"] = true
|
||||
}
|
||||
|
||||
if q.MaxLength != 0 {
|
||||
result["x-max-length"] = q.MaxLength
|
||||
}
|
||||
|
||||
if q.DeliveryLimit != 0 {
|
||||
result["x-delivery-limit"] = q.DeliveryLimit
|
||||
}
|
||||
|
||||
if q.TargetClusterSize != 0 {
|
||||
result["x-quorum-target-group-size"] = q.TargetClusterSize
|
||||
}
|
||||
|
||||
if q.LeaderLocator != nil {
|
||||
result["x-queue-leader-locator"] = q.LeaderLocator.leaderLocator()
|
||||
}
|
||||
|
||||
result["x-queue-type"] = q.queueType().String()
|
||||
return result
|
||||
}
|
||||
|
||||
// ClassicQueueSpecification represents the specification of the classic queue
|
||||
type ClassicQueueSpecification struct {
|
||||
Name string
|
||||
IsAutoDelete bool
|
||||
IsExclusive bool
|
||||
QueueType QueueType
|
||||
MaxLengthBytes int64
|
||||
AutoExpire int64
|
||||
MessageTTL int64
|
||||
OverflowStrategy OverflowStrategy
|
||||
SingleActiveConsumer bool
|
||||
DeadLetterExchange string
|
||||
DeadLetterRoutingKey string
|
||||
MaxLength int64
|
||||
MaxLengthBytes int64
|
||||
MaxPriority int64
|
||||
LeaderLocator LeaderLocator
|
||||
}
|
||||
|
||||
func (q *ClassicQueueSpecification) name() string {
|
||||
return q.Name
|
||||
}
|
||||
|
||||
func (q *ClassicQueueSpecification) isAutoDelete() bool {
|
||||
return q.IsAutoDelete
|
||||
}
|
||||
|
||||
func (q *ClassicQueueSpecification) isExclusive() bool {
|
||||
return q.IsExclusive
|
||||
}
|
||||
|
||||
func (q *ClassicQueueSpecification) queueType() QueueType {
|
||||
return QueueType{Type: Classic}
|
||||
}
|
||||
|
||||
func (q *ClassicQueueSpecification) buildArguments() map[string]any {
|
||||
result := map[string]any{}
|
||||
|
||||
if q.MaxLengthBytes != 0 {
|
||||
result["x-max-length-bytes"] = q.MaxLengthBytes
|
||||
}
|
||||
|
||||
if len(q.DeadLetterExchange) != 0 {
|
||||
result["x-dead-letter-exchange"] = q.DeadLetterExchange
|
||||
}
|
||||
|
||||
if len(q.DeadLetterRoutingKey) != 0 {
|
||||
result["x-dead-letter-routing-key"] = q.DeadLetterRoutingKey
|
||||
}
|
||||
|
||||
if q.AutoExpire != 0 {
|
||||
result["x-expires"] = q.AutoExpire
|
||||
}
|
||||
|
||||
if q.MessageTTL != 0 {
|
||||
result["x-message-ttl"] = q.MessageTTL
|
||||
}
|
||||
|
||||
if q.OverflowStrategy != nil {
|
||||
result["x-overflow"] = q.OverflowStrategy.overflowStrategy()
|
||||
}
|
||||
|
||||
if q.SingleActiveConsumer {
|
||||
result["x-single-active-consumer"] = true
|
||||
}
|
||||
|
||||
if q.MaxLength != 0 {
|
||||
result["x-max-length"] = q.MaxLength
|
||||
}
|
||||
|
||||
if q.MaxPriority != 0 {
|
||||
result["x-max-priority"] = q.MaxPriority
|
||||
}
|
||||
|
||||
if q.LeaderLocator != nil {
|
||||
result["x-queue-leader-locator"] = q.LeaderLocator.leaderLocator()
|
||||
}
|
||||
|
||||
result["x-queue-type"] = q.queueType().String()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type AutoGeneratedQueueSpecification struct {
|
||||
IsAutoDelete bool
|
||||
IsExclusive bool
|
||||
MaxLength int64
|
||||
MaxLengthBytes int64
|
||||
}
|
||||
|
||||
func (a *AutoGeneratedQueueSpecification) name() string {
|
||||
return generateNameWithDefaultPrefix()
|
||||
}
|
||||
|
||||
func (a *AutoGeneratedQueueSpecification) isAutoDelete() bool {
|
||||
return a.IsAutoDelete
|
||||
}
|
||||
|
||||
func (a *AutoGeneratedQueueSpecification) isExclusive() bool {
|
||||
return a.IsExclusive
|
||||
}
|
||||
|
||||
func (a *AutoGeneratedQueueSpecification) queueType() QueueType {
|
||||
return QueueType{Classic}
|
||||
}
|
||||
|
||||
func (a *AutoGeneratedQueueSpecification) buildArguments() map[string]any {
|
||||
result := map[string]any{}
|
||||
|
||||
if a.MaxLengthBytes != 0 {
|
||||
result["x-max-length-bytes"] = a.MaxLengthBytes
|
||||
}
|
||||
|
||||
if a.MaxLength != 0 {
|
||||
result["x-max-length"] = a.MaxLength
|
||||
}
|
||||
|
||||
result["x-queue-type"] = a.queueType().String()
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
// / **** Exchange ****
|
||||
|
||||
// TExchangeType represents the type of exchange
|
||||
type TExchangeType string
|
||||
|
||||
const (
|
||||
|
|
@ -43,15 +290,124 @@ func (e ExchangeType) String() string {
|
|||
return string(e.Type)
|
||||
}
|
||||
|
||||
type ExchangeSpecification struct {
|
||||
Name string
|
||||
IsAutoDelete bool
|
||||
ExchangeType ExchangeType
|
||||
// ExchangeSpecification represents the specification of an exchange
|
||||
type ExchangeSpecification interface {
|
||||
name() string
|
||||
isAutoDelete() bool
|
||||
exchangeType() ExchangeType
|
||||
buildArguments() map[string]any
|
||||
}
|
||||
|
||||
type BindingSpecification struct {
|
||||
type DirectExchangeSpecification struct {
|
||||
Name string
|
||||
IsAutoDelete bool
|
||||
}
|
||||
|
||||
func (d *DirectExchangeSpecification) name() string {
|
||||
return d.Name
|
||||
}
|
||||
|
||||
func (d *DirectExchangeSpecification) isAutoDelete() bool {
|
||||
return d.IsAutoDelete
|
||||
}
|
||||
|
||||
func (d *DirectExchangeSpecification) exchangeType() ExchangeType {
|
||||
return ExchangeType{Type: Direct}
|
||||
}
|
||||
|
||||
func (d *DirectExchangeSpecification) buildArguments() map[string]any {
|
||||
return map[string]any{}
|
||||
}
|
||||
|
||||
type TopicExchangeSpecification struct {
|
||||
Name string
|
||||
IsAutoDelete bool
|
||||
}
|
||||
|
||||
func (t *TopicExchangeSpecification) name() string {
|
||||
return t.Name
|
||||
}
|
||||
|
||||
func (t *TopicExchangeSpecification) isAutoDelete() bool {
|
||||
return t.IsAutoDelete
|
||||
}
|
||||
|
||||
func (t *TopicExchangeSpecification) exchangeType() ExchangeType {
|
||||
return ExchangeType{Type: Topic}
|
||||
}
|
||||
|
||||
func (t *TopicExchangeSpecification) buildArguments() map[string]any {
|
||||
return map[string]any{}
|
||||
}
|
||||
|
||||
type FanOutExchangeSpecification struct {
|
||||
Name string
|
||||
IsAutoDelete bool
|
||||
}
|
||||
|
||||
func (f *FanOutExchangeSpecification) name() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f *FanOutExchangeSpecification) isAutoDelete() bool {
|
||||
return f.IsAutoDelete
|
||||
}
|
||||
|
||||
func (f *FanOutExchangeSpecification) exchangeType() ExchangeType {
|
||||
return ExchangeType{Type: FanOut}
|
||||
}
|
||||
|
||||
func (f *FanOutExchangeSpecification) buildArguments() map[string]any {
|
||||
return map[string]any{}
|
||||
}
|
||||
|
||||
type BindingSpecification interface {
|
||||
sourceExchange() string
|
||||
destination() string
|
||||
bindingKey() string
|
||||
isDestinationQueue() bool
|
||||
}
|
||||
|
||||
type ExchangeToQueueBindingSpecification struct {
|
||||
SourceExchange string
|
||||
DestinationQueue string
|
||||
BindingKey string
|
||||
}
|
||||
|
||||
func (e *ExchangeToQueueBindingSpecification) sourceExchange() string {
|
||||
return e.SourceExchange
|
||||
}
|
||||
|
||||
func (e *ExchangeToQueueBindingSpecification) destination() string {
|
||||
return e.DestinationQueue
|
||||
}
|
||||
|
||||
func (e *ExchangeToQueueBindingSpecification) isDestinationQueue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *ExchangeToQueueBindingSpecification) bindingKey() string {
|
||||
return e.BindingKey
|
||||
}
|
||||
|
||||
type ExchangeToExchangeBindingSpecification struct {
|
||||
SourceExchange string
|
||||
DestinationQueue string
|
||||
DestinationExchange string
|
||||
BindingKey string
|
||||
}
|
||||
|
||||
func (e *ExchangeToExchangeBindingSpecification) sourceExchange() string {
|
||||
return e.SourceExchange
|
||||
}
|
||||
|
||||
func (e *ExchangeToExchangeBindingSpecification) destination() string {
|
||||
return e.DestinationExchange
|
||||
}
|
||||
|
||||
func (e *ExchangeToExchangeBindingSpecification) isDestinationQueue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *ExchangeToExchangeBindingSpecification) bindingKey() string {
|
||||
return e.BindingKey
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue