firemq 0.1.0-aarch64-linux
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.
- checksums.yaml +7 -0
- data/README.md +46 -0
- data/lib/firemq/firemq_client_ffi.rb +2075 -0
- data/lib/firemq/libfiremq_client_ffi.so +0 -0
- data/lib/firemq.rb +261 -0
- metadata +64 -0
|
Binary file
|
data/lib/firemq.rb
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# FireMQ client for Ruby, built on the Rust client with UniFFI.
|
|
4
|
+
#
|
|
5
|
+
# require "firemq"
|
|
6
|
+
#
|
|
7
|
+
# FireMQ.connect("fire://guest:guest@localhost:5673") do |client|
|
|
8
|
+
# client.declare_queue("jobs")
|
|
9
|
+
# client.publish("", "jobs", "hello", headers: { "attempt" => 1 }, content_type: "text/plain")
|
|
10
|
+
# client.subscribe("jobs", prefetch: 10).each do |msg|
|
|
11
|
+
# puts msg.text
|
|
12
|
+
# msg.ack
|
|
13
|
+
# break
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# The native library is found through FIREMQ_LIB (a path to the file), next to
|
|
18
|
+
# lib/firemq/firemq_client_ffi.rb, or on the system library path.
|
|
19
|
+
|
|
20
|
+
require "json"
|
|
21
|
+
require_relative "firemq/firemq_client_ffi"
|
|
22
|
+
|
|
23
|
+
module FireMQ
|
|
24
|
+
VERSION = "0.1.0"
|
|
25
|
+
DEFAULT_URL = "fire://guest:guest@localhost:5673"
|
|
26
|
+
|
|
27
|
+
# Base class of every FireMQ error. +code+ is the AMQP reply code, or 0.
|
|
28
|
+
class Error < StandardError
|
|
29
|
+
attr_reader :code
|
|
30
|
+
|
|
31
|
+
def initialize(message = nil, code = 0)
|
|
32
|
+
super(message)
|
|
33
|
+
@code = code
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The broker refused a request; +code+ is 403, 404, 406 and so on.
|
|
38
|
+
class ServerError < Error; end
|
|
39
|
+
# A mandatory publish matched no queue.
|
|
40
|
+
class UnroutableError < Error; end
|
|
41
|
+
# The broker rejected a publish, for example because a queue was full.
|
|
42
|
+
class NackedError < Error; end
|
|
43
|
+
# The connection or subscription is closed.
|
|
44
|
+
class ClosedError < Error; end
|
|
45
|
+
# Anything else: I/O failures, invalid URLs or JSON, protocol errors.
|
|
46
|
+
class OtherError < Error; end
|
|
47
|
+
|
|
48
|
+
Native = ::FiremqClientFfi
|
|
49
|
+
NativeError = Native::FireMqError
|
|
50
|
+
|
|
51
|
+
# Property names as the protocol spells them, and as the native record spells them.
|
|
52
|
+
PROPERTY_FIELDS = {
|
|
53
|
+
content_type: :content_type, content_encoding: :content_encoding,
|
|
54
|
+
correlation_id: :correlation_id, reply_to: :reply_to, expiration: :expiration,
|
|
55
|
+
message_id: :message_id, timestamp: :timestamp, type: :message_type,
|
|
56
|
+
user_id: :user_id, app_id: :app_id, priority: :priority
|
|
57
|
+
}.freeze
|
|
58
|
+
|
|
59
|
+
# Runs the block and converts native errors into FireMQ::Error subclasses.
|
|
60
|
+
def self.translate
|
|
61
|
+
yield
|
|
62
|
+
rescue NativeError::Server => e
|
|
63
|
+
raise ServerError.new(e.message, e.code)
|
|
64
|
+
rescue NativeError::Unroutable => e
|
|
65
|
+
raise UnroutableError.new(e.message, 312)
|
|
66
|
+
rescue NativeError::Nacked => e
|
|
67
|
+
raise NackedError.new(e.message, e.code)
|
|
68
|
+
rescue NativeError::Closed => e
|
|
69
|
+
raise ClosedError.new(e.message, 0)
|
|
70
|
+
rescue NativeError::Other => e
|
|
71
|
+
raise OtherError.new(e.message, 0)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.json_or_nil(value)
|
|
75
|
+
value.nil? ? nil : JSON.generate(value)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Connects with a URL such as fire://user:pass@host:5673/vhost. With a block, yields the
|
|
79
|
+
# client and closes it afterwards.
|
|
80
|
+
def self.connect(url = DEFAULT_URL)
|
|
81
|
+
client = Client.new(url)
|
|
82
|
+
return client unless block_given?
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
yield client
|
|
86
|
+
ensure
|
|
87
|
+
client.close unless client.closed?
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# A connection to FireMQ.
|
|
92
|
+
class Client
|
|
93
|
+
def initialize(url = DEFAULT_URL)
|
|
94
|
+
@inner = FireMQ.translate { Native::Client.connect(url) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Closes the connection. Unacknowledged messages return to their queues.
|
|
98
|
+
def close
|
|
99
|
+
FireMQ.translate { @inner.close }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def closed?
|
|
103
|
+
@inner.is_closed
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Declares a queue; an empty name asks the broker to generate one. Returns an object with
|
|
107
|
+
# +name+, +messages+ and +consumers+.
|
|
108
|
+
def declare_queue(name = "", durable: true, exclusive: false, auto_delete: false, arguments: nil)
|
|
109
|
+
FireMQ.translate do
|
|
110
|
+
@inner.declare_queue(name, durable, exclusive, auto_delete, FireMQ.json_or_nil(arguments))
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Deletes a queue and returns how many messages it held.
|
|
115
|
+
def delete_queue(name)
|
|
116
|
+
FireMQ.translate { @inner.delete_queue(name) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Removes every ready message and returns how many were removed.
|
|
120
|
+
def purge_queue(name)
|
|
121
|
+
FireMQ.translate { @inner.purge_queue(name) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def declare_exchange(name, type = "direct", durable: true, auto_delete: false, arguments: nil)
|
|
125
|
+
FireMQ.translate do
|
|
126
|
+
@inner.declare_exchange(name, type, durable, auto_delete, FireMQ.json_or_nil(arguments))
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def delete_exchange(name, if_unused: false)
|
|
131
|
+
FireMQ.translate { @inner.delete_exchange(name, if_unused) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def bind_queue(queue, exchange, routing_key = "", arguments: nil)
|
|
135
|
+
FireMQ.translate do
|
|
136
|
+
@inner.bind_queue(queue, exchange, routing_key, FireMQ.json_or_nil(arguments))
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def unbind_queue(queue, exchange, routing_key = "")
|
|
141
|
+
FireMQ.translate { @inner.unbind_queue(queue, exchange, routing_key) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def bind_exchange(destination, source, routing_key = "")
|
|
145
|
+
FireMQ.translate { @inner.bind_exchange(destination, source, routing_key) }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Publishes a message and, by default, waits for the broker's confirm. Properties use the
|
|
149
|
+
# protocol's names: content_type, correlation_id, type, priority, expiration and so on.
|
|
150
|
+
def publish(exchange, routing_key, body, headers: nil, persistent: true, mandatory: false,
|
|
151
|
+
confirm: true, **properties)
|
|
152
|
+
unknown = properties.keys - PROPERTY_FIELDS.keys
|
|
153
|
+
raise ArgumentError, "unknown message properties: #{unknown.join(', ')}" unless unknown.empty?
|
|
154
|
+
|
|
155
|
+
props = Native::MessageProperties.new(
|
|
156
|
+
**properties.compact.to_h { |k, v| [PROPERTY_FIELDS.fetch(k), v] }
|
|
157
|
+
)
|
|
158
|
+
options = Native::PublishOptions.new(
|
|
159
|
+
persistent: persistent, mandatory: mandatory, confirm: confirm,
|
|
160
|
+
properties: props, headers_json: FireMQ.json_or_nil(headers)
|
|
161
|
+
)
|
|
162
|
+
FireMQ.translate { @inner.publish(exchange, routing_key, body.to_s.b, options) }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Starts consuming. +prefetch+ caps unacknowledged deliveries.
|
|
166
|
+
def subscribe(queue, prefetch: 100, no_ack: false)
|
|
167
|
+
Subscription.new(FireMQ.translate { @inner.subscribe(queue, prefetch, no_ack) })
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Takes one message from a queue, or returns nil when it is empty.
|
|
171
|
+
def get(queue, no_ack: false)
|
|
172
|
+
d = FireMQ.translate { @inner.get(queue, no_ack) }
|
|
173
|
+
d && Message.new(d)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Messages from a queue.
|
|
178
|
+
class Subscription
|
|
179
|
+
include Enumerable
|
|
180
|
+
|
|
181
|
+
def initialize(inner)
|
|
182
|
+
@inner = inner
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Waits up to +timeout+ seconds for a message and returns nil on timeout. Raises
|
|
186
|
+
# ClosedError once the subscription has ended.
|
|
187
|
+
def next(timeout: 1.0)
|
|
188
|
+
d = FireMQ.translate { @inner.next((timeout * 1000).to_i) }
|
|
189
|
+
d && Message.new(d)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Yields messages until the subscription ends.
|
|
193
|
+
def each
|
|
194
|
+
return enum_for(:each) unless block_given?
|
|
195
|
+
|
|
196
|
+
loop do
|
|
197
|
+
msg = self.next(timeout: 0.5)
|
|
198
|
+
yield msg if msg
|
|
199
|
+
rescue ClosedError
|
|
200
|
+
break
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Stops the subscription. Messages already received can still be acknowledged.
|
|
205
|
+
def cancel
|
|
206
|
+
FireMQ.translate { @inner.cancel }
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# A delivered message.
|
|
211
|
+
class Message
|
|
212
|
+
def initialize(delivery)
|
|
213
|
+
@d = delivery
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# The body as a binary String.
|
|
217
|
+
def body
|
|
218
|
+
@body ||= @d.body.b
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# The body as UTF-8 text.
|
|
222
|
+
def text
|
|
223
|
+
body.dup.force_encoding(Encoding::UTF_8)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def json
|
|
227
|
+
JSON.parse(body)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def exchange = @d.exchange
|
|
231
|
+
def routing_key = @d.routing_key
|
|
232
|
+
def redelivered? = @d.redelivered
|
|
233
|
+
def delivery_tag = @d.delivery_tag
|
|
234
|
+
|
|
235
|
+
# Application headers. Binary values arrive base64-encoded.
|
|
236
|
+
def headers
|
|
237
|
+
@headers ||= JSON.parse(@d.headers_json)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# The properties that are set, keyed by their protocol names as strings.
|
|
241
|
+
def properties
|
|
242
|
+
@properties ||= begin
|
|
243
|
+
p = @d.properties
|
|
244
|
+
PROPERTY_FIELDS.each_with_object({}) do |(name, field), h|
|
|
245
|
+
v = p.public_send(field)
|
|
246
|
+
h[name.to_s] = v unless v.nil?
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def ack
|
|
252
|
+
FireMQ.translate { @d.ack }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Rejects the message. With +requeue+ it is delivered again; otherwise it is
|
|
256
|
+
# dead-lettered when the queue has a dead-letter exchange, and dropped if not.
|
|
257
|
+
def nack(requeue: true)
|
|
258
|
+
FireMQ.translate { @d.nack(requeue) }
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: firemq
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: aarch64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- The FireMQ Authors
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-11-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: ffi
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.16'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.16'
|
|
27
|
+
description: UniFFI bindings over firemq-client, the Rust client for the FireMQ message
|
|
28
|
+
broker.
|
|
29
|
+
email:
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- README.md
|
|
35
|
+
- lib/firemq.rb
|
|
36
|
+
- lib/firemq/firemq_client_ffi.rb
|
|
37
|
+
- lib/firemq/libfiremq_client_ffi.so
|
|
38
|
+
homepage: https://github.com/FireMQ/firemq
|
|
39
|
+
licenses:
|
|
40
|
+
- Apache-2.0
|
|
41
|
+
metadata:
|
|
42
|
+
source_code_uri: https://github.com/FireMQ/firemq/tree/main/bindings/ruby
|
|
43
|
+
changelog_uri: https://github.com/FireMQ/firemq/blob/main/CHANGELOG.md
|
|
44
|
+
bug_tracker_uri: https://github.com/FireMQ/firemq/issues
|
|
45
|
+
post_install_message:
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.1'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubygems_version: 3.5.22
|
|
61
|
+
signing_key:
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: Ruby client for FireMQ, built on the Rust client
|
|
64
|
+
test_files: []
|