1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::time::Duration;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct KafkaSourceConfig {
37 pub bootstrap_servers: Vec<String>,
40
41 pub group_id: String,
43
44 pub topics: Vec<String>,
46
47 #[serde(default = "default_auto_offset_reset")]
50 pub auto_offset_reset: String,
51
52 #[serde(default = "default_enable_auto_commit")]
54 pub enable_auto_commit: bool,
55
56 #[serde(
58 default = "default_auto_commit_interval",
59 with = "duration_millis"
60 )]
61 pub auto_commit_interval: Duration,
62
63 #[serde(default = "default_max_poll_records")]
65 pub max_poll_records: usize,
66
67 #[serde(default = "default_fetch_wait_max", with = "duration_millis")]
69 pub fetch_wait_max: Duration,
70
71 #[serde(default = "default_fetch_min_bytes")]
73 pub fetch_min_bytes: usize,
74
75 #[serde(default = "default_max_partition_fetch_bytes")]
77 pub max_partition_fetch_bytes: usize,
78
79 #[serde(default = "default_session_timeout", with = "duration_millis")]
82 pub session_timeout: Duration,
83
84 #[serde(default = "default_heartbeat_interval", with = "duration_millis")]
86 pub heartbeat_interval: Duration,
87
88 #[serde(default)]
91 pub security_protocol: SecurityProtocol,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub sasl_mechanism: Option<SaslMechanism>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub sasl_username: Option<String>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub sasl_password: Option<String>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub ssl_config: Option<SslConfig>,
108
109 #[serde(default = "default_num_consumer_threads")]
112 pub num_consumer_threads: usize,
113
114 #[serde(default = "default_queued_max_messages")]
116 pub queued_max_messages: usize,
117
118 #[serde(default = "default_socket_receive_buffer_bytes")]
120 pub socket_receive_buffer_bytes: usize,
121
122 #[serde(default = "default_isolation_level")]
125 pub isolation_level: String,
126
127 #[serde(default)]
130 pub additional_properties: HashMap<String, String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct KafkaSinkConfig {
160 pub bootstrap_servers: Vec<String>,
163
164 pub topic: String,
166
167 #[serde(default = "default_acks")]
170 pub acks: String,
171
172 #[serde(default = "default_retries")]
174 pub retries: u32,
175
176 #[serde(default = "default_max_in_flight_requests")]
178 pub max_in_flight_requests: usize,
179
180 #[serde(default = "default_enable_idempotence")]
182 pub enable_idempotence: bool,
183
184 #[serde(default = "default_request_timeout", with = "duration_millis")]
186 pub request_timeout: Duration,
187
188 #[serde(default = "default_batch_size")]
191 pub batch_size: usize,
192
193 #[serde(default = "default_linger", with = "duration_millis")]
195 pub linger: Duration,
196
197 #[serde(default = "default_buffer_memory")]
199 pub buffer_memory: usize,
200
201 #[serde(default = "default_max_block", with = "duration_millis")]
203 pub max_block: Duration,
204
205 #[serde(default)]
208 pub compression: CompressionType,
209
210 #[serde(default = "default_max_request_size")]
213 pub max_request_size: usize,
214
215 #[serde(default)]
218 pub security_protocol: SecurityProtocol,
219
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub sasl_mechanism: Option<SaslMechanism>,
223
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub sasl_username: Option<String>,
227
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub sasl_password: Option<String>,
231
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub ssl_config: Option<SslConfig>,
235
236 #[serde(default = "default_socket_send_buffer_bytes")]
239 pub socket_send_buffer_bytes: usize,
240
241 #[serde(default = "default_queued_max_messages_producer")]
243 pub queued_max_messages: usize,
244
245 #[serde(skip_serializing_if = "Option::is_none")]
248 pub partitioner: Option<String>,
249
250 #[serde(default)]
253 pub additional_properties: HashMap<String, String>,
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
258#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
259pub enum SecurityProtocol {
260 #[default]
262 Plaintext,
263 Ssl,
265 SaslPlaintext,
267 SaslSsl,
269}
270
271impl SecurityProtocol {
272 pub fn as_str(&self) -> &'static str {
274 match self {
275 SecurityProtocol::Plaintext => "PLAINTEXT",
276 SecurityProtocol::Ssl => "SSL",
277 SecurityProtocol::SaslPlaintext => "SASL_PLAINTEXT",
278 SecurityProtocol::SaslSsl => "SASL_SSL",
279 }
280 }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
285pub enum SaslMechanism {
286 Plain,
288 #[serde(rename = "SCRAM-SHA-256")]
290 ScramSha256,
291 #[serde(rename = "SCRAM-SHA-512")]
293 ScramSha512,
294 Gssapi,
296 OauthBearer,
298}
299
300impl SaslMechanism {
301 pub fn as_str(&self) -> &'static str {
303 match self {
304 SaslMechanism::Plain => "PLAIN",
305 SaslMechanism::ScramSha256 => "SCRAM-SHA-256",
306 SaslMechanism::ScramSha512 => "SCRAM-SHA-512",
307 SaslMechanism::Gssapi => "GSSAPI",
308 SaslMechanism::OauthBearer => "OAUTHBEARER",
309 }
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct SslConfig {
316 pub ca_location: Option<String>,
318
319 pub certificate_location: Option<String>,
321
322 pub key_location: Option<String>,
324
325 pub key_password: Option<String>,
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
331#[serde(rename_all = "lowercase")]
332pub enum CompressionType {
333 #[default]
335 None,
336 Gzip,
338 Snappy,
340 Lz4,
342 Zstd,
344}
345
346impl CompressionType {
347 pub fn as_str(&self) -> &'static str {
349 match self {
350 CompressionType::None => "none",
351 CompressionType::Gzip => "gzip",
352 CompressionType::Snappy => "snappy",
353 CompressionType::Lz4 => "lz4",
354 CompressionType::Zstd => "zstd",
355 }
356 }
357}
358
359fn default_auto_offset_reset() -> String {
361 "latest".to_string()
362}
363
364fn default_enable_auto_commit() -> bool {
365 true
366}
367
368fn default_auto_commit_interval() -> Duration {
369 Duration::from_secs(5)
370}
371
372fn default_max_poll_records() -> usize {
373 500
374}
375
376fn default_fetch_wait_max() -> Duration {
377 Duration::from_millis(500)
378}
379
380fn default_fetch_min_bytes() -> usize {
381 1
382}
383
384fn default_max_partition_fetch_bytes() -> usize {
385 1048576 }
387
388fn default_session_timeout() -> Duration {
389 Duration::from_secs(30)
390}
391
392fn default_heartbeat_interval() -> Duration {
393 Duration::from_secs(3)
394}
395
396fn default_num_consumer_threads() -> usize {
397 1
398}
399
400fn default_queued_max_messages() -> usize {
401 100000
402}
403
404fn default_socket_receive_buffer_bytes() -> usize {
405 65536 }
407
408fn default_isolation_level() -> String {
409 "read_uncommitted".to_string()
410}
411
412fn default_acks() -> String {
414 "all".to_string()
415}
416
417fn default_retries() -> u32 {
418 2147483647 }
420
421fn default_max_in_flight_requests() -> usize {
422 5
423}
424
425fn default_enable_idempotence() -> bool {
426 true
427}
428
429fn default_request_timeout() -> Duration {
430 Duration::from_secs(30)
431}
432
433fn default_batch_size() -> usize {
434 16384 }
436
437fn default_linger() -> Duration {
438 Duration::from_millis(0)
439}
440
441fn default_buffer_memory() -> usize {
442 33554432 }
444
445fn default_max_block() -> Duration {
446 Duration::from_secs(60)
447}
448
449fn default_max_request_size() -> usize {
450 1048576 }
452
453fn default_socket_send_buffer_bytes() -> usize {
454 131072 }
456
457fn default_queued_max_messages_producer() -> usize {
458 100000
459}
460
461mod duration_millis {
463 use serde::{Deserialize, Deserializer, Serializer};
464 use std::time::Duration;
465
466 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
467 where
468 S: Serializer,
469 {
470 serializer.serialize_u64(duration.as_millis() as u64)
471 }
472
473 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
474 where
475 D: Deserializer<'de>,
476 {
477 let millis = u64::deserialize(deserializer)?;
478 Ok(Duration::from_millis(millis))
479 }
480}
481
482impl KafkaSourceConfig {
483 pub fn builder() -> KafkaSourceConfigBuilder {
485 KafkaSourceConfigBuilder::default()
486 }
487
488 pub fn validate(&self) -> Result<(), String> {
500 if self.bootstrap_servers.is_empty() {
501 return Err("bootstrap_servers cannot be empty".to_string());
502 }
503
504 if self.group_id.is_empty() {
505 return Err("group_id cannot be empty".to_string());
506 }
507
508 if self.topics.is_empty() {
509 return Err("topics cannot be empty".to_string());
510 }
511
512 if !["earliest", "latest", "none"].contains(&self.auto_offset_reset.as_str()) {
513 return Err(format!(
514 "invalid auto_offset_reset: {}. Must be 'earliest', 'latest', or 'none'",
515 self.auto_offset_reset
516 ));
517 }
518
519 if !["read_uncommitted", "read_committed"].contains(&self.isolation_level.as_str()) {
520 return Err(format!(
521 "invalid isolation_level: {}. Must be 'read_uncommitted' or 'read_committed'",
522 self.isolation_level
523 ));
524 }
525
526 if matches!(
528 self.security_protocol,
529 SecurityProtocol::SaslPlaintext | SecurityProtocol::SaslSsl
530 ) {
531 if self.sasl_mechanism.is_none() {
532 return Err("sasl_mechanism required when using SASL security protocol".to_string());
533 }
534 if self.sasl_username.is_none() {
535 return Err("sasl_username required when using SASL security protocol".to_string());
536 }
537 if self.sasl_password.is_none() {
538 return Err("sasl_password required when using SASL security protocol".to_string());
539 }
540 }
541
542 Ok(())
543 }
544
545 pub fn to_rdkafka_properties(&self) -> HashMap<String, String> {
547 let mut props = HashMap::new();
548
549 props.insert(
551 "bootstrap.servers".to_string(),
552 self.bootstrap_servers.join(","),
553 );
554 props.insert("group.id".to_string(), self.group_id.clone());
555
556 props.insert(
558 "auto.offset.reset".to_string(),
559 self.auto_offset_reset.clone(),
560 );
561 props.insert(
562 "enable.auto.commit".to_string(),
563 self.enable_auto_commit.to_string(),
564 );
565 props.insert(
566 "auto.commit.interval.ms".to_string(),
567 self.auto_commit_interval.as_millis().to_string(),
568 );
569 props.insert(
570 "fetch.wait.max.ms".to_string(),
571 self.fetch_wait_max.as_millis().to_string(),
572 );
573 props.insert(
574 "fetch.min.bytes".to_string(),
575 self.fetch_min_bytes.to_string(),
576 );
577 props.insert(
578 "max.partition.fetch.bytes".to_string(),
579 self.max_partition_fetch_bytes.to_string(),
580 );
581
582 props.insert(
584 "session.timeout.ms".to_string(),
585 self.session_timeout.as_millis().to_string(),
586 );
587 props.insert(
588 "heartbeat.interval.ms".to_string(),
589 self.heartbeat_interval.as_millis().to_string(),
590 );
591
592 props.insert(
594 "security.protocol".to_string(),
595 self.security_protocol.as_str().to_string(),
596 );
597
598 if let Some(mechanism) = &self.sasl_mechanism {
599 props.insert("sasl.mechanism".to_string(), mechanism.as_str().to_string());
600 }
601
602 if let Some(username) = &self.sasl_username {
603 props.insert("sasl.username".to_string(), username.clone());
604 }
605
606 if let Some(password) = &self.sasl_password {
607 props.insert("sasl.password".to_string(), password.clone());
608 }
609
610 if let Some(ssl_config) = &self.ssl_config {
611 if let Some(ca_location) = &ssl_config.ca_location {
612 props.insert("ssl.ca.location".to_string(), ca_location.clone());
613 }
614 if let Some(cert_location) = &ssl_config.certificate_location {
615 props.insert("ssl.certificate.location".to_string(), cert_location.clone());
616 }
617 if let Some(key_location) = &ssl_config.key_location {
618 props.insert("ssl.key.location".to_string(), key_location.clone());
619 }
620 if let Some(key_password) = &ssl_config.key_password {
621 props.insert("ssl.key.password".to_string(), key_password.clone());
622 }
623 }
624
625 props.insert(
627 "queued.max.messages.kbytes".to_string(),
628 (self.queued_max_messages / 1000).to_string(),
629 );
630 props.insert(
631 "receive.message.max.bytes".to_string(),
632 self.socket_receive_buffer_bytes.to_string(),
633 );
634
635 props.insert("isolation.level".to_string(), self.isolation_level.clone());
637
638 props.extend(self.additional_properties.clone());
640
641 props
642 }
643}
644
645impl KafkaSinkConfig {
646 pub fn builder() -> KafkaSinkConfigBuilder {
648 KafkaSinkConfigBuilder::default()
649 }
650
651 pub fn validate(&self) -> Result<(), String> {
661 if self.bootstrap_servers.is_empty() {
662 return Err("bootstrap_servers cannot be empty".to_string());
663 }
664
665 if self.topic.is_empty() {
666 return Err("topic cannot be empty".to_string());
667 }
668
669 if !["0", "1", "all", "-1"].contains(&self.acks.as_str()) {
670 return Err(format!(
671 "invalid acks: {}. Must be '0', '1', or 'all'",
672 self.acks
673 ));
674 }
675
676 if matches!(
678 self.security_protocol,
679 SecurityProtocol::SaslPlaintext | SecurityProtocol::SaslSsl
680 ) {
681 if self.sasl_mechanism.is_none() {
682 return Err("sasl_mechanism required when using SASL security protocol".to_string());
683 }
684 if self.sasl_username.is_none() {
685 return Err("sasl_username required when using SASL security protocol".to_string());
686 }
687 if self.sasl_password.is_none() {
688 return Err("sasl_password required when using SASL security protocol".to_string());
689 }
690 }
691
692 Ok(())
693 }
694
695 pub fn to_rdkafka_properties(&self) -> HashMap<String, String> {
697 let mut props = HashMap::new();
698
699 props.insert(
701 "bootstrap.servers".to_string(),
702 self.bootstrap_servers.join(","),
703 );
704
705 props.insert("acks".to_string(), self.acks.clone());
707 props.insert("retries".to_string(), self.retries.to_string());
708 props.insert(
709 "max.in.flight.requests.per.connection".to_string(),
710 self.max_in_flight_requests.to_string(),
711 );
712 props.insert(
713 "enable.idempotence".to_string(),
714 self.enable_idempotence.to_string(),
715 );
716 props.insert(
717 "request.timeout.ms".to_string(),
718 self.request_timeout.as_millis().to_string(),
719 );
720
721 props.insert("batch.size".to_string(), self.batch_size.to_string());
723 props.insert(
724 "linger.ms".to_string(),
725 self.linger.as_millis().to_string(),
726 );
727 props.insert("buffer.memory".to_string(), self.buffer_memory.to_string());
728 props.insert(
729 "max.block.ms".to_string(),
730 self.max_block.as_millis().to_string(),
731 );
732
733 props.insert(
735 "compression.type".to_string(),
736 self.compression.as_str().to_string(),
737 );
738
739 props.insert(
741 "max.request.size".to_string(),
742 self.max_request_size.to_string(),
743 );
744
745 props.insert(
747 "security.protocol".to_string(),
748 self.security_protocol.as_str().to_string(),
749 );
750
751 if let Some(mechanism) = &self.sasl_mechanism {
752 props.insert("sasl.mechanism".to_string(), mechanism.as_str().to_string());
753 }
754
755 if let Some(username) = &self.sasl_username {
756 props.insert("sasl.username".to_string(), username.clone());
757 }
758
759 if let Some(password) = &self.sasl_password {
760 props.insert("sasl.password".to_string(), password.clone());
761 }
762
763 if let Some(ssl_config) = &self.ssl_config {
764 if let Some(ca_location) = &ssl_config.ca_location {
765 props.insert("ssl.ca.location".to_string(), ca_location.clone());
766 }
767 if let Some(cert_location) = &ssl_config.certificate_location {
768 props.insert("ssl.certificate.location".to_string(), cert_location.clone());
769 }
770 if let Some(key_location) = &ssl_config.key_location {
771 props.insert("ssl.key.location".to_string(), key_location.clone());
772 }
773 if let Some(key_password) = &ssl_config.key_password {
774 props.insert("ssl.key.password".to_string(), key_password.clone());
775 }
776 }
777
778 props.insert(
780 "send.buffer.bytes".to_string(),
781 self.socket_send_buffer_bytes.to_string(),
782 );
783 props.insert(
784 "queue.buffering.max.messages".to_string(),
785 self.queued_max_messages.to_string(),
786 );
787
788 if let Some(partitioner) = &self.partitioner {
790 props.insert("partitioner".to_string(), partitioner.clone());
791 }
792
793 props.extend(self.additional_properties.clone());
795
796 props
797 }
798}
799
800#[derive(Default)]
802pub struct KafkaSourceConfigBuilder {
803 bootstrap_servers: Option<Vec<String>>,
804 group_id: Option<String>,
805 topics: Option<Vec<String>>,
806 auto_offset_reset: Option<String>,
807 enable_auto_commit: Option<bool>,
808 auto_commit_interval: Option<Duration>,
809 max_poll_records: Option<usize>,
810 fetch_wait_max: Option<Duration>,
811 fetch_min_bytes: Option<usize>,
812 max_partition_fetch_bytes: Option<usize>,
813 session_timeout: Option<Duration>,
814 heartbeat_interval: Option<Duration>,
815 security_protocol: Option<SecurityProtocol>,
816 sasl_mechanism: Option<SaslMechanism>,
817 sasl_username: Option<String>,
818 sasl_password: Option<String>,
819 ssl_config: Option<SslConfig>,
820 num_consumer_threads: Option<usize>,
821 queued_max_messages: Option<usize>,
822 socket_receive_buffer_bytes: Option<usize>,
823 isolation_level: Option<String>,
824 additional_properties: Option<HashMap<String, String>>,
825}
826
827impl KafkaSourceConfigBuilder {
828 pub fn bootstrap_servers(mut self, servers: Vec<String>) -> Self {
829 self.bootstrap_servers = Some(servers);
830 self
831 }
832
833 pub fn group_id(mut self, group_id: String) -> Self {
834 self.group_id = Some(group_id);
835 self
836 }
837
838 pub fn topics(mut self, topics: Vec<String>) -> Self {
839 self.topics = Some(topics);
840 self
841 }
842
843 pub fn auto_offset_reset(mut self, reset: String) -> Self {
844 self.auto_offset_reset = Some(reset);
845 self
846 }
847
848 pub fn enable_auto_commit(mut self, enable: bool) -> Self {
849 self.enable_auto_commit = Some(enable);
850 self
851 }
852
853 pub fn auto_commit_interval(mut self, interval: Duration) -> Self {
854 self.auto_commit_interval = Some(interval);
855 self
856 }
857
858 pub fn max_poll_records(mut self, records: usize) -> Self {
859 self.max_poll_records = Some(records);
860 self
861 }
862
863 pub fn fetch_wait_max(mut self, duration: Duration) -> Self {
864 self.fetch_wait_max = Some(duration);
865 self
866 }
867
868 pub fn fetch_min_bytes(mut self, bytes: usize) -> Self {
869 self.fetch_min_bytes = Some(bytes);
870 self
871 }
872
873 pub fn max_partition_fetch_bytes(mut self, bytes: usize) -> Self {
874 self.max_partition_fetch_bytes = Some(bytes);
875 self
876 }
877
878 pub fn session_timeout(mut self, timeout: Duration) -> Self {
879 self.session_timeout = Some(timeout);
880 self
881 }
882
883 pub fn heartbeat_interval(mut self, interval: Duration) -> Self {
884 self.heartbeat_interval = Some(interval);
885 self
886 }
887
888 pub fn security_protocol(mut self, protocol: SecurityProtocol) -> Self {
889 self.security_protocol = Some(protocol);
890 self
891 }
892
893 pub fn sasl_mechanism(mut self, mechanism: SaslMechanism) -> Self {
894 self.sasl_mechanism = Some(mechanism);
895 self
896 }
897
898 pub fn sasl_username(mut self, username: String) -> Self {
899 self.sasl_username = Some(username);
900 self
901 }
902
903 pub fn sasl_password(mut self, password: String) -> Self {
904 self.sasl_password = Some(password);
905 self
906 }
907
908 pub fn ssl_config(mut self, config: SslConfig) -> Self {
909 self.ssl_config = Some(config);
910 self
911 }
912
913 pub fn num_consumer_threads(mut self, threads: usize) -> Self {
914 self.num_consumer_threads = Some(threads);
915 self
916 }
917
918 pub fn queued_max_messages(mut self, messages: usize) -> Self {
919 self.queued_max_messages = Some(messages);
920 self
921 }
922
923 pub fn socket_receive_buffer_bytes(mut self, bytes: usize) -> Self {
924 self.socket_receive_buffer_bytes = Some(bytes);
925 self
926 }
927
928 pub fn isolation_level(mut self, level: String) -> Self {
929 self.isolation_level = Some(level);
930 self
931 }
932
933 pub fn additional_properties(mut self, properties: HashMap<String, String>) -> Self {
934 self.additional_properties = Some(properties);
935 self
936 }
937
938 pub fn build(self) -> Result<KafkaSourceConfig, String> {
939 let config = KafkaSourceConfig {
940 bootstrap_servers: self
941 .bootstrap_servers
942 .ok_or("bootstrap_servers is required")?,
943 group_id: self.group_id.ok_or("group_id is required")?,
944 topics: self.topics.ok_or("topics is required")?,
945 auto_offset_reset: self.auto_offset_reset.unwrap_or_else(default_auto_offset_reset),
946 enable_auto_commit: self
947 .enable_auto_commit
948 .unwrap_or_else(default_enable_auto_commit),
949 auto_commit_interval: self
950 .auto_commit_interval
951 .unwrap_or_else(default_auto_commit_interval),
952 max_poll_records: self.max_poll_records.unwrap_or_else(default_max_poll_records),
953 fetch_wait_max: self.fetch_wait_max.unwrap_or_else(default_fetch_wait_max),
954 fetch_min_bytes: self.fetch_min_bytes.unwrap_or_else(default_fetch_min_bytes),
955 max_partition_fetch_bytes: self
956 .max_partition_fetch_bytes
957 .unwrap_or_else(default_max_partition_fetch_bytes),
958 session_timeout: self.session_timeout.unwrap_or_else(default_session_timeout),
959 heartbeat_interval: self
960 .heartbeat_interval
961 .unwrap_or_else(default_heartbeat_interval),
962 security_protocol: self.security_protocol.unwrap_or_default(),
963 sasl_mechanism: self.sasl_mechanism,
964 sasl_username: self.sasl_username,
965 sasl_password: self.sasl_password,
966 ssl_config: self.ssl_config,
967 num_consumer_threads: self
968 .num_consumer_threads
969 .unwrap_or_else(default_num_consumer_threads),
970 queued_max_messages: self
971 .queued_max_messages
972 .unwrap_or_else(default_queued_max_messages),
973 socket_receive_buffer_bytes: self
974 .socket_receive_buffer_bytes
975 .unwrap_or_else(default_socket_receive_buffer_bytes),
976 isolation_level: self.isolation_level.unwrap_or_else(default_isolation_level),
977 additional_properties: self.additional_properties.unwrap_or_default(),
978 };
979
980 config.validate()?;
981 Ok(config)
982 }
983}
984
985#[derive(Default)]
987pub struct KafkaSinkConfigBuilder {
988 bootstrap_servers: Option<Vec<String>>,
989 topic: Option<String>,
990 acks: Option<String>,
991 retries: Option<u32>,
992 max_in_flight_requests: Option<usize>,
993 enable_idempotence: Option<bool>,
994 request_timeout: Option<Duration>,
995 batch_size: Option<usize>,
996 linger: Option<Duration>,
997 buffer_memory: Option<usize>,
998 max_block: Option<Duration>,
999 compression: Option<CompressionType>,
1000 max_request_size: Option<usize>,
1001 security_protocol: Option<SecurityProtocol>,
1002 sasl_mechanism: Option<SaslMechanism>,
1003 sasl_username: Option<String>,
1004 sasl_password: Option<String>,
1005 ssl_config: Option<SslConfig>,
1006 socket_send_buffer_bytes: Option<usize>,
1007 queued_max_messages: Option<usize>,
1008 partitioner: Option<String>,
1009 additional_properties: Option<HashMap<String, String>>,
1010}
1011
1012impl KafkaSinkConfigBuilder {
1013 pub fn bootstrap_servers(mut self, servers: Vec<String>) -> Self {
1014 self.bootstrap_servers = Some(servers);
1015 self
1016 }
1017
1018 pub fn topic(mut self, topic: String) -> Self {
1019 self.topic = Some(topic);
1020 self
1021 }
1022
1023 pub fn acks(mut self, acks: String) -> Self {
1024 self.acks = Some(acks);
1025 self
1026 }
1027
1028 pub fn retries(mut self, retries: u32) -> Self {
1029 self.retries = Some(retries);
1030 self
1031 }
1032
1033 pub fn max_in_flight_requests(mut self, requests: usize) -> Self {
1034 self.max_in_flight_requests = Some(requests);
1035 self
1036 }
1037
1038 pub fn enable_idempotence(mut self, enable: bool) -> Self {
1039 self.enable_idempotence = Some(enable);
1040 self
1041 }
1042
1043 pub fn request_timeout(mut self, timeout: Duration) -> Self {
1044 self.request_timeout = Some(timeout);
1045 self
1046 }
1047
1048 pub fn batch_size(mut self, size: usize) -> Self {
1049 self.batch_size = Some(size);
1050 self
1051 }
1052
1053 pub fn linger(mut self, duration: Duration) -> Self {
1054 self.linger = Some(duration);
1055 self
1056 }
1057
1058 pub fn buffer_memory(mut self, memory: usize) -> Self {
1059 self.buffer_memory = Some(memory);
1060 self
1061 }
1062
1063 pub fn max_block(mut self, duration: Duration) -> Self {
1064 self.max_block = Some(duration);
1065 self
1066 }
1067
1068 pub fn compression(mut self, compression: CompressionType) -> Self {
1069 self.compression = Some(compression);
1070 self
1071 }
1072
1073 pub fn max_request_size(mut self, size: usize) -> Self {
1074 self.max_request_size = Some(size);
1075 self
1076 }
1077
1078 pub fn security_protocol(mut self, protocol: SecurityProtocol) -> Self {
1079 self.security_protocol = Some(protocol);
1080 self
1081 }
1082
1083 pub fn sasl_mechanism(mut self, mechanism: SaslMechanism) -> Self {
1084 self.sasl_mechanism = Some(mechanism);
1085 self
1086 }
1087
1088 pub fn sasl_username(mut self, username: String) -> Self {
1089 self.sasl_username = Some(username);
1090 self
1091 }
1092
1093 pub fn sasl_password(mut self, password: String) -> Self {
1094 self.sasl_password = Some(password);
1095 self
1096 }
1097
1098 pub fn ssl_config(mut self, config: SslConfig) -> Self {
1099 self.ssl_config = Some(config);
1100 self
1101 }
1102
1103 pub fn socket_send_buffer_bytes(mut self, bytes: usize) -> Self {
1104 self.socket_send_buffer_bytes = Some(bytes);
1105 self
1106 }
1107
1108 pub fn queued_max_messages(mut self, messages: usize) -> Self {
1109 self.queued_max_messages = Some(messages);
1110 self
1111 }
1112
1113 pub fn partitioner(mut self, partitioner: String) -> Self {
1114 self.partitioner = Some(partitioner);
1115 self
1116 }
1117
1118 pub fn additional_properties(mut self, properties: HashMap<String, String>) -> Self {
1119 self.additional_properties = Some(properties);
1120 self
1121 }
1122
1123 pub fn build(self) -> Result<KafkaSinkConfig, String> {
1124 let config = KafkaSinkConfig {
1125 bootstrap_servers: self
1126 .bootstrap_servers
1127 .ok_or("bootstrap_servers is required")?,
1128 topic: self.topic.ok_or("topic is required")?,
1129 acks: self.acks.unwrap_or_else(default_acks),
1130 retries: self.retries.unwrap_or_else(default_retries),
1131 max_in_flight_requests: self
1132 .max_in_flight_requests
1133 .unwrap_or_else(default_max_in_flight_requests),
1134 enable_idempotence: self
1135 .enable_idempotence
1136 .unwrap_or_else(default_enable_idempotence),
1137 request_timeout: self.request_timeout.unwrap_or_else(default_request_timeout),
1138 batch_size: self.batch_size.unwrap_or_else(default_batch_size),
1139 linger: self.linger.unwrap_or_else(default_linger),
1140 buffer_memory: self.buffer_memory.unwrap_or_else(default_buffer_memory),
1141 max_block: self.max_block.unwrap_or_else(default_max_block),
1142 compression: self.compression.unwrap_or_default(),
1143 max_request_size: self.max_request_size.unwrap_or_else(default_max_request_size),
1144 security_protocol: self.security_protocol.unwrap_or_default(),
1145 sasl_mechanism: self.sasl_mechanism,
1146 sasl_username: self.sasl_username,
1147 sasl_password: self.sasl_password,
1148 ssl_config: self.ssl_config,
1149 socket_send_buffer_bytes: self
1150 .socket_send_buffer_bytes
1151 .unwrap_or_else(default_socket_send_buffer_bytes),
1152 queued_max_messages: self
1153 .queued_max_messages
1154 .unwrap_or_else(default_queued_max_messages_producer),
1155 partitioner: self.partitioner,
1156 additional_properties: self.additional_properties.unwrap_or_default(),
1157 };
1158
1159 config.validate()?;
1160 Ok(config)
1161 }
1162}
1163
1164#[cfg(test)]
1165mod tests {
1166 use super::*;
1167
1168 #[test]
1169 fn test_kafka_source_config_validation() {
1170 let config = KafkaSourceConfig::builder()
1171 .bootstrap_servers(vec!["localhost:9092".to_string()])
1172 .group_id("test-group".to_string())
1173 .topics(vec!["test-topic".to_string()])
1174 .build();
1175
1176 assert!(config.is_ok());
1177 }
1178
1179 #[test]
1180 fn test_kafka_sink_config_validation() {
1181 let config = KafkaSinkConfig::builder()
1182 .bootstrap_servers(vec!["localhost:9092".to_string()])
1183 .topic("test-topic".to_string())
1184 .build();
1185
1186 assert!(config.is_ok());
1187 }
1188
1189 #[test]
1190 fn test_security_protocol_string_conversion() {
1191 assert_eq!(SecurityProtocol::Plaintext.as_str(), "PLAINTEXT");
1192 assert_eq!(SecurityProtocol::Ssl.as_str(), "SSL");
1193 assert_eq!(SecurityProtocol::SaslPlaintext.as_str(), "SASL_PLAINTEXT");
1194 assert_eq!(SecurityProtocol::SaslSsl.as_str(), "SASL_SSL");
1195 }
1196
1197 #[test]
1198 fn test_compression_type_string_conversion() {
1199 assert_eq!(CompressionType::None.as_str(), "none");
1200 assert_eq!(CompressionType::Gzip.as_str(), "gzip");
1201 assert_eq!(CompressionType::Snappy.as_str(), "snappy");
1202 assert_eq!(CompressionType::Lz4.as_str(), "lz4");
1203 assert_eq!(CompressionType::Zstd.as_str(), "zstd");
1204 }
1205
1206 #[test]
1207 fn test_sasl_mechanism_string_conversion() {
1208 assert_eq!(SaslMechanism::Plain.as_str(), "PLAIN");
1209 assert_eq!(SaslMechanism::ScramSha256.as_str(), "SCRAM-SHA-256");
1210 assert_eq!(SaslMechanism::ScramSha512.as_str(), "SCRAM-SHA-512");
1211 }
1212}