prosody 0.3.0 → 0.5.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/.cargo/config.toml +3 -0
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +395 -0
- data/ARCHITECTURE.md +14 -4
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +1 -0
- data/CONFIGURATION.md +167 -0
- data/Cargo.lock +1115 -645
- data/Cargo.toml +7 -6
- data/README.md +436 -146
- data/Rakefile +11 -1
- data/examples/keyed_state.rb +70 -0
- data/examples/keyed_state.rbs +18 -0
- data/examples/keyed_state_windowing.rb +55 -0
- data/examples/keyed_state_windowing.rbs +16 -0
- data/ext/prosody/Cargo.toml +1 -0
- data/ext/prosody/src/admin.rs +1 -5
- data/ext/prosody/src/bridge/mod.rs +17 -32
- data/ext/prosody/src/client/config.rs +501 -28
- data/ext/prosody/src/client/mod.rs +167 -74
- data/ext/prosody/src/client/request.rs +132 -0
- data/ext/prosody/src/client/support.rs +122 -0
- data/ext/prosody/src/handler/context.rs +150 -5
- data/ext/prosody/src/handler/message.rs +67 -0
- data/ext/prosody/src/handler/mod.rs +115 -85
- data/ext/prosody/src/handler/state/mod.rs +488 -0
- data/ext/prosody/src/handler/state/registration.rs +104 -0
- data/ext/prosody/src/handler/state/scan.rs +218 -0
- data/ext/prosody/src/lib.rs +15 -3
- data/ext/prosody/src/published.rs +273 -0
- data/ext/prosody/src/scheduler/mod.rs +2 -2
- data/ext/prosody/src/scheduler/processor.rs +2 -2
- data/ext/prosody/src/scheduler/result.rs +7 -4
- data/ext/prosody/src/util.rs +86 -5
- data/lib/prosody/configuration.rb +71 -11
- data/lib/prosody/handler.rb +65 -8
- data/lib/prosody/native_stubs.rb +550 -9
- data/lib/prosody/request.rb +45 -0
- data/lib/prosody/state.rb +816 -0
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +6 -0
- data/release-please-config.json +4 -0
- data/sig/configuration.rbs +70 -11
- data/sig/handler.rbs +17 -5
- data/sig/processor.rbs +28 -12
- data/sig/prosody.rbs +53 -7
- data/sig/request.rbs +66 -0
- data/sig/sentry.rbs +6 -0
- data/sig/state.rbs +390 -0
- data/steep_expectations.yml +57 -0
- data/typecheck/payload_types.rb +54 -0
- data/typecheck/payload_types.rbs +22 -0
- data/typecheck_negative/payload_types.rb +20 -0
- data/typecheck_negative/payload_types.rbs +9 -0
- metadata +32 -9
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
//! Concrete native handles for keyed state.
|
|
2
|
+
//!
|
|
3
|
+
//! Each Magnus class holds one collection type and one payload type. JSON
|
|
4
|
+
//! values use `serde_magnus`. Message collections return the same [`Message`]
|
|
5
|
+
//! objects that handlers receive.
|
|
6
|
+
//!
|
|
7
|
+
//! [`Bridge::wait_for`] yields the calling fiber while tokio drives each
|
|
8
|
+
//! operation. The active OpenTelemetry carrier joins the core operation span.
|
|
9
|
+
//!
|
|
10
|
+
//! [`ErasedStateError::category`] selects the Ruby error class. Caller mistakes
|
|
11
|
+
//! remain transient, so Prosody does not discard the message.
|
|
12
|
+
|
|
13
|
+
use crate::ROOT_MOD;
|
|
14
|
+
use crate::bridge::Bridge;
|
|
15
|
+
use crate::handler::message::Message;
|
|
16
|
+
use crate::tracing_util::extract_opentelemetry_context;
|
|
17
|
+
use magnus::value::{Lazy, ReprValue};
|
|
18
|
+
use magnus::{Error, ExceptionClass, IntoValue, Module, Ruby, StaticSymbol, TryConvert, Value};
|
|
19
|
+
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
20
|
+
use opentelemetry::trace::FutureExt;
|
|
21
|
+
use prosody::consumer::event_context::{
|
|
22
|
+
DynDequeState, DynMapState, DynValueState, ErasedCategory, ErasedStateError,
|
|
23
|
+
};
|
|
24
|
+
use prosody::consumer::message::ConsumerMessage;
|
|
25
|
+
use prosody::state::Direction;
|
|
26
|
+
use serde_json::Value as JsonValue;
|
|
27
|
+
use serde_magnus::{deserialize, serialize};
|
|
28
|
+
use std::sync::Arc;
|
|
29
|
+
use tracing::Span;
|
|
30
|
+
|
|
31
|
+
/// Lazily resolved `Prosody::PermanentStateError` class (defined in Ruby).
|
|
32
|
+
#[allow(
|
|
33
|
+
clippy::expect_used,
|
|
34
|
+
reason = "mirrors bridge.rs QUEUE_CLASS Lazy pattern"
|
|
35
|
+
)]
|
|
36
|
+
static PERMANENT_STATE_ERROR: Lazy<ExceptionClass> = Lazy::new(|ruby| {
|
|
37
|
+
ruby.get_inner(&ROOT_MOD)
|
|
38
|
+
.const_get("PermanentStateError")
|
|
39
|
+
.expect("Prosody::PermanentStateError")
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/// Lazily resolved `Prosody::TransientStateError` class (defined in Ruby).
|
|
43
|
+
#[allow(
|
|
44
|
+
clippy::expect_used,
|
|
45
|
+
reason = "mirrors bridge.rs QUEUE_CLASS Lazy pattern"
|
|
46
|
+
)]
|
|
47
|
+
static TRANSIENT_STATE_ERROR: Lazy<ExceptionClass> = Lazy::new(|ruby| {
|
|
48
|
+
ruby.get_inner(&ROOT_MOD)
|
|
49
|
+
.const_get("TransientStateError")
|
|
50
|
+
.expect("Prosody::TransientStateError")
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/// Lazily resolved `Prosody::NullValueError` class (defined in Ruby).
|
|
54
|
+
#[allow(
|
|
55
|
+
clippy::expect_used,
|
|
56
|
+
reason = "mirrors bridge.rs QUEUE_CLASS Lazy pattern"
|
|
57
|
+
)]
|
|
58
|
+
static NULL_VALUE_ERROR: Lazy<ExceptionClass> = Lazy::new(|ruby| {
|
|
59
|
+
ruby.get_inner(&ROOT_MOD)
|
|
60
|
+
.const_get("NullValueError")
|
|
61
|
+
.expect("Prosody::NullValueError")
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/// Maps an erased state error to the matching Ruby state-error class.
|
|
65
|
+
///
|
|
66
|
+
/// The category is data, so this never parses the human message. Because the
|
|
67
|
+
/// Ruby classes subclass the existing error hierarchy, the result bridge's
|
|
68
|
+
/// `#permanent?` path reclassifies a rethrown error with no change.
|
|
69
|
+
pub(crate) fn state_error(ruby: &Ruby, error: &ErasedStateError) -> Error {
|
|
70
|
+
let class = match error.category() {
|
|
71
|
+
ErasedCategory::Permanent => ruby.get_inner(&PERMANENT_STATE_ERROR),
|
|
72
|
+
ErasedCategory::Transient => ruby.get_inner(&TRANSIENT_STATE_ERROR),
|
|
73
|
+
};
|
|
74
|
+
Error::new(class, error.message().to_owned())
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Builds a transient state error for a caller-caused condition the glue
|
|
78
|
+
/// detects (a wrong item shape, an invalid direction token, an unrepresentable
|
|
79
|
+
/// value).
|
|
80
|
+
fn transient_state_error(ruby: &Ruby, message: String) -> Error {
|
|
81
|
+
Error::new(ruby.get_inner(&TRANSIENT_STATE_ERROR), message)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Builds a null-value error for a rejected JSON-null write.
|
|
85
|
+
fn null_value_error(ruby: &Ruby, message: String) -> Error {
|
|
86
|
+
Error::new(ruby.get_inner(&NULL_VALUE_ERROR), message)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Converts an optional JSON item into a Ruby value or `nil`.
|
|
90
|
+
fn json_or_nil(ruby: &Ruby, item: Option<JsonValue>) -> Result<Value, Error> {
|
|
91
|
+
match item {
|
|
92
|
+
Some(value) => serialize(ruby, &value),
|
|
93
|
+
None => Ok(ruby.qnil().as_value()),
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// Converts an optional message item into a `Prosody::Message` or `nil`.
|
|
98
|
+
#[allow(
|
|
99
|
+
clippy::unnecessary_wraps,
|
|
100
|
+
reason = "parity with json_or_nil for uniform call sites"
|
|
101
|
+
)]
|
|
102
|
+
fn message_or_nil(ruby: &Ruby, item: Option<ConsumerMessage<JsonValue>>) -> Result<Value, Error> {
|
|
103
|
+
match item {
|
|
104
|
+
Some(message) => Ok(Message::from(message).into_value_with(ruby)),
|
|
105
|
+
None => Ok(ruby.qnil().as_value()),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Converts a Ruby argument into a storable JSON item.
|
|
110
|
+
///
|
|
111
|
+
/// A value with no JSON representation is a caller mistake and rejects
|
|
112
|
+
/// transient. JSON `null` is rejected with a [`crate::NullValueError`] naming
|
|
113
|
+
/// the deletion verb via `null_advice`.
|
|
114
|
+
fn json_write_item(ruby: &Ruby, value: Value, null_advice: &str) -> Result<JsonValue, Error> {
|
|
115
|
+
let value: JsonValue = deserialize(ruby, value).map_err(|error| {
|
|
116
|
+
transient_state_error(ruby, format!("value is not representable as JSON: {error}"))
|
|
117
|
+
})?;
|
|
118
|
+
if value.is_null() {
|
|
119
|
+
return Err(null_value_error(
|
|
120
|
+
ruby,
|
|
121
|
+
format!("JSON null is not a storable value{null_advice}"),
|
|
122
|
+
));
|
|
123
|
+
}
|
|
124
|
+
Ok(value)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Converts a Ruby argument into a storable message item.
|
|
128
|
+
///
|
|
129
|
+
/// A non-message argument is a caller mistake (a JSON payload cannot be stored
|
|
130
|
+
/// in a message collection) and rejects transient. The wrapped
|
|
131
|
+
/// [`ConsumerMessage`] is cloned; see [`Message::consumer_message`].
|
|
132
|
+
fn message_write_item(
|
|
133
|
+
ruby: &Ruby,
|
|
134
|
+
value: Value,
|
|
135
|
+
shape_advice: &str,
|
|
136
|
+
) -> Result<ConsumerMessage<JsonValue>, Error> {
|
|
137
|
+
let message = <&Message>::try_convert(value).map_err(|_| {
|
|
138
|
+
transient_state_error(ruby, format!("expected a Prosody::Message{shape_advice}"))
|
|
139
|
+
})?;
|
|
140
|
+
Ok(message.consumer_message())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Parses a scan-direction token into the core [`Direction`].
|
|
144
|
+
///
|
|
145
|
+
/// An invalid token is a caller mistake and rejects transient.
|
|
146
|
+
pub(crate) fn parse_direction(ruby: &Ruby, direction: StaticSymbol) -> Result<Direction, Error> {
|
|
147
|
+
match direction.name()? {
|
|
148
|
+
"forward" => Ok(Direction::Forward),
|
|
149
|
+
"backward" => Ok(Direction::Backward),
|
|
150
|
+
other => Err(transient_state_error(
|
|
151
|
+
ruby,
|
|
152
|
+
format!("direction: expected :forward or :backward, got :{other}"),
|
|
153
|
+
)),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Drives an erased async op that returns `Result<_, ErasedStateError>` through
|
|
158
|
+
/// [`Bridge::wait_for`] with the extracted carrier active, yielding the op's
|
|
159
|
+
/// `Ok` value (state error mapped to the matching Ruby class).
|
|
160
|
+
macro_rules! run_op {
|
|
161
|
+
($ruby:expr, $this:expr, $handle:expr, $call:ident ( $($arg:expr),* )) => {{
|
|
162
|
+
let handle = Arc::clone($handle);
|
|
163
|
+
let context = extract_opentelemetry_context($ruby, &$this.propagator)?;
|
|
164
|
+
$this
|
|
165
|
+
.bridge
|
|
166
|
+
.wait_for(
|
|
167
|
+
$ruby,
|
|
168
|
+
async move { handle.$call($($arg),*).with_context(context).await },
|
|
169
|
+
Span::current(),
|
|
170
|
+
)?
|
|
171
|
+
.map_err(|error| state_error($ruby, &error))
|
|
172
|
+
}};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Drives an infallible erased async op (returning `()`) through
|
|
176
|
+
/// [`Bridge::wait_for`] with the extracted carrier active.
|
|
177
|
+
macro_rules! run_infallible {
|
|
178
|
+
($ruby:expr, $this:expr, $handle:expr, $call:ident ()) => {{
|
|
179
|
+
let handle = Arc::clone($handle);
|
|
180
|
+
let context = extract_opentelemetry_context($ruby, &$this.propagator)?;
|
|
181
|
+
$this.bridge.wait_for(
|
|
182
|
+
$ruby,
|
|
183
|
+
async move { handle.$call().with_context(context).await },
|
|
184
|
+
Span::current(),
|
|
185
|
+
)?
|
|
186
|
+
}};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
macro_rules! value_state {
|
|
190
|
+
($name:ident, $class:literal, $item:ty, $prepare:expr, $restore:expr) => {
|
|
191
|
+
/// Native single-value handle with one payload type.
|
|
192
|
+
#[magnus::wrap(class = $class)]
|
|
193
|
+
pub struct $name {
|
|
194
|
+
state: Arc<dyn DynValueState<$item>>,
|
|
195
|
+
bridge: Bridge,
|
|
196
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
impl $name {
|
|
200
|
+
pub(crate) fn new(
|
|
201
|
+
state: Arc<dyn DynValueState<$item>>,
|
|
202
|
+
bridge: Bridge,
|
|
203
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
204
|
+
) -> Self {
|
|
205
|
+
Self {
|
|
206
|
+
state,
|
|
207
|
+
bridge,
|
|
208
|
+
propagator,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn get(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
213
|
+
($restore)(ruby, run_op!(ruby, this, &this.state, get())?)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fn set(ruby: &Ruby, this: &Self, value: Value) -> Result<Value, Error> {
|
|
217
|
+
let item = ($prepare)(ruby, value)?;
|
|
218
|
+
run_op!(ruby, this, &this.state, set(item))?;
|
|
219
|
+
Ok(ruby.qnil().as_value())
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fn clear(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
223
|
+
run_op!(ruby, this, &this.state, clear())?;
|
|
224
|
+
Ok(ruby.qnil().as_value())
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fn commit(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
228
|
+
run_op!(ruby, this, &this.state, commit())?;
|
|
229
|
+
Ok(ruby.qnil().as_value())
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn rollback(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
233
|
+
run_infallible!(ruby, this, &this.state, rollback());
|
|
234
|
+
Ok(ruby.qnil().as_value())
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
value_state!(
|
|
241
|
+
NativeJsonValueState,
|
|
242
|
+
"Prosody::NativeJsonValueState",
|
|
243
|
+
JsonValue,
|
|
244
|
+
|ruby, value| json_write_item(ruby, value, "; use clear to remove the value"),
|
|
245
|
+
json_or_nil
|
|
246
|
+
);
|
|
247
|
+
value_state!(
|
|
248
|
+
NativeMessageValueState,
|
|
249
|
+
"Prosody::NativeMessageValueState",
|
|
250
|
+
ConsumerMessage<JsonValue>,
|
|
251
|
+
|ruby, value| message_write_item(
|
|
252
|
+
ruby,
|
|
253
|
+
value,
|
|
254
|
+
"; use clear to delete a message value collection"
|
|
255
|
+
),
|
|
256
|
+
message_or_nil
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
macro_rules! map_state {
|
|
260
|
+
($name:ident, $class:literal, $item:ty, $scan:ident, $prepare:expr, $restore:expr) => {
|
|
261
|
+
/// Native ordered-map handle with one payload type.
|
|
262
|
+
#[magnus::wrap(class = $class)]
|
|
263
|
+
pub struct $name {
|
|
264
|
+
state: Arc<dyn DynMapState<$item>>,
|
|
265
|
+
bridge: Bridge,
|
|
266
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
impl $name {
|
|
270
|
+
pub(crate) fn new(
|
|
271
|
+
state: Arc<dyn DynMapState<$item>>,
|
|
272
|
+
bridge: Bridge,
|
|
273
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
274
|
+
) -> Self {
|
|
275
|
+
Self {
|
|
276
|
+
state,
|
|
277
|
+
bridge,
|
|
278
|
+
propagator,
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fn get(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
283
|
+
($restore)(ruby, run_op!(ruby, this, &this.state, get(key))?)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
fn contains_key(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
287
|
+
Ok(run_op!(ruby, this, &this.state, contains_key(key))?.into_value_with(ruby))
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fn get_many(ruby: &Ruby, this: &Self, keys: Vec<String>) -> Result<Value, Error> {
|
|
291
|
+
let items = run_op!(ruby, this, &this.state, get_many(keys))?;
|
|
292
|
+
let array =
|
|
293
|
+
ruby.ary_try_from_iter(items.into_iter().map(|item| ($restore)(ruby, item)))?;
|
|
294
|
+
Ok(array.as_value())
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
fn set(ruby: &Ruby, this: &Self, key: String, value: Value) -> Result<Value, Error> {
|
|
298
|
+
let item = ($prepare)(ruby, value)?;
|
|
299
|
+
run_op!(ruby, this, &this.state, set(key, item))?;
|
|
300
|
+
Ok(ruby.qnil().as_value())
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
fn remove(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
304
|
+
run_op!(ruby, this, &this.state, remove(key))?;
|
|
305
|
+
Ok(ruby.qnil().as_value())
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn clear(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
309
|
+
run_op!(ruby, this, &this.state, clear())?;
|
|
310
|
+
Ok(ruby.qnil().as_value())
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn scan(ruby: &Ruby, this: &Self, direction: StaticSymbol) -> Result<$scan, Error> {
|
|
314
|
+
let direction = parse_direction(ruby, direction)?;
|
|
315
|
+
let _guard = extract_opentelemetry_context(ruby, &this.propagator)?.attach();
|
|
316
|
+
$scan::new(
|
|
317
|
+
ruby,
|
|
318
|
+
this.state.scan(direction),
|
|
319
|
+
this.bridge.clone(),
|
|
320
|
+
Arc::clone(&this.propagator),
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
fn keys(
|
|
325
|
+
ruby: &Ruby,
|
|
326
|
+
this: &Self,
|
|
327
|
+
direction: StaticSymbol,
|
|
328
|
+
) -> Result<NativeMapKeyScan, Error> {
|
|
329
|
+
let direction = parse_direction(ruby, direction)?;
|
|
330
|
+
let _guard = extract_opentelemetry_context(ruby, &this.propagator)?.attach();
|
|
331
|
+
NativeMapKeyScan::new(
|
|
332
|
+
ruby,
|
|
333
|
+
this.state.keys(direction),
|
|
334
|
+
this.bridge.clone(),
|
|
335
|
+
Arc::clone(&this.propagator),
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
fn commit(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
340
|
+
run_op!(ruby, this, &this.state, commit())?;
|
|
341
|
+
Ok(ruby.qnil().as_value())
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
fn rollback(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
345
|
+
run_infallible!(ruby, this, &this.state, rollback());
|
|
346
|
+
Ok(ruby.qnil().as_value())
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
map_state!(
|
|
353
|
+
NativeJsonMapState,
|
|
354
|
+
"Prosody::NativeJsonMapState",
|
|
355
|
+
JsonValue,
|
|
356
|
+
NativeJsonMapScan,
|
|
357
|
+
|ruby, value| json_write_item(ruby, value, "; use delete(key) to remove the entry"),
|
|
358
|
+
json_or_nil
|
|
359
|
+
);
|
|
360
|
+
map_state!(
|
|
361
|
+
NativeMessageMapState,
|
|
362
|
+
"Prosody::NativeMessageMapState",
|
|
363
|
+
ConsumerMessage<JsonValue>,
|
|
364
|
+
NativeMessageMapScan,
|
|
365
|
+
|ruby, value| message_write_item(
|
|
366
|
+
ruby,
|
|
367
|
+
value,
|
|
368
|
+
"; use delete(key) to remove a message map entry"
|
|
369
|
+
),
|
|
370
|
+
message_or_nil
|
|
371
|
+
);
|
|
372
|
+
macro_rules! deque_state {
|
|
373
|
+
($name:ident, $class:literal, $item:ty, $scan:ident, $prepare:expr, $restore:expr) => {
|
|
374
|
+
/// Native deque handle with one payload type.
|
|
375
|
+
#[magnus::wrap(class = $class)]
|
|
376
|
+
pub struct $name {
|
|
377
|
+
state: Arc<dyn DynDequeState<$item>>,
|
|
378
|
+
bridge: Bridge,
|
|
379
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
impl $name {
|
|
383
|
+
pub(crate) fn new(
|
|
384
|
+
state: Arc<dyn DynDequeState<$item>>,
|
|
385
|
+
bridge: Bridge,
|
|
386
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
387
|
+
) -> Self {
|
|
388
|
+
Self {
|
|
389
|
+
state,
|
|
390
|
+
bridge,
|
|
391
|
+
propagator,
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn len(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
396
|
+
Ok(run_op!(ruby, this, &this.state, len())?.into_value_with(ruby))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
fn is_empty(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
400
|
+
Ok(run_op!(ruby, this, &this.state, is_empty())?.into_value_with(ruby))
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fn get(ruby: &Ruby, this: &Self, index: usize) -> Result<Value, Error> {
|
|
404
|
+
($restore)(ruby, run_op!(ruby, this, &this.state, get(index))?)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
fn peek_front(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
408
|
+
($restore)(ruby, run_op!(ruby, this, &this.state, peek_front())?)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
fn peek_back(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
412
|
+
($restore)(ruby, run_op!(ruby, this, &this.state, peek_back())?)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
fn push_back(ruby: &Ruby, this: &Self, value: Value) -> Result<Value, Error> {
|
|
416
|
+
let item = ($prepare)(ruby, value)?;
|
|
417
|
+
run_op!(ruby, this, &this.state, push_back(item))?;
|
|
418
|
+
Ok(ruby.qnil().as_value())
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
fn push_front(ruby: &Ruby, this: &Self, value: Value) -> Result<Value, Error> {
|
|
422
|
+
let item = ($prepare)(ruby, value)?;
|
|
423
|
+
run_op!(ruby, this, &this.state, push_front(item))?;
|
|
424
|
+
Ok(ruby.qnil().as_value())
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
fn pop_front(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
428
|
+
($restore)(ruby, run_op!(ruby, this, &this.state, pop_front())?)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
fn pop_back(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
432
|
+
($restore)(ruby, run_op!(ruby, this, &this.state, pop_back())?)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fn clear(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
436
|
+
run_op!(ruby, this, &this.state, clear())?;
|
|
437
|
+
Ok(ruby.qnil().as_value())
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
fn scan(ruby: &Ruby, this: &Self, direction: StaticSymbol) -> Result<$scan, Error> {
|
|
441
|
+
let direction = parse_direction(ruby, direction)?;
|
|
442
|
+
let _guard = extract_opentelemetry_context(ruby, &this.propagator)?.attach();
|
|
443
|
+
$scan::new(
|
|
444
|
+
ruby,
|
|
445
|
+
this.state.scan(direction),
|
|
446
|
+
this.bridge.clone(),
|
|
447
|
+
Arc::clone(&this.propagator),
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
fn commit(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
452
|
+
run_op!(ruby, this, &this.state, commit())?;
|
|
453
|
+
Ok(ruby.qnil().as_value())
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
fn rollback(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
457
|
+
run_infallible!(ruby, this, &this.state, rollback());
|
|
458
|
+
Ok(ruby.qnil().as_value())
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
deque_state!(
|
|
465
|
+
NativeJsonDequeState,
|
|
466
|
+
"Prosody::NativeJsonDequeState",
|
|
467
|
+
JsonValue,
|
|
468
|
+
NativeJsonDequeScan,
|
|
469
|
+
|ruby, value| json_write_item(ruby, value, " in a deque"),
|
|
470
|
+
json_or_nil
|
|
471
|
+
);
|
|
472
|
+
deque_state!(
|
|
473
|
+
NativeMessageDequeState,
|
|
474
|
+
"Prosody::NativeMessageDequeState",
|
|
475
|
+
ConsumerMessage<JsonValue>,
|
|
476
|
+
NativeMessageDequeScan,
|
|
477
|
+
|ruby, value| message_write_item(ruby, value, " to push into a message deque"),
|
|
478
|
+
message_or_nil
|
|
479
|
+
);
|
|
480
|
+
mod scan;
|
|
481
|
+
|
|
482
|
+
pub(crate) use scan::{
|
|
483
|
+
NativeJsonDequeScan, NativeJsonMapScan, NativeMapKeyScan, NativeMessageDequeScan,
|
|
484
|
+
NativeMessageMapScan, published_deque_scan, published_map_key_scan, published_map_scan,
|
|
485
|
+
};
|
|
486
|
+
mod registration;
|
|
487
|
+
|
|
488
|
+
pub(crate) use registration::register;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//! Registration for concrete native state classes.
|
|
2
|
+
|
|
3
|
+
use super::{
|
|
4
|
+
NativeJsonDequeScan, NativeJsonDequeState, NativeJsonMapScan, NativeJsonMapState,
|
|
5
|
+
NativeJsonValueState, NativeMapKeyScan, NativeMessageDequeScan, NativeMessageDequeState,
|
|
6
|
+
NativeMessageMapScan, NativeMessageMapState, NativeMessageValueState,
|
|
7
|
+
};
|
|
8
|
+
use crate::{ROOT_MOD, id};
|
|
9
|
+
use magnus::{Error, Module, Ruby, method};
|
|
10
|
+
|
|
11
|
+
macro_rules! register_value {
|
|
12
|
+
($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
|
|
13
|
+
let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
|
|
14
|
+
class.define_method(id!($ruby, "get"), method!(<$type>::get, 0))?;
|
|
15
|
+
class.define_method(id!($ruby, "set"), method!(<$type>::set, 1))?;
|
|
16
|
+
class.define_method(id!($ruby, "clear"), method!(<$type>::clear, 0))?;
|
|
17
|
+
class.define_method(id!($ruby, "commit"), method!(<$type>::commit, 0))?;
|
|
18
|
+
class.define_method(id!($ruby, "rollback"), method!(<$type>::rollback, 0))?;
|
|
19
|
+
}};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
macro_rules! register_map {
|
|
23
|
+
($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
|
|
24
|
+
let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
|
|
25
|
+
class.define_method(id!($ruby, "get"), method!(<$type>::get, 1))?;
|
|
26
|
+
class.define_method(
|
|
27
|
+
id!($ruby, "contains_key"),
|
|
28
|
+
method!(<$type>::contains_key, 1),
|
|
29
|
+
)?;
|
|
30
|
+
class.define_method(id!($ruby, "get_many"), method!(<$type>::get_many, 1))?;
|
|
31
|
+
class.define_method(id!($ruby, "set"), method!(<$type>::set, 2))?;
|
|
32
|
+
class.define_method(id!($ruby, "remove"), method!(<$type>::remove, 1))?;
|
|
33
|
+
class.define_method(id!($ruby, "clear"), method!(<$type>::clear, 0))?;
|
|
34
|
+
class.define_method(id!($ruby, "scan"), method!(<$type>::scan, 1))?;
|
|
35
|
+
class.define_method(id!($ruby, "keys"), method!(<$type>::keys, 1))?;
|
|
36
|
+
class.define_method(id!($ruby, "commit"), method!(<$type>::commit, 0))?;
|
|
37
|
+
class.define_method(id!($ruby, "rollback"), method!(<$type>::rollback, 0))?;
|
|
38
|
+
}};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
macro_rules! register_deque {
|
|
42
|
+
($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
|
|
43
|
+
let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
|
|
44
|
+
class.define_method(id!($ruby, "len"), method!(<$type>::len, 0))?;
|
|
45
|
+
class.define_method(id!($ruby, "is_empty"), method!(<$type>::is_empty, 0))?;
|
|
46
|
+
class.define_method(id!($ruby, "get"), method!(<$type>::get, 1))?;
|
|
47
|
+
class.define_method(id!($ruby, "peek_front"), method!(<$type>::peek_front, 0))?;
|
|
48
|
+
class.define_method(id!($ruby, "peek_back"), method!(<$type>::peek_back, 0))?;
|
|
49
|
+
class.define_method(id!($ruby, "push_back"), method!(<$type>::push_back, 1))?;
|
|
50
|
+
class.define_method(id!($ruby, "push_front"), method!(<$type>::push_front, 1))?;
|
|
51
|
+
class.define_method(id!($ruby, "pop_front"), method!(<$type>::pop_front, 0))?;
|
|
52
|
+
class.define_method(id!($ruby, "pop_back"), method!(<$type>::pop_back, 0))?;
|
|
53
|
+
class.define_method(id!($ruby, "clear"), method!(<$type>::clear, 0))?;
|
|
54
|
+
class.define_method(id!($ruby, "scan"), method!(<$type>::scan, 1))?;
|
|
55
|
+
class.define_method(id!($ruby, "commit"), method!(<$type>::commit, 0))?;
|
|
56
|
+
class.define_method(id!($ruby, "rollback"), method!(<$type>::rollback, 0))?;
|
|
57
|
+
}};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
macro_rules! register_scan {
|
|
61
|
+
($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
|
|
62
|
+
let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
|
|
63
|
+
class.define_method(id!($ruby, "next"), method!(<$type>::next, 0))?;
|
|
64
|
+
class.define_method(id!($ruby, "close"), method!(<$type>::close, 0))?;
|
|
65
|
+
}};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Registers the concrete native state classes.
|
|
69
|
+
///
|
|
70
|
+
/// # Errors
|
|
71
|
+
///
|
|
72
|
+
/// Returns a Magnus error if class or method definition fails.
|
|
73
|
+
pub(crate) fn register(ruby: &Ruby) -> Result<(), Error> {
|
|
74
|
+
let module = ruby.get_inner(&ROOT_MOD);
|
|
75
|
+
|
|
76
|
+
register_value!(ruby, module, "NativeJsonValueState", NativeJsonValueState);
|
|
77
|
+
register_value!(
|
|
78
|
+
ruby,
|
|
79
|
+
module,
|
|
80
|
+
"NativeMessageValueState",
|
|
81
|
+
NativeMessageValueState
|
|
82
|
+
);
|
|
83
|
+
register_map!(ruby, module, "NativeJsonMapState", NativeJsonMapState);
|
|
84
|
+
register_map!(ruby, module, "NativeMessageMapState", NativeMessageMapState);
|
|
85
|
+
register_deque!(ruby, module, "NativeJsonDequeState", NativeJsonDequeState);
|
|
86
|
+
register_deque!(
|
|
87
|
+
ruby,
|
|
88
|
+
module,
|
|
89
|
+
"NativeMessageDequeState",
|
|
90
|
+
NativeMessageDequeState
|
|
91
|
+
);
|
|
92
|
+
register_scan!(ruby, module, "NativeJsonDequeScan", NativeJsonDequeScan);
|
|
93
|
+
register_scan!(ruby, module, "NativeJsonMapScan", NativeJsonMapScan);
|
|
94
|
+
register_scan!(
|
|
95
|
+
ruby,
|
|
96
|
+
module,
|
|
97
|
+
"NativeMessageDequeScan",
|
|
98
|
+
NativeMessageDequeScan
|
|
99
|
+
);
|
|
100
|
+
register_scan!(ruby, module, "NativeMessageMapScan", NativeMessageMapScan);
|
|
101
|
+
register_scan!(ruby, module, "NativeMapKeyScan", NativeMapKeyScan);
|
|
102
|
+
|
|
103
|
+
Ok(())
|
|
104
|
+
}
|