amqp-client 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AMQP
4
+ class Client
5
+ # Queue abstraction
6
+ class Queue
7
+ # Should only be initialized from the Client
8
+ # @api private
9
+ def initialize(client, name)
10
+ @client = client
11
+ @name = name
12
+ end
13
+
14
+ # Publish to the queue, wait for confirm
15
+ # @param (see Client#publish)
16
+ # @option (see Client#publish)
17
+ # @raise (see Client#publish)
18
+ # @return [self]
19
+ def publish(body, **properties)
20
+ @client.publish(body, "", @name, **properties)
21
+ self
22
+ end
23
+
24
+ # Subscribe/consume from the queue
25
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
26
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
27
+ # @param worker_threads [Integer] Number of threads processing messages,
28
+ # 0 means that the thread calling this method will be blocked
29
+ # @param arguments [Hash] Custom arguments to the consumer
30
+ # @yield [Message] Delivered message from the queue
31
+ # @return [self]
32
+ def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
33
+ @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
34
+ self
35
+ end
36
+
37
+ # Bind the queue to an exchange
38
+ # @param exchange [String] Name of the exchange to bind to
39
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
40
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
41
+ # @return [self]
42
+ def bind(exchange, binding_key, arguments: {})
43
+ @client.bind(@name, exchange, binding_key, arguments: arguments)
44
+ self
45
+ end
46
+
47
+ # Unbind the queue from an exchange
48
+ # @param exchange [String] Name of the exchange to unbind from
49
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
50
+ # @param arguments [Hash] Arguments matching the binding that's being removed
51
+ # @return [self]
52
+ def unbind(exchange, binding_key, arguments: {})
53
+ @client.unbind(@name, exchange, binding_key, arguments: arguments)
54
+ self
55
+ end
56
+
57
+ # Purge/empty the queue
58
+ # @return [self]
59
+ def purge
60
+ @client.purge(@name)
61
+ self
62
+ end
63
+
64
+ # Delete the queue
65
+ # @return [nil]
66
+ def delete
67
+ @client.delete_queue(@name)
68
+ nil
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,122 +1,152 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AMQP
4
- # Encode/decode AMQP Tables
5
- module Table
6
- module_function
4
+ class Client
5
+ # Encode and decode an AMQP table to/from hash, only used internally
6
+ # @api private
7
+ module Table
8
+ # Encodes a hash into a byte array
9
+ # @param hash [Hash]
10
+ # @return [String] Byte array
11
+ def self.encode(hash)
12
+ return "" if hash.empty?
7
13
 
8
- def encode(hash)
9
- tbl = ""
10
- hash.each do |k, v|
11
- key = k.to_s
12
- tbl += [key.bytesize, key, encode_field(v)].pack("C a* a*")
14
+ arr = []
15
+ fmt = String.new
16
+ hash.each do |k, value|
17
+ key = k.to_s
18
+ arr.push(key.bytesize, key)
19
+ fmt << "Ca*"
20
+ encode_field(value, arr, fmt)
21
+ end
22
+ arr.pack(fmt)
13
23
  end
14
- tbl
15
- end
16
-
17
- def decode(bytes)
18
- h = {}
19
- pos = 0
20
24
 
21
- while pos < bytes.bytesize
22
- key_len = bytes[pos].ord
23
- pos += 1
24
- key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
25
- pos += key_len
26
- rest = bytes.byteslice(pos, bytes.bytesize - pos)
27
- len, value = decode_field(rest)
28
- pos += len + 1
29
- h[key] = value
25
+ # Decodes an AMQP table into a hash
26
+ # @return [Hash<String, Object>]
27
+ def self.decode(bytes)
28
+ hash = {}
29
+ pos = 0
30
+ while pos < bytes.bytesize
31
+ key_len = bytes.getbyte(pos)
32
+ pos += 1
33
+ key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
34
+ pos += key_len
35
+ len, value = decode_field(bytes, pos)
36
+ pos += len + 1
37
+ hash[key] = value
38
+ end
39
+ hash
30
40
  end
31
- h
32
- end
33
41
 
34
- def encode_field(value)
35
- case value
36
- when Integer
37
- if value > 2**31
38
- ["l", value].pack("a q>")
39
- else
40
- ["I", value].pack("a l>")
42
+ # Encoding a single value in a table
43
+ # @return [nil]
44
+ # @api private
45
+ def self.encode_field(value, arr, fmt)
46
+ case value
47
+ when Integer
48
+ if value > 2**31
49
+ arr.push("l", value)
50
+ fmt << "aq>"
51
+ else
52
+ arr.push("I", value)
53
+ fmt << "al>"
54
+ end
55
+ when Float
56
+ arr.push("d", value)
57
+ fmt << "aG"
58
+ when String
59
+ arr.push("S", value.bytesize, value)
60
+ fmt << "aL>a*"
61
+ when Time
62
+ arr.push("T", value.to_i)
63
+ fmt << "aQ>"
64
+ when Array
65
+ value_arr = []
66
+ value_fmt = String.new
67
+ value.each { |e| encode_field(e, value_arr, value_fmt) }
68
+ bytes = value_arr.pack(value_fmt)
69
+ arr.push("A", bytes.bytesize, bytes)
70
+ fmt << "aL>a*"
71
+ when Hash
72
+ bytes = Table.encode(value)
73
+ arr.push("F", bytes.bytesize, bytes)
74
+ fmt << "aL>a*"
75
+ when true
76
+ arr.push("t", 1)
77
+ fmt << "aC"
78
+ when false
79
+ arr.push("t", 0)
80
+ fmt << "aC"
81
+ when nil
82
+ arr << "V"
83
+ fmt << "a"
84
+ else raise ArgumentError, "unsupported table field type: #{value.class}"
41
85
  end
42
- when Float
43
- ["d", value].pack("a G")
44
- when String
45
- ["S", value.bytesize, value].pack("a L> a*")
46
- when Time
47
- ["T", value.to_i].pack("a Q>")
48
- when Array
49
- bytes = value.map { |e| encode_field(e) }.join
50
- ["A", bytes.bytesize, bytes].pack("a L> a*")
51
- when Hash
52
- bytes = Table.encode(value)
53
- ["F", bytes.bytesize, bytes].pack("a L> a*")
54
- when true
55
- ["t", 1].pack("a C")
56
- when false
57
- ["t", 0].pack("a C")
58
- when nil
59
- ["V"].pack("a")
60
- else raise "unsupported table field type: #{value.class}"
86
+ nil
61
87
  end
62
- end
63
88
 
64
- # returns [length of field including type, value of field]
65
- def decode_field(bytes)
66
- type = bytes[0]
67
- pos = 1
68
- case type
69
- when "S"
70
- len = bytes.byteslice(pos, 4).unpack1("L>")
71
- pos += 4
72
- [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
73
- when "F"
74
- len = bytes.byteslice(pos, 4).unpack1("L>")
75
- pos += 4
76
- [4 + len, decode(bytes.byteslice(pos, len))]
77
- when "A"
78
- len = bytes.byteslice(pos, 4).unpack1("L>")
79
- a = []
80
- while pos < len
81
- length, value = decode_field(bytes.byteslice(pos, -1))
82
- pos += length + 1
83
- a << value
84
- end
85
- [4 + len, a]
86
- when "t"
87
- [1, bytes[pos].ord == 1]
88
- when "b"
89
- [1, bytes.byteslice(pos, 1).unpack1("c")]
90
- when "B"
91
- [1, bytes.byteslice(pos, 1).unpack1("C")]
92
- when "s"
93
- [2, bytes.byteslice(pos, 2).unpack1("s")]
94
- when "u"
95
- [2, bytes.byteslice(pos, 2).unpack1("S")]
96
- when "I"
97
- [4, bytes.byteslice(pos, 4).unpack1("l>")]
98
- when "i"
99
- [4, bytes.byteslice(pos, 4).unpack1("L>")]
100
- when "l"
101
- [8, bytes.byteslice(pos, 8).unpack1("q>")]
102
- when "f"
103
- [4, bytes.byteslice(pos, 4).unpack1("g")]
104
- when "d"
105
- [8, bytes.byteslice(pos, 8).unpack1("G")]
106
- when "D"
107
- scale = bytes[pos].ord
89
+ # Decodes a single value
90
+ # @return [Array<Integer, Object>] Bytes read and the parsed value
91
+ # @api private
92
+ def self.decode_field(bytes, pos)
93
+ type = bytes[pos]
108
94
  pos += 1
109
- value = bytes.byteslice(pos, 4).unpack1("L>")
110
- d = value / 10**scale
111
- [5, d]
112
- when "x"
113
- len = bytes.byteslice(pos, 4).unpack1("L>")
114
- [4 + len, bytes.byteslice(pos, len)]
115
- when "T"
116
- [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
117
- when "V"
118
- [0, nil]
119
- else raise "unsupported table field type: #{type}"
95
+ case type
96
+ when "S"
97
+ len = bytes.byteslice(pos, 4).unpack1("L>")
98
+ pos += 4
99
+ [4 + len, bytes.byteslice(pos, len).force_encoding("utf-8")]
100
+ when "F"
101
+ len = bytes.byteslice(pos, 4).unpack1("L>")
102
+ pos += 4
103
+ [4 + len, decode(bytes.byteslice(pos, len))]
104
+ when "A"
105
+ len = bytes.byteslice(pos, 4).unpack1("L>")
106
+ pos += 4
107
+ array_end = pos + len
108
+ a = []
109
+ while pos < array_end
110
+ length, value = decode_field(bytes, pos)
111
+ pos += length + 1
112
+ a << value
113
+ end
114
+ [4 + len, a]
115
+ when "t"
116
+ [1, bytes.getbyte(pos) == 1]
117
+ when "b"
118
+ [1, bytes.byteslice(pos, 1).unpack1("c")]
119
+ when "B"
120
+ [1, bytes.byteslice(pos, 1).unpack1("C")]
121
+ when "s"
122
+ [2, bytes.byteslice(pos, 2).unpack1("s")]
123
+ when "u"
124
+ [2, bytes.byteslice(pos, 2).unpack1("S")]
125
+ when "I"
126
+ [4, bytes.byteslice(pos, 4).unpack1("l>")]
127
+ when "i"
128
+ [4, bytes.byteslice(pos, 4).unpack1("L>")]
129
+ when "l"
130
+ [8, bytes.byteslice(pos, 8).unpack1("q>")]
131
+ when "f"
132
+ [4, bytes.byteslice(pos, 4).unpack1("g")]
133
+ when "d"
134
+ [8, bytes.byteslice(pos, 8).unpack1("G")]
135
+ when "D"
136
+ scale = bytes.getbyte(pos)
137
+ pos += 1
138
+ value = bytes.byteslice(pos, 4).unpack1("L>")
139
+ d = value / 10**scale
140
+ [5, d]
141
+ when "x"
142
+ len = bytes.byteslice(pos, 4).unpack1("L>")
143
+ [4 + len, bytes.byteslice(pos, len)]
144
+ when "T"
145
+ [8, Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))]
146
+ when "V"
147
+ [0, nil]
148
+ else raise ArgumentError, "unsupported table field type: #{type}"
149
+ end
120
150
  end
121
151
  end
122
152
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module AMQP
4
4
  class Client
5
- VERSION = "1.0.0"
5
+ # Version of the client library
6
+ VERSION = "1.1.1"
6
7
  end
7
8
  end
data/lib/amqp/client.rb CHANGED
@@ -3,11 +3,27 @@
3
3
  require "set"
4
4
  require_relative "client/version"
5
5
  require_relative "client/connection"
6
+ require_relative "client/exchange"
7
+ require_relative "client/queue"
6
8
 
9
+ # AMQP 0-9-1 Protocol, this library only implements the Client
10
+ # @see Client
7
11
  module AMQP
8
12
  # AMQP 0-9-1 Client
13
+ # @see Connection
9
14
  class Client
10
- def initialize(uri, **options)
15
+ # Create a new Client object, this won't establish a connection yet, use {#connect} or {#start} for that
16
+ # @param uri [String] URL on the format amqp://username:password@hostname/vhost,
17
+ # use amqps:// for encrypted connection
18
+ # @option options [Boolean] connection_name (PROGRAM_NAME) Set a name for the connection to be able to identify
19
+ # the client from the broker
20
+ # @option options [Boolean] verify_peer (true) Verify broker's TLS certificate, set to false for self-signed certs
21
+ # @option options [Integer] heartbeat (0) Heartbeat timeout, defaults to 0 and relies on TCP keepalive instead
22
+ # @option options [Integer] frame_max (131_072) Maximum frame size,
23
+ # the smallest of the client's and the broker's values will be used
24
+ # @option options [Integer] channel_max (2048) Maxium number of channels the client will be allowed to have open.
25
+ # Maxium allowed is 65_536. The smallest of the client's and the broker's value will be used.
26
+ def initialize(uri = "", **options)
11
27
  @uri = uri
12
28
  @options = options
13
29
 
@@ -17,12 +33,23 @@ module AMQP
17
33
  @connq = SizedQueue.new(1)
18
34
  end
19
35
 
20
- # Opens an AMQP connection, does not try to reconnect
36
+ # @!group Connect and disconnect
37
+
38
+ # Establishes and returns a new AMQP connection
39
+ # @see Connection#initialize
40
+ # @return [Connection]
41
+ # @example
42
+ # connection = AMQP::Client.new("amqps://server.rmq.cloudamqp.com", connection_name: "My connection").connect
21
43
  def connect(read_loop_thread: true)
22
- Connection.connect(@uri, read_loop_thread: read_loop_thread, **@options)
44
+ Connection.new(@uri, read_loop_thread: read_loop_thread, **@options)
23
45
  end
24
46
 
25
- # Opens an AMQP connection using the high level API, will try to reconnect
47
+ # Opens an AMQP connection using the high level API, will try to reconnect if successfully connected at first
48
+ # @return [self]
49
+ # @example
50
+ # amqp = AMQP::Client.new("amqps://server.rmq.cloudamqp.com")
51
+ # amqp.start
52
+ # amqp.queue("foobar")
26
53
  def start
27
54
  @stopped = false
28
55
  Thread.new(connect(read_loop_thread: false)) do |conn|
@@ -42,7 +69,7 @@ module AMQP
42
69
  @connq << conn
43
70
  end
44
71
  conn.read_loop # blocks until connection is closed, then reconnect
45
- rescue AMQP::Client::Error => e
72
+ rescue Error => e
46
73
  warn "AMQP-Client reconnect error: #{e.inspect}"
47
74
  sleep @options[:reconnect_interval] || 1
48
75
  ensure
@@ -52,76 +79,67 @@ module AMQP
52
79
  self
53
80
  end
54
81
 
82
+ # Close the currently open connection
83
+ # @return [nil]
55
84
  def stop
85
+ return if @stopped
86
+
56
87
  @stopped = true
57
88
  conn = @connq.pop
58
89
  conn.close
59
90
  nil
60
91
  end
61
92
 
62
- def queue(name, durable: true, exclusive: false, auto_delete: false, arguments: {})
93
+ # @!endgroup
94
+ # @!group High level objects
95
+
96
+ # Declare a queue
97
+ # @param name [String] Name of the queue
98
+ # @param durable [Boolean] If true the queue will survive broker restarts,
99
+ # messages in the queue will only survive if they are published as persistent
100
+ # @param auto_delete [Boolean] If true the queue will be deleted when the last consumer stops consuming
101
+ # (it won't be deleted until at least one consumer has consumed from it)
102
+ # @param arguments [Hash] Custom arguments, such as queue-ttl etc.
103
+ # @return [Queue]
104
+ # @example
105
+ # amqp = AMQP::Client.new.start
106
+ # q = amqp.queue("foobar")
107
+ # q.publish("body")
108
+ def queue(name, durable: true, auto_delete: false, arguments: {})
63
109
  raise ArgumentError, "Currently only supports named, durable queues" if name.empty?
64
110
 
65
111
  @queues.fetch(name) do
66
112
  with_connection do |conn|
67
- conn.with_channel do |ch| # use a temp channel in case the declaration fails
68
- ch.queue_declare(name, durable: durable, exclusive: exclusive, auto_delete: auto_delete, arguments: arguments)
69
- end
113
+ conn.channel(1).queue_declare(name, durable: durable, auto_delete: auto_delete, arguments: arguments)
70
114
  end
71
115
  @queues[name] = Queue.new(self, name)
72
116
  end
73
117
  end
74
118
 
119
+ # Declare an exchange and return a high level Exchange object
120
+ # @return [Exchange]
121
+ # @example
122
+ # amqp = AMQP::Client.new.start
123
+ # x = amqp.exchange("my.hash.exchange", "x-consistent-hash")
124
+ # x.publish("body", "routing-key")
75
125
  def exchange(name, type, durable: true, auto_delete: false, internal: false, arguments: {})
76
126
  @exchanges.fetch(name) do
77
127
  with_connection do |conn|
78
- conn.with_channel do |ch|
79
- ch.exchange_declare(name, type, durable: durable, auto_delete: auto_delete, internal: internal, arguments: arguments)
80
- end
128
+ conn.channel(1).exchange_declare(name, type, durable: durable, auto_delete: auto_delete,
129
+ internal: internal, arguments: arguments)
81
130
  end
82
131
  @exchanges[name] = Exchange.new(self, name)
83
132
  end
84
133
  end
85
134
 
86
- # High level representation of an exchange
87
- class Exchange
88
- def initialize(client, name)
89
- @client = client
90
- @name = name
91
- end
92
-
93
- def publish(body, routing_key, arguments: {})
94
- @client.publish(body, @name, routing_key, arguments: arguments)
95
- end
96
-
97
- # Bind to another exchange
98
- def bind(exchange, routing_key, arguments: {})
99
- @client.exchange_bind(@name, exchange, routing_key, arguments: arguments)
100
- end
101
-
102
- # Unbind from another exchange
103
- def unbind(exchange, routing_key, arguments: {})
104
- @client.exchange_unbind(@name, exchange, routing_key, arguments: arguments)
105
- end
106
-
107
- def delete
108
- @client.delete_exchange(@name)
109
- end
110
- end
111
-
112
- def subscribe(queue_name, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
113
- @subscriptions.add? [queue_name, no_ack, prefetch, worker_threads, arguments, blk]
114
-
115
- with_connection do |conn|
116
- ch = conn.channel
117
- ch.basic_qos(prefetch)
118
- ch.basic_consume(queue_name, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments) do |msg|
119
- blk.call(msg)
120
- end
121
- end
122
- end
135
+ # @!endgroup
136
+ # @!group Publish
123
137
 
124
138
  # Publish a (persistent) message and wait for confirmation
139
+ # @param (see Connection::Channel#basic_publish_confirm)
140
+ # @option (see Connection::Channel#basic_publish_confirm)
141
+ # @return (see Connection::Channel#basic_publish_confirm)
142
+ # @raise (see Connection::Channel#basic_publish_confirm)
125
143
  def publish(body, exchange, routing_key, **properties)
126
144
  with_connection do |conn|
127
145
  properties = { delivery_mode: 2 }.merge!(properties)
@@ -130,6 +148,10 @@ module AMQP
130
148
  end
131
149
 
132
150
  # Publish a (persistent) message but don't wait for a confirmation
151
+ # @param (see Connection::Channel#basic_publish)
152
+ # @option (see Connection::Channel#basic_publish)
153
+ # @return (see Connection::Channel#basic_publish)
154
+ # @raise (see Connection::Channel#basic_publish)
133
155
  def publish_and_forget(body, exchange, routing_key, **properties)
134
156
  with_connection do |conn|
135
157
  properties = { delivery_mode: 2 }.merge!(properties)
@@ -137,94 +159,123 @@ module AMQP
137
159
  end
138
160
  end
139
161
 
162
+ # Wait for unconfirmed publishes
163
+ # @return [Boolean] True if successful, false if any message negatively acknowledged
140
164
  def wait_for_confirms
141
165
  with_connection do |conn|
142
166
  conn.channel(1).wait_for_confirms
143
167
  end
144
168
  end
145
169
 
146
- def bind(queue, exchange, routing_key, arguments: {})
147
- with_connection do |conn|
148
- conn.channel(1).queue_bind(queue, exchange, routing_key, arguments: arguments)
149
- end
150
- end
170
+ # @!endgroup
171
+ # @!group Queue actions
172
+
173
+ # Consume messages from a queue
174
+ # @param queue [String] Name of the queue to subscribe to
175
+ # @param no_ack [Boolean] When false messages have to be manually acknowledged (or rejected)
176
+ # @param prefetch [Integer] Specify how many messages to prefetch for consumers with no_ack is false
177
+ # @param worker_threads [Integer] Number of threads processing messages,
178
+ # 0 means that the thread calling this method will be blocked
179
+ # @param arguments [Hash] Custom arguments to the consumer
180
+ # @yield [Message] Delivered message from the queue
181
+ # @return [Array<(String, Array<Thread>)>] Returns consumer_tag and an array of worker threads
182
+ # @return [nil] When `worker_threads` is 0 the method will return when the consumer is cancelled
183
+ def subscribe(queue, no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
184
+ @subscriptions.add? [queue, no_ack, prefetch, worker_threads, arguments, blk]
151
185
 
152
- def unbind(queue, exchange, routing_key, arguments: {})
153
186
  with_connection do |conn|
154
- conn.channel(1).queue_unbind(queue, exchange, routing_key, arguments: arguments)
187
+ ch = conn.channel
188
+ ch.basic_qos(prefetch)
189
+ ch.basic_consume(queue, no_ack: no_ack, worker_threads: worker_threads, arguments: arguments, &blk)
155
190
  end
156
191
  end
157
192
 
158
- def exchange_bind(destination, source, routing_key, arguments: {})
193
+ # Bind a queue to an exchange
194
+ # @param queue [String] Name of the queue to bind
195
+ # @param exchange [String] Name of the exchange to bind to
196
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
197
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
198
+ # @return [nil]
199
+ def bind(queue, exchange, binding_key, arguments: {})
159
200
  with_connection do |conn|
160
- conn.channel(1).exchange_bind(destination, source, routing_key, arguments: arguments)
201
+ conn.channel(1).queue_bind(queue, exchange, binding_key, arguments: arguments)
161
202
  end
162
203
  end
163
204
 
164
- def exchange_unbind(destination, source, routing_key, arguments: {})
205
+ # Unbind a queue from an exchange
206
+ # @param queue [String] Name of the queue to unbind
207
+ # @param exchange [String] Name of the exchange to unbind from
208
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with
209
+ # @param arguments [Hash] Arguments matching the binding that's being removed
210
+ # @return [nil]
211
+ def unbind(queue, exchange, binding_key, arguments: {})
165
212
  with_connection do |conn|
166
- conn.channel(1).exchange_unbind(destination, source, routing_key, arguments: arguments)
213
+ conn.channel(1).queue_unbind(queue, exchange, binding_key, arguments: arguments)
167
214
  end
168
215
  end
169
216
 
217
+ # Purge a queue
218
+ # @param queue [String] Name of the queue
219
+ # @return [nil]
170
220
  def purge(queue)
171
221
  with_connection do |conn|
172
222
  conn.channel(1).queue_purge(queue)
173
223
  end
174
224
  end
175
225
 
176
- def delete_queue(name)
226
+ # Delete a queue
227
+ # @param name [String] Name of the queue
228
+ # @param if_unused [Boolean] Only delete if the queue doesn't have consumers, raises a ChannelClosed error otherwise
229
+ # @param if_empty [Boolean] Only delete if the queue is empty, raises a ChannelClosed error otherwise
230
+ # @return [Integer] Number of messages in the queue when deleted
231
+ def delete_queue(name, if_unused: false, if_empty: false)
177
232
  with_connection do |conn|
178
- conn.channel(1).queue_delete(name)
233
+ msgs = conn.channel(1).queue_delete(name, if_unused: if_unused, if_empty: if_empty)
179
234
  @queues.delete(name)
235
+ msgs
180
236
  end
181
237
  end
182
238
 
183
- def delete_exchange(name)
239
+ # @!endgroup
240
+ # @!group Exchange actions
241
+
242
+ # Bind an exchange to an exchange
243
+ # @param destination [String] Name of the exchange to bind
244
+ # @param source [String] Name of the exchange to bind to
245
+ # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
246
+ # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
247
+ # @return [nil]
248
+ def exchange_bind(destination, source, binding_key, arguments: {})
184
249
  with_connection do |conn|
185
- conn.channel(1).exchange_delete(name)
186
- @exchanges.delete(name)
250
+ conn.channel(1).exchange_bind(destination, source, binding_key, arguments: arguments)
187
251
  end
188
252
  end
189
253
 
190
- # Queue abstraction
191
- class Queue
192
- def initialize(client, name)
193
- @client = client
194
- @name = name
195
- end
196
-
197
- def publish(body, **properties)
198
- @client.publish(body, "", @name, **properties)
199
- self
200
- end
201
-
202
- def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
203
- @client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
204
- self
205
- end
206
-
207
- def bind(exchange, routing_key, **headers)
208
- @client.bind(@name, exchange, routing_key, **headers)
209
- self
210
- end
211
-
212
- def unbind(exchange, routing_key, **headers)
213
- @client.unbind(@name, exchange, routing_key, **headers)
214
- self
215
- end
216
-
217
- def purge
218
- @client.purge(@name)
219
- self
254
+ # Unbind an exchange from an exchange
255
+ # @param destination [String] Name of the exchange to unbind
256
+ # @param source [String] Name of the exchange to unbind from
257
+ # @param binding_key [String] Binding key which the exchange is bound to the exchange with
258
+ # @param arguments [Hash] Arguments matching the binding that's being removed
259
+ # @return [nil]
260
+ def exchange_unbind(destination, source, binding_key, arguments: {})
261
+ with_connection do |conn|
262
+ conn.channel(1).exchange_unbind(destination, source, binding_key, arguments: arguments)
220
263
  end
264
+ end
221
265
 
222
- def delete
223
- @client.delete_queue(@name)
266
+ # Delete an exchange
267
+ # @param name [String] Name of the exchange
268
+ # @return [nil]
269
+ def delete_exchange(name)
270
+ with_connection do |conn|
271
+ conn.channel(1).exchange_delete(name)
272
+ @exchanges.delete(name)
224
273
  nil
225
274
  end
226
275
  end
227
276
 
277
+ # @!endgroup
278
+
228
279
  private
229
280
 
230
281
  def with_connection