amqp-client 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.0"
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.0
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,7 +23,6 @@ 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
@@ -36,6 +35,8 @@ files:
36
35
  - lib/amqp/client/errors.rb
37
36
  - lib/amqp/client/frames.rb
38
37
  - lib/amqp/client/message.rb
38
+ - lib/amqp/client/properties.rb
39
+ - lib/amqp/client/table.rb
39
40
  - lib/amqp/client/version.rb
40
41
  homepage: https://github.com/cloudamqp/amqp-client.rb
41
42
  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