amqp-client 0.1.0 → 0.2.3

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,5 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AMQP
4
- Message = Struct.new(:exchange_name, :routing_key, :properties, :body, :redelivered)
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)
7
+ end
8
+
9
+ def reject(requeue: false)
10
+ channel.basic_reject(delivery_tag, requeue)
11
+ end
12
+ end
13
+
14
+ ReturnMessage = Struct.new(:reply_code, :reply_text, :exchange, :routing_key, :properties, :body)
5
15
  end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./table"
4
+
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*"
21
+ end
22
+
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*"
29
+ end
30
+
31
+ if headers
32
+ headers.is_a?(Hash) || raise(ArgumentError, "headers must be a hash")
33
+
34
+ flags |= (1 << 13)
35
+ tbl = Table.encode(headers)
36
+ arr << tbl.bytesize << tbl
37
+ fmt << "L>a*"
38
+ end
39
+
40
+ if delivery_mode
41
+ headers.is_a?(Integer) || raise(ArgumentError, "delivery_mode must be an int")
42
+
43
+ flags |= (1 << 12)
44
+ arr << delivery_mode
45
+ fmt << "C"
46
+ end
47
+
48
+ if priority
49
+ priority.is_a?(Integer) || raise(ArgumentError, "priority must be an int")
50
+ flags |= (1 << 11)
51
+ arr << priority
52
+ fmt << "C"
53
+ end
54
+
55
+ if correlation_id
56
+ priority.is_a?(String) || raise(ArgumentError, "correlation_id must be a string")
57
+
58
+ flags |= (1 << 10)
59
+ arr << correlation_id.bytesize << correlation_id
60
+ fmt << "Ca*"
61
+ end
62
+
63
+ if reply_to
64
+ reply_to.is_a?(String) || raise(ArgumentError, "reply_to must be a string")
65
+
66
+ flags |= (1 << 9)
67
+ arr << reply_to.bytesize << reply_to
68
+ fmt << "Ca*"
69
+ end
70
+
71
+ if expiration
72
+ expiration.is_a?(String) || raise(ArgumentError, "expiration must be a string")
73
+
74
+ flags |= (1 << 8)
75
+ arr << expiration.bytesize << expiration
76
+ fmt << "Ca*"
77
+ end
78
+
79
+ if message_id
80
+ message_id.is_a?(String) || raise(ArgumentError, "message_id must be a string")
81
+
82
+ flags |= (1 << 7)
83
+ arr << message_id.bytesize << message_id
84
+ fmt << "Ca*"
85
+ end
86
+
87
+ if timestamp
88
+ timestamp.is_a?(Time) || raise(ArgumentError, "timestamp must be a time")
89
+
90
+ flags |= (1 << 6)
91
+ arr << timestamp.to_i
92
+ fmt << "Q>"
93
+ end
94
+
95
+ if type
96
+ type.is_a?(String) || raise(ArgumentError, "type must be a string")
97
+
98
+ flags |= (1 << 5)
99
+ arr << type.bytesize << type
100
+ fmt << "Ca*"
101
+ end
102
+
103
+ if user_id
104
+ user_id.is_a?(String) || raise(ArgumentError, "user_id must be a string")
105
+
106
+ flags |= (1 << 4)
107
+ arr << user_id.bytesize << user_id
108
+ fmt << "Ca*"
109
+ end
110
+
111
+ if app_id
112
+ app_id.is_a?(String) || raise(ArgumentError, "app_id must be a string")
113
+
114
+ flags |= (1 << 3)
115
+ arr << app_id.bytesize << app_id
116
+ fmt << "Ca*"
117
+ end
118
+
119
+ arr[0] = flags
120
+ arr.pack(fmt)
121
+ end
122
+
123
+ def self.decode(bytes)
124
+ h = new
125
+ flags = bytes.unpack1("S>")
126
+ pos = 2
127
+ if (flags & 0x8000).positive?
128
+ len = bytes[pos].ord
129
+ pos += 1
130
+ h[:content_type] = bytes.byteslice(pos, len).force_encoding("utf-8")
131
+ pos += len
132
+ end
133
+ if (flags & 0x4000).positive?
134
+ len = bytes[pos].ord
135
+ pos += 1
136
+ h[:content_encoding] = bytes.byteslice(pos, len).force_encoding("utf-8")
137
+ pos += len
138
+ end
139
+ if (flags & 0x2000).positive?
140
+ len = bytes.byteslice(pos, 4).unpack1("L>")
141
+ pos += 4
142
+ h[:headers] = Table.decode(bytes.byteslice(pos, len))
143
+ pos += len
144
+ end
145
+ if (flags & 0x1000).positive?
146
+ h[:delivery_mode] = bytes[pos].ord
147
+ pos += 1
148
+ end
149
+ if (flags & 0x0800).positive?
150
+ h[:priority] = bytes[pos].ord
151
+ pos += 1
152
+ end
153
+ if (flags & 0x0400).positive?
154
+ len = bytes[pos].ord
155
+ pos += 1
156
+ h[:correlation_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
157
+ pos += len
158
+ end
159
+ if (flags & 0x0200).positive?
160
+ len = bytes[pos].ord
161
+ pos += 1
162
+ h[:reply_to] = bytes.byteslice(pos, len).force_encoding("utf-8")
163
+ pos += len
164
+ end
165
+ if (flags & 0x0100).positive?
166
+ len = bytes[pos].ord
167
+ pos += 1
168
+ h[:expiration] = bytes.byteslice(pos, len).force_encoding("utf-8")
169
+ pos += len
170
+ end
171
+ if (flags & 0x0080).positive?
172
+ len = bytes[pos].ord
173
+ pos += 1
174
+ h[:message_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
175
+ pos += len
176
+ end
177
+ if (flags & 0x0040).positive?
178
+ h[:timestamp] = Time.at(bytes.byteslice(pos, 8).unpack1("Q>"))
179
+ pos += 8
180
+ end
181
+ if (flags & 0x0020).positive?
182
+ len = bytes[pos].ord
183
+ pos += 1
184
+ h[:type] = bytes.byteslice(pos, len).force_encoding("utf-8")
185
+ pos += len
186
+ end
187
+ if (flags & 0x0010).positive?
188
+ len = bytes[pos].ord
189
+ pos += 1
190
+ h[:user_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
191
+ pos += len
192
+ end
193
+ if (flags & 0x0008).positive?
194
+ len = bytes[pos].ord
195
+ pos += 1
196
+ h[:app_id] = bytes.byteslice(pos, len).force_encoding("utf-8")
197
+ end
198
+ h
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AMQP
4
+ # Encode/decode AMQP Tables
5
+ module Table
6
+ module_function
7
+
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*")
13
+ end
14
+ tbl
15
+ end
16
+
17
+ def decode(bytes)
18
+ h = {}
19
+ pos = 0
20
+
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
30
+ end
31
+ h
32
+ end
33
+
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>")
41
+ 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}"
61
+ end
62
+ end
63
+
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
108
+ 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}"
120
+ end
121
+ end
122
+ end
123
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AMQP
4
4
  class Client
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amqp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carl Hörberg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-28 00:00:00.000000000 Z
11
+ date: 2021-08-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Work in progress
14
14
  email:
@@ -23,19 +23,21 @@ files:
23
23
  - ".rubocop_todo.yml"
24
24
  - CHANGELOG.md
25
25
  - Gemfile
26
- - Gemfile.lock
27
26
  - LICENSE.txt
28
27
  - README.md
29
28
  - Rakefile
30
29
  - amqp-client.gemspec
31
30
  - bin/console
32
31
  - bin/setup
32
+ - lib/amqp-client.rb
33
33
  - lib/amqp/client.rb
34
34
  - lib/amqp/client/channel.rb
35
35
  - lib/amqp/client/connection.rb
36
36
  - lib/amqp/client/errors.rb
37
37
  - lib/amqp/client/frames.rb
38
38
  - lib/amqp/client/message.rb
39
+ - lib/amqp/client/properties.rb
40
+ - lib/amqp/client/table.rb
39
41
  - lib/amqp/client/version.rb
40
42
  homepage: https://github.com/cloudamqp/amqp-client.rb
41
43
  licenses:
data/Gemfile.lock DELETED
@@ -1,42 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- amqp-client (0.1.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.4.2)
10
- minitest (5.14.2)
11
- parallel (1.20.1)
12
- parser (3.0.1.0)
13
- ast (~> 2.4.1)
14
- rainbow (3.0.0)
15
- rake (13.0.3)
16
- regexp_parser (2.1.1)
17
- rexml (3.2.4)
18
- rubocop (1.12.1)
19
- parallel (~> 1.10)
20
- parser (>= 3.0.0.0)
21
- rainbow (>= 2.2.2, < 4.0)
22
- regexp_parser (>= 1.8, < 3.0)
23
- rexml
24
- rubocop-ast (>= 1.2.0, < 2.0)
25
- ruby-progressbar (~> 1.7)
26
- unicode-display_width (>= 1.4.0, < 3.0)
27
- rubocop-ast (1.4.1)
28
- parser (>= 2.7.1.5)
29
- ruby-progressbar (1.11.0)
30
- unicode-display_width (2.0.0)
31
-
32
- PLATFORMS
33
- x86_64-linux
34
-
35
- DEPENDENCIES
36
- amqp-client!
37
- minitest (~> 5.0)
38
- rake (~> 13.0)
39
- rubocop (~> 1.7)
40
-
41
- BUNDLED WITH
42
- 2.2.15