prosody 0.5.1 → 0.6.0
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 +4 -4
- data/.config/rail.toml +26 -0
- data/.release-please-manifest.json +1 -1
- data/.ruby-version +1 -1
- data/.taplo.toml +1 -1
- data/AGENTS.md +29 -15
- data/CHANGELOG.md +7 -0
- data/CONFIGURATION.md +19 -16
- data/Cargo.lock +254 -235
- data/Cargo.toml +11 -8
- data/README.md +100 -23
- data/examples/keyed_state.rb +10 -2
- data/examples/keyed_state.rbs +1 -0
- data/ext/prosody/Cargo.toml +1 -1
- data/ext/prosody/src/admin.rs +36 -36
- data/ext/prosody/src/bridge/mod.rs +5 -10
- data/ext/prosody/src/client/config/connections.rs +170 -0
- data/ext/prosody/src/client/config/middleware.rs +184 -0
- data/ext/prosody/src/client/config/mod.rs +396 -0
- data/ext/prosody/src/client/config/state.rs +323 -0
- data/ext/prosody/src/client/mod.rs +31 -117
- data/ext/prosody/src/client/readers.rs +106 -0
- data/ext/prosody/src/client/request.rs +2 -2
- data/ext/prosody/src/client/support.rs +13 -32
- data/ext/prosody/src/gvl.rs +8 -6
- data/ext/prosody/src/handler/{context.rs → context/mod.rs} +36 -150
- data/ext/prosody/src/handler/context/vending.rs +138 -0
- data/ext/prosody/src/handler/message.rs +36 -0
- data/ext/prosody/src/handler/mod.rs +18 -8
- data/ext/prosody/src/handler/state/deque.rs +144 -0
- data/ext/prosody/src/handler/state/mod.rs +163 -268
- data/ext/prosody/src/handler/state/query.rs +264 -0
- data/ext/prosody/src/handler/state/registration.rs +15 -86
- data/ext/prosody/src/handler/state/scan.rs +33 -141
- data/ext/prosody/src/handler/state/set.rs +98 -0
- data/ext/prosody/src/lib.rs +54 -36
- data/ext/prosody/src/logging.rs +6 -6
- data/ext/prosody/src/published.rs +171 -152
- data/ext/prosody/src/scheduler/result.rs +2 -1
- data/ext/prosody/src/util.rs +65 -3
- data/lib/prosody/client.rb +32 -0
- data/lib/prosody/configuration.rb +20 -12
- data/lib/prosody/demand.rb +27 -0
- data/lib/prosody/native_stubs/client.rb +157 -0
- data/lib/prosody/native_stubs/context.rb +178 -0
- data/lib/prosody/native_stubs/message.rb +133 -0
- data/lib/prosody/native_stubs.rb +11 -956
- data/lib/prosody/state/deque.rb +253 -0
- data/lib/prosody/state/map.rb +313 -0
- data/lib/prosody/state/set.rb +121 -0
- data/lib/prosody/state/value.rb +58 -0
- data/lib/prosody/state.rb +157 -677
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +2 -1
- data/sig/configuration.rbs +19 -10
- data/sig/prosody.rbs +37 -4
- data/sig/published.rbs +102 -0
- data/sig/state.rbs +109 -122
- data/typecheck/payload_types.rb +12 -0
- data/typecheck/payload_types.rbs +1 -0
- metadata +30 -11
- data/ext/prosody/src/client/config.rs +0 -1300
|
@@ -22,7 +22,7 @@ struct NativeExciseRequest {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
pub(super) fn request(ruby: &Ruby, this: &Client, request: Value) -> Result<Value, Error> {
|
|
25
|
-
|
|
25
|
+
this.fork.check(ruby)?;
|
|
26
26
|
let _guard = ensure_runtime_context(ruby);
|
|
27
27
|
let request: NativeRequest = deserialize(ruby, request)?;
|
|
28
28
|
let (subsystems, timeout) = request_parameters(ruby, request.subsystems, request.timeout)?;
|
|
@@ -57,7 +57,7 @@ pub(super) fn request(ruby: &Ruby, this: &Client, request: Value) -> Result<Valu
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
pub(super) fn request_excise(ruby: &Ruby, this: &Client, request: Value) -> Result<Value, Error> {
|
|
60
|
-
|
|
60
|
+
this.fork.check(ruby)?;
|
|
61
61
|
let _guard = ensure_runtime_context(ruby);
|
|
62
62
|
let request: NativeExciseRequest = deserialize(ruby, request)?;
|
|
63
63
|
let (subsystems, timeout) = request_parameters(ruby, request.subsystems, request.timeout)?;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use super::{
|
|
2
|
-
Arc, Class, Client,
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
Arc, Class, Client, Error, FutureExt, Module, Object, RClass, RModule, ROOT_MOD, ReprValue,
|
|
3
|
+
ResponseError, Ruby, RubyHandler, SharedHighLevelClient, Shutdown, Value, function, id, kwargs,
|
|
4
|
+
method, readers, request,
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
pub(super) fn validate_handler(ruby: &Ruby, handler: Value) -> Result<(), Error> {
|
|
@@ -24,29 +24,6 @@ pub(super) fn shutdown(client: &SharedHighLevelClient<RubyHandler>) -> Shutdown
|
|
|
24
24
|
.shared()
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
pub(super) fn read_cache(
|
|
28
|
-
ruby: &Ruby,
|
|
29
|
-
seconds: Option<f64>,
|
|
30
|
-
disabled: bool,
|
|
31
|
-
) -> Result<ErasedReadCache, Error> {
|
|
32
|
-
match (seconds, disabled) {
|
|
33
|
-
(None, false) => Ok(ErasedReadCache::Inherit),
|
|
34
|
-
(None, true) => Ok(ErasedReadCache::Disabled),
|
|
35
|
-
(Some(seconds), false) => Duration::try_from_secs_f64(seconds)
|
|
36
|
-
.map(ErasedReadCache::Ttl)
|
|
37
|
-
.map_err(|_| {
|
|
38
|
-
Error::new(
|
|
39
|
-
ruby.exception_arg_error(),
|
|
40
|
-
"read_cache must be finite and non-negative",
|
|
41
|
-
)
|
|
42
|
-
}),
|
|
43
|
-
(Some(_), true) => Err(Error::new(
|
|
44
|
-
ruby.exception_arg_error(),
|
|
45
|
-
"read_cache cannot specify a TTL and be disabled",
|
|
46
|
-
)),
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
27
|
/// Initializes the client module in Ruby.
|
|
51
28
|
///
|
|
52
29
|
/// Defines the `Prosody::Client` class and its methods, making the client
|
|
@@ -77,10 +54,10 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
77
54
|
)?;
|
|
78
55
|
class.define_method(id!(ruby, "subscribe"), method!(Client::subscribe, 1))?;
|
|
79
56
|
class.define_method(
|
|
80
|
-
id!(ruby, "
|
|
81
|
-
method!(Client::
|
|
57
|
+
id!(ruby, "assigned_partition_count"),
|
|
58
|
+
method!(Client::assigned_partition_count, 0),
|
|
82
59
|
)?;
|
|
83
|
-
class.define_method(id!(ruby, "
|
|
60
|
+
class.define_method(id!(ruby, "stalled?"), method!(Client::is_stalled, 0))?;
|
|
84
61
|
class.define_method(id!(ruby, "unsubscribe"), method!(Client::unsubscribe, 0))?;
|
|
85
62
|
class.define_method(id!(ruby, "shutdown"), method!(Client::shutdown, 0))?;
|
|
86
63
|
class.define_method(
|
|
@@ -89,15 +66,19 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
89
66
|
)?;
|
|
90
67
|
class.define_method(
|
|
91
68
|
id!(ruby, "published_value"),
|
|
92
|
-
method!(
|
|
69
|
+
method!(readers::published_value, 3),
|
|
93
70
|
)?;
|
|
94
71
|
class.define_method(
|
|
95
72
|
id!(ruby, "published_map"),
|
|
96
|
-
method!(
|
|
73
|
+
method!(readers::published_map, 3),
|
|
74
|
+
)?;
|
|
75
|
+
class.define_method(
|
|
76
|
+
id!(ruby, "published_set"),
|
|
77
|
+
method!(readers::published_set, 3),
|
|
97
78
|
)?;
|
|
98
79
|
class.define_method(
|
|
99
80
|
id!(ruby, "published_deque"),
|
|
100
|
-
method!(
|
|
81
|
+
method!(readers::published_deque, 3),
|
|
101
82
|
)?;
|
|
102
83
|
|
|
103
84
|
Ok(())
|
data/ext/prosody/src/gvl.rs
CHANGED
|
@@ -54,7 +54,6 @@ use tracing::error;
|
|
|
54
54
|
///
|
|
55
55
|
/// Adapted from: <https://github.com/temporalio/sdk-ruby/blob/main/temporalio/ext/src/util.rs>,
|
|
56
56
|
/// plus various examples in the Rust/Ruby ecosystem.
|
|
57
|
-
#[allow(unsafe_code)]
|
|
58
57
|
pub(crate) fn without_gvl<F, R, U>(func: F, unblock: U) -> Result<R, GvlError>
|
|
59
58
|
where
|
|
60
59
|
F: FnOnce() -> R,
|
|
@@ -76,20 +75,22 @@ where
|
|
|
76
75
|
let result = catch_unwind(AssertUnwindSafe(|| (*func)())).map_err(|_| GvlError::Panicked);
|
|
77
76
|
|
|
78
77
|
// SAFETY: We box the result, returning a raw pointer. The caller
|
|
79
|
-
// will Box::from_raw it exactly once after
|
|
78
|
+
// will Box::from_raw it exactly once after
|
|
79
|
+
// `rb_thread_call_without_gvl`.
|
|
80
80
|
Box::into_raw(Box::new(result)).cast::<c_void>()
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// SAFETY: Another FFI callback. Ruby may call this multiple times, so we do
|
|
84
84
|
// NOT call `Box::from_raw` here. Instead, we just dereference the pointer
|
|
85
|
-
// each time. We also catch any panic and abort to avoid unwinding across
|
|
85
|
+
// each time. We also catch any panic and abort to avoid unwinding across
|
|
86
|
+
// FFI.
|
|
86
87
|
unsafe extern "C" fn anon_unblock<U>(data: *mut c_void)
|
|
87
88
|
where
|
|
88
89
|
U: FnMut() + Send,
|
|
89
90
|
{
|
|
90
91
|
// We take a pointer to `U`; we do NOT consume the Box here.
|
|
91
|
-
// Note that `&mut *ptr` is not automatically UnwindSafe, so we wrap the
|
|
92
|
-
// in `AssertUnwindSafe`.
|
|
92
|
+
// Note that `&mut *ptr` is not automatically UnwindSafe, so we wrap the
|
|
93
|
+
// call in `AssertUnwindSafe`.
|
|
93
94
|
let closure_ptr = data.cast::<U>();
|
|
94
95
|
|
|
95
96
|
if catch_unwind(AssertUnwindSafe(|| {
|
|
@@ -117,7 +118,8 @@ where
|
|
|
117
118
|
// - Call `anon_func` once, passing `func_ptr`.
|
|
118
119
|
// - Potentially call `anon_unblock` multiple times with `unblock_ptr`.
|
|
119
120
|
// After `rb_thread_call_without_gvl` returns, we are guaranteed that Ruby
|
|
120
|
-
// won't call `anon_unblock` again, so we can safely free the unblock
|
|
121
|
+
// won't call `anon_unblock` again, so we can safely free the unblock
|
|
122
|
+
// closure.
|
|
121
123
|
let raw_result = unsafe {
|
|
122
124
|
rb_thread_call_without_gvl(
|
|
123
125
|
Some(anon_func::<F, R>),
|
|
@@ -3,18 +3,16 @@
|
|
|
3
3
|
//! This module provides a Ruby-compatible wrapper around the Prosody library's
|
|
4
4
|
//! `MessageContext`, allowing Ruby code to interact with message context
|
|
5
5
|
//! information from Kafka messages and schedule timer events.
|
|
6
|
+
//! The `vending` submodule owns the keyed-state vend methods.
|
|
6
7
|
|
|
7
8
|
use crate::bridge::Bridge;
|
|
8
|
-
use crate::handler::state::{
|
|
9
|
-
NativeJsonDequeState, NativeJsonMapState, NativeJsonValueState, NativeMessageDequeState,
|
|
10
|
-
NativeMessageMapState, NativeMessageValueState, state_error,
|
|
11
|
-
};
|
|
12
9
|
use crate::tracing_util::extract_opentelemetry_context;
|
|
13
10
|
use crate::{ROOT_MOD, id};
|
|
14
11
|
use educe::Educe;
|
|
15
12
|
use magnus::value::ReprValue;
|
|
16
13
|
use magnus::{Error, Module, RClass, Ruby, Value, method};
|
|
17
14
|
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
15
|
+
use prosody::consumer::DemandType;
|
|
18
16
|
use prosody::consumer::event_context::BoxEventContext;
|
|
19
17
|
use prosody::timers::TimerType;
|
|
20
18
|
use prosody::timers::datetime::CompactDateTime;
|
|
@@ -22,6 +20,8 @@ use std::sync::Arc;
|
|
|
22
20
|
use tracing::{Span, debug, info_span};
|
|
23
21
|
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
|
24
22
|
|
|
23
|
+
mod vending;
|
|
24
|
+
|
|
25
25
|
/// Nanosecond threshold for rounding Ruby Time objects to the nearest second.
|
|
26
26
|
/// At 0.5 seconds, times round up; below 0.5 seconds, they round down.
|
|
27
27
|
const NANOSECOND_ROUNDING_THRESHOLD: u32 = 500_000_000;
|
|
@@ -49,6 +49,9 @@ pub struct Context {
|
|
|
49
49
|
/// OpenTelemetry propagator for distributed tracing
|
|
50
50
|
#[educe(Debug(ignore))]
|
|
51
51
|
propagator: Arc<TextMapCompositePropagator>,
|
|
52
|
+
|
|
53
|
+
/// Whether this dispatch is a normal delivery or a retry after a failure.
|
|
54
|
+
demand: DemandType,
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
impl Context {
|
|
@@ -59,18 +62,41 @@ impl Context {
|
|
|
59
62
|
/// * `inner` - The Prosody event context to wrap
|
|
60
63
|
/// * `bridge` - The bridge for handling async operations
|
|
61
64
|
/// * `propagator` - Shared OpenTelemetry propagator for distributed tracing
|
|
65
|
+
/// * `demand` - The demand this dispatch serves
|
|
62
66
|
pub fn new(
|
|
63
67
|
inner: BoxEventContext<serde_json::Value>,
|
|
64
68
|
bridge: Bridge,
|
|
65
69
|
propagator: Arc<TextMapCompositePropagator>,
|
|
70
|
+
demand: DemandType,
|
|
66
71
|
) -> Self {
|
|
67
72
|
Self {
|
|
68
73
|
inner,
|
|
69
74
|
bridge,
|
|
70
75
|
propagator,
|
|
76
|
+
demand,
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
/// Returns the dispatch demand as a `Prosody::Demand`.
|
|
81
|
+
///
|
|
82
|
+
/// A normal delivery has kind `:normal` and 0 retries. A retry after a
|
|
83
|
+
/// failure has kind `:failure` and a retry count that starts at 1. The
|
|
84
|
+
/// count is an estimate.
|
|
85
|
+
///
|
|
86
|
+
/// # Errors
|
|
87
|
+
///
|
|
88
|
+
/// Returns a Ruby error if `Prosody::Demand` cannot be built.
|
|
89
|
+
fn demand(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
90
|
+
let (kind, retries) = match this.demand {
|
|
91
|
+
DemandType::Normal => ("normal", 0),
|
|
92
|
+
DemandType::Failure { retry: retries } => ("failure", retries),
|
|
93
|
+
};
|
|
94
|
+
let class: RClass = ruby.get_inner(&ROOT_MOD).const_get(id!(ruby, "Demand"))?;
|
|
95
|
+
// `Data.new` maps positional members to keywords; `new_instance` would
|
|
96
|
+
// bypass it and call `initialize` with positional arguments.
|
|
97
|
+
class.funcall(id!(ruby, "new"), (ruby.sym_new(kind), retries))
|
|
98
|
+
}
|
|
99
|
+
|
|
74
100
|
/// Check if cancellation has been requested.
|
|
75
101
|
///
|
|
76
102
|
/// Cancellation includes both message-level cancellation (e.g., timeout)
|
|
@@ -312,8 +338,8 @@ impl Context {
|
|
|
312
338
|
)
|
|
313
339
|
})?;
|
|
314
340
|
|
|
315
|
-
// Convert CompactDateTime objects to Ruby Time objects using idiomatic
|
|
316
|
-
// pattern
|
|
341
|
+
// Convert CompactDateTime objects to Ruby Time objects using idiomatic
|
|
342
|
+
// iterator pattern
|
|
317
343
|
let ruby_array = ruby.ary_try_from_iter(
|
|
318
344
|
scheduled_times
|
|
319
345
|
.into_iter()
|
|
@@ -322,131 +348,6 @@ impl Context {
|
|
|
322
348
|
|
|
323
349
|
Ok(ruby_array.as_value())
|
|
324
350
|
}
|
|
325
|
-
|
|
326
|
-
/// Vends the handle for the named JSON value collection.
|
|
327
|
-
///
|
|
328
|
-
/// Vending verifies the collection's registration (core-side); no span is
|
|
329
|
-
/// opened here — vended handles outlive the call, and every operation opens
|
|
330
|
-
/// its own span.
|
|
331
|
-
///
|
|
332
|
-
/// # Errors
|
|
333
|
-
///
|
|
334
|
-
/// Returns a permanent state error if the name is unregistered or its
|
|
335
|
-
/// registered identity mismatches.
|
|
336
|
-
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
337
|
-
fn value_state(ruby: &Ruby, this: &Self, name: String) -> Result<NativeJsonValueState, Error> {
|
|
338
|
-
let handle = this
|
|
339
|
-
.inner
|
|
340
|
-
.value_state(&name)
|
|
341
|
-
.map_err(|error| state_error(ruby, &error))?;
|
|
342
|
-
Ok(NativeJsonValueState::new(
|
|
343
|
-
Arc::from(handle),
|
|
344
|
-
this.bridge.clone(),
|
|
345
|
-
Arc::clone(&this.propagator),
|
|
346
|
-
))
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/// Vends the handle for the named JSON map collection.
|
|
350
|
-
///
|
|
351
|
-
/// # Errors
|
|
352
|
-
///
|
|
353
|
-
/// See [`value_state`](Self::value_state).
|
|
354
|
-
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
355
|
-
fn map_state(ruby: &Ruby, this: &Self, name: String) -> Result<NativeJsonMapState, Error> {
|
|
356
|
-
let handle = this
|
|
357
|
-
.inner
|
|
358
|
-
.map_state(&name)
|
|
359
|
-
.map_err(|error| state_error(ruby, &error))?;
|
|
360
|
-
Ok(NativeJsonMapState::new(
|
|
361
|
-
Arc::from(handle),
|
|
362
|
-
this.bridge.clone(),
|
|
363
|
-
Arc::clone(&this.propagator),
|
|
364
|
-
))
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/// Vends the handle for the named JSON deque collection.
|
|
368
|
-
///
|
|
369
|
-
/// # Errors
|
|
370
|
-
///
|
|
371
|
-
/// See [`value_state`](Self::value_state).
|
|
372
|
-
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
373
|
-
fn deque_state(ruby: &Ruby, this: &Self, name: String) -> Result<NativeJsonDequeState, Error> {
|
|
374
|
-
let handle = this
|
|
375
|
-
.inner
|
|
376
|
-
.deque_state(&name)
|
|
377
|
-
.map_err(|error| state_error(ruby, &error))?;
|
|
378
|
-
Ok(NativeJsonDequeState::new(
|
|
379
|
-
Arc::from(handle),
|
|
380
|
-
this.bridge.clone(),
|
|
381
|
-
Arc::clone(&this.propagator),
|
|
382
|
-
))
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/// Vends the handle for the named Kafka-message value collection.
|
|
386
|
-
///
|
|
387
|
-
/// # Errors
|
|
388
|
-
///
|
|
389
|
-
/// See [`value_state`](Self::value_state).
|
|
390
|
-
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
391
|
-
fn message_value_state(
|
|
392
|
-
ruby: &Ruby,
|
|
393
|
-
this: &Self,
|
|
394
|
-
name: String,
|
|
395
|
-
) -> Result<NativeMessageValueState, Error> {
|
|
396
|
-
let handle = this
|
|
397
|
-
.inner
|
|
398
|
-
.message_value_state(&name)
|
|
399
|
-
.map_err(|error| state_error(ruby, &error))?;
|
|
400
|
-
Ok(NativeMessageValueState::new(
|
|
401
|
-
Arc::from(handle),
|
|
402
|
-
this.bridge.clone(),
|
|
403
|
-
Arc::clone(&this.propagator),
|
|
404
|
-
))
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/// Vends the handle for the named Kafka-message map collection.
|
|
408
|
-
///
|
|
409
|
-
/// # Errors
|
|
410
|
-
///
|
|
411
|
-
/// See [`value_state`](Self::value_state).
|
|
412
|
-
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
413
|
-
fn message_map_state(
|
|
414
|
-
ruby: &Ruby,
|
|
415
|
-
this: &Self,
|
|
416
|
-
name: String,
|
|
417
|
-
) -> Result<NativeMessageMapState, Error> {
|
|
418
|
-
let handle = this
|
|
419
|
-
.inner
|
|
420
|
-
.message_map_state(&name)
|
|
421
|
-
.map_err(|error| state_error(ruby, &error))?;
|
|
422
|
-
Ok(NativeMessageMapState::new(
|
|
423
|
-
Arc::from(handle),
|
|
424
|
-
this.bridge.clone(),
|
|
425
|
-
Arc::clone(&this.propagator),
|
|
426
|
-
))
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/// Vends the handle for the named Kafka-message deque collection.
|
|
430
|
-
///
|
|
431
|
-
/// # Errors
|
|
432
|
-
///
|
|
433
|
-
/// See [`value_state`](Self::value_state).
|
|
434
|
-
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
435
|
-
fn message_deque_state(
|
|
436
|
-
ruby: &Ruby,
|
|
437
|
-
this: &Self,
|
|
438
|
-
name: String,
|
|
439
|
-
) -> Result<NativeMessageDequeState, Error> {
|
|
440
|
-
let handle = this
|
|
441
|
-
.inner
|
|
442
|
-
.message_deque_state(&name)
|
|
443
|
-
.map_err(|error| state_error(ruby, &error))?;
|
|
444
|
-
Ok(NativeMessageDequeState::new(
|
|
445
|
-
Arc::from(handle),
|
|
446
|
-
this.bridge.clone(),
|
|
447
|
-
Arc::clone(&this.propagator),
|
|
448
|
-
))
|
|
449
|
-
}
|
|
450
351
|
}
|
|
451
352
|
|
|
452
353
|
/// Initializes the Context class in Ruby.
|
|
@@ -471,6 +372,7 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
471
372
|
method!(Context::should_cancel, 0),
|
|
472
373
|
)?;
|
|
473
374
|
class.define_method(id!(ruby, "on_cancel"), method!(Context::on_cancel, 0))?;
|
|
375
|
+
class.define_method(id!(ruby, "demand"), method!(Context::demand, 0))?;
|
|
474
376
|
|
|
475
377
|
// Timer scheduling methods
|
|
476
378
|
class.define_method(id!(ruby, "schedule"), method!(Context::schedule, 1))?;
|
|
@@ -485,24 +387,7 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
485
387
|
)?;
|
|
486
388
|
class.define_method(id!(ruby, "scheduled"), method!(Context::scheduled, 0))?;
|
|
487
389
|
|
|
488
|
-
|
|
489
|
-
class.define_method(id!(ruby, "value_state"), method!(Context::value_state, 1))?;
|
|
490
|
-
class.define_method(id!(ruby, "map_state"), method!(Context::map_state, 1))?;
|
|
491
|
-
class.define_method(id!(ruby, "deque_state"), method!(Context::deque_state, 1))?;
|
|
492
|
-
class.define_method(
|
|
493
|
-
id!(ruby, "message_value_state"),
|
|
494
|
-
method!(Context::message_value_state, 1),
|
|
495
|
-
)?;
|
|
496
|
-
class.define_method(
|
|
497
|
-
id!(ruby, "message_map_state"),
|
|
498
|
-
method!(Context::message_map_state, 1),
|
|
499
|
-
)?;
|
|
500
|
-
class.define_method(
|
|
501
|
-
id!(ruby, "message_deque_state"),
|
|
502
|
-
method!(Context::message_deque_state, 1),
|
|
503
|
-
)?;
|
|
504
|
-
|
|
505
|
-
Ok(())
|
|
390
|
+
vending::define_methods(ruby, class)
|
|
506
391
|
}
|
|
507
392
|
|
|
508
393
|
/// Converts a Ruby Time object to `CompactDateTime`.
|
|
@@ -571,7 +456,8 @@ fn time_to_compact_datetime(ruby: &Ruby, ruby_time: Value) -> Result<CompactDate
|
|
|
571
456
|
/// Returns an error if the Ruby Time class cannot be accessed or if
|
|
572
457
|
/// creating the Time object fails.
|
|
573
458
|
fn compact_datetime_to_time(ruby: &Ruby, compact_time: CompactDateTime) -> Result<Value, Error> {
|
|
574
|
-
// Direct access to epoch seconds - CompactDateTime has no sub-second
|
|
459
|
+
// Direct access to epoch seconds - CompactDateTime has no sub-second
|
|
460
|
+
// precision
|
|
575
461
|
let epoch_seconds = i64::from(compact_time.epoch_seconds());
|
|
576
462
|
|
|
577
463
|
// Create Ruby Time with zero nanoseconds (CompactDateTime precision limit)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
//! Keyed-state vending for `Prosody::Context`.
|
|
2
|
+
//!
|
|
3
|
+
//! Each vend method opens the handle for one registered collection in the
|
|
4
|
+
//! current event and wraps it in its native handle class. Vending verifies the
|
|
5
|
+
//! collection's registration in core. It opens no span: vended handles outlive
|
|
6
|
+
//! the call, and every handle operation opens its own span.
|
|
7
|
+
|
|
8
|
+
use super::Context;
|
|
9
|
+
use crate::bridge::Bridge;
|
|
10
|
+
use crate::handler::state::{
|
|
11
|
+
NativeJsonDequeState, NativeJsonMapState, NativeJsonValueState, NativeMessageDequeState,
|
|
12
|
+
NativeMessageMapState, NativeMessageValueState, NativeSetState, state_error,
|
|
13
|
+
};
|
|
14
|
+
use crate::id;
|
|
15
|
+
use magnus::{Error, Module, RClass, RString, Ruby, method};
|
|
16
|
+
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
17
|
+
use prosody::consumer::event_context::ErasedStateError;
|
|
18
|
+
use std::sync::Arc;
|
|
19
|
+
|
|
20
|
+
/// Wraps a core handle in its native handle class.
|
|
21
|
+
///
|
|
22
|
+
/// # Errors
|
|
23
|
+
///
|
|
24
|
+
/// Returns a permanent state error if the name is unregistered or its
|
|
25
|
+
/// registered identity mismatches.
|
|
26
|
+
fn vend<T: ?Sized, N>(
|
|
27
|
+
ruby: &Ruby,
|
|
28
|
+
this: &Context,
|
|
29
|
+
handle: Result<Box<T>, ErasedStateError>,
|
|
30
|
+
new: fn(Arc<T>, Bridge, Arc<TextMapCompositePropagator>) -> N,
|
|
31
|
+
) -> Result<N, Error> {
|
|
32
|
+
let handle = handle.map_err(|error| state_error(ruby, &error))?;
|
|
33
|
+
Ok(new(
|
|
34
|
+
Arc::from(handle),
|
|
35
|
+
this.bridge.clone(),
|
|
36
|
+
Arc::clone(&this.propagator),
|
|
37
|
+
))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn value_state(ruby: &Ruby, this: &Context, name: RString) -> Result<NativeJsonValueState, Error> {
|
|
41
|
+
vend(
|
|
42
|
+
ruby,
|
|
43
|
+
this,
|
|
44
|
+
this.inner.value_state(&name.to_string()?),
|
|
45
|
+
NativeJsonValueState::new,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn map_state(ruby: &Ruby, this: &Context, name: RString) -> Result<NativeJsonMapState, Error> {
|
|
50
|
+
vend(
|
|
51
|
+
ruby,
|
|
52
|
+
this,
|
|
53
|
+
this.inner.map_state(&name.to_string()?),
|
|
54
|
+
NativeJsonMapState::new,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn set_state(ruby: &Ruby, this: &Context, name: RString) -> Result<NativeSetState, Error> {
|
|
59
|
+
vend(
|
|
60
|
+
ruby,
|
|
61
|
+
this,
|
|
62
|
+
this.inner.set_state(&name.to_string()?),
|
|
63
|
+
NativeSetState::new,
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn deque_state(ruby: &Ruby, this: &Context, name: RString) -> Result<NativeJsonDequeState, Error> {
|
|
68
|
+
vend(
|
|
69
|
+
ruby,
|
|
70
|
+
this,
|
|
71
|
+
this.inner.deque_state(&name.to_string()?),
|
|
72
|
+
NativeJsonDequeState::new,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn message_value_state(
|
|
77
|
+
ruby: &Ruby,
|
|
78
|
+
this: &Context,
|
|
79
|
+
name: RString,
|
|
80
|
+
) -> Result<NativeMessageValueState, Error> {
|
|
81
|
+
vend(
|
|
82
|
+
ruby,
|
|
83
|
+
this,
|
|
84
|
+
this.inner.message_value_state(&name.to_string()?),
|
|
85
|
+
NativeMessageValueState::new,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn message_map_state(
|
|
90
|
+
ruby: &Ruby,
|
|
91
|
+
this: &Context,
|
|
92
|
+
name: RString,
|
|
93
|
+
) -> Result<NativeMessageMapState, Error> {
|
|
94
|
+
vend(
|
|
95
|
+
ruby,
|
|
96
|
+
this,
|
|
97
|
+
this.inner.message_map_state(&name.to_string()?),
|
|
98
|
+
NativeMessageMapState::new,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn message_deque_state(
|
|
103
|
+
ruby: &Ruby,
|
|
104
|
+
this: &Context,
|
|
105
|
+
name: RString,
|
|
106
|
+
) -> Result<NativeMessageDequeState, Error> {
|
|
107
|
+
vend(
|
|
108
|
+
ruby,
|
|
109
|
+
this,
|
|
110
|
+
this.inner.message_deque_state(&name.to_string()?),
|
|
111
|
+
NativeMessageDequeState::new,
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Registers the keyed-state vend methods on the `Prosody::Context` class.
|
|
116
|
+
///
|
|
117
|
+
/// # Errors
|
|
118
|
+
///
|
|
119
|
+
/// Returns a Magnus error if a method definition fails.
|
|
120
|
+
pub(super) fn define_methods(ruby: &Ruby, class: RClass) -> Result<(), Error> {
|
|
121
|
+
class.define_method(id!(ruby, "value_state"), method!(value_state, 1))?;
|
|
122
|
+
class.define_method(id!(ruby, "map_state"), method!(map_state, 1))?;
|
|
123
|
+
class.define_method(id!(ruby, "set_state"), method!(set_state, 1))?;
|
|
124
|
+
class.define_method(id!(ruby, "deque_state"), method!(deque_state, 1))?;
|
|
125
|
+
class.define_method(
|
|
126
|
+
id!(ruby, "message_value_state"),
|
|
127
|
+
method!(message_value_state, 1),
|
|
128
|
+
)?;
|
|
129
|
+
class.define_method(
|
|
130
|
+
id!(ruby, "message_map_state"),
|
|
131
|
+
method!(message_map_state, 1),
|
|
132
|
+
)?;
|
|
133
|
+
class.define_method(
|
|
134
|
+
id!(ruby, "message_deque_state"),
|
|
135
|
+
method!(message_deque_state, 1),
|
|
136
|
+
)?;
|
|
137
|
+
Ok(())
|
|
138
|
+
}
|
|
@@ -7,6 +7,7 @@ use crate::{ROOT_MOD, id};
|
|
|
7
7
|
use educe::Educe;
|
|
8
8
|
use magnus::value::ReprValue;
|
|
9
9
|
use magnus::{Error, Module, RClass, Ruby, Value, method};
|
|
10
|
+
use prosody::SourceSystem;
|
|
10
11
|
use prosody::consumer::Keyed;
|
|
11
12
|
use prosody::consumer::message::ConsumerMessage;
|
|
12
13
|
use serde_magnus::serialize;
|
|
@@ -118,6 +119,17 @@ impl Message {
|
|
|
118
119
|
serialize(ruby, this.inner.payload())
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
/// Returns the producer's source system, or `None` when the record has
|
|
123
|
+
/// no source system header.
|
|
124
|
+
fn source_system(&self) -> Option<&str> {
|
|
125
|
+
self.inner.source_system().map(SourceSystem::as_str)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Reports whether the producer requested a response to this record.
|
|
129
|
+
fn response_requested(&self) -> bool {
|
|
130
|
+
self.inner.response_requested()
|
|
131
|
+
}
|
|
132
|
+
|
|
121
133
|
/// Clones the wrapped `ConsumerMessage` for a message-collection write.
|
|
122
134
|
///
|
|
123
135
|
/// The wrapper holds the real `ConsumerMessage`, so a message write clones
|
|
@@ -157,6 +169,14 @@ impl ExciseMessage {
|
|
|
157
169
|
(epoch_micros, ruby.to_symbol("microsecond")),
|
|
158
170
|
)
|
|
159
171
|
}
|
|
172
|
+
|
|
173
|
+
fn source_system(&self) -> Option<&str> {
|
|
174
|
+
self.inner.source_system().map(SourceSystem::as_str)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fn response_requested(&self) -> bool {
|
|
178
|
+
self.inner.response_requested()
|
|
179
|
+
}
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
impl From<ConsumerMessage<serde_json::Value>> for Message {
|
|
@@ -199,6 +219,14 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
199
219
|
class.define_method(id!(ruby, "key"), method!(Message::key, 0))?;
|
|
200
220
|
class.define_method(id!(ruby, "timestamp"), method!(Message::timestamp, 0))?;
|
|
201
221
|
class.define_method(id!(ruby, "payload"), method!(Message::payload, 0))?;
|
|
222
|
+
class.define_method(
|
|
223
|
+
id!(ruby, "source_system"),
|
|
224
|
+
method!(Message::source_system, 0),
|
|
225
|
+
)?;
|
|
226
|
+
class.define_method(
|
|
227
|
+
id!(ruby, "response_requested?"),
|
|
228
|
+
method!(Message::response_requested, 0),
|
|
229
|
+
)?;
|
|
202
230
|
|
|
203
231
|
let class = module.define_class(id!(ruby, "ExciseMessage"), ruby.class_object())?;
|
|
204
232
|
class.define_method(id!(ruby, "topic"), method!(ExciseMessage::topic, 0))?;
|
|
@@ -206,6 +234,14 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
206
234
|
class.define_method(id!(ruby, "offset"), method!(ExciseMessage::offset, 0))?;
|
|
207
235
|
class.define_method(id!(ruby, "key"), method!(ExciseMessage::key, 0))?;
|
|
208
236
|
class.define_method(id!(ruby, "timestamp"), method!(ExciseMessage::timestamp, 0))?;
|
|
237
|
+
class.define_method(
|
|
238
|
+
id!(ruby, "source_system"),
|
|
239
|
+
method!(ExciseMessage::source_system, 0),
|
|
240
|
+
)?;
|
|
241
|
+
class.define_method(
|
|
242
|
+
id!(ruby, "response_requested?"),
|
|
243
|
+
method!(ExciseMessage::response_requested, 0),
|
|
244
|
+
)?;
|
|
209
245
|
|
|
210
246
|
Ok(())
|
|
211
247
|
}
|