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
|
@@ -1,1300 +0,0 @@
|
|
|
1
|
-
//! # Configuration Module for Prosody Client
|
|
2
|
-
//!
|
|
3
|
-
//! This module handles the conversion between Ruby configuration objects and
|
|
4
|
-
//! the native Rust configuration structures needed by the Prosody library.
|
|
5
|
-
//! It defines serialization/deserialization logic and conversion traits that
|
|
6
|
-
//! transform Ruby configuration values into appropriate Prosody configuration
|
|
7
|
-
//! builders.
|
|
8
|
-
|
|
9
|
-
use magnus::{Error, Ruby, Value};
|
|
10
|
-
use prosody::PeerConfiguration;
|
|
11
|
-
use prosody::PeerEndpoint;
|
|
12
|
-
use prosody::cassandra::config::CassandraConfigurationBuilder;
|
|
13
|
-
use prosody::consumer::ConsumerConfigurationBuilder;
|
|
14
|
-
use prosody::consumer::KeyedStateConfiguration;
|
|
15
|
-
use prosody::consumer::SpanRelation;
|
|
16
|
-
use prosody::consumer::kafka_state::{message_deque_state, message_map_state, message_state};
|
|
17
|
-
use prosody::consumer::middleware::deduplication::DeduplicationConfigurationBuilder;
|
|
18
|
-
use prosody::consumer::middleware::defer::DeferConfigurationBuilder;
|
|
19
|
-
use prosody::consumer::middleware::monopolization::MonopolizationConfigurationBuilder;
|
|
20
|
-
use prosody::consumer::middleware::retry::RetryConfigurationBuilder;
|
|
21
|
-
use prosody::consumer::middleware::scheduler::SchedulerConfigurationBuilder;
|
|
22
|
-
use prosody::consumer::middleware::timeout::TimeoutConfigurationBuilder;
|
|
23
|
-
use prosody::consumer::middleware::topic::FailureTopicConfigurationBuilder;
|
|
24
|
-
use prosody::high_level::ConsumerBuilders;
|
|
25
|
-
use prosody::high_level::mode::Mode;
|
|
26
|
-
use prosody::loader::KafkaLoader;
|
|
27
|
-
use prosody::loader::KafkaLoaderConfiguration;
|
|
28
|
-
use prosody::producer::ProducerConfigurationBuilder;
|
|
29
|
-
use prosody::state::descriptor::{
|
|
30
|
-
DequeDescriptor, MapDescriptor, StateDescriptor, deque_state, map_state, value_state,
|
|
31
|
-
};
|
|
32
|
-
use prosody::state::order_codec::Utf8KeyCodec;
|
|
33
|
-
use prosody::subsystem::SubsystemName;
|
|
34
|
-
use prosody::telemetry::emitter::TelemetryEmitterConfiguration;
|
|
35
|
-
use prosody::timers::duration::CompactDuration;
|
|
36
|
-
use prosody::{ByteSize, JsonCodec};
|
|
37
|
-
use serde::{Deserialize, Deserializer};
|
|
38
|
-
use serde_magnus::deserialize;
|
|
39
|
-
use serde_untagged::UntaggedEnumVisitor;
|
|
40
|
-
use std::net::SocketAddr;
|
|
41
|
-
use std::num::NonZeroUsize;
|
|
42
|
-
use std::path::PathBuf;
|
|
43
|
-
use std::time::Duration;
|
|
44
|
-
|
|
45
|
-
/// Configuration structure for the Prosody client that maps Ruby configuration
|
|
46
|
-
/// values to their native Rust equivalents.
|
|
47
|
-
///
|
|
48
|
-
/// This structure contains all possible configuration options that can be
|
|
49
|
-
/// provided by the Ruby side, which are then converted to the appropriate
|
|
50
|
-
/// Prosody configuration builder types.
|
|
51
|
-
#[derive(Clone, Debug, Default, Deserialize)]
|
|
52
|
-
pub struct NativeConfiguration {
|
|
53
|
-
/// List of Kafka bootstrap server addresses
|
|
54
|
-
bootstrap_servers: Option<Vec<String>>,
|
|
55
|
-
|
|
56
|
-
/// Whether to use mock mode (for testing)
|
|
57
|
-
mock: Option<bool>,
|
|
58
|
-
|
|
59
|
-
/// Maximum time to wait for a send operation to complete (in seconds)
|
|
60
|
-
send_timeout: Option<f32>,
|
|
61
|
-
|
|
62
|
-
/// Kafka consumer group ID
|
|
63
|
-
group_id: Option<String>,
|
|
64
|
-
|
|
65
|
-
/// Global shared cache capacity across all partitions for message
|
|
66
|
-
/// deduplication
|
|
67
|
-
idempotence_cache_size: Option<u32>,
|
|
68
|
-
|
|
69
|
-
/// Version string for cache-busting deduplication hashes
|
|
70
|
-
idempotence_version: Option<String>,
|
|
71
|
-
|
|
72
|
-
/// TTL for deduplication records in Cassandra (in seconds)
|
|
73
|
-
idempotence_ttl: Option<f64>,
|
|
74
|
-
|
|
75
|
-
/// List of Kafka topics to subscribe to
|
|
76
|
-
subscribed_topics: Option<Vec<String>>,
|
|
77
|
-
|
|
78
|
-
/// List of event types that the consumer is allowed to process
|
|
79
|
-
allowed_events: Option<Vec<String>>,
|
|
80
|
-
|
|
81
|
-
/// Identifier for the system producing messages
|
|
82
|
-
source_system: Option<String>,
|
|
83
|
-
|
|
84
|
-
/// Maximum number of concurrent message processing tasks
|
|
85
|
-
max_concurrency: Option<u32>,
|
|
86
|
-
|
|
87
|
-
/// Maximum number of messages to process before committing offsets
|
|
88
|
-
max_uncommitted: Option<u16>,
|
|
89
|
-
|
|
90
|
-
/// Threshold in seconds after which a stalled consumer is detected
|
|
91
|
-
stall_threshold: Option<f32>,
|
|
92
|
-
|
|
93
|
-
/// Maximum time to wait for a clean shutdown (in seconds)
|
|
94
|
-
shutdown_timeout: Option<f32>,
|
|
95
|
-
|
|
96
|
-
/// Interval between Kafka poll operations (in seconds)
|
|
97
|
-
poll_interval: Option<f32>,
|
|
98
|
-
|
|
99
|
-
/// Interval between offset commit operations (in seconds)
|
|
100
|
-
commit_interval: Option<f32>,
|
|
101
|
-
|
|
102
|
-
/// Operation mode of the client (`pipeline`, `low_latency`, `best_effort`)
|
|
103
|
-
mode: Option<String>,
|
|
104
|
-
|
|
105
|
-
/// Base delay for retry operations (in seconds)
|
|
106
|
-
retry_base: Option<f32>,
|
|
107
|
-
|
|
108
|
-
/// Maximum number of retry attempts
|
|
109
|
-
max_retries: Option<u32>,
|
|
110
|
-
|
|
111
|
-
/// Maximum delay between retries (in seconds)
|
|
112
|
-
max_retry_delay: Option<f32>,
|
|
113
|
-
|
|
114
|
-
/// Topic to send failed messages to
|
|
115
|
-
failure_topic: Option<String>,
|
|
116
|
-
|
|
117
|
-
/// Configuration for the health probe port
|
|
118
|
-
probe_port: Option<ProbePort>,
|
|
119
|
-
|
|
120
|
-
/// List of Cassandra contact nodes (hostnames or IPs)
|
|
121
|
-
cassandra_nodes: Option<Vec<String>>,
|
|
122
|
-
|
|
123
|
-
/// Keyspace used for persistent Prosody data in Cassandra.
|
|
124
|
-
cassandra_keyspace: Option<String>,
|
|
125
|
-
|
|
126
|
-
/// Preferred datacenter for Cassandra query routing
|
|
127
|
-
cassandra_datacenter: Option<String>,
|
|
128
|
-
|
|
129
|
-
/// Preferred rack identifier for Cassandra topology-aware routing
|
|
130
|
-
cassandra_rack: Option<String>,
|
|
131
|
-
|
|
132
|
-
/// Username for authenticating with Cassandra
|
|
133
|
-
cassandra_user: Option<String>,
|
|
134
|
-
|
|
135
|
-
/// Password for authenticating with Cassandra
|
|
136
|
-
cassandra_password: Option<String>,
|
|
137
|
-
|
|
138
|
-
/// Retention period for persistent timer and deferral data in Cassandra,
|
|
139
|
-
/// in seconds.
|
|
140
|
-
cassandra_retention: Option<f32>,
|
|
141
|
-
|
|
142
|
-
/// Timer slab partitioning duration in seconds.
|
|
143
|
-
/// Controls how timers are grouped for storage and retrieval.
|
|
144
|
-
slab_size: Option<f32>,
|
|
145
|
-
|
|
146
|
-
// Scheduler configuration
|
|
147
|
-
/// Target proportion of execution time for failure/retry task processing
|
|
148
|
-
/// (0.0 to 1.0). Controls bandwidth allocation between Normal and
|
|
149
|
-
/// Failure task classes.
|
|
150
|
-
scheduler_failure_weight: Option<f64>,
|
|
151
|
-
|
|
152
|
-
/// Wait duration (in seconds) at which urgency boost reaches maximum
|
|
153
|
-
/// intensity.
|
|
154
|
-
scheduler_max_wait: Option<f32>,
|
|
155
|
-
|
|
156
|
-
/// Maximum urgency boost (in seconds of virtual time) for waiting tasks.
|
|
157
|
-
scheduler_wait_weight: Option<f64>,
|
|
158
|
-
|
|
159
|
-
/// Cache capacity for tracking per-key virtual time in the scheduler.
|
|
160
|
-
scheduler_cache_size: Option<u32>,
|
|
161
|
-
|
|
162
|
-
// Monopolization configuration
|
|
163
|
-
/// Whether monopolization detection is enabled.
|
|
164
|
-
monopolization_enabled: Option<bool>,
|
|
165
|
-
|
|
166
|
-
/// Threshold for monopolization detection (0.0 to 1.0).
|
|
167
|
-
monopolization_threshold: Option<f64>,
|
|
168
|
-
|
|
169
|
-
/// Rolling window duration (in seconds) for monopolization detection.
|
|
170
|
-
monopolization_window: Option<f32>,
|
|
171
|
-
|
|
172
|
-
/// Cache size for tracking key execution intervals.
|
|
173
|
-
monopolization_cache_size: Option<u32>,
|
|
174
|
-
|
|
175
|
-
// Defer configuration
|
|
176
|
-
/// Whether deferral is enabled for new messages.
|
|
177
|
-
defer_enabled: Option<bool>,
|
|
178
|
-
|
|
179
|
-
/// Base exponential backoff delay for deferred retries (in seconds).
|
|
180
|
-
defer_base: Option<f32>,
|
|
181
|
-
|
|
182
|
-
/// Maximum delay between deferred retries (in seconds).
|
|
183
|
-
defer_max_delay: Option<f32>,
|
|
184
|
-
|
|
185
|
-
/// Failure rate threshold for disabling deferral (0.0 to 1.0).
|
|
186
|
-
defer_failure_threshold: Option<f64>,
|
|
187
|
-
|
|
188
|
-
/// Sliding window duration (in seconds) for failure rate tracking.
|
|
189
|
-
defer_failure_window: Option<f32>,
|
|
190
|
-
|
|
191
|
-
/// Maximum messages retained by the shared Kafka loader.
|
|
192
|
-
loader_cache_size: Option<u32>,
|
|
193
|
-
|
|
194
|
-
/// Maximum number of deferred store entries kept in the write-through cache
|
|
195
|
-
/// per Cassandra defer store.
|
|
196
|
-
defer_store_cache_size: Option<u32>,
|
|
197
|
-
|
|
198
|
-
/// Timeout for Kafka loader seek operations (in seconds).
|
|
199
|
-
loader_seek_timeout: Option<f32>,
|
|
200
|
-
|
|
201
|
-
/// Messages to read sequentially before seeking.
|
|
202
|
-
loader_discard_threshold: Option<i64>,
|
|
203
|
-
|
|
204
|
-
// Timeout configuration
|
|
205
|
-
/// Fixed timeout duration for handler execution (in seconds).
|
|
206
|
-
timeout: Option<f32>,
|
|
207
|
-
|
|
208
|
-
// Telemetry emitter configuration
|
|
209
|
-
/// Kafka topic to produce telemetry events to.
|
|
210
|
-
telemetry_topic: Option<String>,
|
|
211
|
-
|
|
212
|
-
/// Whether the telemetry emitter is enabled.
|
|
213
|
-
telemetry_enabled: Option<bool>,
|
|
214
|
-
|
|
215
|
-
// OTel span linking
|
|
216
|
-
/// Span linking for message execution spans (`child` or `follows_from`).
|
|
217
|
-
message_spans: Option<String>,
|
|
218
|
-
|
|
219
|
-
/// Span linking for timer execution spans (`child` or `follows_from`).
|
|
220
|
-
timer_spans: Option<String>,
|
|
221
|
-
|
|
222
|
-
/// Address for the peer listener.
|
|
223
|
-
peer_bind_address: Option<String>,
|
|
224
|
-
|
|
225
|
-
/// gRPC connect URI that peers use for this client.
|
|
226
|
-
peer_advertised_connect: Option<String>,
|
|
227
|
-
|
|
228
|
-
/// Network name used to identify direct routes.
|
|
229
|
-
peer_network_name: Option<String>,
|
|
230
|
-
|
|
231
|
-
/// Maximum number of peer channels and registrations held in each cache.
|
|
232
|
-
peer_cache_capacity: Option<usize>,
|
|
233
|
-
|
|
234
|
-
/// Duration of each peer registration lease, in seconds.
|
|
235
|
-
peer_registration_ttl: Option<f64>,
|
|
236
|
-
|
|
237
|
-
// Keyed-state configuration
|
|
238
|
-
/// Keyed-state collections to register before subscribe.
|
|
239
|
-
state_collections: Option<Vec<StateCollectionConfig>>,
|
|
240
|
-
|
|
241
|
-
/// Subsystem under which published collections are advertised.
|
|
242
|
-
subsystem: Option<String>,
|
|
243
|
-
|
|
244
|
-
/// Root directory for the local keyed-state cache. Must not be empty.
|
|
245
|
-
state_cache_dir: Option<String>,
|
|
246
|
-
|
|
247
|
-
/// Capacity of the owning keyed-state cache.
|
|
248
|
-
state_owned_cache_size: Option<String>,
|
|
249
|
-
|
|
250
|
-
/// Capacity of the published-state read-through cache.
|
|
251
|
-
state_read_cache_size: Option<String>,
|
|
252
|
-
|
|
253
|
-
/// Default cache policy for published-state reads.
|
|
254
|
-
state_read_cache: Option<ReadCacheConfig>,
|
|
255
|
-
|
|
256
|
-
/// Delay in whole seconds before the keyed-state recovery sweep.
|
|
257
|
-
///
|
|
258
|
-
/// Crosses as an `f64` so fractional/negative/non-finite values reach the
|
|
259
|
-
/// whole-number guard rather than being silently truncated.
|
|
260
|
-
state_recovery_delay: Option<f64>,
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/// Declares one keyed-state collection to register before subscribe.
|
|
264
|
-
#[derive(Clone, Debug, Deserialize)]
|
|
265
|
-
struct StateCollectionConfig {
|
|
266
|
-
/// The collection name. Prosody requires it to be non-empty and unique.
|
|
267
|
-
name: String,
|
|
268
|
-
|
|
269
|
-
/// The collection kind: `"value"`, `"map"`, or `"deque"`.
|
|
270
|
-
kind: String,
|
|
271
|
-
|
|
272
|
-
/// The item payload: `"json"` or `"message"`.
|
|
273
|
-
payload: String,
|
|
274
|
-
|
|
275
|
-
/// Optional per-write TTL in whole seconds. Crosses as `f64` so
|
|
276
|
-
/// fractional/negative/non-finite values reach the whole-number guard.
|
|
277
|
-
ttl_seconds: Option<f64>,
|
|
278
|
-
|
|
279
|
-
/// Optional opt-out of transactional staging.
|
|
280
|
-
read_uncommitted: Option<bool>,
|
|
281
|
-
|
|
282
|
-
/// Whether other consumer groups may read this JSON collection.
|
|
283
|
-
published: Option<bool>,
|
|
284
|
-
|
|
285
|
-
/// Optional map-only keyset bound (`0..=4096`). The binding rejects values
|
|
286
|
-
/// that cannot map to an unsigned integer. Prosody enforces the ceiling.
|
|
287
|
-
keyset_limit: Option<f64>,
|
|
288
|
-
|
|
289
|
-
/// Optional deque-only window capacity (`>= 1`). Runtime-only and not
|
|
290
|
-
/// persisted. Crosses as `f64` so fractional/negative/non-finite values
|
|
291
|
-
/// reach the whole-number guard.
|
|
292
|
-
capacity: Option<f64>,
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
#[derive(Clone, Debug, Deserialize)]
|
|
296
|
-
#[serde(untagged)]
|
|
297
|
-
enum ReadCacheConfig {
|
|
298
|
-
Disabled(bool),
|
|
299
|
-
Ttl(f64),
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/// Configuration for the health probe port.
|
|
303
|
-
///
|
|
304
|
-
/// This enum represents the three possible states for the probe port
|
|
305
|
-
/// configuration:
|
|
306
|
-
/// - Unconfigured: The default state, where the standard configuration is used
|
|
307
|
-
/// - Disabled: Explicitly disables the probe port
|
|
308
|
-
/// - Configured: Sets the probe port to a specific port number
|
|
309
|
-
#[derive(Copy, Clone, Debug, Default)]
|
|
310
|
-
pub enum ProbePort {
|
|
311
|
-
/// Use default configuration
|
|
312
|
-
#[default]
|
|
313
|
-
Unconfigured,
|
|
314
|
-
|
|
315
|
-
/// Explicitly disable the probe port
|
|
316
|
-
Disabled,
|
|
317
|
-
|
|
318
|
-
/// Use a specific port number
|
|
319
|
-
Configured(u16),
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
impl<'de> Deserialize<'de> for ProbePort {
|
|
323
|
-
/// Deserializes a probe port configuration from various possible input
|
|
324
|
-
/// formats.
|
|
325
|
-
///
|
|
326
|
-
/// # Arguments
|
|
327
|
-
///
|
|
328
|
-
/// * `deserializer` - The deserializer to use
|
|
329
|
-
///
|
|
330
|
-
/// # Returns
|
|
331
|
-
///
|
|
332
|
-
/// A `ProbePort` enum variant based on the input:
|
|
333
|
-
/// - If a u16 is provided, it returns `ProbePort::Configured(port)`
|
|
334
|
-
/// - If a boolean `true` is provided, it returns `ProbePort::Unconfigured`
|
|
335
|
-
/// - If a boolean `false` is provided, it returns `ProbePort::Disabled`
|
|
336
|
-
/// - If nothing is provided, it returns `ProbePort::Unconfigured`
|
|
337
|
-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
338
|
-
where
|
|
339
|
-
D: Deserializer<'de>,
|
|
340
|
-
{
|
|
341
|
-
UntaggedEnumVisitor::new()
|
|
342
|
-
.u16(|port| Ok(Self::Configured(port)))
|
|
343
|
-
.bool(|enabled| {
|
|
344
|
-
if enabled {
|
|
345
|
-
Ok(Self::Unconfigured)
|
|
346
|
-
} else {
|
|
347
|
-
Ok(Self::Disabled)
|
|
348
|
-
}
|
|
349
|
-
})
|
|
350
|
-
.unit(|| Ok(Self::Unconfigured))
|
|
351
|
-
.deserialize(deserializer)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
impl NativeConfiguration {
|
|
356
|
-
/// Converts a Ruby value into a `NativeConfiguration`.
|
|
357
|
-
///
|
|
358
|
-
/// # Arguments
|
|
359
|
-
///
|
|
360
|
-
/// * `ruby` - Reference to the Ruby VM
|
|
361
|
-
/// * `val` - The Ruby value to convert
|
|
362
|
-
///
|
|
363
|
-
/// # Returns
|
|
364
|
-
///
|
|
365
|
-
/// The converted `NativeConfiguration` if successful
|
|
366
|
-
///
|
|
367
|
-
/// # Errors
|
|
368
|
-
///
|
|
369
|
-
/// Returns a Magnus error if deserialization fails
|
|
370
|
-
pub fn from_value(ruby: &Ruby, val: Value) -> Result<Self, Error> {
|
|
371
|
-
deserialize(ruby, val)
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
impl<'a> From<&'a NativeConfiguration> for ProducerConfigurationBuilder {
|
|
376
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
377
|
-
/// `ProducerConfigurationBuilder`.
|
|
378
|
-
///
|
|
379
|
-
/// This takes the relevant producer settings from the configuration and
|
|
380
|
-
/// sets them on a new `ProducerConfigurationBuilder` instance.
|
|
381
|
-
///
|
|
382
|
-
/// # Arguments
|
|
383
|
-
///
|
|
384
|
-
/// * `config` - The configuration to convert
|
|
385
|
-
///
|
|
386
|
-
/// # Returns
|
|
387
|
-
///
|
|
388
|
-
/// A configured `ProducerConfigurationBuilder`
|
|
389
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
390
|
-
let mut builder = Self::default();
|
|
391
|
-
|
|
392
|
-
if let Some(bootstrap_servers) = &config.bootstrap_servers {
|
|
393
|
-
builder.bootstrap_servers(bootstrap_servers.clone());
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if let Some(send_timeout) = &config.send_timeout {
|
|
397
|
-
builder.send_timeout(Duration::from_secs_f32(*send_timeout));
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if let Some(idempotence_cache_size) = &config.idempotence_cache_size {
|
|
401
|
-
builder.idempotence_cache_size(*idempotence_cache_size as usize);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if let Some(source_system) = &config.source_system {
|
|
405
|
-
builder.source_system(source_system.clone());
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if let Some(mock) = &config.mock {
|
|
409
|
-
builder.mock(*mock);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
builder
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
impl<'a> From<&'a NativeConfiguration> for ConsumerConfigurationBuilder {
|
|
417
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
418
|
-
/// `ConsumerConfigurationBuilder`.
|
|
419
|
-
///
|
|
420
|
-
/// This takes the relevant consumer settings from the configuration and
|
|
421
|
-
/// sets them on a new `ConsumerConfigurationBuilder` instance.
|
|
422
|
-
///
|
|
423
|
-
/// # Arguments
|
|
424
|
-
///
|
|
425
|
-
/// * `config` - The configuration to convert
|
|
426
|
-
///
|
|
427
|
-
/// # Returns
|
|
428
|
-
///
|
|
429
|
-
/// A configured `ConsumerConfigurationBuilder`
|
|
430
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
431
|
-
let mut builder = Self::default();
|
|
432
|
-
|
|
433
|
-
if let Some(bootstrap_servers) = &config.bootstrap_servers {
|
|
434
|
-
builder.bootstrap_servers(bootstrap_servers.clone());
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if let Some(group_id) = &config.group_id {
|
|
438
|
-
builder.group_id(group_id.clone());
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if let Some(subscribed_topics) = &config.subscribed_topics {
|
|
442
|
-
builder.subscribed_topics(subscribed_topics.clone());
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if let Some(allowed_events) = &config.allowed_events {
|
|
446
|
-
builder.allowed_events(allowed_events.clone());
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
if let Some(max_uncommitted) = &config.max_uncommitted {
|
|
450
|
-
builder.max_uncommitted(*max_uncommitted as usize);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if let Some(stall_threshold) = &config.stall_threshold {
|
|
454
|
-
builder.stall_threshold(Duration::from_secs_f32(*stall_threshold));
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
if let Some(shutdown_timeout) = &config.shutdown_timeout {
|
|
458
|
-
builder.shutdown_timeout(Duration::from_secs_f32(*shutdown_timeout));
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if let Some(poll_interval) = &config.poll_interval {
|
|
462
|
-
builder.poll_interval(Duration::from_secs_f32(*poll_interval));
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if let Some(commit_interval) = &config.commit_interval {
|
|
466
|
-
builder.commit_interval(Duration::from_secs_f32(*commit_interval));
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
if let Some(mock) = &config.mock {
|
|
470
|
-
builder.mock(*mock);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if let Some(probe_port) = &config.probe_port {
|
|
474
|
-
match probe_port {
|
|
475
|
-
ProbePort::Unconfigured => {}
|
|
476
|
-
ProbePort::Disabled => {
|
|
477
|
-
builder.probe_port(None);
|
|
478
|
-
}
|
|
479
|
-
ProbePort::Configured(port) => {
|
|
480
|
-
builder.probe_port(*port);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if let Some(slab_size) = &config.slab_size {
|
|
486
|
-
builder.slab_size(Duration::from_secs_f32(*slab_size));
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
builder
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
impl<'a> From<&'a NativeConfiguration> for RetryConfigurationBuilder {
|
|
494
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
495
|
-
/// `RetryConfigurationBuilder`.
|
|
496
|
-
///
|
|
497
|
-
/// This takes the relevant retry settings from the configuration and
|
|
498
|
-
/// sets them on a new `RetryConfigurationBuilder` instance.
|
|
499
|
-
///
|
|
500
|
-
/// # Arguments
|
|
501
|
-
///
|
|
502
|
-
/// * `config` - The configuration to convert
|
|
503
|
-
///
|
|
504
|
-
/// # Returns
|
|
505
|
-
///
|
|
506
|
-
/// A configured `RetryConfigurationBuilder`
|
|
507
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
508
|
-
let mut builder = Self::default();
|
|
509
|
-
|
|
510
|
-
if let Some(retry_base) = &config.retry_base {
|
|
511
|
-
builder.base(Duration::from_secs_f32(*retry_base));
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if let Some(max_retries) = &config.max_retries {
|
|
515
|
-
builder.max_retries(*max_retries);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if let Some(max_retry_delay) = &config.max_retry_delay {
|
|
519
|
-
builder.max_delay(Duration::from_secs_f32(*max_retry_delay));
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
builder
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
impl<'a> From<&'a NativeConfiguration> for FailureTopicConfigurationBuilder {
|
|
527
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
528
|
-
/// `FailureTopicConfigurationBuilder`.
|
|
529
|
-
///
|
|
530
|
-
/// This takes the relevant failure topic settings from the configuration
|
|
531
|
-
/// and sets them on a new `FailureTopicConfigurationBuilder` instance.
|
|
532
|
-
///
|
|
533
|
-
/// # Arguments
|
|
534
|
-
///
|
|
535
|
-
/// * `config` - The configuration to convert
|
|
536
|
-
///
|
|
537
|
-
/// # Returns
|
|
538
|
-
///
|
|
539
|
-
/// A configured `FailureTopicConfigurationBuilder`
|
|
540
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
541
|
-
let mut builder = Self::default();
|
|
542
|
-
|
|
543
|
-
if let Some(failure_topic) = &config.failure_topic {
|
|
544
|
-
builder.failure_topic(failure_topic.clone());
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
builder
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
impl<'a> TryFrom<&'a NativeConfiguration> for Mode {
|
|
552
|
-
type Error = String;
|
|
553
|
-
|
|
554
|
-
/// Attempts to convert a `NativeConfiguration` reference into a Prosody
|
|
555
|
-
/// Mode.
|
|
556
|
-
///
|
|
557
|
-
/// This extracts the mode setting from the configuration and converts it
|
|
558
|
-
/// to a Prosody Mode enum value.
|
|
559
|
-
///
|
|
560
|
-
/// # Arguments
|
|
561
|
-
///
|
|
562
|
-
/// * `value` - The configuration to convert
|
|
563
|
-
///
|
|
564
|
-
/// # Returns
|
|
565
|
-
///
|
|
566
|
-
/// The corresponding `Mode` if successful
|
|
567
|
-
///
|
|
568
|
-
/// # Errors
|
|
569
|
-
///
|
|
570
|
-
/// Returns a String error if the mode is unrecognized
|
|
571
|
-
fn try_from(value: &'a NativeConfiguration) -> Result<Self, Self::Error> {
|
|
572
|
-
let Some(mode_str) = value.mode.as_deref() else {
|
|
573
|
-
return Ok(Mode::default());
|
|
574
|
-
};
|
|
575
|
-
|
|
576
|
-
match mode_str {
|
|
577
|
-
"pipeline" => Ok(Mode::Pipeline),
|
|
578
|
-
"low_latency" => Ok(Mode::LowLatency),
|
|
579
|
-
"best_effort" => Ok(Mode::BestEffort),
|
|
580
|
-
string => Err(format!("unrecognized mode: {string}")),
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
impl<'a> From<&'a NativeConfiguration> for CassandraConfigurationBuilder {
|
|
586
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
587
|
-
/// `CassandraConfigurationBuilder`.
|
|
588
|
-
///
|
|
589
|
-
/// This takes the relevant Cassandra settings from the configuration and
|
|
590
|
-
/// sets them on a new `CassandraConfigurationBuilder` instance.
|
|
591
|
-
///
|
|
592
|
-
/// # Arguments
|
|
593
|
-
///
|
|
594
|
-
/// * `config` - The configuration to convert
|
|
595
|
-
///
|
|
596
|
-
/// # Returns
|
|
597
|
-
///
|
|
598
|
-
/// A configured `CassandraConfigurationBuilder`
|
|
599
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
600
|
-
let mut builder = Self::default();
|
|
601
|
-
|
|
602
|
-
if let Some(nodes) = &config.cassandra_nodes {
|
|
603
|
-
builder.nodes(nodes.clone());
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
if let Some(keyspace) = &config.cassandra_keyspace {
|
|
607
|
-
builder.keyspace(keyspace.clone());
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if let Some(datacenter) = &config.cassandra_datacenter {
|
|
611
|
-
builder.datacenter(Some(datacenter.clone()));
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if let Some(rack) = &config.cassandra_rack {
|
|
615
|
-
builder.rack(Some(rack.clone()));
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
if let Some(user) = &config.cassandra_user {
|
|
619
|
-
builder.user(Some(user.clone()));
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if let Some(password) = &config.cassandra_password {
|
|
623
|
-
builder.password(Some(password.clone()));
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
if let Some(retention) = &config.cassandra_retention {
|
|
627
|
-
builder.retention(Duration::from_secs_f32(*retention));
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
builder
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
impl<'a> From<&'a NativeConfiguration> for SchedulerConfigurationBuilder {
|
|
635
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
636
|
-
/// `SchedulerConfigurationBuilder`.
|
|
637
|
-
///
|
|
638
|
-
/// This takes the relevant scheduler settings from the configuration and
|
|
639
|
-
/// sets them on a new `SchedulerConfigurationBuilder` instance.
|
|
640
|
-
///
|
|
641
|
-
/// # Arguments
|
|
642
|
-
///
|
|
643
|
-
/// * `config` - The configuration to convert
|
|
644
|
-
///
|
|
645
|
-
/// # Returns
|
|
646
|
-
///
|
|
647
|
-
/// A configured `SchedulerConfigurationBuilder`
|
|
648
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
649
|
-
let mut builder = Self::default();
|
|
650
|
-
|
|
651
|
-
if let Some(max_concurrency) = &config.max_concurrency {
|
|
652
|
-
builder.max_concurrency(*max_concurrency as usize);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if let Some(failure_weight) = &config.scheduler_failure_weight {
|
|
656
|
-
builder.failure_weight(*failure_weight);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if let Some(max_wait) = &config.scheduler_max_wait {
|
|
660
|
-
builder.max_wait(Duration::from_secs_f32(*max_wait));
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
if let Some(wait_weight) = &config.scheduler_wait_weight {
|
|
664
|
-
builder.wait_weight(*wait_weight);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if let Some(cache_size) = &config.scheduler_cache_size {
|
|
668
|
-
builder.cache_size(*cache_size as usize);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
builder
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
impl<'a> From<&'a NativeConfiguration> for MonopolizationConfigurationBuilder {
|
|
676
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
677
|
-
/// `MonopolizationConfigurationBuilder`.
|
|
678
|
-
///
|
|
679
|
-
/// This takes the relevant monopolization settings from the configuration
|
|
680
|
-
/// and sets them on a new `MonopolizationConfigurationBuilder` instance.
|
|
681
|
-
///
|
|
682
|
-
/// # Arguments
|
|
683
|
-
///
|
|
684
|
-
/// * `config` - The configuration to convert
|
|
685
|
-
///
|
|
686
|
-
/// # Returns
|
|
687
|
-
///
|
|
688
|
-
/// A configured `MonopolizationConfigurationBuilder`
|
|
689
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
690
|
-
let mut builder = Self::default();
|
|
691
|
-
|
|
692
|
-
if let Some(enabled) = &config.monopolization_enabled {
|
|
693
|
-
builder.enabled(*enabled);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if let Some(threshold) = &config.monopolization_threshold {
|
|
697
|
-
builder.monopolization_threshold(*threshold);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
if let Some(window) = &config.monopolization_window {
|
|
701
|
-
builder.window_duration(Duration::from_secs_f32(*window));
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
if let Some(cache_size) = &config.monopolization_cache_size {
|
|
705
|
-
builder.cache_size(*cache_size as usize);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
builder
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
impl<'a> From<&'a NativeConfiguration> for DeferConfigurationBuilder {
|
|
713
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
714
|
-
/// `DeferConfigurationBuilder`.
|
|
715
|
-
///
|
|
716
|
-
/// This takes the relevant defer settings from the configuration and
|
|
717
|
-
/// sets them on a new `DeferConfigurationBuilder` instance.
|
|
718
|
-
///
|
|
719
|
-
/// # Arguments
|
|
720
|
-
///
|
|
721
|
-
/// * `config` - The configuration to convert
|
|
722
|
-
///
|
|
723
|
-
/// # Returns
|
|
724
|
-
///
|
|
725
|
-
/// A configured `DeferConfigurationBuilder`
|
|
726
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
727
|
-
let mut builder = Self::default();
|
|
728
|
-
|
|
729
|
-
if let Some(enabled) = &config.defer_enabled {
|
|
730
|
-
builder.enabled(*enabled);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
if let Some(base) = &config.defer_base {
|
|
734
|
-
builder.base(Duration::from_secs_f32(*base));
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if let Some(max_delay) = &config.defer_max_delay {
|
|
738
|
-
builder.max_delay(Duration::from_secs_f32(*max_delay));
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
if let Some(failure_threshold) = &config.defer_failure_threshold {
|
|
742
|
-
builder.failure_threshold(*failure_threshold);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
if let Some(failure_window) = &config.defer_failure_window {
|
|
746
|
-
builder.failure_window(Duration::from_secs_f32(*failure_window));
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if let Some(store_cache_size) = &config.defer_store_cache_size {
|
|
750
|
-
builder.store_cache_size(*store_cache_size as usize);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
builder
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
impl<'a> From<&'a NativeConfiguration> for TimeoutConfigurationBuilder {
|
|
758
|
-
/// Converts a `NativeConfiguration` reference into a
|
|
759
|
-
/// `TimeoutConfigurationBuilder`.
|
|
760
|
-
///
|
|
761
|
-
/// This takes the relevant timeout settings from the configuration and
|
|
762
|
-
/// sets them on a new `TimeoutConfigurationBuilder` instance.
|
|
763
|
-
///
|
|
764
|
-
/// # Arguments
|
|
765
|
-
///
|
|
766
|
-
/// * `config` - The configuration to convert
|
|
767
|
-
///
|
|
768
|
-
/// # Returns
|
|
769
|
-
///
|
|
770
|
-
/// A configured `TimeoutConfigurationBuilder`
|
|
771
|
-
fn from(config: &'a NativeConfiguration) -> Self {
|
|
772
|
-
let mut builder = Self::default();
|
|
773
|
-
|
|
774
|
-
if let Some(timeout) = &config.timeout {
|
|
775
|
-
builder.timeout(Some(Duration::from_secs_f32(*timeout)));
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
builder
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
impl<'a> TryFrom<&'a NativeConfiguration> for DeduplicationConfigurationBuilder {
|
|
783
|
-
type Error = String;
|
|
784
|
-
|
|
785
|
-
/// Attempts to convert a `NativeConfiguration` reference into a
|
|
786
|
-
/// `DeduplicationConfigurationBuilder`.
|
|
787
|
-
///
|
|
788
|
-
/// This takes the relevant deduplication settings from the configuration
|
|
789
|
-
/// and sets them on a new `DeduplicationConfigurationBuilder` instance.
|
|
790
|
-
///
|
|
791
|
-
/// Consumer deduplication is mandatory in the core (it is the keyed-state
|
|
792
|
-
/// commit oracle), so `cache_capacity` is `NonZeroUsize` and a zero
|
|
793
|
-
/// capacity is unrepresentable rather than a silent "disable". An explicit
|
|
794
|
-
/// `idempotence_cache_size` of `0` is therefore rejected here rather than
|
|
795
|
-
/// silently defaulting; this mirrors the sibling `prosody-js` binding and
|
|
796
|
-
/// the core's own rejection of `PROSODY_IDEMPOTENCE_CACHE_SIZE=0`.
|
|
797
|
-
///
|
|
798
|
-
/// # Arguments
|
|
799
|
-
///
|
|
800
|
-
/// * `config` - The configuration to convert
|
|
801
|
-
///
|
|
802
|
-
/// # Returns
|
|
803
|
-
///
|
|
804
|
-
/// A configured `DeduplicationConfigurationBuilder` if successful
|
|
805
|
-
///
|
|
806
|
-
/// # Errors
|
|
807
|
-
///
|
|
808
|
-
/// Returns a `String` error if `idempotence_cache_size` is explicitly set
|
|
809
|
-
/// to `0`.
|
|
810
|
-
fn try_from(config: &'a NativeConfiguration) -> Result<Self, Self::Error> {
|
|
811
|
-
let mut builder = Self::default();
|
|
812
|
-
|
|
813
|
-
if let Some(cache_capacity) = &config.idempotence_cache_size {
|
|
814
|
-
let cache_capacity = NonZeroUsize::new(*cache_capacity as usize)
|
|
815
|
-
.ok_or_else(|| "idempotence_cache_size must be greater than 0".to_owned())?;
|
|
816
|
-
builder.cache_capacity(cache_capacity);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
if let Some(version) = &config.idempotence_version {
|
|
820
|
-
builder.version(version.clone());
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if let Some(ttl) = &config.idempotence_ttl
|
|
824
|
-
&& ttl.is_finite()
|
|
825
|
-
&& *ttl >= 0.0_f64
|
|
826
|
-
{
|
|
827
|
-
builder.ttl(Duration::from_secs_f64(*ttl));
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
Ok(builder)
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
impl<'a> TryFrom<&'a NativeConfiguration> for TelemetryEmitterConfiguration {
|
|
835
|
-
type Error = String;
|
|
836
|
-
|
|
837
|
-
/// Attempts to convert a `NativeConfiguration` reference into a
|
|
838
|
-
/// `TelemetryEmitterConfiguration`.
|
|
839
|
-
///
|
|
840
|
-
/// This takes the relevant telemetry emitter settings from the
|
|
841
|
-
/// configuration and constructs a `TelemetryEmitterConfiguration`,
|
|
842
|
-
/// falling back to environment-variable-aware defaults for any unset
|
|
843
|
-
/// fields.
|
|
844
|
-
///
|
|
845
|
-
/// # Arguments
|
|
846
|
-
///
|
|
847
|
-
/// * `config` - The configuration to convert
|
|
848
|
-
///
|
|
849
|
-
/// # Returns
|
|
850
|
-
///
|
|
851
|
-
/// A configured `TelemetryEmitterConfiguration` if successful
|
|
852
|
-
///
|
|
853
|
-
/// # Errors
|
|
854
|
-
///
|
|
855
|
-
/// Returns a `String` error if a related environment variable contains an
|
|
856
|
-
/// unparseable value.
|
|
857
|
-
fn try_from(config: &'a NativeConfiguration) -> Result<Self, Self::Error> {
|
|
858
|
-
let mut builder = Self::builder();
|
|
859
|
-
|
|
860
|
-
if let Some(topic) = &config.telemetry_topic {
|
|
861
|
-
builder.topic(topic.clone());
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
if let Some(enabled) = &config.telemetry_enabled {
|
|
865
|
-
builder.enabled(*enabled);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
builder.build().map_err(|e| e.to_string())
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/// The kind of a keyed-state collection.
|
|
873
|
-
enum CollectionKind {
|
|
874
|
-
/// A single-value collection.
|
|
875
|
-
Value,
|
|
876
|
-
/// A `String`-keyed ordered map.
|
|
877
|
-
Map,
|
|
878
|
-
/// A deque.
|
|
879
|
-
Deque,
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
/// The item payload of a keyed-state collection.
|
|
883
|
-
enum CollectionPayload {
|
|
884
|
-
/// JSON values.
|
|
885
|
-
Json,
|
|
886
|
-
/// The full Kafka message the handler received.
|
|
887
|
-
Message,
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
/// Parses a collection-kind token.
|
|
891
|
-
///
|
|
892
|
-
/// # Errors
|
|
893
|
-
///
|
|
894
|
-
/// Returns a permanent-category error naming the field if the token is not
|
|
895
|
-
/// `"value"`, `"map"`, or `"deque"`.
|
|
896
|
-
fn parse_kind(index: usize, kind: &str) -> Result<CollectionKind, String> {
|
|
897
|
-
match kind {
|
|
898
|
-
"value" => Ok(CollectionKind::Value),
|
|
899
|
-
"map" => Ok(CollectionKind::Map),
|
|
900
|
-
"deque" => Ok(CollectionKind::Deque),
|
|
901
|
-
other => Err(format!(
|
|
902
|
-
"state_collections[{index}].kind: expected \"value\", \"map\", or \"deque\", got \
|
|
903
|
-
{other:?}"
|
|
904
|
-
)),
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/// Parses a collection-payload token.
|
|
909
|
-
///
|
|
910
|
-
/// # Errors
|
|
911
|
-
///
|
|
912
|
-
/// Returns a permanent-category error naming the field if the token is not
|
|
913
|
-
/// `"json"` or `"message"`.
|
|
914
|
-
fn parse_payload(index: usize, payload: &str) -> Result<CollectionPayload, String> {
|
|
915
|
-
match payload {
|
|
916
|
-
"json" => Ok(CollectionPayload::Json),
|
|
917
|
-
"message" => Ok(CollectionPayload::Message),
|
|
918
|
-
other => Err(format!(
|
|
919
|
-
"state_collections[{index}].payload: expected \"json\" or \"message\", got {other:?}"
|
|
920
|
-
)),
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
/// Validates a numeric field as a whole number within `min..=max`.
|
|
925
|
-
///
|
|
926
|
-
/// The field arrives as an `f64` (the raw Ruby number, un-coerced) so that
|
|
927
|
-
/// fractional, negative, and non-finite values reach this guard instead of
|
|
928
|
-
/// being silently truncated or wrapped by an earlier integer conversion.
|
|
929
|
-
///
|
|
930
|
-
/// # Errors
|
|
931
|
-
///
|
|
932
|
-
/// Returns a permanent-category error naming the field if the value is not a
|
|
933
|
-
/// whole number in the inclusive range.
|
|
934
|
-
fn whole_number_field(value: f64, field: &str, min: u32, max: u32) -> Result<u32, String> {
|
|
935
|
-
if value.is_finite()
|
|
936
|
-
&& value.fract() == 0.0
|
|
937
|
-
&& value >= f64::from(min)
|
|
938
|
-
&& value <= f64::from(max)
|
|
939
|
-
{
|
|
940
|
-
Ok(value as u32)
|
|
941
|
-
} else {
|
|
942
|
-
Err(format!("{field}: must be a whole number in {min}..={max}"))
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
fn build_peer_config(config: &NativeConfiguration) -> Result<PeerConfiguration, String> {
|
|
947
|
-
let mut builder = PeerConfiguration::builder();
|
|
948
|
-
if let Some(value) = &config.peer_bind_address {
|
|
949
|
-
builder.bind_address(
|
|
950
|
-
value
|
|
951
|
-
.parse::<SocketAddr>()
|
|
952
|
-
.map_err(|error| format!("peer_bind_address: {error}"))?,
|
|
953
|
-
);
|
|
954
|
-
}
|
|
955
|
-
if let Some(value) = &config.peer_advertised_connect {
|
|
956
|
-
builder.advertised_connect(
|
|
957
|
-
PeerEndpoint::try_from(value.clone())
|
|
958
|
-
.map_err(|error| format!("peer_advertised_connect: {error}"))?,
|
|
959
|
-
);
|
|
960
|
-
}
|
|
961
|
-
if let Some(value) = &config.peer_network_name {
|
|
962
|
-
builder.network_name(value.clone());
|
|
963
|
-
}
|
|
964
|
-
if let Some(value) = config.peer_cache_capacity {
|
|
965
|
-
builder.peer_cache_capacity(value);
|
|
966
|
-
}
|
|
967
|
-
if let Some(value) = config.peer_registration_ttl {
|
|
968
|
-
builder.registration_ttl(
|
|
969
|
-
Duration::try_from_secs_f64(value)
|
|
970
|
-
.map_err(|_| "peer_registration_ttl: must be a valid duration".to_owned())?,
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
builder.build().map_err(|error| error.to_string())
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
/// Applies the shared descriptor options (TTL, commit mode) fluently.
|
|
977
|
-
fn with_def<D: StateDescriptor>(
|
|
978
|
-
descriptor: D,
|
|
979
|
-
ttl_seconds: Option<u32>,
|
|
980
|
-
read_uncommitted: Option<bool>,
|
|
981
|
-
published: Option<bool>,
|
|
982
|
-
) -> D {
|
|
983
|
-
let mut descriptor = descriptor;
|
|
984
|
-
if let Some(ttl) = ttl_seconds {
|
|
985
|
-
descriptor = descriptor.ttl(CompactDuration::new(ttl));
|
|
986
|
-
}
|
|
987
|
-
if read_uncommitted == Some(true) {
|
|
988
|
-
descriptor = descriptor.read_uncommitted();
|
|
989
|
-
}
|
|
990
|
-
if let Some(published) = published {
|
|
991
|
-
descriptor = descriptor.published(published);
|
|
992
|
-
}
|
|
993
|
-
descriptor
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
/// Applies the map-only keyset bound when configured.
|
|
997
|
-
fn with_keyset<KC, V>(
|
|
998
|
-
descriptor: MapDescriptor<KC, V>,
|
|
999
|
-
keyset_limit: Option<u32>,
|
|
1000
|
-
) -> MapDescriptor<KC, V> {
|
|
1001
|
-
match keyset_limit {
|
|
1002
|
-
Some(limit) => descriptor.keyset_limit(limit as usize),
|
|
1003
|
-
None => descriptor,
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
/// Applies the deque-only window capacity when configured.
|
|
1008
|
-
fn with_capacity<T>(
|
|
1009
|
-
descriptor: DequeDescriptor<T>,
|
|
1010
|
-
capacity: Option<NonZeroUsize>,
|
|
1011
|
-
) -> DequeDescriptor<T> {
|
|
1012
|
-
match capacity {
|
|
1013
|
-
Some(cap) => descriptor.capacity(cap),
|
|
1014
|
-
None => descriptor,
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
/// Maps one collection into its descriptor over the closed 3×2 (kind ×
|
|
1019
|
-
/// payload) matrix.
|
|
1020
|
-
///
|
|
1021
|
-
/// # Errors
|
|
1022
|
-
///
|
|
1023
|
-
/// Returns a permanent-category error when a host value cannot be mapped into
|
|
1024
|
-
/// a Prosody type.
|
|
1025
|
-
fn register_state_collection(
|
|
1026
|
-
keyed: &mut KeyedStateConfiguration,
|
|
1027
|
-
index: usize,
|
|
1028
|
-
collection: &StateCollectionConfig,
|
|
1029
|
-
) -> Result<(), String> {
|
|
1030
|
-
let kind = parse_kind(index, &collection.kind)?;
|
|
1031
|
-
let payload = parse_payload(index, &collection.payload)?;
|
|
1032
|
-
|
|
1033
|
-
let ttl_seconds = match collection.ttl_seconds {
|
|
1034
|
-
Some(value) => Some(whole_number_field(
|
|
1035
|
-
value,
|
|
1036
|
-
&format!("state_collections[{index}].ttl_seconds"),
|
|
1037
|
-
0,
|
|
1038
|
-
u32::MAX,
|
|
1039
|
-
)?),
|
|
1040
|
-
None => None,
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
let keyset_limit = keyset_limit(collection.keyset_limit, &kind, index)?;
|
|
1044
|
-
let capacity = capacity(collection.capacity, &kind, index)?;
|
|
1045
|
-
|
|
1046
|
-
let read_uncommitted = collection.read_uncommitted;
|
|
1047
|
-
let name = collection.name.as_str();
|
|
1048
|
-
match (kind, payload) {
|
|
1049
|
-
(CollectionKind::Value, CollectionPayload::Json) => {
|
|
1050
|
-
let _ = keyed.register(with_def(
|
|
1051
|
-
value_state::<JsonCodec>(name),
|
|
1052
|
-
ttl_seconds,
|
|
1053
|
-
read_uncommitted,
|
|
1054
|
-
collection.published,
|
|
1055
|
-
));
|
|
1056
|
-
}
|
|
1057
|
-
(CollectionKind::Map, CollectionPayload::Json) => {
|
|
1058
|
-
let descriptor = with_def(
|
|
1059
|
-
map_state::<Utf8KeyCodec, JsonCodec>(name),
|
|
1060
|
-
ttl_seconds,
|
|
1061
|
-
read_uncommitted,
|
|
1062
|
-
collection.published,
|
|
1063
|
-
);
|
|
1064
|
-
let _ = keyed.register(with_keyset(descriptor, keyset_limit));
|
|
1065
|
-
}
|
|
1066
|
-
(CollectionKind::Deque, CollectionPayload::Json) => {
|
|
1067
|
-
let descriptor = with_def(
|
|
1068
|
-
deque_state::<JsonCodec>(name),
|
|
1069
|
-
ttl_seconds,
|
|
1070
|
-
read_uncommitted,
|
|
1071
|
-
collection.published,
|
|
1072
|
-
);
|
|
1073
|
-
let _ = keyed.register(with_capacity(descriptor, capacity));
|
|
1074
|
-
}
|
|
1075
|
-
(CollectionKind::Value, CollectionPayload::Message) => {
|
|
1076
|
-
let _ = keyed.register(with_def(
|
|
1077
|
-
message_state::<KafkaLoader<JsonCodec>>(name),
|
|
1078
|
-
ttl_seconds,
|
|
1079
|
-
read_uncommitted,
|
|
1080
|
-
collection.published,
|
|
1081
|
-
));
|
|
1082
|
-
}
|
|
1083
|
-
(CollectionKind::Map, CollectionPayload::Message) => {
|
|
1084
|
-
let descriptor = with_def(
|
|
1085
|
-
message_map_state::<Utf8KeyCodec, KafkaLoader<JsonCodec>>(name),
|
|
1086
|
-
ttl_seconds,
|
|
1087
|
-
read_uncommitted,
|
|
1088
|
-
collection.published,
|
|
1089
|
-
);
|
|
1090
|
-
let _ = keyed.register(with_keyset(descriptor, keyset_limit));
|
|
1091
|
-
}
|
|
1092
|
-
(CollectionKind::Deque, CollectionPayload::Message) => {
|
|
1093
|
-
let descriptor = with_def(
|
|
1094
|
-
message_deque_state::<KafkaLoader<JsonCodec>>(name),
|
|
1095
|
-
ttl_seconds,
|
|
1096
|
-
read_uncommitted,
|
|
1097
|
-
collection.published,
|
|
1098
|
-
);
|
|
1099
|
-
let _ = keyed.register(with_capacity(descriptor, capacity));
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
Ok(())
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
fn keyset_limit(
|
|
1107
|
-
value: Option<f64>,
|
|
1108
|
-
kind: &CollectionKind,
|
|
1109
|
-
index: usize,
|
|
1110
|
-
) -> Result<Option<u32>, String> {
|
|
1111
|
-
let Some(value) = value else {
|
|
1112
|
-
return Ok(None);
|
|
1113
|
-
};
|
|
1114
|
-
if !matches!(kind, CollectionKind::Map) {
|
|
1115
|
-
return Err(format!(
|
|
1116
|
-
"state_collections[{index}].keyset_limit: only valid for map collections"
|
|
1117
|
-
));
|
|
1118
|
-
}
|
|
1119
|
-
whole_number_field(
|
|
1120
|
-
value,
|
|
1121
|
-
&format!("state_collections[{index}].keyset_limit"),
|
|
1122
|
-
0,
|
|
1123
|
-
u32::MAX,
|
|
1124
|
-
)
|
|
1125
|
-
.map(Some)
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
fn capacity(
|
|
1129
|
-
value: Option<f64>,
|
|
1130
|
-
kind: &CollectionKind,
|
|
1131
|
-
index: usize,
|
|
1132
|
-
) -> Result<Option<NonZeroUsize>, String> {
|
|
1133
|
-
let Some(value) = value else {
|
|
1134
|
-
return Ok(None);
|
|
1135
|
-
};
|
|
1136
|
-
if !matches!(kind, CollectionKind::Deque) {
|
|
1137
|
-
return Err(format!(
|
|
1138
|
-
"state_collections[{index}].capacity: only valid for deque collections"
|
|
1139
|
-
));
|
|
1140
|
-
}
|
|
1141
|
-
let value = whole_number_field(
|
|
1142
|
-
value,
|
|
1143
|
-
&format!("state_collections[{index}].capacity"),
|
|
1144
|
-
1,
|
|
1145
|
-
u32::MAX,
|
|
1146
|
-
)?;
|
|
1147
|
-
Ok(NonZeroUsize::new(value as usize))
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
/// Builds the `KeyedStateConfiguration` by mapping each declared collection.
|
|
1151
|
-
/// The normal Prosody construction path validates the result.
|
|
1152
|
-
///
|
|
1153
|
-
/// # Errors
|
|
1154
|
-
///
|
|
1155
|
-
/// Returns an error if a host value cannot be mapped.
|
|
1156
|
-
fn build_keyed_state_config(
|
|
1157
|
-
config: &NativeConfiguration,
|
|
1158
|
-
) -> Result<KeyedStateConfiguration, String> {
|
|
1159
|
-
let mut builder = KeyedStateConfiguration::builder();
|
|
1160
|
-
|
|
1161
|
-
if let Some(dir) = &config.state_cache_dir {
|
|
1162
|
-
builder.cache_dir(PathBuf::from(dir));
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
if let Some(seconds) = config.state_recovery_delay {
|
|
1166
|
-
let seconds = whole_number_field(seconds, "state_recovery_delay", 0, u32::MAX)?;
|
|
1167
|
-
builder.recovery_delay(CompactDuration::new(seconds));
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
if let Some(size) = &config.state_owned_cache_size {
|
|
1171
|
-
let size = size
|
|
1172
|
-
.parse::<ByteSize>()
|
|
1173
|
-
.map_err(|error| format!("state_owned_cache_size: {error}"))?;
|
|
1174
|
-
builder.owned_cache_size(Some(size));
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
if let Some(size) = &config.state_read_cache_size {
|
|
1178
|
-
let size = size
|
|
1179
|
-
.parse::<ByteSize>()
|
|
1180
|
-
.map_err(|error| format!("state_read_cache_size: {error}"))?;
|
|
1181
|
-
builder.read_cache_size(Some(size));
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
if let Some(cache) = &config.state_read_cache {
|
|
1185
|
-
match cache {
|
|
1186
|
-
ReadCacheConfig::Disabled(false) => {
|
|
1187
|
-
builder.read_cache_ttl(None);
|
|
1188
|
-
}
|
|
1189
|
-
ReadCacheConfig::Disabled(true) => {
|
|
1190
|
-
return Err(
|
|
1191
|
-
"state_read_cache: true is ambiguous; use a duration or false".to_owned(),
|
|
1192
|
-
);
|
|
1193
|
-
}
|
|
1194
|
-
ReadCacheConfig::Ttl(seconds) => {
|
|
1195
|
-
let ttl = Duration::try_from_secs_f64(*seconds).map_err(|_| {
|
|
1196
|
-
"state_read_cache: duration must be finite and non-negative".to_owned()
|
|
1197
|
-
})?;
|
|
1198
|
-
builder.read_cache_ttl(Some(ttl));
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
if let Some(subsystem) = &config.subsystem {
|
|
1204
|
-
builder.subsystem(Some(
|
|
1205
|
-
SubsystemName::try_new(subsystem).map_err(|error| error.to_string())?,
|
|
1206
|
-
));
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
let mut keyed = builder.build().map_err(|error| error.to_string())?;
|
|
1210
|
-
|
|
1211
|
-
if let Some(collections) = &config.state_collections {
|
|
1212
|
-
for (index, collection) in collections.iter().enumerate() {
|
|
1213
|
-
register_state_collection(&mut keyed, index, collection)?;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
Ok(keyed)
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
impl<'a> TryFrom<&'a NativeConfiguration> for ConsumerBuilders {
|
|
1221
|
-
type Error = String;
|
|
1222
|
-
|
|
1223
|
-
/// Attempts to convert a `NativeConfiguration` reference into a
|
|
1224
|
-
/// `ConsumerBuilders`.
|
|
1225
|
-
///
|
|
1226
|
-
/// This creates all the consumer-related configuration builders from
|
|
1227
|
-
/// the configuration.
|
|
1228
|
-
///
|
|
1229
|
-
/// # Arguments
|
|
1230
|
-
///
|
|
1231
|
-
/// * `config` - The configuration to convert
|
|
1232
|
-
///
|
|
1233
|
-
/// # Returns
|
|
1234
|
-
///
|
|
1235
|
-
/// A `ConsumerBuilders` containing all consumer-related configuration
|
|
1236
|
-
/// builders if successful
|
|
1237
|
-
///
|
|
1238
|
-
/// # Errors
|
|
1239
|
-
///
|
|
1240
|
-
/// Returns a `String` error if:
|
|
1241
|
-
/// - The telemetry emitter configuration cannot be built (e.g. an
|
|
1242
|
-
/// environment variable contains an unparseable value).
|
|
1243
|
-
/// - `message_spans` or `timer_spans` contains an unrecognized value
|
|
1244
|
-
/// (expected `"child"` or `"follows_from"`).
|
|
1245
|
-
/// - The Kafka loader configuration cannot be built (e.g. a tuning value
|
|
1246
|
-
/// fails validation).
|
|
1247
|
-
fn try_from(config: &'a NativeConfiguration) -> Result<Self, Self::Error> {
|
|
1248
|
-
let mut consumer: ConsumerConfigurationBuilder = config.into();
|
|
1249
|
-
|
|
1250
|
-
if let Some(s) = &config.message_spans {
|
|
1251
|
-
let relation = s
|
|
1252
|
-
.parse::<SpanRelation>()
|
|
1253
|
-
.map_err(|e| format!("message_spans: {e}"))?;
|
|
1254
|
-
consumer.message_spans(relation);
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
if let Some(s) = &config.timer_spans {
|
|
1258
|
-
let relation = s
|
|
1259
|
-
.parse::<SpanRelation>()
|
|
1260
|
-
.map_err(|e| format!("timer_spans: {e}"))?;
|
|
1261
|
-
consumer.timer_spans(relation);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
// Route the shared Kafka message loader settings onto the consumer.
|
|
1265
|
-
if config.loader_cache_size.is_some()
|
|
1266
|
-
|| config.loader_seek_timeout.is_some()
|
|
1267
|
-
|| config.loader_discard_threshold.is_some()
|
|
1268
|
-
{
|
|
1269
|
-
let mut loader = KafkaLoaderConfiguration::builder();
|
|
1270
|
-
|
|
1271
|
-
if let Some(cache_size) = &config.loader_cache_size {
|
|
1272
|
-
loader.cache_size(*cache_size as usize);
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
if let Some(seek_timeout) = &config.loader_seek_timeout {
|
|
1276
|
-
loader.seek_timeout(Duration::from_secs_f32(*seek_timeout));
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
if let Some(discard_threshold) = &config.loader_discard_threshold {
|
|
1280
|
-
loader.discard_threshold(*discard_threshold);
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
consumer.loader(loader.build().map_err(|e| e.to_string())?);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
Ok(Self {
|
|
1287
|
-
consumer,
|
|
1288
|
-
retry: config.into(),
|
|
1289
|
-
failure_topic: config.into(),
|
|
1290
|
-
scheduler: config.into(),
|
|
1291
|
-
monopolization: config.into(),
|
|
1292
|
-
defer: config.into(),
|
|
1293
|
-
timeout: config.into(),
|
|
1294
|
-
dedup: config.try_into()?,
|
|
1295
|
-
emitter: config.try_into()?,
|
|
1296
|
-
keyed_state: build_keyed_state_config(config)?,
|
|
1297
|
-
peer: build_peer_config(config)?,
|
|
1298
|
-
})
|
|
1299
|
-
}
|
|
1300
|
-
}
|