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.
@@ -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