amqp-client 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,111 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AMQP
4
- Message = Struct.new(:channel, :delivery_tag, :exchange_name, :routing_key, :properties, :body, :redelivered, :consumer_tag) do
5
- def ack
6
- channel.basic_ack(delivery_tag)
4
+ class Client
5
+ # A message delivered from the broker
6
+ class Message
7
+ # @api private
8
+ def initialize(channel, consumer_tag, delivery_tag, exchange, routing_key, redelivered)
9
+ @channel = channel
10
+ @consumer_tag = consumer_tag
11
+ @delivery_tag = delivery_tag
12
+ @exchange = exchange
13
+ @routing_key = routing_key
14
+ @redelivered = redelivered
15
+ @properties = nil
16
+ @body = ""
17
+ end
18
+
19
+ # The channel the message was deliviered to
20
+ # @return [Connection::Channel]
21
+ attr_reader :channel
22
+
23
+ # The tag of the consumer the message was deliviered to
24
+ # @return [String]
25
+ # @return [nil] If the message was polled and not deliviered to a consumer
26
+ attr_reader :consumer_tag
27
+
28
+ # The delivery tag of the message, used for acknowledge or reject the message
29
+ # @return [Integer]
30
+ attr_reader :delivery_tag
31
+
32
+ # Name of the exchange the message was published to
33
+ # @return [String]
34
+ attr_reader :exchange
35
+
36
+ # The routing key the message was published with
37
+ # @return [String]
38
+ attr_reader :routing_key
39
+
40
+ # True if the message have been delivered before
41
+ # @return [Boolean]
42
+ attr_reader :redelivered
43
+
44
+ # Message properties
45
+ # @return [Properties]
46
+ attr_accessor :properties
47
+
48
+ # The message body
49
+ # @return [String]
50
+ attr_accessor :body
51
+
52
+ # Acknowledge the message
53
+ # @return [nil]
54
+ def ack
55
+ @channel.basic_ack(@delivery_tag)
56
+ end
57
+
58
+ # Reject the message
59
+ # @param requeue [Boolean] If true the message will be put back into the queue again, ready to be redelivered
60
+ # @return [nil]
61
+ def reject(requeue: false)
62
+ @channel.basic_reject(@delivery_tag, requeue: requeue)
63
+ end
64
+
65
+ # @see #exchange
66
+ # @deprecated
67
+ # @!attribute [r] exchange_name
68
+ # @return [String]
69
+ def exchange_name
70
+ @exchange
71
+ end
7
72
  end
8
73
 
9
- def reject(requeue: false)
10
- channel.basic_reject(delivery_tag, requeue: requeue)
74
+ # A published message returned by the broker due to some error
75
+ class ReturnMessage
76
+ # @api private
77
+ def initialize(reply_code, reply_text, exchange, routing_key)
78
+ @reply_code = reply_code
79
+ @reply_text = reply_text
80
+ @exchange = exchange
81
+ @routing_key = routing_key
82
+ @properties = nil
83
+ @body = ""
84
+ end
85
+
86
+ # Error code
87
+ # @return [Integer]
88
+ attr_reader :reply_code
89
+
90
+ # Description on why the message was returned
91
+ # @return [String]
92
+ attr_reader :reply_text
93
+
94
+ # Name of the exchange the message was published to
95
+ # @return [String]
96
+ attr_reader :exchange
97
+
98
+ # The routing key the message was published with
99
+ # @return [String]
100
+ attr_reader :routing_key
101
+
102
+ # Message properties
103
+ # @return [Properties]
104
+ attr_accessor :properties
105
+
106
+ # The message body
107
+ # @return [String]
108
+ attr_accessor :body
11
109
  end
12
110
  end
13
-
14
- ReturnMessage = Struct.new(:reply_code, :reply_text, :exchange, :routing_key, :properties, :body)
15
111
  end
@@ -3,201 +3,298 @@
3
3
  require_relative "./table"
4
4
 
5
5
  module AMQP
6
- # Encode/decode AMQP Properties
7
- Properties = Struct.new(:content_type, :content_encoding, :headers, :delivery_mode, :priority, :correlation_id,
8
- :reply_to, :expiration, :message_id, :timestamp, :type, :user_id, :app_id,
9
- keyword_init: true) do
10
- def encode
11
- flags = 0
12
- arr = [flags]
13
- fmt = String.new("S>")
14
-
15
- if content_type
16
- content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
17
-
18
- flags |= (1 << 15)
19
- arr << content_type.bytesize << content_type
20
- fmt << "Ca*"
6
+ class Client
7
+ # Encode/decode AMQP Properties
8
+ class Properties
9
+ def initialize(content_type: nil, content_encoding: nil, headers: nil, delivery_mode: nil, priority: nil, correlation_id: nil,
10
+ reply_to: nil, expiration: nil, message_id: nil, timestamp: nil, type: nil, user_id: nil, app_id: nil)
11
+ @content_type = content_type
12
+ @content_encoding = content_encoding
13
+ @headers = headers
14
+ @delivery_mode = delivery_mode
15
+ @priority = priority
16
+ @correlation_id = correlation_id
17
+ @reply_to = reply_to
18
+ @expiration = expiration
19
+ @message_id = message_id
20
+ @timestamp = timestamp
21
+ @type = type
22
+ @user_id = user_id
23
+ @app_id = app_id
21
24
  end
22
25
 
23
- if content_encoding
24
- content_encoding.is_a?(String) || raise(ArgumentError, "content_encoding must be a string")
25
-
26
- flags |= (1 << 14)
27
- arr << content_encoding.bytesize << content_encoding
28
- fmt << "Ca*"
26
+ # Properties as a Hash
27
+ # @return [Hash] Properties
28
+ def to_h
29
+ {
30
+ content_type: content_type,
31
+ content_encoding: content_encoding,
32
+ headers: headers,
33
+ delivery_mode: delivery_mode,
34
+ priority: priority,
35
+ correlation_id: correlation_id,
36
+ reply_to: reply_to,
37
+ expiration: expiration,
38
+ message_id: message_id,
39
+ timestamp: timestamp,
40
+ type: type,
41
+ user_id: user_id,
42
+ app_id: app_id
43
+ }
29
44
  end
30
45
 
31
- if headers
32
- headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
46
+ alias to_hash to_h
47
+
48
+ # Returns true if two Property objects holds the same information
49
+ # @return [Boolean]
50
+ def ==(other)
51
+ return false unless other.is_a? self.class
33
52
 
34
- flags |= (1 << 13)
35
- tbl = Table.encode(headers)
36
- arr << tbl.bytesize << tbl
37
- fmt << "L>a*"
53
+ instance_variables.all? { |v| instance_variable_get(v) == other.instance_variable_get(v) }
38
54
  end
39
55
 
40
- if delivery_mode
41
- delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
42
- delivery_mode.between?(0, 2) || raise(ArgumentError, "delivery_mode must be be between 0 and 2")
56
+ # Content type of the message body
57
+ # @return [String, nil]
58
+ attr_accessor :content_type
59
+ # Content encoding of the body
60
+ # @return [String, nil]
61
+ attr_accessor :content_encoding
62
+ # Headers, for applications and header exchange routing
63
+ # @return [Hash<String, Object>, nil]
64
+ attr_accessor :headers
65
+ # Message persistent level
66
+ # @note The exchange and queue have to durable as well for the message to be persistent
67
+ # @return [1] Transient message
68
+ # @return [2] Persistent message
69
+ # @return [nil] Not specified (implicitly transient)
70
+ attr_accessor :delivery_mode
71
+ # A priority of the message (between 0 and 255)
72
+ # @return [Integer, nil]
73
+ attr_accessor :priority
74
+ # Message correlation id, commonly used to correlate RPC requests and responses
75
+ # @return [String, nil]
76
+ attr_accessor :correlation_id
77
+ # Queue to reply RPC responses to
78
+ # @return [String, nil]
79
+ attr_accessor :reply_to
80
+ # Number of seconds the message will stay in the queue
81
+ # @return [String, nil]
82
+ attr_accessor :expiration
83
+ # Application message identifier
84
+ # @return [String, nil]
85
+ attr_accessor :message_id
86
+ # Message timestamp, often indicates when the message was originally generated
87
+ # @return [Date, nil]
88
+ attr_accessor :timestamp
89
+ # Message type name
90
+ # @return [String, nil]
91
+ attr_accessor :type
92
+ # The user that published the message
93
+ # @return [String, nil]
94
+ attr_accessor :user_id
95
+ # Name of application that generated the message
96
+ # @return [String, nil]
97
+ attr_accessor :app_id
43
98
 
44
- flags |= (1 << 12)
45
- arr << delivery_mode
46
- fmt << "C"
47
- end
99
+ # Encode properties into a byte array
100
+ # @param properties [Hash]
101
+ # @return [String] byte array
102
+ def self.encode(properties)
103
+ return "\x00\x00" if properties.empty?
48
104
 
49
- if priority
50
- priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
51
- flags |= (1 << 11)
52
- arr << priority
53
- fmt << "C"
54
- end
105
+ flags = 0
106
+ arr = [flags]
107
+ fmt = String.new("S>", capacity: 37)
55
108
 
56
- if correlation_id
57
- priority.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
109
+ if (content_type = properties[:content_type])
110
+ content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
58
111
 
59
- flags |= (1 << 10)
60
- arr << correlation_id.bytesize << correlation_id
61
- fmt << "Ca*"
62
- end
112
+ flags |= (1 << 15)
113
+ arr << content_type.bytesize << content_type
114
+ fmt << "Ca*"
115
+ end
63
116
 
64
- if reply_to
65
- reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
117
+ if (content_encoding = properties[:content_encoding])
118
+ content_encoding.is_a?(String) || raise(ArgumentError, "content_encoding must be a string")
66
119
 
67
- flags |= (1 << 9)
68
- arr << reply_to.bytesize << reply_to
69
- fmt << "Ca*"
70
- end
120
+ flags |= (1 << 14)
121
+ arr << content_encoding.bytesize << content_encoding
122
+ fmt << "Ca*"
123
+ end
71
124
 
72
- if expiration
73
- self.expiration = expiration.to_s if expiration.is_a?(Integer)
74
- expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
125
+ if (headers = properties[:headers])
126
+ headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
75
127
 
76
- flags |= (1 << 8)
77
- arr << expiration.bytesize << expiration
78
- fmt << "Ca*"
79
- end
128
+ flags |= (1 << 13)
129
+ tbl = Table.encode(headers)
130
+ arr << tbl.bytesize << tbl
131
+ fmt << "L>a*"
132
+ end
80
133
 
81
- if message_id
82
- message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
134
+ if (delivery_mode = properties[:delivery_mode])
135
+ delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
83
136
 
84
- flags |= (1 << 7)
85
- arr << message_id.bytesize << message_id
86
- fmt << "Ca*"
87
- end
137
+ flags |= (1 << 12)
138
+ arr << delivery_mode
139
+ fmt << "C"
140
+ end
88
141
 
89
- if timestamp
90
- timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
142
+ if (priority = properties[:priority])
143
+ priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
91
144
 
92
- flags |= (1 << 6)
93
- arr << timestamp.to_i
94
- fmt << "Q>"
95
- end
145
+ flags |= (1 << 11)
146
+ arr << priority
147
+ fmt << "C"
148
+ end
96
149
 
97
- if type
98
- type.is_a?(String) || raise(ArgumentError, "type must be a string")
150
+ if (correlation_id = properties[:correlation_id])
151
+ correlation_id.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
99
152
 
100
- flags |= (1 << 5)
101
- arr << type.bytesize << type
102
- fmt << "Ca*"
103
- end
153
+ flags |= (1 << 10)
154
+ arr << correlation_id.bytesize << correlation_id
155
+ fmt << "Ca*"
156
+ end
104
157
 
105
- if user_id
106
- user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
158
+ if (reply_to = properties[:reply_to])
159
+ reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
107
160
 
108
- flags |= (1 << 4)
109
- arr << user_id.bytesize << user_id
110
- fmt << "Ca*"
111
- end
161
+ flags |= (1 << 9)
162
+ arr << reply_to.bytesize << reply_to
163
+ fmt << "Ca*"
164
+ end
112
165
 
113
- if app_id
114
- app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
166
+ if (expiration = properties[:expiration])
167
+ expiration = expiration.to_s if expiration.is_a?(Integer)
168
+ expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
115
169
 
116
- flags |= (1 << 3)
117
- arr << app_id.bytesize << app_id
118
- fmt << "Ca*"
119
- end
170
+ flags |= (1 << 8)
171
+ arr << expiration.bytesize << expiration
172
+ fmt << "Ca*"
173
+ end
120
174
 
121
- arr[0] = flags
122
- arr.pack(fmt)
123
- end
175
+ if (message_id = properties[:message_id])
176
+ message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
124
177
 
125
- def self.decode(bytes)
126
- h = new
127
- flags = bytes.unpack1("S>")
128
- pos = 2
129
- if (flags & 0x8000).positive?
130
- len = bytes[pos].ord
131
- pos += 1
132
- h[:content_type] = bytes.byteslice(pos, len).force_encoding("utf-8")
133
- pos += len
134
- end
135
- if (flags & 0x4000).positive?
136
- len = bytes[pos].ord
137
- pos += 1
138
- h[:content_encoding] = bytes.byteslice(pos, len).force_encoding("utf-8")
139
- pos += len
140
- end
141
- if (flags & 0x2000).positive?
142
- len = bytes.byteslice(pos, 4).unpack1("L>")
143
- pos += 4
144
- h[:headers] = Table.decode(bytes.byteslice(pos, len))
145
- pos += len
146
- end
147
- if (flags & 0x1000).positive?
148
- h[:delivery_mode] = bytes[pos].ord
149
- pos += 1
150
- end
151
- if (flags & 0x0800).positive?
152
- h[:priority] = bytes[pos].ord
153
- pos += 1
154
- end
155
- if (flags & 0x0400).positive?
156
- len = bytes[pos].ord
157
- pos += 1
158
- h[:correlation_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
159
- pos += len
160
- end
161
- if (flags & 0x0200).positive?
162
- len = bytes[pos].ord
163
- pos += 1
164
- h[:reply_to] = bytes.byteslice(pos, len).force_encoding("utf-8")
165
- pos += len
166
- end
167
- if (flags & 0x0100).positive?
168
- len = bytes[pos].ord
169
- pos += 1
170
- h[:expiration] = bytes.byteslice(pos, len).force_encoding("utf-8")
171
- pos += len
172
- end
173
- if (flags & 0x0080).positive?
174
- len = bytes[pos].ord
175
- pos += 1
176
- h[:message_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
177
- pos += len
178
- end
179
- if (flags & 0x0040).positive?
180
- h[:timestamp] = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
181
- pos += 8
182
- end
183
- if (flags & 0x0020).positive?
184
- len = bytes[pos].ord
185
- pos += 1
186
- h[:type] = bytes.byteslice(pos, len).force_encoding("utf-8")
187
- pos += len
188
- end
189
- if (flags & 0x0010).positive?
190
- len = bytes[pos].ord
191
- pos += 1
192
- h[:user_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
193
- pos += len
178
+ flags |= (1 << 7)
179
+ arr << message_id.bytesize << message_id
180
+ fmt << "Ca*"
181
+ end
182
+
183
+ if (timestamp = properties[:timestamp])
184
+ timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
185
+
186
+ flags |= (1 << 6)
187
+ arr << timestamp.to_i
188
+ fmt << "Q>"
189
+ end
190
+
191
+ if (type = properties[:type])
192
+ type.is_a?(String) || raise(ArgumentError, "type must be a string")
193
+
194
+ flags |= (1 << 5)
195
+ arr << type.bytesize << type
196
+ fmt << "Ca*"
197
+ end
198
+
199
+ if (user_id = properties[:user_id])
200
+ user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
201
+
202
+ flags |= (1 << 4)
203
+ arr << user_id.bytesize << user_id
204
+ fmt << "Ca*"
205
+ end
206
+
207
+ if (app_id = properties[:app_id])
208
+ app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
209
+
210
+ flags |= (1 << 3)
211
+ arr << app_id.bytesize << app_id
212
+ fmt << "Ca*"
213
+ end
214
+
215
+ arr[0] = flags
216
+ arr.pack(fmt)
194
217
  end
195
- if (flags & 0x0008).positive?
196
- len = bytes[pos].ord
197
- pos += 1
198
- h[:app_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
218
+
219
+ # Decode a byte array
220
+ # @return [Properties]
221
+ def self.decode(bytes, pos = 0)
222
+ p = new
223
+ flags = bytes.byteslice(pos, 2).unpack1("S>")
224
+ pos += 2
225
+ if (flags & 0x8000).positive?
226
+ len = bytes.getbyte(pos)
227
+ pos += 1
228
+ p.content_type = bytes.byteslice(pos, len).force_encoding("utf-8")
229
+ pos += len
230
+ end
231
+ if (flags & 0x4000).positive?
232
+ len = bytes.getbyte(pos)
233
+ pos += 1
234
+ p.content_encoding = bytes.byteslice(pos, len).force_encoding("utf-8")
235
+ pos += len
236
+ end
237
+ if (flags & 0x2000).positive?
238
+ len = bytes.byteslice(pos, 4).unpack1("L>")
239
+ pos += 4
240
+ p.headers = Table.decode(bytes.byteslice(pos, len))
241
+ pos += len
242
+ end
243
+ if (flags & 0x1000).positive?
244
+ p.delivery_mode = bytes.getbyte(pos)
245
+ pos += 1
246
+ end
247
+ if (flags & 0x0800).positive?
248
+ p.priority = bytes.getbyte(pos)
249
+ pos += 1
250
+ end
251
+ if (flags & 0x0400).positive?
252
+ len = bytes.getbyte(pos)
253
+ pos += 1
254
+ p.correlation_id = bytes.byteslice(pos, len).force_encoding("utf-8")
255
+ pos += len
256
+ end
257
+ if (flags & 0x0200).positive?
258
+ len = bytes.getbyte(pos)
259
+ pos += 1
260
+ p.reply_to = bytes.byteslice(pos, len).force_encoding("utf-8")
261
+ pos += len
262
+ end
263
+ if (flags & 0x0100).positive?
264
+ len = bytes.getbyte(pos)
265
+ pos += 1
266
+ p.expiration = bytes.byteslice(pos, len).force_encoding("utf-8")
267
+ pos += len
268
+ end
269
+ if (flags & 0x0080).positive?
270
+ len = bytes.getbyte(pos)
271
+ pos += 1
272
+ p.message_id = bytes.byteslice(pos, len).force_encoding("utf-8")
273
+ pos += len
274
+ end
275
+ if (flags & 0x0040).positive?
276
+ p.timestamp = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
277
+ pos += 8
278
+ end
279
+ if (flags & 0x0020).positive?
280
+ len = bytes.getbyte(pos)
281
+ pos += 1
282
+ p.type = bytes.byteslice(pos, len).force_encoding("utf-8")
283
+ pos += len
284
+ end
285
+ if (flags & 0x0010).positive?
286
+ len = bytes.getbyte(pos)
287
+ pos += 1
288
+ p.user_id = bytes.byteslice(pos, len).force_encoding("utf-8")
289
+ pos += len
290
+ end
291
+ if (flags & 0x0008).positive?
292
+ len = bytes.getbyte(pos)
293
+ pos += 1
294
+ p.app_id = bytes.byteslice(pos, len).force_encoding("utf-8")
295
+ end
296
+ p
199
297
  end
200
- h
201
298
  end
202
299
  end
203
300
  end