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
|
## 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
|
// Create the management interface for the connection
|
||||||
// so we can declare exchanges, queues, and bindings
|
// so we can declare exchanges, queues, and bindings
|
||||||
management := amqpConnection.Management()
|
management := amqpConnection.Management()
|
||||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &rabbitmq_amqp.ExchangeSpecification{
|
exchangeInfo, err := management.DeclareExchange(context.TODO(), &rabbitmq_amqp.TopicExchangeSpecification{
|
||||||
Name: exchangeName,
|
Name: exchangeName,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -46,9 +46,8 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declare a Quorum queue
|
// Declare a Quorum queue
|
||||||
queueInfo, err := management.DeclareQueue(context.TODO(), &rabbitmq_amqp.QueueSpecification{
|
queueInfo, err := management.DeclareQueue(context.TODO(), &rabbitmq_amqp.QuorumQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
QueueType: rabbitmq_amqp.QueueType{Type: rabbitmq_amqp.Quorum},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -57,7 +56,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind the queue to the exchange
|
// 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,
|
SourceExchange: exchangeName,
|
||||||
DestinationQueue: queueName,
|
DestinationQueue: queueName,
|
||||||
BindingKey: routingKey,
|
BindingKey: routingKey,
|
||||||
|
|
@ -70,8 +69,10 @@ func main() {
|
||||||
|
|
||||||
// Create a consumer to receive messages from the queue
|
// 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
|
// 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 {
|
if err != nil {
|
||||||
rabbitmq_amqp.Error("Error creating consumer", err)
|
rabbitmq_amqp.Error("Error creating consumer", err)
|
||||||
return
|
return
|
||||||
|
|
@ -105,8 +106,10 @@ func main() {
|
||||||
}
|
}
|
||||||
}(consumerContext)
|
}(consumerContext)
|
||||||
|
|
||||||
addr, _ := rabbitmq_amqp.ExchangeAddress(&exchangeName, &routingKey)
|
publisher, err := amqpConnection.NewPublisher(context.Background(), &rabbitmq_amqp.ExchangeAddress{
|
||||||
publisher, err := amqpConnection.Publisher(context.Background(), addr, "getting-started-publisher")
|
Exchange: exchangeName,
|
||||||
|
Key: routingKey,
|
||||||
|
}, "getting-started-publisher")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rabbitmq_amqp.Error("Error creating publisher", err)
|
rabbitmq_amqp.Error("Error creating publisher", err)
|
||||||
return
|
return
|
||||||
|
|
@ -151,10 +154,12 @@ func main() {
|
||||||
err = consumer.Close(context.Background())
|
err = consumer.Close(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rabbitmq_amqp.Error("[Consumer]", err)
|
rabbitmq_amqp.Error("[Consumer]", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// Close the publisher
|
// Close the publisher
|
||||||
err = publisher.Close(context.Background())
|
err = publisher.Close(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
rabbitmq_amqp.Error("[Publisher]", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
8
go.mod
8
go.mod
|
|
@ -5,18 +5,18 @@ go 1.22.0
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-amqp v1.4.0-beta.1
|
github.com/Azure/go-amqp v1.4.0-beta.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2
|
github.com/onsi/ginkgo/v2 v2.22.1
|
||||||
github.com/onsi/gomega v1.34.2
|
github.com/onsi/gomega v1.36.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/google/go-cmp v0.6.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/net v0.33.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.21.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
|
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/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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||||
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM=
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM=
|
||||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
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/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 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
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.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,71 @@ package rabbitmq_amqp
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Azure/go-amqp"
|
||||||
"strings"
|
"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
|
// 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 {
|
if exchange == nil && queue == nil {
|
||||||
return "", errors.New("exchange or queue must be set")
|
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
|
return "/" + queues + "/" + encodePathSegments(*queue) + urlAppend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExchangeAddress Creates the address for the exchange
|
// exchangeAddress Creates the address for the exchange
|
||||||
// See Address for more information
|
// See address for more information
|
||||||
func ExchangeAddress(exchange, key *string) (string, error) {
|
func exchangeAddress(exchange, key *string) (string, error) {
|
||||||
return Address(exchange, key, nil, nil)
|
return address(exchange, key, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueueAddress Creates the address for the queue.
|
// queueAddress Creates the address for the queue.
|
||||||
// See Address for more information
|
// See address for more information
|
||||||
func QueueAddress(queue *string) (string, error) {
|
func queueAddress(queue *string) (string, error) {
|
||||||
return Address(nil, nil, queue, nil)
|
return address(nil, nil, queue, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PurgeQueueAddress Creates the address for purging the queue.
|
// PurgeQueueAddress Creates the address for purging the queue.
|
||||||
// See Address for more information
|
// See address for more information
|
||||||
func PurgeQueueAddress(queue *string) (string, error) {
|
func purgeQueueAddress(queue *string) (string, error) {
|
||||||
parameter := "/messages"
|
parameter := "/messages"
|
||||||
return Address(nil, nil, queue, ¶meter)
|
return address(nil, nil, queue, ¶meter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodePathSegments takes a string and returns its percent-encoded representation.
|
// 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)
|
return fmt.Sprintf(format, bindings, sourceNameEncoded, destinationType, destinationNameEncoded, keyEncoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAddress(address string) bool {
|
func validateAddress(address string) error {
|
||||||
return strings.HasPrefix(address, fmt.Sprintf("/%s/", exchanges)) || strings.HasPrefix(address, fmt.Sprintf("/%s/", queues))
|
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"
|
. "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() {
|
It("With exchange, queue and key should raise and error", func() {
|
||||||
queue := "my_queue"
|
queue := "my_queue"
|
||||||
exchange := "my_exchange"
|
exchange := "my_exchange"
|
||||||
|
|
||||||
_, err := Address(&exchange, nil, &queue, nil)
|
_, err := address(&exchange, nil, &queue, nil)
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
Expect(err.Error()).To(Equal("exchange and queue cannot be set together"))
|
Expect(err.Error()).To(Equal("exchange and queue cannot be set together"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Without exchange and queue should raise and error", func() {
|
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).NotTo(BeNil())
|
||||||
Expect(err.Error()).To(Equal("exchange or queue must be set"))
|
Expect(err.Error()).To(Equal("exchange or queue must be set"))
|
||||||
})
|
})
|
||||||
|
|
@ -25,14 +25,14 @@ var _ = Describe("Address builder test ", func() {
|
||||||
exchange := "my_exchange"
|
exchange := "my_exchange"
|
||||||
key := "my_key"
|
key := "my_key"
|
||||||
|
|
||||||
address, err := Address(&exchange, &key, nil, nil)
|
address, err := address(&exchange, &key, nil, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(address).To(Equal("/exchanges/my_exchange/my_key"))
|
Expect(address).To(Equal("/exchanges/my_exchange/my_key"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("With exchange should return address", func() {
|
It("With exchange should return address", func() {
|
||||||
exchange := "my_exchange"
|
exchange := "my_exchange"
|
||||||
address, err := Address(&exchange, nil, nil, nil)
|
address, err := address(&exchange, nil, nil, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(address).To(Equal("/exchanges/my_exchange"))
|
Expect(address).To(Equal("/exchanges/my_exchange"))
|
||||||
})
|
})
|
||||||
|
|
@ -42,21 +42,21 @@ var _ = Describe("Address builder test ", func() {
|
||||||
exchange := "my_ exchange/()"
|
exchange := "my_ exchange/()"
|
||||||
key := "my_key "
|
key := "my_key "
|
||||||
|
|
||||||
address, err := Address(&exchange, &key, nil, nil)
|
address, err := address(&exchange, &key, nil, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(address).To(Equal("/exchanges/my_%20exchange%2F%28%29/my_key%20"))
|
Expect(address).To(Equal("/exchanges/my_%20exchange%2F%28%29/my_key%20"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("With queue should return address", func() {
|
It("With queue should return address", func() {
|
||||||
queue := "my_queue>"
|
queue := "my_queue>"
|
||||||
address, err := Address(nil, nil, &queue, nil)
|
address, err := address(nil, nil, &queue, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(address).To(Equal("/queues/my_queue%3E"))
|
Expect(address).To(Equal("/queues/my_queue%3E"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("With queue and urlParameters should return address", func() {
|
It("With queue and urlParameters should return address", func() {
|
||||||
queue := "my_queue"
|
queue := "my_queue"
|
||||||
address, err := PurgeQueueAddress(&queue)
|
address, err := purgeQueueAddress(&queue)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(address).To(Equal("/queues/my_queue/messages"))
|
Expect(address).To(Equal("/queues/my_queue/messages"))
|
||||||
})
|
})
|
||||||
|
|
@ -2,6 +2,7 @@ package rabbitmq_amqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -31,18 +32,9 @@ func (b *AMQPBinding) SourceExchange(sourceName string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *AMQPBinding) DestinationExchange(destinationName string) {
|
func (b *AMQPBinding) Destination(name string, isQueue bool) {
|
||||||
if len(destinationName) > 0 {
|
b.destinationName = name
|
||||||
b.destinationName = destinationName
|
b.toQueue = isQueue
|
||||||
b.toQueue = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *AMQPBinding) DestinationQueue(queueName string) {
|
|
||||||
if len(queueName) > 0 {
|
|
||||||
b.destinationName = queueName
|
|
||||||
b.toQueue = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind creates a binding between an exchange and a queue or exchange
|
// 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.
|
// Returns the binding path that can be used to unbind the binding.
|
||||||
// Given a virtual host, the binding path is unique.
|
// Given a virtual host, the binding path is unique.
|
||||||
func (b *AMQPBinding) Bind(ctx context.Context) (string, error) {
|
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()
|
path := bindingPath()
|
||||||
kv := make(map[string]any)
|
kv := make(map[string]any)
|
||||||
kv["binding_key"] = b.bindingKey
|
kv["binding_key"] = b.bindingKey
|
||||||
kv["source"] = b.sourceName
|
kv["source"] = b.sourceName
|
||||||
kv["destination_queue"] = b.destinationName
|
kv[destination] = b.destinationName
|
||||||
kv["arguments"] = make(map[string]any)
|
kv["arguments"] = make(map[string]any)
|
||||||
_, err := b.management.Request(ctx, kv, path, commandPost, []int{responseCode204})
|
_, err := b.management.Request(ctx, kv, path, commandPost, []int{responseCode204})
|
||||||
bindingPathWithExchangeQueueKey := bindingPathWithExchangeQueueKey(b.toQueue, b.sourceName, b.destinationName, b.bindingKey)
|
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() {
|
It("AMQP Bindings between Exchange and Queue Should succeed", func() {
|
||||||
const exchangeName = "Exchange_AMQP Bindings between Exchange and Queue should uccess"
|
const exchangeName = "Exchange_AMQP Bindings between Exchange and Queue should uccess"
|
||||||
const queueName = "Queue_AMQP Bindings between Exchange and Queue should succeed"
|
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,
|
Name: exchangeName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(exchangeInfo).NotTo(BeNil())
|
Expect(exchangeInfo).NotTo(BeNil())
|
||||||
Expect(exchangeInfo.Name()).To(Equal(exchangeName))
|
Expect(exchangeInfo.Name()).To(Equal(exchangeName))
|
||||||
|
|
||||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
queueInfo, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfo).NotTo(BeNil())
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
Expect(queueInfo.Name()).To(Equal(queueName))
|
Expect(queueInfo.Name()).To(Equal(queueName))
|
||||||
bindingPath, err := management.Bind(context.TODO(), &BindingSpecification{
|
bindingPath, err := management.Bind(context.TODO(), &ExchangeToQueueBindingSpecification{
|
||||||
SourceExchange: exchangeName,
|
SourceExchange: exchangeName,
|
||||||
DestinationQueue: queueName,
|
DestinationQueue: queueName,
|
||||||
BindingKey: "routing-key",
|
BindingKey: "routing-key",
|
||||||
|
|
@ -49,4 +49,68 @@ var _ = Describe("AMQP Bindings test ", func() {
|
||||||
err = management.DeleteQueue(context.TODO(), queueName)
|
err = management.DeleteQueue(context.TODO(), queueName)
|
||||||
Expect(err).To(BeNil())
|
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
|
session *amqp.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpConnection) Publisher(ctx context.Context, destinationAdd string, linkName string) (*Publisher, error) {
|
// NewPublisher creates a new Publisher that sends messages to the provided destination.
|
||||||
if !validateAddress(destinationAdd) {
|
// The destination is a TargetAddress that can be a Queue or an Exchange with a routing key.
|
||||||
return nil, fmt.Errorf("invalid destination address, the address should start with /%s/ or/%s/ ", exchanges, queues)
|
// 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))
|
sender, err := a.session.NewSender(ctx, destinationAdd, createSenderLinkOptions(destinationAdd, linkName, AtLeastOnce))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
// NewConsumer creates a new Consumer that listens to the provided destination. Destination is a QueueAddress.
|
||||||
if !validateAddress(destinationAdd) {
|
func (a *AmqpConnection) NewConsumer(ctx context.Context, destination *QueueAddress, linkName string) (*Consumer, error) {
|
||||||
return nil, fmt.Errorf("invalid destination address, the address should start with /%s/ or/%s/ ", exchanges, queues)
|
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))
|
receiver, err := a.session.NewReceiver(ctx, destinationAdd, createReceiverLinkOptions(destinationAdd, linkName, AtLeastOnce))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -9,46 +9,41 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Consumer tests", func() {
|
var _ = Describe("NewConsumer tests", func() {
|
||||||
|
|
||||||
It("AMQP Consumer should fail due to context cancellation", func() {
|
It("AMQP NewConsumer should fail due to context cancellation", func() {
|
||||||
qName := generateNameWithDateTime("AMQP Consumer should fail due to context cancellation")
|
qName := generateNameWithDateTime("AMQP NewConsumer should fail due to context cancellation")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
addr, _ := QueueAddress(&qName)
|
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
IsAutoDelete: false,
|
|
||||||
IsExclusive: false,
|
|
||||||
QueueType: QueueType{Quorum},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queue).NotTo(BeNil())
|
Expect(queue).NotTo(BeNil())
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
|
||||||
cancel()
|
cancel()
|
||||||
_, err = connection.Consumer(ctx, addr, "test")
|
_, err = connection.NewConsumer(ctx, &QueueAddress{
|
||||||
|
Queue: qName,
|
||||||
|
}, "test")
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
Expect(err.Error()).To(ContainSubstring("context canceled"))
|
Expect(err.Error()).To(ContainSubstring("context canceled"))
|
||||||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
Expect(connection.Close(context.Background())).To(BeNil())
|
||||||
})
|
})
|
||||||
|
|
||||||
It("AMQP Consumer should ack and empty the queue", func() {
|
It("AMQP NewConsumer should ack and empty the queue", func() {
|
||||||
qName := generateNameWithDateTime("AMQP Consumer should ack and empty the queue")
|
qName := generateNameWithDateTime("AMQP NewConsumer should ack and empty the queue")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
IsAutoDelete: false,
|
|
||||||
IsExclusive: false,
|
|
||||||
QueueType: QueueType{Quorum},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queue).NotTo(BeNil())
|
Expect(queue).NotTo(BeNil())
|
||||||
publishMessages(qName, 10)
|
publishMessages(qName, 10)
|
||||||
addr, _ := QueueAddress(&qName)
|
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(consumer).NotTo(BeNil())
|
Expect(consumer).NotTo(BeNil())
|
||||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||||
|
|
@ -66,22 +61,18 @@ var _ = Describe("Consumer tests", func() {
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
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)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
IsAutoDelete: false,
|
|
||||||
IsExclusive: false,
|
|
||||||
QueueType: QueueType{Quorum},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queue).NotTo(BeNil())
|
Expect(queue).NotTo(BeNil())
|
||||||
publishMessages(qName, 1)
|
publishMessages(qName, 1)
|
||||||
addr, _ := QueueAddress(&qName)
|
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(consumer).NotTo(BeNil())
|
Expect(consumer).NotTo(BeNil())
|
||||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||||
|
|
@ -97,22 +88,18 @@ var _ = Describe("Consumer tests", func() {
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
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)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
IsAutoDelete: false,
|
|
||||||
IsExclusive: false,
|
|
||||||
QueueType: QueueType{Quorum},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queue).NotTo(BeNil())
|
Expect(queue).NotTo(BeNil())
|
||||||
publishMessages(qName, 1)
|
publishMessages(qName, 1)
|
||||||
addr, _ := QueueAddress(&qName)
|
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(consumer).NotTo(BeNil())
|
Expect(consumer).NotTo(BeNil())
|
||||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||||
|
|
@ -136,22 +123,18 @@ var _ = Describe("Consumer tests", func() {
|
||||||
Expect(connection.Close(context.Background())).To(BeNil())
|
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
|
// 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)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
queue, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
queue, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
IsAutoDelete: false,
|
|
||||||
IsExclusive: false,
|
|
||||||
QueueType: QueueType{Quorum},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queue).NotTo(BeNil())
|
Expect(queue).NotTo(BeNil())
|
||||||
publishMessages(qName, 2)
|
publishMessages(qName, 2)
|
||||||
addr, _ := QueueAddress(&qName)
|
consumer, err := connection.NewConsumer(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||||
consumer, err := connection.Consumer(context.Background(), addr, "test")
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(consumer).NotTo(BeNil())
|
Expect(consumer).NotTo(BeNil())
|
||||||
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
Expect(consumer).To(BeAssignableToTypeOf(&Consumer{}))
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package rabbitmq_amqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"github.com/Azure/go-amqp"
|
"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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +64,7 @@ func (e *AmqpExchange) IsAutoDelete() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AmqpExchange) Delete(ctx context.Context) error {
|
func (e *AmqpExchange) Delete(ctx context.Context) error {
|
||||||
path, err := ExchangeAddress(&e.name, nil)
|
path, err := exchangeAddress(&e.name, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ var _ = Describe("AMQP Exchange test ", func() {
|
||||||
|
|
||||||
It("AMQP Exchange Declare with Default and Delete should succeed", func() {
|
It("AMQP Exchange Declare with Default and Delete should succeed", func() {
|
||||||
const exchangeName = "AMQP Exchange Declare and Delete with Default should succeed"
|
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,
|
Name: exchangeName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
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() {
|
It("AMQP Exchange Declare with Topic and Delete should succeed", func() {
|
||||||
const exchangeName = "AMQP Exchange Declare with Topic and Delete should succeed"
|
const exchangeName = "AMQP Exchange Declare with Topic and Delete should succeed"
|
||||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &ExchangeSpecification{
|
exchangeInfo, err := management.DeclareExchange(context.TODO(), &TopicExchangeSpecification{
|
||||||
Name: exchangeName,
|
Name: exchangeName,
|
||||||
ExchangeType: ExchangeType{Topic},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(exchangeInfo).NotTo(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() {
|
It("AMQP Exchange Declare with FanOut and Delete should succeed", func() {
|
||||||
const exchangeName = "AMQP Exchange Declare with FanOut and Delete should succeed"
|
const exchangeName = "AMQP Exchange Declare with FanOut and Delete should succeed"
|
||||||
//exchangeSpec := management.Exchange(exchangeName).ExchangeType(ExchangeType{FanOut})
|
exchangeInfo, err := management.DeclareExchange(context.TODO(), &FanOutExchangeSpecification{
|
||||||
exchangeInfo, err := management.DeclareExchange(context.TODO(), &ExchangeSpecification{
|
Name: exchangeName,
|
||||||
Name: exchangeName,
|
|
||||||
ExchangeType: ExchangeType{FanOut},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(exchangeInfo).NotTo(BeNil())
|
Expect(exchangeInfo).NotTo(BeNil())
|
||||||
|
|
@ -59,4 +56,19 @@ var _ = Describe("AMQP Exchange test ", func() {
|
||||||
err = management.DeleteExchange(context.TODO(), exchangeName)
|
err = management.DeleteExchange(context.TODO(), exchangeName)
|
||||||
Expect(err).To(BeNil())
|
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
|
return make(map[string]any), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification *QueueSpecification) (*AmqpQueueInfo, error) {
|
func (a *AmqpManagement) DeclareQueue(ctx context.Context, specification QueueSpecification) (*AmqpQueueInfo, error) {
|
||||||
var amqpQueue *AmqpQueue
|
if specification == nil {
|
||||||
|
return nil, fmt.Errorf("queue specification cannot be nil. You need to provide a valid QueueSpecification")
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
return amqpQueue.Declare(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,14 +189,14 @@ func (a *AmqpManagement) DeleteQueue(ctx context.Context, name string) error {
|
||||||
return q.Delete(ctx)
|
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 {
|
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 := newAmqpExchange(a, exchangeSpecification.name())
|
||||||
exchange.AutoDelete(exchangeSpecification.IsAutoDelete)
|
exchange.AutoDelete(exchangeSpecification.isAutoDelete())
|
||||||
exchange.ExchangeType(exchangeSpecification.ExchangeType)
|
exchange.ExchangeType(exchangeSpecification.exchangeType())
|
||||||
return exchange.Declare(ctx)
|
return exchange.Declare(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,12 +205,15 @@ func (a *AmqpManagement) DeleteExchange(ctx context.Context, name string) error
|
||||||
return e.Delete(ctx)
|
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 := newAMQPBinding(a)
|
||||||
bind.SourceExchange(bindingSpecification.SourceExchange)
|
bind.SourceExchange(bindingSpecification.sourceExchange())
|
||||||
bind.DestinationQueue(bindingSpecification.DestinationQueue)
|
bind.Destination(bindingSpecification.destination(), bindingSpecification.isDestinationQueue())
|
||||||
bind.DestinationExchange(bindingSpecification.DestinationExchange)
|
bind.BindingKey(bindingSpecification.bindingKey())
|
||||||
bind.BindingKey(bindingSpecification.BindingKey)
|
|
||||||
return bind.Bind(ctx)
|
return bind.Bind(ctx)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -225,7 +222,7 @@ func (a *AmqpManagement) Unbind(ctx context.Context, bindingPath string) error {
|
||||||
return bind.Unbind(ctx, bindingPath)
|
return bind.Unbind(ctx, bindingPath)
|
||||||
}
|
}
|
||||||
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*AmqpQueueInfo, error) {
|
func (a *AmqpManagement) QueueInfo(ctx context.Context, queueName string) (*AmqpQueueInfo, error) {
|
||||||
path, err := QueueAddress(&queueName)
|
path, err := queueAddress(&queueName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package rabbitmq_amqp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -10,23 +11,63 @@ type PublishResult struct {
|
||||||
Message *amqp.Message
|
Message *amqp.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Publisher is a publisher that sends messages to a specific destination address.
|
||||||
type Publisher struct {
|
type Publisher struct {
|
||||||
sender *amqp.Sender
|
sender *amqp.Sender
|
||||||
|
staticTargetAddress bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPublisher(sender *amqp.Sender) *Publisher {
|
func newPublisher(sender *amqp.Sender, staticTargetAddress bool) *Publisher {
|
||||||
return &Publisher{sender: sender}
|
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.
|
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 outcome is a DeliveryState that indicates if the message was accepted or rejected.
|
|
||||||
// RabbitMQ supports the following DeliveryState types:
|
The message is sent and the outcome of the operation is returned.
|
||||||
// - StateAccepted
|
The outcome is a DeliveryState that indicates if the message was accepted or rejected.
|
||||||
// - StateReleased
|
RabbitMQ supports the following DeliveryState types:
|
||||||
// - StateRejected
|
|
||||||
// See: https://www.rabbitmq.com/docs/next/amqp#outcomes for more information.
|
- 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) {
|
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)
|
r, err := m.sender.SendWithReceipt(ctx, message, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,17 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("AMQP publisher ", func() {
|
var _ = Describe("AMQP publisher ", func() {
|
||||||
It("Send a message to a queue with a Message Target Publisher", func() {
|
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 Publisher")
|
qName := generateNameWithDateTime("Send a message to a queue with a Message Target NewPublisher")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
queueInfo, err := connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfo).NotTo(BeNil())
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
dest, _ := QueueAddress(&qName)
|
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||||
publisher, err := connection.Publisher(context.Background(), dest, "test")
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
Expect(publisher).To(BeAssignableToTypeOf(&Publisher{}))
|
Expect(publisher).To(BeAssignableToTypeOf(&Publisher{}))
|
||||||
|
|
@ -36,48 +35,34 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
Expect(connection.Management().DeleteQueue(context.Background(), qName)).To(BeNil())
|
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)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
exchangeName := "Nope"
|
exchangeName := "Nope"
|
||||||
addr, err := ExchangeAddress(&exchangeName, nil)
|
publisher, err := connection.NewPublisher(context.Background(), &ExchangeAddress{Exchange: exchangeName}, "test")
|
||||||
Expect(err).To(BeNil())
|
|
||||||
publisher, err := connection.Publisher(context.Background(), addr, "test")
|
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
Expect(publisher).To(BeNil())
|
Expect(publisher).To(BeNil())
|
||||||
Expect(connection.Close(context.Background())).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() {
|
It("publishResult should released to a not existing routing key", func() {
|
||||||
eName := generateNameWithDateTime("publishResult should released to a not existing routing key")
|
eName := generateNameWithDateTime("publishResult should released to a not existing routing key")
|
||||||
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
exchange, err := connection.Management().DeclareExchange(context.Background(), &ExchangeSpecification{
|
exchange, err := connection.Management().DeclareExchange(context.Background(), &TopicExchangeSpecification{
|
||||||
Name: eName,
|
Name: eName,
|
||||||
IsAutoDelete: false,
|
IsAutoDelete: false,
|
||||||
ExchangeType: ExchangeType{Type: Topic},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(exchange).NotTo(BeNil())
|
Expect(exchange).NotTo(BeNil())
|
||||||
routingKeyNope := "I don't exist"
|
routingKeyNope := "I don't exist"
|
||||||
addr, err := ExchangeAddress(&eName, &routingKeyNope)
|
|
||||||
Expect(err).To(BeNil())
|
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(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
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)
|
connection, err := Dial(context.Background(), []string{"amqp://"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(connection).NotTo(BeNil())
|
Expect(connection).NotTo(BeNil())
|
||||||
_, err = connection.Management().DeclareQueue(context.Background(), &QueueSpecification{
|
_, err = connection.Management().DeclareQueue(context.Background(), &QuorumQueueSpecification{
|
||||||
Name: qName,
|
Name: qName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
dest, _ := QueueAddress(&qName)
|
publisher, err := connection.NewPublisher(context.Background(), &QueueAddress{Queue: qName}, "test")
|
||||||
publisher, err := connection.Publisher(context.Background(), dest, "test")
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
publishResult, err := publisher.Publish(context.Background(), amqp.NewMessage([]byte("hello")))
|
||||||
|
|
@ -110,4 +94,104 @@ var _ = Describe("AMQP publisher ", func() {
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
Expect(connection.Close(context.Background()))
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/Azure/go-amqp"
|
"github.com/Azure/go-amqp"
|
||||||
)
|
)
|
||||||
|
|
@ -70,22 +71,8 @@ type AmqpQueue struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpQueue) DeadLetterExchange(dlx string) {
|
func (a *AmqpQueue) SetArguments(arguments map[string]any) {
|
||||||
if len(dlx) != 0 {
|
a.arguments = arguments
|
||||||
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) QueueType(queueType QueueType) {
|
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) {
|
func (a *AmqpQueue) Exclusive(isExclusive bool) {
|
||||||
a.isExclusive = isExclusive
|
a.isExclusive = isExclusive
|
||||||
}
|
}
|
||||||
|
|
@ -124,32 +104,40 @@ func newAmqpQueue(management *AmqpManagement, queueName string) *AmqpQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpQueue) validate() error {
|
func (a *AmqpQueue) validate() error {
|
||||||
if a.arguments["max-length-bytes"] != nil {
|
if a.arguments["x-max-length-bytes"] != nil {
|
||||||
err := validatePositive("max length", a.arguments["max-length-bytes"].(int64))
|
err := validatePositive("max length", a.arguments["x-max-length-bytes"].(int64))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpQueue) Declare(ctx context.Context) (*AmqpQueueInfo, error) {
|
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 {
|
if err := a.validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.name == "" {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +154,7 @@ func (a *AmqpQueue) Declare(ctx context.Context) (*AmqpQueueInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpQueue) Delete(ctx context.Context) error {
|
func (a *AmqpQueue) Delete(ctx context.Context) error {
|
||||||
path, err := QueueAddress(&a.name)
|
path, err := queueAddress(&a.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +163,7 @@ func (a *AmqpQueue) Delete(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AmqpQueue) Purge(ctx context.Context) (int, error) {
|
func (a *AmqpQueue) Purge(ctx context.Context) (int, error) {
|
||||||
path, err := PurgeQueueAddress(&a.name)
|
path, err := purgeQueueAddress(&a.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
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() {
|
It("AMQP Queue Declare With Response and Get/Delete should succeed", func() {
|
||||||
const queueName = "AMQP Queue Declare With Response and Delete should succeed"
|
var queueName = generateName("AMQP Queue Declare With Response and Delete should succeed")
|
||||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
queueInfo, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -35,7 +35,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
Expect(queueInfo.IsDurable()).To(BeTrue())
|
Expect(queueInfo.IsDurable()).To(BeTrue())
|
||||||
Expect(queueInfo.IsAutoDelete()).To(BeFalse())
|
Expect(queueInfo.IsAutoDelete()).To(BeFalse())
|
||||||
Expect(queueInfo.IsExclusive()).To(BeFalse())
|
Expect(queueInfo.IsExclusive()).To(BeFalse())
|
||||||
Expect(queueInfo.Type()).To(Equal(Classic))
|
Expect(queueInfo.Type()).To(Equal(Quorum))
|
||||||
|
|
||||||
// validate GET (query queue info)
|
// validate GET (query queue info)
|
||||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
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() {
|
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,
|
Name: queueName,
|
||||||
IsAutoDelete: true,
|
IsAutoDelete: true,
|
||||||
IsExclusive: true,
|
IsExclusive: true,
|
||||||
QueueType: QueueType{Classic},
|
AutoExpire: 1000,
|
||||||
MaxLengthBytes: CapacityGB(1),
|
MessageTTL: 1000,
|
||||||
|
OverflowStrategy: &DropHeadOverflowStrategy{},
|
||||||
|
SingleActiveConsumer: true,
|
||||||
DeadLetterExchange: "dead-letter-exchange",
|
DeadLetterExchange: "dead-letter-exchange",
|
||||||
DeadLetterRoutingKey: "dead-letter-routing-key",
|
DeadLetterRoutingKey: "dead-letter-routing-key",
|
||||||
|
MaxLength: 9_000,
|
||||||
|
MaxLengthBytes: CapacityGB(1),
|
||||||
|
MaxPriority: 2,
|
||||||
|
LeaderLocator: &BalancedLeaderLocator{},
|
||||||
})
|
})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
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-exchange", "dead-letter-exchange"))
|
||||||
Expect(queueInfo.Arguments()).To(HaveKeyWithValue("x-dead-letter-routing-key", "dead-letter-routing-key"))
|
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)
|
// validate GET (query queue info)
|
||||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
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() {
|
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
|
// Quorum queue will ignore Exclusive and AutoDelete settings
|
||||||
// since they are not supported by quorum queues
|
// since they are not supported by quorum queues
|
||||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
queueInfo, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
IsAutoDelete: true,
|
|
||||||
IsExclusive: true,
|
|
||||||
QueueType: QueueType{Quorum},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
@ -113,12 +123,10 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
// Stream queue will ignore Exclusive and AutoDelete settings
|
// Stream queue will ignore Exclusive and AutoDelete settings
|
||||||
// since they are not supported by quorum queues
|
// since they are not supported by quorum queues
|
||||||
|
|
||||||
queueInfo, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
queueInfo, err := management.DeclareQueue(context.TODO(), &ClassicQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
IsAutoDelete: true,
|
IsAutoDelete: false,
|
||||||
IsExclusive: true,
|
IsExclusive: false})
|
||||||
QueueType: QueueType{Stream},
|
|
||||||
})
|
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(queueInfo).NotTo(BeNil())
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
|
|
@ -126,7 +134,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
Expect(queueInfo.IsDurable()).To(BeTrue())
|
Expect(queueInfo.IsDurable()).To(BeTrue())
|
||||||
Expect(queueInfo.IsAutoDelete()).To(BeFalse())
|
Expect(queueInfo.IsAutoDelete()).To(BeFalse())
|
||||||
Expect(queueInfo.IsExclusive()).To(BeFalse())
|
Expect(queueInfo.IsExclusive()).To(BeFalse())
|
||||||
Expect(queueInfo.Type()).To(Equal(Stream))
|
Expect(queueInfo.Type()).To(Equal(Classic))
|
||||||
// validate GET (query queue info)
|
// validate GET (query queue info)
|
||||||
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
queueInfoReceived, err := management.QueueInfo(context.TODO(), queueName)
|
||||||
Expect(queueInfoReceived).To(Equal(queueInfo))
|
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() {
|
It("AMQP Declare Queue should fail with Precondition fail", func() {
|
||||||
// The first queue is declared as Classic, and it should succeed
|
// 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
|
// 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")
|
//queueName := generateName("AMQP Declare Queue should fail with Precondition fail")
|
||||||
|
queueName := "ab"
|
||||||
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
_, err := management.DeclareQueue(context.TODO(), &ClassicQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
QueueType: QueueType{Classic},
|
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
_, err = management.DeclareQueue(context.TODO(), &QueueSpecification{
|
_, err = management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
QueueType: QueueType{Quorum},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Expect(err).NotTo(BeNil())
|
Expect(err).NotTo(BeNil())
|
||||||
|
|
@ -169,7 +166,7 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
|
|
||||||
It("AMQP Declare Queue should fail during validation", func() {
|
It("AMQP Declare Queue should fail during validation", func() {
|
||||||
queueName := generateName("AMQP Declare Queue should fail during validation")
|
queueName := generateName("AMQP Declare Queue should fail during validation")
|
||||||
_, err := management.DeclareQueue(context.TODO(), &QueueSpecification{
|
_, err := management.DeclareQueue(context.TODO(), &QuorumQueueSpecification{
|
||||||
Name: queueName,
|
Name: queueName,
|
||||||
MaxLengthBytes: -1,
|
MaxLengthBytes: -1,
|
||||||
})
|
})
|
||||||
|
|
@ -178,8 +175,18 @@ var _ = Describe("AMQP Queue test ", func() {
|
||||||
Expect(err).To(HaveOccurred())
|
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() {
|
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(err).To(BeNil())
|
||||||
Expect(queueInfo).NotTo(BeNil())
|
Expect(queueInfo).NotTo(BeNil())
|
||||||
Expect(queueInfo.Name()).To(ContainSubstring("client.gen-"))
|
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() {
|
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")
|
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,
|
Name: queueName,
|
||||||
})
|
})
|
||||||
Expect(err).To(BeNil())
|
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)
|
conn, err := Dial(context.TODO(), []string{"amqp://guest:guest@localhost"}, nil)
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
address, err := QueueAddress(&queueName)
|
publisher, err := conn.NewPublisher(context.TODO(), &QueueAddress{Queue: queueName}, "test")
|
||||||
Expect(err).To(BeNil())
|
|
||||||
|
|
||||||
publisher, err := conn.Publisher(context.TODO(), address, "test")
|
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(publisher).NotTo(BeNil())
|
Expect(publisher).NotTo(BeNil())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,264 @@ func (e QueueType) String() string {
|
||||||
return string(e.Type)
|
return string(e.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueueSpecification represents the specification of a queue
|
type QueueSpecification interface {
|
||||||
type QueueSpecification struct {
|
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
|
Name string
|
||||||
IsAutoDelete bool
|
IsAutoDelete bool
|
||||||
IsExclusive bool
|
IsExclusive bool
|
||||||
QueueType QueueType
|
AutoExpire int64
|
||||||
MaxLengthBytes int64
|
MessageTTL int64
|
||||||
|
OverflowStrategy OverflowStrategy
|
||||||
|
SingleActiveConsumer bool
|
||||||
DeadLetterExchange string
|
DeadLetterExchange string
|
||||||
DeadLetterRoutingKey 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
|
type TExchangeType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -43,15 +290,124 @@ func (e ExchangeType) String() string {
|
||||||
return string(e.Type)
|
return string(e.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExchangeSpecification struct {
|
// ExchangeSpecification represents the specification of an exchange
|
||||||
Name string
|
type ExchangeSpecification interface {
|
||||||
IsAutoDelete bool
|
name() string
|
||||||
ExchangeType ExchangeType
|
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
|
SourceExchange string
|
||||||
DestinationQueue string
|
|
||||||
DestinationExchange string
|
DestinationExchange string
|
||||||
BindingKey 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