prosody 0.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.cargo/config.toml +2 -0
  3. data/.release-please-manifest.json +3 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/.standard.yml +9 -0
  7. data/.taplo.toml +6 -0
  8. data/ARCHITECTURE.md +591 -0
  9. data/CHANGELOG.md +92 -0
  10. data/Cargo.lock +3513 -0
  11. data/Cargo.toml +77 -0
  12. data/LICENSE +21 -0
  13. data/Makefile +36 -0
  14. data/README.md +946 -0
  15. data/Rakefile +26 -0
  16. data/ext/prosody/Cargo.toml +38 -0
  17. data/ext/prosody/extconf.rb +6 -0
  18. data/ext/prosody/src/admin.rs +171 -0
  19. data/ext/prosody/src/bridge/callback.rs +60 -0
  20. data/ext/prosody/src/bridge/mod.rs +332 -0
  21. data/ext/prosody/src/client/config.rs +819 -0
  22. data/ext/prosody/src/client/mod.rs +379 -0
  23. data/ext/prosody/src/gvl.rs +149 -0
  24. data/ext/prosody/src/handler/context.rs +436 -0
  25. data/ext/prosody/src/handler/message.rs +144 -0
  26. data/ext/prosody/src/handler/mod.rs +338 -0
  27. data/ext/prosody/src/handler/trigger.rs +93 -0
  28. data/ext/prosody/src/lib.rs +82 -0
  29. data/ext/prosody/src/logging.rs +353 -0
  30. data/ext/prosody/src/scheduler/cancellation.rs +67 -0
  31. data/ext/prosody/src/scheduler/handle.rs +50 -0
  32. data/ext/prosody/src/scheduler/mod.rs +169 -0
  33. data/ext/prosody/src/scheduler/processor.rs +166 -0
  34. data/ext/prosody/src/scheduler/result.rs +197 -0
  35. data/ext/prosody/src/tracing_util.rs +56 -0
  36. data/ext/prosody/src/util.rs +219 -0
  37. data/lib/prosody/configuration.rb +333 -0
  38. data/lib/prosody/handler.rb +177 -0
  39. data/lib/prosody/native_stubs.rb +417 -0
  40. data/lib/prosody/processor.rb +321 -0
  41. data/lib/prosody/sentry.rb +36 -0
  42. data/lib/prosody/version.rb +10 -0
  43. data/lib/prosody.rb +42 -0
  44. data/release-please-config.json +10 -0
  45. data/sig/configuration.rbs +252 -0
  46. data/sig/handler.rbs +79 -0
  47. data/sig/processor.rbs +100 -0
  48. data/sig/prosody.rbs +171 -0
  49. data/sig/version.rbs +9 -0
  50. metadata +193 -0
@@ -0,0 +1,436 @@
1
+ //! Ruby wrapper for the Prosody message context.
2
+ //!
3
+ //! This module provides a Ruby-compatible wrapper around the Prosody library's
4
+ //! `MessageContext`, allowing Ruby code to interact with message context
5
+ //! information from Kafka messages and schedule timer events.
6
+
7
+ use crate::bridge::Bridge;
8
+ use crate::tracing_util::extract_opentelemetry_context;
9
+ use crate::{ROOT_MOD, id};
10
+ use educe::Educe;
11
+ use magnus::value::ReprValue;
12
+ use magnus::{Error, Module, RClass, Ruby, Value, method};
13
+ use opentelemetry::propagation::TextMapCompositePropagator;
14
+ use prosody::consumer::event_context::BoxEventContext;
15
+ use prosody::timers::TimerType;
16
+ use prosody::timers::datetime::CompactDateTime;
17
+ use std::sync::Arc;
18
+ use tracing::{Span, debug, info_span};
19
+ use tracing_opentelemetry::OpenTelemetrySpanExt;
20
+
21
+ /// Nanosecond threshold for rounding Ruby Time objects to the nearest second.
22
+ /// At 0.5 seconds, times round up; below 0.5 seconds, they round down.
23
+ const NANOSECOND_ROUNDING_THRESHOLD: u32 = 500_000_000;
24
+
25
+ /// Ruby wrapper for a Kafka message context.
26
+ ///
27
+ /// This struct wraps the native Prosody `MessageContext` and exposes it to Ruby
28
+ /// code as the `Prosody::Context` class. The context contains metadata and
29
+ /// control capabilities related to the processing of a Kafka message.
30
+ #[derive(Educe, Clone)]
31
+ #[educe(Debug)]
32
+ #[magnus::wrap(class = "Prosody::Context")]
33
+ pub struct Context {
34
+ /// The underlying Prosody message context.
35
+ ///
36
+ /// This field is marked as hidden in debug output to prevent logging large
37
+ /// data.
38
+ #[allow(dead_code)]
39
+ #[educe(Debug(ignore))]
40
+ inner: BoxEventContext,
41
+
42
+ /// Bridge for handling async operations
43
+ #[educe(Debug(ignore))]
44
+ bridge: Bridge,
45
+
46
+ /// OpenTelemetry propagator for distributed tracing
47
+ #[educe(Debug(ignore))]
48
+ propagator: Arc<TextMapCompositePropagator>,
49
+ }
50
+
51
+ impl Context {
52
+ /// Creates a new `Context` from a Prosody `EventContext` and Bridge.
53
+ ///
54
+ /// # Arguments
55
+ ///
56
+ /// * `inner` - The Prosody event context to wrap
57
+ /// * `bridge` - The bridge for handling async operations
58
+ /// * `propagator` - Shared OpenTelemetry propagator for distributed tracing
59
+ pub fn new(
60
+ inner: BoxEventContext,
61
+ bridge: Bridge,
62
+ propagator: Arc<TextMapCompositePropagator>,
63
+ ) -> Self {
64
+ Self {
65
+ inner,
66
+ bridge,
67
+ propagator,
68
+ }
69
+ }
70
+
71
+ /// Check if cancellation has been requested.
72
+ ///
73
+ /// Cancellation includes both message-level cancellation (e.g., timeout)
74
+ /// and partition shutdown.
75
+ ///
76
+ /// # Returns
77
+ ///
78
+ /// Boolean indicating whether cancellation has been requested.
79
+ fn should_cancel(&self) -> bool {
80
+ self.inner.should_cancel()
81
+ }
82
+
83
+ /// Wait for cancellation to be signaled.
84
+ ///
85
+ /// Cancellation includes both message-level cancellation (e.g., timeout)
86
+ /// and partition shutdown. This method blocks until cancellation is
87
+ /// signaled.
88
+ ///
89
+ /// # Arguments
90
+ ///
91
+ /// * `ruby` - Reference to the Ruby VM
92
+ ///
93
+ /// # Returns
94
+ ///
95
+ /// Nothing on success.
96
+ fn on_cancel(ruby: &Ruby, this: &Self) -> Result<(), Error> {
97
+ let inner = this.inner.clone();
98
+ this.bridge.wait_for(
99
+ ruby,
100
+ async move { inner.on_cancel().await },
101
+ Span::current(),
102
+ )
103
+ }
104
+
105
+ /// Schedule a timer to fire at the specified time.
106
+ ///
107
+ /// # Arguments
108
+ ///
109
+ /// * `ruby` - Reference to the Ruby VM
110
+ /// * `ruby_time` - Ruby Time object specifying when the timer should fire
111
+ ///
112
+ /// # Returns
113
+ ///
114
+ /// Nothing on success.
115
+ ///
116
+ /// # Errors
117
+ ///
118
+ /// Returns an error if the time is invalid or if scheduling fails.
119
+ fn schedule(ruby: &Ruby, this: &Self, ruby_time: Value) -> Result<(), Error> {
120
+ let compact_time = time_to_compact_datetime(ruby, ruby_time)?;
121
+ let inner = this.inner.clone();
122
+
123
+ // Extract OpenTelemetry context from Ruby for distributed tracing
124
+ let context = extract_opentelemetry_context(ruby, &this.propagator)?;
125
+
126
+ // Create span for tracing and set parent context
127
+ let span = info_span!("schedule", time = %compact_time);
128
+ if let Err(err) = span.set_parent(context) {
129
+ debug!("failed to set parent span: {err:#}");
130
+ }
131
+
132
+ this.bridge
133
+ .wait_for(
134
+ ruby,
135
+ async move { inner.schedule(compact_time, TimerType::Application).await },
136
+ span,
137
+ )?
138
+ .map_err(|error| {
139
+ Error::new(
140
+ ruby.exception_runtime_error(),
141
+ format!("Failed to schedule timer: {error:#}"),
142
+ )
143
+ })
144
+ }
145
+
146
+ /// Clear all scheduled timers and schedule a new one at the specified time.
147
+ ///
148
+ /// # Arguments
149
+ ///
150
+ /// * `ruby` - Reference to the Ruby VM
151
+ /// * `ruby_time` - Ruby Time object specifying when the new timer should
152
+ /// fire
153
+ ///
154
+ /// # Returns
155
+ ///
156
+ /// Nothing on success.
157
+ ///
158
+ /// # Errors
159
+ ///
160
+ /// Returns an error if the time is invalid or if scheduling fails.
161
+ fn clear_and_schedule(ruby: &Ruby, this: &Self, ruby_time: Value) -> Result<(), Error> {
162
+ let compact_time = time_to_compact_datetime(ruby, ruby_time)?;
163
+ let inner = this.inner.clone();
164
+
165
+ // Extract OpenTelemetry context from Ruby for distributed tracing
166
+ let context = extract_opentelemetry_context(ruby, &this.propagator)?;
167
+
168
+ // Create span for tracing and set parent context
169
+ let span = info_span!("clear_and_schedule", time = %compact_time);
170
+ if let Err(err) = span.set_parent(context) {
171
+ debug!("failed to set parent span: {err:#}");
172
+ }
173
+
174
+ this.bridge
175
+ .wait_for(
176
+ ruby,
177
+ async move {
178
+ inner
179
+ .clear_and_schedule(compact_time, TimerType::Application)
180
+ .await
181
+ },
182
+ span,
183
+ )?
184
+ .map_err(|error| {
185
+ Error::new(
186
+ ruby.exception_runtime_error(),
187
+ format!("Failed to clear and schedule timer: {error:#}"),
188
+ )
189
+ })
190
+ }
191
+
192
+ /// Unschedule a timer at the specified time.
193
+ ///
194
+ /// # Arguments
195
+ ///
196
+ /// * `ruby` - Reference to the Ruby VM
197
+ /// * `ruby_time` - Ruby Time object specifying which timer to unschedule
198
+ ///
199
+ /// # Returns
200
+ ///
201
+ /// Nothing on success.
202
+ ///
203
+ /// # Errors
204
+ ///
205
+ /// Returns an error if the time is invalid or if unscheduling fails.
206
+ fn unschedule(ruby: &Ruby, this: &Self, ruby_time: Value) -> Result<(), Error> {
207
+ let compact_time = time_to_compact_datetime(ruby, ruby_time)?;
208
+ let inner = this.inner.clone();
209
+
210
+ // Extract OpenTelemetry context from Ruby for distributed tracing
211
+ let context = extract_opentelemetry_context(ruby, &this.propagator)?;
212
+
213
+ // Create span for tracing and set parent context
214
+ let span = info_span!("unschedule", time = %compact_time);
215
+ if let Err(err) = span.set_parent(context) {
216
+ debug!("failed to set parent span: {err:#}");
217
+ }
218
+
219
+ this.bridge
220
+ .wait_for(
221
+ ruby,
222
+ async move { inner.unschedule(compact_time, TimerType::Application).await },
223
+ span,
224
+ )?
225
+ .map_err(|error| {
226
+ Error::new(
227
+ ruby.exception_runtime_error(),
228
+ format!("Failed to unschedule timer: {error:#}"),
229
+ )
230
+ })
231
+ }
232
+
233
+ /// Clear all scheduled timers.
234
+ ///
235
+ /// # Arguments
236
+ ///
237
+ /// * `ruby` - Reference to the Ruby VM
238
+ ///
239
+ /// # Returns
240
+ ///
241
+ /// Nothing on success.
242
+ ///
243
+ /// # Errors
244
+ ///
245
+ /// Returns an error if clearing fails.
246
+ fn clear_scheduled(ruby: &Ruby, this: &Self) -> Result<(), Error> {
247
+ let inner = this.inner.clone();
248
+
249
+ // Extract OpenTelemetry context from Ruby for distributed tracing
250
+ let context = extract_opentelemetry_context(ruby, &this.propagator)?;
251
+
252
+ // Create span for tracing and set parent context
253
+ let span = info_span!("clear_scheduled");
254
+ if let Err(err) = span.set_parent(context) {
255
+ debug!("failed to set parent span: {err:#}");
256
+ }
257
+
258
+ this.bridge
259
+ .wait_for(
260
+ ruby,
261
+ async move { inner.clear_scheduled(TimerType::Application).await },
262
+ span,
263
+ )?
264
+ .map_err(|error| {
265
+ Error::new(
266
+ ruby.exception_runtime_error(),
267
+ format!("Failed to clear scheduled timers: {error:#}"),
268
+ )
269
+ })
270
+ }
271
+
272
+ /// Get all scheduled timer times.
273
+ ///
274
+ /// # Arguments
275
+ ///
276
+ /// * `ruby` - Reference to the Ruby VM
277
+ ///
278
+ /// # Returns
279
+ ///
280
+ /// Array of Ruby Time objects representing all scheduled timer times.
281
+ ///
282
+ /// # Errors
283
+ ///
284
+ /// Returns an error if retrieving scheduled times fails or if
285
+ /// converting times to Ruby objects fails.
286
+ fn scheduled(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
287
+ let inner = this.inner.clone();
288
+
289
+ // Extract OpenTelemetry context from Ruby for distributed tracing
290
+ let context = extract_opentelemetry_context(ruby, &this.propagator)?;
291
+
292
+ // Create span for tracing and set parent context
293
+ let span = info_span!("scheduled");
294
+ if let Err(err) = span.set_parent(context) {
295
+ debug!("failed to set parent span: {err:#}");
296
+ }
297
+
298
+ let scheduled_times = this
299
+ .bridge
300
+ .wait_for(
301
+ ruby,
302
+ async move { inner.scheduled(TimerType::Application).await },
303
+ span,
304
+ )?
305
+ .map_err(|e| {
306
+ Error::new(
307
+ ruby.exception_runtime_error(),
308
+ format!("Failed to get scheduled times: {e}"),
309
+ )
310
+ })?;
311
+
312
+ // Convert CompactDateTime objects to Ruby Time objects using idiomatic iterator
313
+ // pattern
314
+ let ruby_array = ruby.ary_try_from_iter(
315
+ scheduled_times
316
+ .into_iter()
317
+ .map(|compact_time| compact_datetime_to_time(ruby, compact_time)),
318
+ )?;
319
+
320
+ Ok(ruby_array.as_value())
321
+ }
322
+ }
323
+
324
+ /// Initializes the Context class in Ruby.
325
+ ///
326
+ /// Registers the `Prosody::Context` class in the Ruby runtime, making it
327
+ /// available to Ruby code with all timer scheduling methods.
328
+ ///
329
+ /// # Arguments
330
+ ///
331
+ /// * `ruby` - Reference to the Ruby VM
332
+ ///
333
+ /// # Errors
334
+ ///
335
+ /// Returns a Magnus error if the class definition fails
336
+ pub fn init(ruby: &Ruby) -> Result<(), Error> {
337
+ let module = ruby.get_inner(&ROOT_MOD);
338
+ let class = module.define_class(id!(ruby, "Context"), ruby.class_object())?;
339
+
340
+ // Cancellation methods
341
+ class.define_method(
342
+ id!(ruby, "should_cancel?"),
343
+ method!(Context::should_cancel, 0),
344
+ )?;
345
+ class.define_method(id!(ruby, "on_cancel"), method!(Context::on_cancel, 0))?;
346
+
347
+ // Timer scheduling methods
348
+ class.define_method(id!(ruby, "schedule"), method!(Context::schedule, 1))?;
349
+ class.define_method(
350
+ id!(ruby, "clear_and_schedule"),
351
+ method!(Context::clear_and_schedule, 1),
352
+ )?;
353
+ class.define_method(id!(ruby, "unschedule"), method!(Context::unschedule, 1))?;
354
+ class.define_method(
355
+ id!(ruby, "clear_scheduled"),
356
+ method!(Context::clear_scheduled, 0),
357
+ )?;
358
+ class.define_method(id!(ruby, "scheduled"), method!(Context::scheduled, 0))?;
359
+
360
+ Ok(())
361
+ }
362
+
363
+ /// Converts a Ruby Time object to `CompactDateTime`.
364
+ ///
365
+ /// `CompactDateTime` stores only epoch seconds (u32) with second-level
366
+ /// precision. This function extracts nanoseconds from Ruby Time to implement
367
+ /// proper rounding.
368
+ ///
369
+ /// # Arguments
370
+ ///
371
+ /// * `_ruby` - Reference to the Ruby VM (ensures Ruby thread safety)
372
+ /// * `ruby_time` - Ruby Time object to convert
373
+ ///
374
+ /// # Returns
375
+ ///
376
+ /// `CompactDateTime` representing the same time, rounded to nearest second.
377
+ ///
378
+ /// # Errors
379
+ ///
380
+ /// Returns an error if the time is outside the `CompactDateTime` range
381
+ /// (1970-2106) or if the Ruby Time object is invalid.
382
+ fn time_to_compact_datetime(ruby: &Ruby, ruby_time: Value) -> Result<CompactDateTime, Error> {
383
+ // Extract epoch seconds and nanoseconds from Ruby Time
384
+ let epoch_seconds: i64 = ruby_time.funcall(id!(ruby, "to_i"), ())?;
385
+ let nanos: u32 = ruby_time.funcall(id!(ruby, "nsec"), ())?;
386
+
387
+ // Validate CompactDateTime range (1970-2106)
388
+ let seconds_u32 = u32::try_from(epoch_seconds).map_err(|_| {
389
+ Error::new(
390
+ ruby.exception_arg_error(),
391
+ format!("Time {epoch_seconds} is outside CompactDateTime range (1970-2106)"),
392
+ )
393
+ })?;
394
+
395
+ // Apply CompactDateTime's rounding logic
396
+ let final_seconds = if nanos >= NANOSECOND_ROUNDING_THRESHOLD {
397
+ seconds_u32.checked_add(1).ok_or_else(|| {
398
+ Error::new(
399
+ ruby.exception_arg_error(),
400
+ "Time overflow during rounding to nearest second",
401
+ )
402
+ })?
403
+ } else {
404
+ seconds_u32
405
+ };
406
+
407
+ Ok(CompactDateTime::from(final_seconds))
408
+ }
409
+
410
+ /// Converts a `CompactDateTime` to a Ruby Time object.
411
+ ///
412
+ /// `CompactDateTime` only stores epoch seconds, so the resulting Ruby Time
413
+ /// will have zero nanoseconds. This is the most efficient conversion.
414
+ ///
415
+ /// # Arguments
416
+ ///
417
+ /// * `ruby` - Reference to the Ruby VM
418
+ /// * `compact_time` - `CompactDateTime` to convert
419
+ ///
420
+ /// # Returns
421
+ ///
422
+ /// Ruby Time object representing the same time with zero nanoseconds.
423
+ ///
424
+ /// # Errors
425
+ ///
426
+ /// Returns an error if the Ruby Time class cannot be accessed or if
427
+ /// creating the Time object fails.
428
+ fn compact_datetime_to_time(ruby: &Ruby, compact_time: CompactDateTime) -> Result<Value, Error> {
429
+ // Direct access to epoch seconds - CompactDateTime has no sub-second precision
430
+ let epoch_seconds = i64::from(compact_time.epoch_seconds());
431
+
432
+ // Create Ruby Time with zero nanoseconds (CompactDateTime precision limit)
433
+ ruby.module_kernel()
434
+ .const_get::<_, RClass>(id!(ruby, "Time"))?
435
+ .funcall(id!(ruby, "at"), (epoch_seconds,))
436
+ }
@@ -0,0 +1,144 @@
1
+ //! Defines the Ruby-facing wrapper for Kafka consumer messages.
2
+ //!
3
+ //! This module implements a Ruby-accessible wrapper around the Prosody consumer
4
+ //! message type, exposing message properties and content as Ruby objects.
5
+
6
+ use crate::{ROOT_MOD, id};
7
+ use educe::Educe;
8
+ use magnus::value::ReprValue;
9
+ use magnus::{Error, Module, RClass, Ruby, Value, method};
10
+ use prosody::consumer::Keyed;
11
+ use prosody::consumer::message::ConsumerMessage;
12
+ use serde_magnus::serialize;
13
+
14
+ /// Ruby-accessible wrapper for Kafka consumer messages.
15
+ ///
16
+ /// Provides Ruby bindings to access Kafka message data including topic,
17
+ /// partition, offset, key, timestamp, and payload.
18
+ #[derive(Educe, Clone)]
19
+ #[educe(Debug)]
20
+ #[magnus::wrap(class = "Prosody::Message", frozen_shareable)]
21
+ pub struct Message {
22
+ /// The wrapped Prosody consumer message
23
+ #[educe(Debug(ignore))]
24
+ inner: ConsumerMessage,
25
+ }
26
+
27
+ impl Message {
28
+ /// Returns the topic name this message was published to.
29
+ ///
30
+ /// # Returns
31
+ ///
32
+ /// A string slice containing the topic name.
33
+ fn topic(&self) -> &'static str {
34
+ self.inner.topic().as_ref()
35
+ }
36
+
37
+ /// Returns the Kafka partition number for this message.
38
+ ///
39
+ /// # Returns
40
+ ///
41
+ /// The partition number as an i32.
42
+ fn partition(&self) -> i32 {
43
+ self.inner.partition()
44
+ }
45
+
46
+ /// Returns the Kafka offset for this message within its partition.
47
+ ///
48
+ /// # Returns
49
+ ///
50
+ /// The message offset as an i64.
51
+ fn offset(&self) -> i64 {
52
+ self.inner.offset()
53
+ }
54
+
55
+ /// Returns the message key.
56
+ ///
57
+ /// # Returns
58
+ ///
59
+ /// A string slice containing the message key.
60
+ fn key(&self) -> &str {
61
+ self.inner.key()
62
+ }
63
+
64
+ /// Converts the message timestamp to a Ruby Time object.
65
+ ///
66
+ /// # Arguments
67
+ ///
68
+ /// * `ruby` - Reference to the Ruby VM
69
+ /// * `this` - Reference to the Message instance
70
+ ///
71
+ /// # Returns
72
+ ///
73
+ /// A Ruby Time object representing the message timestamp.
74
+ ///
75
+ /// # Errors
76
+ ///
77
+ /// Returns an error if the Ruby Time class cannot be accessed or if
78
+ /// creating the Time object fails.
79
+ fn timestamp(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
80
+ let epoch_micros = this.inner.timestamp().timestamp_micros();
81
+ ruby.module_kernel()
82
+ .const_get::<_, RClass>(id!(ruby, "Time"))?
83
+ .funcall(
84
+ id!(ruby, "at"),
85
+ (epoch_micros, ruby.to_symbol("microsecond")),
86
+ )
87
+ }
88
+
89
+ /// Deserializes the message payload to a Ruby value.
90
+ ///
91
+ /// # Arguments
92
+ ///
93
+ /// * `_ruby` - Reference to the Ruby VM (unused but required by Magnus)
94
+ /// * `this` - Reference to the Message instance
95
+ ///
96
+ /// # Returns
97
+ ///
98
+ /// A Ruby value containing the deserialized message payload.
99
+ ///
100
+ /// # Errors
101
+ ///
102
+ /// Returns an error if payload deserialization fails.
103
+ fn payload(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
104
+ serialize(ruby, this.inner.payload())
105
+ }
106
+ }
107
+
108
+ impl From<ConsumerMessage> for Message {
109
+ /// Creates a new Message wrapper from a Prosody `ConsumerMessage`.
110
+ ///
111
+ /// # Arguments
112
+ ///
113
+ /// * `value` - The Prosody `ConsumerMessage` to wrap
114
+ fn from(value: ConsumerMessage) -> Self {
115
+ Self { inner: value }
116
+ }
117
+ }
118
+
119
+ /// Initializes the `Prosody::Message` Ruby class and defines its methods.
120
+ ///
121
+ /// # Arguments
122
+ ///
123
+ /// * `ruby` - Reference to the Ruby VM
124
+ ///
125
+ /// # Returns
126
+ ///
127
+ /// OK on successful initialization.
128
+ ///
129
+ /// # Errors
130
+ ///
131
+ /// Returns an error if class or method definition fails.
132
+ pub fn init(ruby: &Ruby) -> Result<(), Error> {
133
+ let module = ruby.get_inner(&ROOT_MOD);
134
+ let class = module.define_class(id!(ruby, "Message"), ruby.class_object())?;
135
+
136
+ class.define_method(id!(ruby, "topic"), method!(Message::topic, 0))?;
137
+ class.define_method(id!(ruby, "partition"), method!(Message::partition, 0))?;
138
+ class.define_method(id!(ruby, "offset"), method!(Message::offset, 0))?;
139
+ class.define_method(id!(ruby, "key"), method!(Message::key, 0))?;
140
+ class.define_method(id!(ruby, "timestamp"), method!(Message::timestamp, 0))?;
141
+ class.define_method(id!(ruby, "payload"), method!(Message::payload, 0))?;
142
+
143
+ Ok(())
144
+ }