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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.config/rail.toml +26 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/.ruby-version +1 -1
  5. data/.taplo.toml +1 -1
  6. data/AGENTS.md +29 -15
  7. data/CHANGELOG.md +7 -0
  8. data/CONFIGURATION.md +19 -16
  9. data/Cargo.lock +254 -235
  10. data/Cargo.toml +11 -8
  11. data/README.md +100 -23
  12. data/examples/keyed_state.rb +10 -2
  13. data/examples/keyed_state.rbs +1 -0
  14. data/ext/prosody/Cargo.toml +1 -1
  15. data/ext/prosody/src/admin.rs +36 -36
  16. data/ext/prosody/src/bridge/mod.rs +5 -10
  17. data/ext/prosody/src/client/config/connections.rs +170 -0
  18. data/ext/prosody/src/client/config/middleware.rs +184 -0
  19. data/ext/prosody/src/client/config/mod.rs +396 -0
  20. data/ext/prosody/src/client/config/state.rs +323 -0
  21. data/ext/prosody/src/client/mod.rs +31 -117
  22. data/ext/prosody/src/client/readers.rs +106 -0
  23. data/ext/prosody/src/client/request.rs +2 -2
  24. data/ext/prosody/src/client/support.rs +13 -32
  25. data/ext/prosody/src/gvl.rs +8 -6
  26. data/ext/prosody/src/handler/{context.rs → context/mod.rs} +36 -150
  27. data/ext/prosody/src/handler/context/vending.rs +138 -0
  28. data/ext/prosody/src/handler/message.rs +36 -0
  29. data/ext/prosody/src/handler/mod.rs +18 -8
  30. data/ext/prosody/src/handler/state/deque.rs +144 -0
  31. data/ext/prosody/src/handler/state/mod.rs +163 -268
  32. data/ext/prosody/src/handler/state/query.rs +264 -0
  33. data/ext/prosody/src/handler/state/registration.rs +15 -86
  34. data/ext/prosody/src/handler/state/scan.rs +33 -141
  35. data/ext/prosody/src/handler/state/set.rs +98 -0
  36. data/ext/prosody/src/lib.rs +54 -36
  37. data/ext/prosody/src/logging.rs +6 -6
  38. data/ext/prosody/src/published.rs +171 -152
  39. data/ext/prosody/src/scheduler/result.rs +2 -1
  40. data/ext/prosody/src/util.rs +65 -3
  41. data/lib/prosody/client.rb +32 -0
  42. data/lib/prosody/configuration.rb +20 -12
  43. data/lib/prosody/demand.rb +27 -0
  44. data/lib/prosody/native_stubs/client.rb +157 -0
  45. data/lib/prosody/native_stubs/context.rb +178 -0
  46. data/lib/prosody/native_stubs/message.rb +133 -0
  47. data/lib/prosody/native_stubs.rb +11 -956
  48. data/lib/prosody/state/deque.rb +253 -0
  49. data/lib/prosody/state/map.rb +313 -0
  50. data/lib/prosody/state/set.rb +121 -0
  51. data/lib/prosody/state/value.rb +58 -0
  52. data/lib/prosody/state.rb +157 -677
  53. data/lib/prosody/version.rb +1 -1
  54. data/lib/prosody.rb +2 -1
  55. data/sig/configuration.rbs +19 -10
  56. data/sig/prosody.rbs +37 -4
  57. data/sig/published.rbs +102 -0
  58. data/sig/state.rbs +109 -122
  59. data/typecheck/payload_types.rb +12 -0
  60. data/typecheck/payload_types.rbs +1 -0
  61. metadata +30 -11
  62. 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
- }