amqp-client 1.0.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +2 -2
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +29 -12
- data/CHANGELOG.md +21 -0
- data/Gemfile +4 -0
- data/README.md +63 -28
- data/Rakefile +3 -1
- data/amqp-client.gemspec +1 -1
- data/lib/amqp/client/channel.rb +39 -32
- data/lib/amqp/client/connection.rb +107 -64
- data/lib/amqp/client/errors.rb +18 -1
- data/lib/amqp/client/{frames.rb → frame_bytes.rb} +34 -36
- data/lib/amqp/client/message.rb +101 -44
- data/lib/amqp/client/properties.rb +143 -76
- data/lib/amqp/client/queue.rb +15 -21
- data/lib/amqp/client/table.rb +51 -32
- data/lib/amqp/client/version.rb +1 -1
- data/lib/amqp/client.rb +27 -9
- data/sig/amqp-client.rbs +264 -0
- metadata +5 -4
@@ -5,44 +5,111 @@ require_relative "./table"
|
|
5
5
|
module AMQP
|
6
6
|
class Client
|
7
7
|
# Encode/decode AMQP Properties
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
8
|
+
class Properties
|
9
|
+
# rubocop:disable Metrics/ParameterLists
|
10
|
+
def initialize(content_type: nil, content_encoding: nil, headers: nil, delivery_mode: nil,
|
11
|
+
priority: nil, correlation_id: nil, reply_to: nil, expiration: nil,
|
12
|
+
message_id: nil, timestamp: nil, type: nil, user_id: nil, app_id: nil)
|
13
|
+
@content_type = content_type
|
14
|
+
@content_encoding = content_encoding
|
15
|
+
@headers = headers
|
16
|
+
@delivery_mode = delivery_mode
|
17
|
+
@priority = priority
|
18
|
+
@correlation_id = correlation_id
|
19
|
+
@reply_to = reply_to
|
20
|
+
@expiration = expiration
|
21
|
+
@message_id = message_id
|
22
|
+
@timestamp = timestamp
|
23
|
+
@type = type
|
24
|
+
@user_id = user_id
|
25
|
+
@app_id = app_id
|
26
|
+
end
|
27
|
+
# rubocop:enable Metrics/ParameterLists
|
28
|
+
|
29
|
+
# Properties as a Hash
|
30
|
+
# @return [Hash] Properties
|
31
|
+
def to_h
|
32
|
+
{
|
33
|
+
content_type: content_type,
|
34
|
+
content_encoding: content_encoding,
|
35
|
+
headers: headers,
|
36
|
+
delivery_mode: delivery_mode,
|
37
|
+
priority: priority,
|
38
|
+
correlation_id: correlation_id,
|
39
|
+
reply_to: reply_to,
|
40
|
+
expiration: expiration,
|
41
|
+
message_id: message_id,
|
42
|
+
timestamp: timestamp,
|
43
|
+
type: type,
|
44
|
+
user_id: user_id,
|
45
|
+
app_id: app_id
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
alias to_hash to_h
|
50
|
+
|
51
|
+
# Returns true if two Property objects holds the same information
|
52
|
+
# @return [Boolean]
|
53
|
+
def ==(other)
|
54
|
+
return false unless other.is_a? self.class
|
55
|
+
|
56
|
+
instance_variables.all? { |v| instance_variable_get(v) == other.instance_variable_get(v) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Content type of the message body
|
60
|
+
# @return [String, nil]
|
61
|
+
attr_accessor :content_type
|
62
|
+
# Content encoding of the body
|
63
|
+
# @return [String, nil]
|
64
|
+
attr_accessor :content_encoding
|
65
|
+
# Headers, for applications and header exchange routing
|
66
|
+
# @return [Hash<String, Object>, nil]
|
67
|
+
attr_accessor :headers
|
68
|
+
# Message persistent level
|
69
|
+
# @note The exchange and queue have to durable as well for the message to be persistent
|
70
|
+
# @return [1] Transient message
|
71
|
+
# @return [2] Persistent message
|
72
|
+
# @return [nil] Not specified (implicitly transient)
|
73
|
+
attr_accessor :delivery_mode
|
74
|
+
# A priority of the message (between 0 and 255)
|
75
|
+
# @return [Integer, nil]
|
76
|
+
attr_accessor :priority
|
77
|
+
# Message correlation id, commonly used to correlate RPC requests and responses
|
78
|
+
# @return [String, nil]
|
79
|
+
attr_accessor :correlation_id
|
80
|
+
# Queue to reply RPC responses to
|
81
|
+
# @return [String, nil]
|
82
|
+
attr_accessor :reply_to
|
83
|
+
# Number of seconds the message will stay in the queue
|
84
|
+
# @return [String, nil]
|
85
|
+
attr_accessor :expiration
|
86
|
+
# Application message identifier
|
87
|
+
# @return [String, nil]
|
88
|
+
attr_accessor :message_id
|
89
|
+
# Message timestamp, often indicates when the message was originally generated
|
90
|
+
# @return [Date, nil]
|
91
|
+
attr_accessor :timestamp
|
92
|
+
# Message type name
|
93
|
+
# @return [String, nil]
|
94
|
+
attr_accessor :type
|
95
|
+
# The user that published the message
|
96
|
+
# @return [String, nil]
|
97
|
+
attr_accessor :user_id
|
98
|
+
# Name of application that generated the message
|
99
|
+
# @return [String, nil]
|
100
|
+
attr_accessor :app_id
|
101
|
+
|
37
102
|
# Encode properties into a byte array
|
103
|
+
# @param properties [Hash]
|
38
104
|
# @return [String] byte array
|
39
|
-
def encode
|
105
|
+
def self.encode(properties)
|
106
|
+
return "\x00\x00" if properties.empty?
|
107
|
+
|
40
108
|
flags = 0
|
41
109
|
arr = [flags]
|
42
|
-
fmt =
|
43
|
-
fmt.pos = 2
|
110
|
+
fmt = String.new("S>", capacity: 37)
|
44
111
|
|
45
|
-
if content_type
|
112
|
+
if (content_type = properties[:content_type])
|
46
113
|
content_type.is_a?(String) || raise(ArgumentError, "content_type must be a string")
|
47
114
|
|
48
115
|
flags |= (1 << 15)
|
@@ -50,7 +117,7 @@ module AMQP
|
|
50
117
|
fmt << "Ca*"
|
51
118
|
end
|
52
119
|
|
53
|
-
if content_encoding
|
120
|
+
if (content_encoding = properties[:content_encoding])
|
54
121
|
content_encoding.is_a?(String) || raise(ArgumentError, "content_encoding must be a string")
|
55
122
|
|
56
123
|
flags |= (1 << 14)
|
@@ -58,7 +125,7 @@ module AMQP
|
|
58
125
|
fmt << "Ca*"
|
59
126
|
end
|
60
127
|
|
61
|
-
if headers
|
128
|
+
if (headers = properties[:headers])
|
62
129
|
headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
|
63
130
|
|
64
131
|
flags |= (1 << 13)
|
@@ -67,31 +134,31 @@ module AMQP
|
|
67
134
|
fmt << "L>a*"
|
68
135
|
end
|
69
136
|
|
70
|
-
if delivery_mode
|
137
|
+
if (delivery_mode = properties[:delivery_mode])
|
71
138
|
delivery_mode.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
|
72
|
-
delivery_mode.between?(0, 2) || raise(ArgumentError, "delivery_mode must be be between 0 and 2")
|
73
139
|
|
74
140
|
flags |= (1 << 12)
|
75
141
|
arr << delivery_mode
|
76
142
|
fmt << "C"
|
77
143
|
end
|
78
144
|
|
79
|
-
if priority
|
145
|
+
if (priority = properties[:priority])
|
80
146
|
priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
|
147
|
+
|
81
148
|
flags |= (1 << 11)
|
82
149
|
arr << priority
|
83
150
|
fmt << "C"
|
84
151
|
end
|
85
152
|
|
86
|
-
if correlation_id
|
87
|
-
|
153
|
+
if (correlation_id = properties[:correlation_id])
|
154
|
+
correlation_id.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
|
88
155
|
|
89
156
|
flags |= (1 << 10)
|
90
157
|
arr << correlation_id.bytesize << correlation_id
|
91
158
|
fmt << "Ca*"
|
92
159
|
end
|
93
160
|
|
94
|
-
if reply_to
|
161
|
+
if (reply_to = properties[:reply_to])
|
95
162
|
reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
|
96
163
|
|
97
164
|
flags |= (1 << 9)
|
@@ -99,8 +166,8 @@ module AMQP
|
|
99
166
|
fmt << "Ca*"
|
100
167
|
end
|
101
168
|
|
102
|
-
if expiration
|
103
|
-
|
169
|
+
if (expiration = properties[:expiration])
|
170
|
+
expiration = expiration.to_s if expiration.is_a?(Integer)
|
104
171
|
expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string or integer")
|
105
172
|
|
106
173
|
flags |= (1 << 8)
|
@@ -108,7 +175,7 @@ module AMQP
|
|
108
175
|
fmt << "Ca*"
|
109
176
|
end
|
110
177
|
|
111
|
-
if message_id
|
178
|
+
if (message_id = properties[:message_id])
|
112
179
|
message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
|
113
180
|
|
114
181
|
flags |= (1 << 7)
|
@@ -116,7 +183,7 @@ module AMQP
|
|
116
183
|
fmt << "Ca*"
|
117
184
|
end
|
118
185
|
|
119
|
-
if timestamp
|
186
|
+
if (timestamp = properties[:timestamp])
|
120
187
|
timestamp.is_a?(Integer) || timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be an Integer or a Time")
|
121
188
|
|
122
189
|
flags |= (1 << 6)
|
@@ -124,7 +191,7 @@ module AMQP
|
|
124
191
|
fmt << "Q>"
|
125
192
|
end
|
126
193
|
|
127
|
-
if type
|
194
|
+
if (type = properties[:type])
|
128
195
|
type.is_a?(String) || raise(ArgumentError, "type must be a string")
|
129
196
|
|
130
197
|
flags |= (1 << 5)
|
@@ -132,7 +199,7 @@ module AMQP
|
|
132
199
|
fmt << "Ca*"
|
133
200
|
end
|
134
201
|
|
135
|
-
if user_id
|
202
|
+
if (user_id = properties[:user_id])
|
136
203
|
user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
|
137
204
|
|
138
205
|
flags |= (1 << 4)
|
@@ -140,7 +207,7 @@ module AMQP
|
|
140
207
|
fmt << "Ca*"
|
141
208
|
end
|
142
209
|
|
143
|
-
if app_id
|
210
|
+
if (app_id = properties[:app_id])
|
144
211
|
app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
|
145
212
|
|
146
213
|
flags |= (1 << 3)
|
@@ -149,87 +216,87 @@ module AMQP
|
|
149
216
|
end
|
150
217
|
|
151
218
|
arr[0] = flags
|
152
|
-
arr.pack(fmt
|
219
|
+
arr.pack(fmt)
|
153
220
|
end
|
154
221
|
|
155
222
|
# Decode a byte array
|
156
223
|
# @return [Properties]
|
157
|
-
def self.decode(bytes)
|
158
|
-
|
159
|
-
flags = bytes.unpack1("S>")
|
160
|
-
pos
|
224
|
+
def self.decode(bytes, pos = 0)
|
225
|
+
p = new
|
226
|
+
flags = bytes.byteslice(pos, 2).unpack1("S>")
|
227
|
+
pos += 2
|
161
228
|
if (flags & 0x8000).positive?
|
162
|
-
len = bytes
|
229
|
+
len = bytes.getbyte(pos)
|
163
230
|
pos += 1
|
164
|
-
|
231
|
+
p.content_type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
165
232
|
pos += len
|
166
233
|
end
|
167
234
|
if (flags & 0x4000).positive?
|
168
|
-
len = bytes
|
235
|
+
len = bytes.getbyte(pos)
|
169
236
|
pos += 1
|
170
|
-
|
237
|
+
p.content_encoding = bytes.byteslice(pos, len).force_encoding("utf-8")
|
171
238
|
pos += len
|
172
239
|
end
|
173
240
|
if (flags & 0x2000).positive?
|
174
241
|
len = bytes.byteslice(pos, 4).unpack1("L>")
|
175
242
|
pos += 4
|
176
|
-
|
243
|
+
p.headers = Table.decode(bytes.byteslice(pos, len))
|
177
244
|
pos += len
|
178
245
|
end
|
179
246
|
if (flags & 0x1000).positive?
|
180
|
-
|
247
|
+
p.delivery_mode = bytes.getbyte(pos)
|
181
248
|
pos += 1
|
182
249
|
end
|
183
250
|
if (flags & 0x0800).positive?
|
184
|
-
|
251
|
+
p.priority = bytes.getbyte(pos)
|
185
252
|
pos += 1
|
186
253
|
end
|
187
254
|
if (flags & 0x0400).positive?
|
188
|
-
len = bytes
|
255
|
+
len = bytes.getbyte(pos)
|
189
256
|
pos += 1
|
190
|
-
|
257
|
+
p.correlation_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
191
258
|
pos += len
|
192
259
|
end
|
193
260
|
if (flags & 0x0200).positive?
|
194
|
-
len = bytes
|
261
|
+
len = bytes.getbyte(pos)
|
195
262
|
pos += 1
|
196
|
-
|
263
|
+
p.reply_to = bytes.byteslice(pos, len).force_encoding("utf-8")
|
197
264
|
pos += len
|
198
265
|
end
|
199
266
|
if (flags & 0x0100).positive?
|
200
|
-
len = bytes
|
267
|
+
len = bytes.getbyte(pos)
|
201
268
|
pos += 1
|
202
|
-
|
269
|
+
p.expiration = bytes.byteslice(pos, len).force_encoding("utf-8")
|
203
270
|
pos += len
|
204
271
|
end
|
205
272
|
if (flags & 0x0080).positive?
|
206
|
-
len = bytes
|
273
|
+
len = bytes.getbyte(pos)
|
207
274
|
pos += 1
|
208
|
-
|
275
|
+
p.message_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
209
276
|
pos += len
|
210
277
|
end
|
211
278
|
if (flags & 0x0040).positive?
|
212
|
-
|
279
|
+
p.timestamp = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
|
213
280
|
pos += 8
|
214
281
|
end
|
215
282
|
if (flags & 0x0020).positive?
|
216
|
-
len = bytes
|
283
|
+
len = bytes.getbyte(pos)
|
217
284
|
pos += 1
|
218
|
-
|
285
|
+
p.type = bytes.byteslice(pos, len).force_encoding("utf-8")
|
219
286
|
pos += len
|
220
287
|
end
|
221
288
|
if (flags & 0x0010).positive?
|
222
|
-
len = bytes
|
289
|
+
len = bytes.getbyte(pos)
|
223
290
|
pos += 1
|
224
|
-
|
291
|
+
p.user_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
225
292
|
pos += len
|
226
293
|
end
|
227
294
|
if (flags & 0x0008).positive?
|
228
|
-
len = bytes
|
295
|
+
len = bytes.getbyte(pos)
|
229
296
|
pos += 1
|
230
|
-
|
297
|
+
p.app_id = bytes.byteslice(pos, len).force_encoding("utf-8")
|
231
298
|
end
|
232
|
-
|
299
|
+
p
|
233
300
|
end
|
234
301
|
end
|
235
302
|
end
|
data/lib/amqp/client/queue.rb
CHANGED
@@ -11,30 +11,24 @@ module AMQP
|
|
11
11
|
@name = name
|
12
12
|
end
|
13
13
|
|
14
|
-
# Publish to the queue
|
15
|
-
# @param
|
16
|
-
# @
|
17
|
-
# @
|
18
|
-
# @
|
19
|
-
# @option properties [Hash<String, Object>] headers Custom headers
|
20
|
-
# @option properties [Integer] delivery_mode 2 for persisted message, transient messages for all other values
|
21
|
-
# @option properties [Integer] priority A priority of the message (between 0 and 255)
|
22
|
-
# @option properties [Integer] correlation_id A correlation id, most often used used for RPC communication
|
23
|
-
# @option properties [String] reply_to Queue to reply RPC responses to
|
24
|
-
# @option properties [Integer, String] expiration Number of seconds the message will stay in the queue
|
25
|
-
# @option properties [String] message_id Can be used to uniquely identify the message, e.g. for deduplication
|
26
|
-
# @option properties [Date] timestamp Often used for the time the message was originally generated
|
27
|
-
# @option properties [String] type Can indicate what kind of message this is
|
28
|
-
# @option properties [String] user_id Can be used to verify that this is the user that published the message
|
29
|
-
# @option properties [String] app_id Can be used to indicates which app that generated the message
|
30
|
-
# @return [Queue] self
|
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]
|
31
19
|
def publish(body, **properties)
|
32
20
|
@client.publish(body, "", @name, **properties)
|
33
21
|
self
|
34
22
|
end
|
35
23
|
|
36
24
|
# Subscribe/consume from the queue
|
37
|
-
# @
|
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]
|
38
32
|
def subscribe(no_ack: false, prefetch: 1, worker_threads: 1, arguments: {}, &blk)
|
39
33
|
@client.subscribe(@name, no_ack: no_ack, prefetch: prefetch, worker_threads: worker_threads, arguments: arguments, &blk)
|
40
34
|
self
|
@@ -44,7 +38,7 @@ module AMQP
|
|
44
38
|
# @param exchange [String] Name of the exchange to bind to
|
45
39
|
# @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
|
46
40
|
# @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
|
47
|
-
# @return [
|
41
|
+
# @return [self]
|
48
42
|
def bind(exchange, binding_key, arguments: {})
|
49
43
|
@client.bind(@name, exchange, binding_key, arguments: arguments)
|
50
44
|
self
|
@@ -54,14 +48,14 @@ module AMQP
|
|
54
48
|
# @param exchange [String] Name of the exchange to unbind from
|
55
49
|
# @param binding_key [String] Binding key which the queue is bound to the exchange with
|
56
50
|
# @param arguments [Hash] Arguments matching the binding that's being removed
|
57
|
-
# @return [
|
51
|
+
# @return [self]
|
58
52
|
def unbind(exchange, binding_key, arguments: {})
|
59
53
|
@client.unbind(@name, exchange, binding_key, arguments: arguments)
|
60
54
|
self
|
61
55
|
end
|
62
56
|
|
63
57
|
# Purge/empty the queue
|
64
|
-
# @return [
|
58
|
+
# @return [self]
|
65
59
|
def purge
|
66
60
|
@client.purge(@name)
|
67
61
|
self
|
data/lib/amqp/client/table.rb
CHANGED
@@ -5,32 +5,34 @@ module AMQP
|
|
5
5
|
# Encode and decode an AMQP table to/from hash, only used internally
|
6
6
|
# @api private
|
7
7
|
module Table
|
8
|
-
module_function
|
9
|
-
|
10
8
|
# Encodes a hash into a byte array
|
9
|
+
# @param hash [Hash]
|
11
10
|
# @return [String] Byte array
|
12
|
-
def encode(hash)
|
13
|
-
|
14
|
-
|
11
|
+
def self.encode(hash)
|
12
|
+
return "" if hash.empty?
|
13
|
+
|
14
|
+
arr = []
|
15
|
+
fmt = String.new
|
16
|
+
hash.each do |k, value|
|
15
17
|
key = k.to_s
|
16
|
-
|
17
|
-
|
18
|
+
arr.push(key.bytesize, key)
|
19
|
+
fmt << "Ca*"
|
20
|
+
encode_field(value, arr, fmt)
|
18
21
|
end
|
19
|
-
|
22
|
+
arr.pack(fmt)
|
20
23
|
end
|
21
24
|
|
22
25
|
# Decodes an AMQP table into a hash
|
23
26
|
# @return [Hash<String, Object>]
|
24
|
-
def decode(bytes)
|
27
|
+
def self.decode(bytes)
|
25
28
|
hash = {}
|
26
29
|
pos = 0
|
27
30
|
while pos < bytes.bytesize
|
28
|
-
key_len = bytes
|
31
|
+
key_len = bytes.getbyte(pos)
|
29
32
|
pos += 1
|
30
33
|
key = bytes.byteslice(pos, key_len).force_encoding("utf-8")
|
31
34
|
pos += key_len
|
32
|
-
|
33
|
-
len, value = decode_field(rest)
|
35
|
+
len, value = decode_field(bytes, pos)
|
34
36
|
pos += len + 1
|
35
37
|
hash[key] = value
|
36
38
|
end
|
@@ -38,43 +40,58 @@ module AMQP
|
|
38
40
|
end
|
39
41
|
|
40
42
|
# Encoding a single value in a table
|
43
|
+
# @return [nil]
|
41
44
|
# @api private
|
42
|
-
def encode_field(value)
|
45
|
+
def self.encode_field(value, arr, fmt)
|
43
46
|
case value
|
44
47
|
when Integer
|
45
48
|
if value > 2**31
|
46
|
-
|
49
|
+
arr.push("l", value)
|
50
|
+
fmt << "aq>"
|
47
51
|
else
|
48
|
-
|
52
|
+
arr.push("I", value)
|
53
|
+
fmt << "al>"
|
49
54
|
end
|
50
55
|
when Float
|
51
|
-
|
56
|
+
arr.push("d", value)
|
57
|
+
fmt << "aG"
|
52
58
|
when String
|
53
|
-
|
59
|
+
arr.push("S", value.bytesize, value)
|
60
|
+
fmt << "aL>a*"
|
54
61
|
when Time
|
55
|
-
|
62
|
+
arr.push("T", value.to_i)
|
63
|
+
fmt << "aQ>"
|
56
64
|
when Array
|
57
|
-
|
58
|
-
|
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*"
|
59
71
|
when Hash
|
60
72
|
bytes = Table.encode(value)
|
61
|
-
|
73
|
+
arr.push("F", bytes.bytesize, bytes)
|
74
|
+
fmt << "aL>a*"
|
62
75
|
when true
|
63
|
-
|
76
|
+
arr.push("t", 1)
|
77
|
+
fmt << "aC"
|
64
78
|
when false
|
65
|
-
|
79
|
+
arr.push("t", 0)
|
80
|
+
fmt << "aC"
|
66
81
|
when nil
|
67
|
-
|
82
|
+
arr << "V"
|
83
|
+
fmt << "a"
|
68
84
|
else raise ArgumentError, "unsupported table field type: #{value.class}"
|
69
85
|
end
|
86
|
+
nil
|
70
87
|
end
|
71
88
|
|
72
89
|
# Decodes a single value
|
73
|
-
# @return [Array<Integer, Object>] Bytes read and the parsed
|
90
|
+
# @return [Array<Integer, Object>] Bytes read and the parsed value
|
74
91
|
# @api private
|
75
|
-
def decode_field(bytes)
|
76
|
-
type = bytes[
|
77
|
-
pos
|
92
|
+
def self.decode_field(bytes, pos)
|
93
|
+
type = bytes[pos]
|
94
|
+
pos += 1
|
78
95
|
case type
|
79
96
|
when "S"
|
80
97
|
len = bytes.byteslice(pos, 4).unpack1("L>")
|
@@ -86,15 +103,17 @@ module AMQP
|
|
86
103
|
[4 + len, decode(bytes.byteslice(pos, len))]
|
87
104
|
when "A"
|
88
105
|
len = bytes.byteslice(pos, 4).unpack1("L>")
|
106
|
+
pos += 4
|
107
|
+
array_end = pos + len
|
89
108
|
a = []
|
90
|
-
while pos <
|
91
|
-
length, value = decode_field(bytes
|
109
|
+
while pos < array_end
|
110
|
+
length, value = decode_field(bytes, pos)
|
92
111
|
pos += length + 1
|
93
112
|
a << value
|
94
113
|
end
|
95
114
|
[4 + len, a]
|
96
115
|
when "t"
|
97
|
-
[1, bytes
|
116
|
+
[1, bytes.getbyte(pos) == 1]
|
98
117
|
when "b"
|
99
118
|
[1, bytes.byteslice(pos, 1).unpack1("c")]
|
100
119
|
when "B"
|
@@ -114,7 +133,7 @@ module AMQP
|
|
114
133
|
when "d"
|
115
134
|
[8, bytes.byteslice(pos, 8).unpack1("G")]
|
116
135
|
when "D"
|
117
|
-
scale = bytes
|
136
|
+
scale = bytes.getbyte(pos)
|
118
137
|
pos += 1
|
119
138
|
value = bytes.byteslice(pos, 4).unpack1("L>")
|
120
139
|
d = value / 10**scale
|