prosody 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.cargo/config.toml +2 -0
  3. data/.release-please-manifest.json +3 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/.standard.yml +9 -0
  7. data/.taplo.toml +6 -0
  8. data/ARCHITECTURE.md +591 -0
  9. data/CHANGELOG.md +92 -0
  10. data/Cargo.lock +3513 -0
  11. data/Cargo.toml +77 -0
  12. data/LICENSE +21 -0
  13. data/Makefile +36 -0
  14. data/README.md +946 -0
  15. data/Rakefile +26 -0
  16. data/ext/prosody/Cargo.toml +38 -0
  17. data/ext/prosody/extconf.rb +6 -0
  18. data/ext/prosody/src/admin.rs +171 -0
  19. data/ext/prosody/src/bridge/callback.rs +60 -0
  20. data/ext/prosody/src/bridge/mod.rs +332 -0
  21. data/ext/prosody/src/client/config.rs +819 -0
  22. data/ext/prosody/src/client/mod.rs +379 -0
  23. data/ext/prosody/src/gvl.rs +149 -0
  24. data/ext/prosody/src/handler/context.rs +436 -0
  25. data/ext/prosody/src/handler/message.rs +144 -0
  26. data/ext/prosody/src/handler/mod.rs +338 -0
  27. data/ext/prosody/src/handler/trigger.rs +93 -0
  28. data/ext/prosody/src/lib.rs +82 -0
  29. data/ext/prosody/src/logging.rs +353 -0
  30. data/ext/prosody/src/scheduler/cancellation.rs +67 -0
  31. data/ext/prosody/src/scheduler/handle.rs +50 -0
  32. data/ext/prosody/src/scheduler/mod.rs +169 -0
  33. data/ext/prosody/src/scheduler/processor.rs +166 -0
  34. data/ext/prosody/src/scheduler/result.rs +197 -0
  35. data/ext/prosody/src/tracing_util.rs +56 -0
  36. data/ext/prosody/src/util.rs +219 -0
  37. data/lib/prosody/configuration.rb +333 -0
  38. data/lib/prosody/handler.rb +177 -0
  39. data/lib/prosody/native_stubs.rb +417 -0
  40. data/lib/prosody/processor.rb +321 -0
  41. data/lib/prosody/sentry.rb +36 -0
  42. data/lib/prosody/version.rb +10 -0
  43. data/lib/prosody.rb +42 -0
  44. data/release-please-config.json +10 -0
  45. data/sig/configuration.rbs +252 -0
  46. data/sig/handler.rbs +79 -0
  47. data/sig/processor.rbs +100 -0
  48. data/sig/prosody.rbs +171 -0
  49. data/sig/version.rbs +9 -0
  50. metadata +193 -0
@@ -0,0 +1,819 @@
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::cassandra::config::CassandraConfigurationBuilder;
11
+ use prosody::consumer::ConsumerConfigurationBuilder;
12
+ use prosody::consumer::SpanRelation;
13
+ use prosody::consumer::middleware::deduplication::DeduplicationConfigurationBuilder;
14
+ use prosody::consumer::middleware::defer::DeferConfigurationBuilder;
15
+ use prosody::consumer::middleware::monopolization::MonopolizationConfigurationBuilder;
16
+ use prosody::consumer::middleware::retry::RetryConfigurationBuilder;
17
+ use prosody::consumer::middleware::scheduler::SchedulerConfigurationBuilder;
18
+ use prosody::consumer::middleware::timeout::TimeoutConfigurationBuilder;
19
+ use prosody::consumer::middleware::topic::FailureTopicConfigurationBuilder;
20
+ use prosody::high_level::ConsumerBuilders;
21
+ use prosody::high_level::mode::Mode;
22
+ use prosody::producer::ProducerConfigurationBuilder;
23
+ use prosody::telemetry::emitter::TelemetryEmitterConfiguration;
24
+ use serde::{Deserialize, Deserializer};
25
+ use serde_magnus::deserialize;
26
+ use serde_untagged::UntaggedEnumVisitor;
27
+ use std::time::Duration;
28
+
29
+ /// Configuration structure for the Prosody client that maps Ruby configuration
30
+ /// values to their native Rust equivalents.
31
+ ///
32
+ /// This structure contains all possible configuration options that can be
33
+ /// provided by the Ruby side, which are then converted to the appropriate
34
+ /// Prosody configuration builder types.
35
+ #[derive(Clone, Debug, Default, Deserialize)]
36
+ pub struct NativeConfiguration {
37
+ /// List of Kafka bootstrap server addresses
38
+ bootstrap_servers: Option<Vec<String>>,
39
+
40
+ /// Whether to use mock mode (for testing)
41
+ mock: Option<bool>,
42
+
43
+ /// Maximum time to wait for a send operation to complete (in seconds)
44
+ send_timeout: Option<f32>,
45
+
46
+ /// Kafka consumer group ID
47
+ group_id: Option<String>,
48
+
49
+ /// Global shared cache capacity across all partitions for message
50
+ /// deduplication
51
+ idempotence_cache_size: Option<u32>,
52
+
53
+ /// Version string for cache-busting deduplication hashes
54
+ idempotence_version: Option<String>,
55
+
56
+ /// TTL for deduplication records in Cassandra (in seconds)
57
+ idempotence_ttl: Option<f64>,
58
+
59
+ /// List of Kafka topics to subscribe to
60
+ subscribed_topics: Option<Vec<String>>,
61
+
62
+ /// List of event types that the consumer is allowed to process
63
+ allowed_events: Option<Vec<String>>,
64
+
65
+ /// Identifier for the system producing messages
66
+ source_system: Option<String>,
67
+
68
+ /// Maximum number of concurrent message processing tasks
69
+ max_concurrency: Option<u32>,
70
+
71
+ /// Maximum number of messages to process before committing offsets
72
+ max_uncommitted: Option<u16>,
73
+
74
+ /// Threshold in seconds after which a stalled consumer is detected
75
+ stall_threshold: Option<f32>,
76
+
77
+ /// Maximum time to wait for a clean shutdown (in seconds)
78
+ shutdown_timeout: Option<f32>,
79
+
80
+ /// Interval between Kafka poll operations (in seconds)
81
+ poll_interval: Option<f32>,
82
+
83
+ /// Interval between offset commit operations (in seconds)
84
+ commit_interval: Option<f32>,
85
+
86
+ /// Operation mode of the client (`pipeline`, `low_latency`, `best_effort`)
87
+ mode: Option<String>,
88
+
89
+ /// Base delay for retry operations (in seconds)
90
+ retry_base: Option<f32>,
91
+
92
+ /// Maximum number of retry attempts
93
+ max_retries: Option<u32>,
94
+
95
+ /// Maximum delay between retries (in seconds)
96
+ max_retry_delay: Option<f32>,
97
+
98
+ /// Topic to send failed messages to
99
+ failure_topic: Option<String>,
100
+
101
+ /// Configuration for the health probe port
102
+ probe_port: Option<ProbePort>,
103
+
104
+ /// List of Cassandra contact nodes (hostnames or IPs)
105
+ cassandra_nodes: Option<Vec<String>>,
106
+
107
+ /// Keyspace to use for storing timer data in Cassandra
108
+ cassandra_keyspace: Option<String>,
109
+
110
+ /// Preferred datacenter for Cassandra query routing
111
+ cassandra_datacenter: Option<String>,
112
+
113
+ /// Preferred rack identifier for Cassandra topology-aware routing
114
+ cassandra_rack: Option<String>,
115
+
116
+ /// Username for authenticating with Cassandra
117
+ cassandra_user: Option<String>,
118
+
119
+ /// Password for authenticating with Cassandra
120
+ cassandra_password: Option<String>,
121
+
122
+ /// Retention period for failed/unprocessed timer data in Cassandra (in
123
+ /// seconds)
124
+ cassandra_retention: Option<f32>,
125
+
126
+ /// Timer slab partitioning duration in seconds.
127
+ /// Controls how timers are grouped for storage and retrieval.
128
+ slab_size: Option<f32>,
129
+
130
+ // Scheduler configuration
131
+ /// Target proportion of execution time for failure/retry task processing
132
+ /// (0.0 to 1.0). Controls bandwidth allocation between Normal and
133
+ /// Failure task classes.
134
+ scheduler_failure_weight: Option<f64>,
135
+
136
+ /// Wait duration (in seconds) at which urgency boost reaches maximum
137
+ /// intensity.
138
+ scheduler_max_wait: Option<f32>,
139
+
140
+ /// Maximum urgency boost (in seconds of virtual time) for waiting tasks.
141
+ scheduler_wait_weight: Option<f64>,
142
+
143
+ /// Cache capacity for tracking per-key virtual time in the scheduler.
144
+ scheduler_cache_size: Option<u32>,
145
+
146
+ // Monopolization configuration
147
+ /// Whether monopolization detection is enabled.
148
+ monopolization_enabled: Option<bool>,
149
+
150
+ /// Threshold for monopolization detection (0.0 to 1.0).
151
+ monopolization_threshold: Option<f64>,
152
+
153
+ /// Rolling window duration (in seconds) for monopolization detection.
154
+ monopolization_window: Option<f32>,
155
+
156
+ /// Cache size for tracking key execution intervals.
157
+ monopolization_cache_size: Option<u32>,
158
+
159
+ // Defer configuration
160
+ /// Whether deferral is enabled for new messages.
161
+ defer_enabled: Option<bool>,
162
+
163
+ /// Base exponential backoff delay for deferred retries (in seconds).
164
+ defer_base: Option<f32>,
165
+
166
+ /// Maximum delay between deferred retries (in seconds).
167
+ defer_max_delay: Option<f32>,
168
+
169
+ /// Failure rate threshold for enabling deferral (0.0 to 1.0).
170
+ defer_failure_threshold: Option<f64>,
171
+
172
+ /// Sliding window duration (in seconds) for failure rate tracking.
173
+ defer_failure_window: Option<f32>,
174
+
175
+ /// Cache size for defer middleware.
176
+ defer_cache_size: Option<u32>,
177
+
178
+ /// Timeout for Kafka seek operations (in seconds).
179
+ defer_seek_timeout: Option<f32>,
180
+
181
+ /// Messages to read sequentially before seeking.
182
+ defer_discard_threshold: Option<i64>,
183
+
184
+ // Timeout configuration
185
+ /// Fixed timeout duration for handler execution (in seconds).
186
+ timeout: Option<f32>,
187
+
188
+ // Telemetry emitter configuration
189
+ /// Kafka topic to produce telemetry events to.
190
+ telemetry_topic: Option<String>,
191
+
192
+ /// Whether the telemetry emitter is enabled.
193
+ telemetry_enabled: Option<bool>,
194
+
195
+ // OTel span linking
196
+ /// Span linking for message execution spans (`child` or `follows_from`).
197
+ message_spans: Option<String>,
198
+
199
+ /// Span linking for timer execution spans (`child` or `follows_from`).
200
+ timer_spans: Option<String>,
201
+ }
202
+
203
+ /// Configuration for the health probe port.
204
+ ///
205
+ /// This enum represents the three possible states for the probe port
206
+ /// configuration:
207
+ /// - Unconfigured: The default state, where the standard configuration is used
208
+ /// - Disabled: Explicitly disables the probe port
209
+ /// - Configured: Sets the probe port to a specific port number
210
+ #[derive(Copy, Clone, Debug, Default)]
211
+ pub enum ProbePort {
212
+ /// Use default configuration
213
+ #[default]
214
+ Unconfigured,
215
+
216
+ /// Explicitly disable the probe port
217
+ Disabled,
218
+
219
+ /// Use a specific port number
220
+ Configured(u16),
221
+ }
222
+
223
+ impl<'de> Deserialize<'de> for ProbePort {
224
+ /// Deserializes a probe port configuration from various possible input
225
+ /// formats.
226
+ ///
227
+ /// # Arguments
228
+ ///
229
+ /// * `deserializer` - The deserializer to use
230
+ ///
231
+ /// # Returns
232
+ ///
233
+ /// A `ProbePort` enum variant based on the input:
234
+ /// - If a u16 is provided, it returns `ProbePort::Configured(port)`
235
+ /// - If a boolean `true` is provided, it returns `ProbePort::Unconfigured`
236
+ /// - If a boolean `false` is provided, it returns `ProbePort::Disabled`
237
+ /// - If nothing is provided, it returns `ProbePort::Unconfigured`
238
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239
+ where
240
+ D: Deserializer<'de>,
241
+ {
242
+ UntaggedEnumVisitor::new()
243
+ .u16(|port| Ok(Self::Configured(port)))
244
+ .bool(|enabled| {
245
+ if enabled {
246
+ Ok(Self::Unconfigured)
247
+ } else {
248
+ Ok(Self::Disabled)
249
+ }
250
+ })
251
+ .unit(|| Ok(Self::Unconfigured))
252
+ .deserialize(deserializer)
253
+ }
254
+ }
255
+
256
+ impl NativeConfiguration {
257
+ /// Converts a Ruby value into a `NativeConfiguration`.
258
+ ///
259
+ /// # Arguments
260
+ ///
261
+ /// * `ruby` - Reference to the Ruby VM
262
+ /// * `val` - The Ruby value to convert
263
+ ///
264
+ /// # Returns
265
+ ///
266
+ /// The converted `NativeConfiguration` if successful
267
+ ///
268
+ /// # Errors
269
+ ///
270
+ /// Returns a Magnus error if deserialization fails
271
+ pub fn from_value(ruby: &Ruby, val: Value) -> Result<Self, Error> {
272
+ deserialize(ruby, val)
273
+ }
274
+ }
275
+
276
+ impl<'a> From<&'a NativeConfiguration> for ProducerConfigurationBuilder {
277
+ /// Converts a `NativeConfiguration` reference into a
278
+ /// `ProducerConfigurationBuilder`.
279
+ ///
280
+ /// This takes the relevant producer settings from the configuration and
281
+ /// sets them on a new `ProducerConfigurationBuilder` instance.
282
+ ///
283
+ /// # Arguments
284
+ ///
285
+ /// * `config` - The configuration to convert
286
+ ///
287
+ /// # Returns
288
+ ///
289
+ /// A configured `ProducerConfigurationBuilder`
290
+ fn from(config: &'a NativeConfiguration) -> Self {
291
+ let mut builder = Self::default();
292
+
293
+ if let Some(bootstrap_servers) = &config.bootstrap_servers {
294
+ builder.bootstrap_servers(bootstrap_servers.clone());
295
+ }
296
+
297
+ if let Some(send_timeout) = &config.send_timeout {
298
+ builder.send_timeout(Duration::from_secs_f32(*send_timeout));
299
+ }
300
+
301
+ if let Some(idempotence_cache_size) = &config.idempotence_cache_size {
302
+ builder.idempotence_cache_size(*idempotence_cache_size as usize);
303
+ }
304
+
305
+ if let Some(source_system) = &config.source_system {
306
+ builder.source_system(source_system.clone());
307
+ }
308
+
309
+ if let Some(mock) = &config.mock {
310
+ builder.mock(*mock);
311
+ }
312
+
313
+ builder
314
+ }
315
+ }
316
+
317
+ impl<'a> From<&'a NativeConfiguration> for ConsumerConfigurationBuilder {
318
+ /// Converts a `NativeConfiguration` reference into a
319
+ /// `ConsumerConfigurationBuilder`.
320
+ ///
321
+ /// This takes the relevant consumer settings from the configuration and
322
+ /// sets them on a new `ConsumerConfigurationBuilder` instance.
323
+ ///
324
+ /// # Arguments
325
+ ///
326
+ /// * `config` - The configuration to convert
327
+ ///
328
+ /// # Returns
329
+ ///
330
+ /// A configured `ConsumerConfigurationBuilder`
331
+ fn from(config: &'a NativeConfiguration) -> Self {
332
+ let mut builder = Self::default();
333
+
334
+ if let Some(bootstrap_servers) = &config.bootstrap_servers {
335
+ builder.bootstrap_servers(bootstrap_servers.clone());
336
+ }
337
+
338
+ if let Some(group_id) = &config.group_id {
339
+ builder.group_id(group_id.clone());
340
+ }
341
+
342
+ if let Some(subscribed_topics) = &config.subscribed_topics {
343
+ builder.subscribed_topics(subscribed_topics.clone());
344
+ }
345
+
346
+ if let Some(allowed_events) = &config.allowed_events {
347
+ builder.allowed_events(allowed_events.clone());
348
+ }
349
+
350
+ if let Some(max_uncommitted) = &config.max_uncommitted {
351
+ builder.max_uncommitted(*max_uncommitted as usize);
352
+ }
353
+
354
+ if let Some(stall_threshold) = &config.stall_threshold {
355
+ builder.stall_threshold(Duration::from_secs_f32(*stall_threshold));
356
+ }
357
+
358
+ if let Some(shutdown_timeout) = &config.shutdown_timeout {
359
+ builder.shutdown_timeout(Duration::from_secs_f32(*shutdown_timeout));
360
+ }
361
+
362
+ if let Some(poll_interval) = &config.poll_interval {
363
+ builder.poll_interval(Duration::from_secs_f32(*poll_interval));
364
+ }
365
+
366
+ if let Some(commit_interval) = &config.commit_interval {
367
+ builder.commit_interval(Duration::from_secs_f32(*commit_interval));
368
+ }
369
+
370
+ if let Some(mock) = &config.mock {
371
+ builder.mock(*mock);
372
+ }
373
+
374
+ if let Some(probe_port) = &config.probe_port {
375
+ match probe_port {
376
+ ProbePort::Unconfigured => {}
377
+ ProbePort::Disabled => {
378
+ builder.probe_port(None);
379
+ }
380
+ ProbePort::Configured(port) => {
381
+ builder.probe_port(*port);
382
+ }
383
+ }
384
+ }
385
+
386
+ if let Some(slab_size) = &config.slab_size {
387
+ builder.slab_size(Duration::from_secs_f32(*slab_size));
388
+ }
389
+
390
+ builder
391
+ }
392
+ }
393
+
394
+ impl<'a> From<&'a NativeConfiguration> for RetryConfigurationBuilder {
395
+ /// Converts a `NativeConfiguration` reference into a
396
+ /// `RetryConfigurationBuilder`.
397
+ ///
398
+ /// This takes the relevant retry settings from the configuration and
399
+ /// sets them on a new `RetryConfigurationBuilder` instance.
400
+ ///
401
+ /// # Arguments
402
+ ///
403
+ /// * `config` - The configuration to convert
404
+ ///
405
+ /// # Returns
406
+ ///
407
+ /// A configured `RetryConfigurationBuilder`
408
+ fn from(config: &'a NativeConfiguration) -> Self {
409
+ let mut builder = Self::default();
410
+
411
+ if let Some(retry_base) = &config.retry_base {
412
+ builder.base(Duration::from_secs_f32(*retry_base));
413
+ }
414
+
415
+ if let Some(max_retries) = &config.max_retries {
416
+ builder.max_retries(*max_retries);
417
+ }
418
+
419
+ if let Some(max_retry_delay) = &config.max_retry_delay {
420
+ builder.max_delay(Duration::from_secs_f32(*max_retry_delay));
421
+ }
422
+
423
+ builder
424
+ }
425
+ }
426
+
427
+ impl<'a> From<&'a NativeConfiguration> for FailureTopicConfigurationBuilder {
428
+ /// Converts a `NativeConfiguration` reference into a
429
+ /// `FailureTopicConfigurationBuilder`.
430
+ ///
431
+ /// This takes the relevant failure topic settings from the configuration
432
+ /// and sets them on a new `FailureTopicConfigurationBuilder` instance.
433
+ ///
434
+ /// # Arguments
435
+ ///
436
+ /// * `config` - The configuration to convert
437
+ ///
438
+ /// # Returns
439
+ ///
440
+ /// A configured `FailureTopicConfigurationBuilder`
441
+ fn from(config: &'a NativeConfiguration) -> Self {
442
+ let mut builder = Self::default();
443
+
444
+ if let Some(failure_topic) = &config.failure_topic {
445
+ builder.failure_topic(failure_topic.clone());
446
+ }
447
+
448
+ builder
449
+ }
450
+ }
451
+
452
+ impl<'a> TryFrom<&'a NativeConfiguration> for Mode {
453
+ type Error = String;
454
+
455
+ /// Attempts to convert a `NativeConfiguration` reference into a Prosody
456
+ /// Mode.
457
+ ///
458
+ /// This extracts the mode setting from the configuration and converts it
459
+ /// to a Prosody Mode enum value.
460
+ ///
461
+ /// # Arguments
462
+ ///
463
+ /// * `value` - The configuration to convert
464
+ ///
465
+ /// # Returns
466
+ ///
467
+ /// The corresponding `Mode` if successful
468
+ ///
469
+ /// # Errors
470
+ ///
471
+ /// Returns a String error if the mode is unrecognized
472
+ fn try_from(value: &'a NativeConfiguration) -> Result<Self, Self::Error> {
473
+ let Some(mode_str) = value.mode.as_deref() else {
474
+ return Ok(Mode::default());
475
+ };
476
+
477
+ match mode_str {
478
+ "pipeline" => Ok(Mode::Pipeline),
479
+ "low_latency" => Ok(Mode::LowLatency),
480
+ "best_effort" => Ok(Mode::BestEffort),
481
+ string => Err(format!("unrecognized mode: {string}")),
482
+ }
483
+ }
484
+ }
485
+
486
+ impl<'a> From<&'a NativeConfiguration> for CassandraConfigurationBuilder {
487
+ /// Converts a `NativeConfiguration` reference into a
488
+ /// `CassandraConfigurationBuilder`.
489
+ ///
490
+ /// This takes the relevant Cassandra settings from the configuration and
491
+ /// sets them on a new `CassandraConfigurationBuilder` instance.
492
+ ///
493
+ /// # Arguments
494
+ ///
495
+ /// * `config` - The configuration to convert
496
+ ///
497
+ /// # Returns
498
+ ///
499
+ /// A configured `CassandraConfigurationBuilder`
500
+ fn from(config: &'a NativeConfiguration) -> Self {
501
+ let mut builder = Self::default();
502
+
503
+ if let Some(nodes) = &config.cassandra_nodes {
504
+ builder.nodes(nodes.clone());
505
+ }
506
+
507
+ if let Some(keyspace) = &config.cassandra_keyspace {
508
+ builder.keyspace(keyspace.clone());
509
+ }
510
+
511
+ if let Some(datacenter) = &config.cassandra_datacenter {
512
+ builder.datacenter(Some(datacenter.clone()));
513
+ }
514
+
515
+ if let Some(rack) = &config.cassandra_rack {
516
+ builder.rack(Some(rack.clone()));
517
+ }
518
+
519
+ if let Some(user) = &config.cassandra_user {
520
+ builder.user(Some(user.clone()));
521
+ }
522
+
523
+ if let Some(password) = &config.cassandra_password {
524
+ builder.password(Some(password.clone()));
525
+ }
526
+
527
+ if let Some(retention) = &config.cassandra_retention {
528
+ builder.retention(Duration::from_secs_f32(*retention));
529
+ }
530
+
531
+ builder
532
+ }
533
+ }
534
+
535
+ impl<'a> From<&'a NativeConfiguration> for SchedulerConfigurationBuilder {
536
+ /// Converts a `NativeConfiguration` reference into a
537
+ /// `SchedulerConfigurationBuilder`.
538
+ ///
539
+ /// This takes the relevant scheduler settings from the configuration and
540
+ /// sets them on a new `SchedulerConfigurationBuilder` instance.
541
+ ///
542
+ /// # Arguments
543
+ ///
544
+ /// * `config` - The configuration to convert
545
+ ///
546
+ /// # Returns
547
+ ///
548
+ /// A configured `SchedulerConfigurationBuilder`
549
+ fn from(config: &'a NativeConfiguration) -> Self {
550
+ let mut builder = Self::default();
551
+
552
+ if let Some(max_concurrency) = &config.max_concurrency {
553
+ builder.max_concurrency(*max_concurrency as usize);
554
+ }
555
+
556
+ if let Some(failure_weight) = &config.scheduler_failure_weight {
557
+ builder.failure_weight(*failure_weight);
558
+ }
559
+
560
+ if let Some(max_wait) = &config.scheduler_max_wait {
561
+ builder.max_wait(Duration::from_secs_f32(*max_wait));
562
+ }
563
+
564
+ if let Some(wait_weight) = &config.scheduler_wait_weight {
565
+ builder.wait_weight(*wait_weight);
566
+ }
567
+
568
+ if let Some(cache_size) = &config.scheduler_cache_size {
569
+ builder.cache_size(*cache_size as usize);
570
+ }
571
+
572
+ builder
573
+ }
574
+ }
575
+
576
+ impl<'a> From<&'a NativeConfiguration> for MonopolizationConfigurationBuilder {
577
+ /// Converts a `NativeConfiguration` reference into a
578
+ /// `MonopolizationConfigurationBuilder`.
579
+ ///
580
+ /// This takes the relevant monopolization settings from the configuration
581
+ /// and sets them on a new `MonopolizationConfigurationBuilder` instance.
582
+ ///
583
+ /// # Arguments
584
+ ///
585
+ /// * `config` - The configuration to convert
586
+ ///
587
+ /// # Returns
588
+ ///
589
+ /// A configured `MonopolizationConfigurationBuilder`
590
+ fn from(config: &'a NativeConfiguration) -> Self {
591
+ let mut builder = Self::default();
592
+
593
+ if let Some(enabled) = &config.monopolization_enabled {
594
+ builder.enabled(*enabled);
595
+ }
596
+
597
+ if let Some(threshold) = &config.monopolization_threshold {
598
+ builder.monopolization_threshold(*threshold);
599
+ }
600
+
601
+ if let Some(window) = &config.monopolization_window {
602
+ builder.window_duration(Duration::from_secs_f32(*window));
603
+ }
604
+
605
+ if let Some(cache_size) = &config.monopolization_cache_size {
606
+ builder.cache_size(*cache_size as usize);
607
+ }
608
+
609
+ builder
610
+ }
611
+ }
612
+
613
+ impl<'a> From<&'a NativeConfiguration> for DeferConfigurationBuilder {
614
+ /// Converts a `NativeConfiguration` reference into a
615
+ /// `DeferConfigurationBuilder`.
616
+ ///
617
+ /// This takes the relevant defer settings from the configuration and
618
+ /// sets them on a new `DeferConfigurationBuilder` instance.
619
+ ///
620
+ /// # Arguments
621
+ ///
622
+ /// * `config` - The configuration to convert
623
+ ///
624
+ /// # Returns
625
+ ///
626
+ /// A configured `DeferConfigurationBuilder`
627
+ fn from(config: &'a NativeConfiguration) -> Self {
628
+ let mut builder = Self::default();
629
+
630
+ if let Some(enabled) = &config.defer_enabled {
631
+ builder.enabled(*enabled);
632
+ }
633
+
634
+ if let Some(base) = &config.defer_base {
635
+ builder.base(Duration::from_secs_f32(*base));
636
+ }
637
+
638
+ if let Some(max_delay) = &config.defer_max_delay {
639
+ builder.max_delay(Duration::from_secs_f32(*max_delay));
640
+ }
641
+
642
+ if let Some(failure_threshold) = &config.defer_failure_threshold {
643
+ builder.failure_threshold(*failure_threshold);
644
+ }
645
+
646
+ if let Some(failure_window) = &config.defer_failure_window {
647
+ builder.failure_window(Duration::from_secs_f32(*failure_window));
648
+ }
649
+
650
+ if let Some(cache_size) = &config.defer_cache_size {
651
+ builder.cache_size(*cache_size as usize);
652
+ }
653
+
654
+ if let Some(seek_timeout) = &config.defer_seek_timeout {
655
+ builder.seek_timeout(Duration::from_secs_f32(*seek_timeout));
656
+ }
657
+
658
+ if let Some(discard_threshold) = &config.defer_discard_threshold {
659
+ builder.discard_threshold(*discard_threshold);
660
+ }
661
+
662
+ builder
663
+ }
664
+ }
665
+
666
+ impl<'a> From<&'a NativeConfiguration> for TimeoutConfigurationBuilder {
667
+ /// Converts a `NativeConfiguration` reference into a
668
+ /// `TimeoutConfigurationBuilder`.
669
+ ///
670
+ /// This takes the relevant timeout settings from the configuration and
671
+ /// sets them on a new `TimeoutConfigurationBuilder` instance.
672
+ ///
673
+ /// # Arguments
674
+ ///
675
+ /// * `config` - The configuration to convert
676
+ ///
677
+ /// # Returns
678
+ ///
679
+ /// A configured `TimeoutConfigurationBuilder`
680
+ fn from(config: &'a NativeConfiguration) -> Self {
681
+ let mut builder = Self::default();
682
+
683
+ if let Some(timeout) = &config.timeout {
684
+ builder.timeout(Some(Duration::from_secs_f32(*timeout)));
685
+ }
686
+
687
+ builder
688
+ }
689
+ }
690
+
691
+ impl<'a> From<&'a NativeConfiguration> for DeduplicationConfigurationBuilder {
692
+ /// Converts a `NativeConfiguration` reference into a
693
+ /// `DeduplicationConfigurationBuilder`.
694
+ ///
695
+ /// This takes the relevant deduplication settings from the configuration
696
+ /// and sets them on a new `DeduplicationConfigurationBuilder` instance.
697
+ ///
698
+ /// # Arguments
699
+ ///
700
+ /// * `config` - The configuration to convert
701
+ ///
702
+ /// # Returns
703
+ ///
704
+ /// A configured `DeduplicationConfigurationBuilder`
705
+ fn from(config: &'a NativeConfiguration) -> Self {
706
+ let mut builder = Self::default();
707
+
708
+ if let Some(cache_capacity) = &config.idempotence_cache_size {
709
+ builder.cache_capacity(*cache_capacity as usize);
710
+ }
711
+
712
+ if let Some(version) = &config.idempotence_version {
713
+ builder.version(version.clone());
714
+ }
715
+
716
+ if let Some(ttl) = &config.idempotence_ttl
717
+ && ttl.is_finite()
718
+ && *ttl >= 0.0_f64
719
+ {
720
+ builder.ttl(Duration::from_secs_f64(*ttl));
721
+ }
722
+
723
+ builder
724
+ }
725
+ }
726
+
727
+ impl<'a> TryFrom<&'a NativeConfiguration> for TelemetryEmitterConfiguration {
728
+ type Error = String;
729
+
730
+ /// Attempts to convert a `NativeConfiguration` reference into a
731
+ /// `TelemetryEmitterConfiguration`.
732
+ ///
733
+ /// This takes the relevant telemetry emitter settings from the
734
+ /// configuration and constructs a `TelemetryEmitterConfiguration`,
735
+ /// falling back to environment-variable-aware defaults for any unset
736
+ /// fields.
737
+ ///
738
+ /// # Arguments
739
+ ///
740
+ /// * `config` - The configuration to convert
741
+ ///
742
+ /// # Returns
743
+ ///
744
+ /// A configured `TelemetryEmitterConfiguration` if successful
745
+ ///
746
+ /// # Errors
747
+ ///
748
+ /// Returns a `String` error if a related environment variable contains an
749
+ /// unparseable value.
750
+ fn try_from(config: &'a NativeConfiguration) -> Result<Self, Self::Error> {
751
+ let mut builder = Self::builder();
752
+
753
+ if let Some(topic) = &config.telemetry_topic {
754
+ builder.topic(topic.clone());
755
+ }
756
+
757
+ if let Some(enabled) = &config.telemetry_enabled {
758
+ builder.enabled(*enabled);
759
+ }
760
+
761
+ builder.build().map_err(|e| e.to_string())
762
+ }
763
+ }
764
+
765
+ impl<'a> TryFrom<&'a NativeConfiguration> for ConsumerBuilders {
766
+ type Error = String;
767
+
768
+ /// Attempts to convert a `NativeConfiguration` reference into a
769
+ /// `ConsumerBuilders`.
770
+ ///
771
+ /// This creates all the consumer-related configuration builders from
772
+ /// the configuration.
773
+ ///
774
+ /// # Arguments
775
+ ///
776
+ /// * `config` - The configuration to convert
777
+ ///
778
+ /// # Returns
779
+ ///
780
+ /// A `ConsumerBuilders` containing all consumer-related configuration
781
+ /// builders if successful
782
+ ///
783
+ /// # Errors
784
+ ///
785
+ /// Returns a `String` error if:
786
+ /// - The telemetry emitter configuration cannot be built (e.g. an
787
+ /// environment variable contains an unparseable value).
788
+ /// - `message_spans` or `timer_spans` contains an unrecognized value
789
+ /// (expected `"child"` or `"follows_from"`).
790
+ fn try_from(config: &'a NativeConfiguration) -> Result<Self, Self::Error> {
791
+ let mut consumer: ConsumerConfigurationBuilder = config.into();
792
+
793
+ if let Some(s) = &config.message_spans {
794
+ let relation = s
795
+ .parse::<SpanRelation>()
796
+ .map_err(|e| format!("message_spans: {e}"))?;
797
+ consumer.message_spans(relation);
798
+ }
799
+
800
+ if let Some(s) = &config.timer_spans {
801
+ let relation = s
802
+ .parse::<SpanRelation>()
803
+ .map_err(|e| format!("timer_spans: {e}"))?;
804
+ consumer.timer_spans(relation);
805
+ }
806
+
807
+ Ok(Self {
808
+ consumer,
809
+ retry: config.into(),
810
+ failure_topic: config.into(),
811
+ scheduler: config.into(),
812
+ monopolization: config.into(),
813
+ defer: config.into(),
814
+ timeout: config.into(),
815
+ dedup: config.into(),
816
+ emitter: config.try_into()?,
817
+ })
818
+ }
819
+ }