diff --git a/CHANGELOG.md b/CHANGELOG.md index ad9f301..fc71ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog for rabtap +## v1.25 (2020-10-30) + +* fix: rabtap info: workaround for RabbitMQ API returning an `"undefined"` + string where an integer was expected (#47) + ## v1.24 (2020-09-28) * new: support TLS client certificates (contributed by Francois Gouteroux) diff --git a/Makefile b/Makefile index 9afe144..521cb94 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ toxiproxy-cmd: # run rabbitmq server for integration test using docker container. run-broker: - podman run -ti --rm -p 5672:5672 -p 15672:15672 rabbitmq:3-management + podman run -ti --rm -p 5672:5672 -p 15672:15672 rabbitmq:3.8.5-management dist-clean: clean rm -f *.out $(BINARY_WIN64) $(BINARY_LINUX64) $(BINARY_DARWIN64) diff --git a/pkg/rabbitmq_rest_client.go b/pkg/rabbitmq_rest_client.go index ce63bc4..fefe3b1 100644 --- a/pkg/rabbitmq_rest_client.go +++ b/pkg/rabbitmq_rest_client.go @@ -511,10 +511,12 @@ type RabbitExchange struct { } `json:"message_stats,omitempty"` } +type OptInt int + // ChannelDetails model channel_details in RabbitConsumer type ChannelDetails struct { PeerHost string `json:"peer_host"` - PeerPort int `json:"peer_port"` + PeerPort OptInt `json:"peer_port"` ConnectionName string `json:"connection_name"` User string `json:"user"` Number int `json:"number"` @@ -522,12 +524,24 @@ type ChannelDetails struct { Name string `json:"name"` } +// UnmarshalJSON is a workaround to deserialize int attributes in the +// RabbitMQ API which are sometimes returned as strings, (i.e. the +// value "undefined"). +func (d *OptInt) UnmarshalJSON(data []byte) error { + if data[0] == '"' { + return nil + } + type Alias int + aux := (*Alias)(d) + return json.Unmarshal(data, aux) +} + // UnmarshalJSON is a custom unmarshaler as a WORKAROUND for RabbitMQ API // returning "[]" instead of null. To make sure deserialization does not // break, we catch this case, and return an empty ChannelDetails struct. // see e.g. https://github.com/rabbitmq/rabbitmq-management/issues/424 func (d *ChannelDetails) UnmarshalJSON(data []byte) error { - // akias ChannelDetails to avoid recursion when callung Unmarshal + // alias ChannelDetails to avoid recursion when calling Unmarshal type Alias ChannelDetails aux := &struct { *Alias diff --git a/pkg/rabbitmq_rest_client_test.go b/pkg/rabbitmq_rest_client_test.go index af5cec0..d4f49ae 100644 --- a/pkg/rabbitmq_rest_client_test.go +++ b/pkg/rabbitmq_rest_client_test.go @@ -4,6 +4,7 @@ package rabtap import ( "crypto/tls" + "encoding/json" "fmt" "net/http" "net/http/httptest" @@ -172,6 +173,76 @@ func TestRabbitClientGetConnections(t *testing.T) { assert.Equal(t, "172.17.0.1:40874 -> 172.17.0.2:5672", conn[0].Name) } +func TestRabbitClientDeserializePeerPortInConsumerToInt(t *testing.T) { + msg := ` +[ + { + "arguments": {}, + "ack_required": true, + "active": true, + "activity_status": "up", + "channel_details": { + "connection_name": "XXX", + "name": "XXX (1)", + "node": "XXX", + "number": 1, + "peer_host": "undefined", + "peer_port": 1234, + "user": "none" + }, + "consumer_tag": "amq.ctag-InRAvLn4GW3j2mRwPmWJxA", + "exclusive": false, + "prefetch_count": 20, + "queue": { + "name": "logstream", + "vhost": "/" + } + } +] +` + var consumer []RabbitConsumer + err := json.Unmarshal([]byte(msg), &consumer) + assert.NoError(t, err) + assert.Equal(t, OptInt(1234), consumer[0].ChannelDetails.PeerPort) + +} + +func TestRabbitClientDeserializePeerPortInConsumerAsStringWithoutError(t *testing.T) { + // RabbitMQ sometimes returns "undefined" for the peer_port attribute, + // but we expect an integer. + msg := ` +[ + { + "arguments": {}, + "ack_required": true, + "active": true, + "activity_status": "up", + "channel_details": { + "connection_name": "XXX", + "name": "XXX (1)", + "node": "XXX", + "number": 1, + "peer_host": "undefined", + "peer_port": "undefined", + "user": "none" + }, + "consumer_tag": "amq.ctag-InRAvLn4GW3j2mRwPmWJxA", + "exclusive": false, + "prefetch_count": 20, + "queue": { + "name": "logstream", + "vhost": "/" + } + } +] +` + var consumer []RabbitConsumer + err := json.Unmarshal([]byte(msg), &consumer) + assert.NoError(t, err) + assert.Equal(t, OptInt(0), consumer[0].ChannelDetails.PeerPort) + +} + // test of GET /api/consumers endpoint workaround for empty channel_details func TestRabbitClientGetConsumersChannelDetailsIsEmptyArray(t *testing.T) {