Skip to main content

rocketmq_client_rust/base/
client_config.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::env;
16use std::sync::atomic::AtomicBool;
17use std::sync::atomic::Ordering;
18use std::sync::Arc;
19use std::time::Duration;
20
21use cheetah_string::CheetahString;
22use rocketmq_common::common::message::message_queue::MessageQueue;
23use rocketmq_common::common::tls_config::TlsConfig;
24use rocketmq_common::utils::name_server_address_utils::NameServerAddressUtils;
25use rocketmq_common::utils::network_util::NetworkUtil;
26use rocketmq_common::TimeUtils::current_nano;
27use rocketmq_remoting::protocol::namespace_util::NamespaceUtil;
28use rocketmq_remoting::protocol::request_type::RequestType;
29use rocketmq_remoting::protocol::LanguageCode;
30
31use crate::base::access_channel::AccessChannel;
32
33#[derive(Clone)]
34pub struct ClientConfig {
35    pub namesrv_addr: Option<CheetahString>,
36    pub client_ip: Option<CheetahString>,
37    pub instance_name: CheetahString,
38    pub client_callback_executor_threads: usize,
39    pub namespace: Option<CheetahString>,
40    pub namespace_initialized: Arc<AtomicBool>,
41    pub namespace_v2: Option<CheetahString>,
42    pub access_channel: AccessChannel,
43    pub poll_name_server_interval: u32,
44    pub heartbeat_broker_interval: u32,
45    pub persist_consumer_offset_interval: u32,
46    pub pull_time_delay_millis_when_exception: u32,
47    pub unit_mode: bool,
48    pub unit_name: Option<CheetahString>,
49    pub decode_read_body: bool,
50    pub decode_decompress_body: bool,
51    pub vip_channel_enabled: bool,
52    pub use_heartbeat_v2: bool,
53    pub enable_concurrent_heartbeat: bool,
54    pub use_tls: bool,
55    pub tls_config: TlsConfig,
56    pub socks_proxy_config: CheetahString,
57    pub mq_client_api_timeout: u64,
58    pub detect_timeout: u32,
59    pub detect_interval: u32,
60    pub language: LanguageCode,
61    pub enable_stream_request_type: bool,
62    pub send_latency_enable: bool,
63    pub start_detector_enable: bool,
64    pub enable_heartbeat_channel_event_listener: bool,
65    pub enable_trace: bool,
66    pub trace_topic: Option<CheetahString>,
67    pub trace_msg_batch_num: usize,
68    pub max_page_size_in_get_metadata: usize,
69    /// Thread pool size for concurrent heartbeat operations.
70    /// Only effective when enable_concurrent_heartbeat is true.
71    /// Default: number of CPU cores (matches Java: Runtime.getRuntime().availableProcessors())
72    // java client has this option, but we keep it for compatibility and future will remove it if it's not needed
73    pub concurrent_heartbeat_thread_pool_size: usize,
74}
75
76impl Default for ClientConfig {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl ClientConfig {
83    pub const SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY: &'static str = "com.rocketmq.sendMessageWithVIPChannel";
84    pub const SOCKS_PROXY_CONFIG: &'static str = "com.rocketmq.socks.proxy.config";
85    pub const DECODE_READ_BODY: &'static str = "com.rocketmq.read.body";
86    pub const DECODE_DECOMPRESS_BODY: &'static str = "com.rocketmq.decompress.body";
87    pub const SEND_LATENCY_ENABLE: &'static str = "com.rocketmq.sendLatencyEnable";
88    pub const START_DETECTOR_ENABLE: &'static str = "com.rocketmq.startDetectorEnable";
89    pub const HEART_BEAT_V2: &'static str = "com.rocketmq.heartbeat.v2";
90    pub const ENABLE_CONCURRENT_HEARTBEAT: &'static str = "com.rocketmq.enableConcurrentHeartbeat";
91
92    pub fn new() -> Self {
93        ClientConfig {
94            namesrv_addr: NameServerAddressUtils::get_name_server_addresses().map(|addr| addr.into()),
95            client_ip: NetworkUtil::get_local_address().map(|addr| addr.into()),
96            instance_name: env::var("rocketmq.client.name")
97                .unwrap_or_else(|_| "DEFAULT".to_string())
98                .into(),
99            client_callback_executor_threads: num_cpus::get(),
100            namespace: None,
101            namespace_initialized: Arc::new(AtomicBool::new(false)),
102            namespace_v2: None,
103            access_channel: AccessChannel::Local,
104            poll_name_server_interval: Duration::from_secs(30).as_millis() as u32,
105            heartbeat_broker_interval: Duration::from_secs(30).as_millis() as u32,
106            persist_consumer_offset_interval: Duration::from_secs(5).as_millis() as u32,
107            pull_time_delay_millis_when_exception: 1000,
108            unit_mode: false,
109            unit_name: None,
110            decode_read_body: env::var(Self::DECODE_READ_BODY)
111                .unwrap_or_else(|_| "true".to_string())
112                .parse::<bool>()
113                .unwrap_or(true),
114            decode_decompress_body: env::var(Self::DECODE_DECOMPRESS_BODY)
115                .unwrap_or_else(|_| "true".to_string())
116                .parse::<bool>()
117                .unwrap_or(true),
118            vip_channel_enabled: env::var(Self::SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY)
119                .unwrap_or_else(|_| "false".to_string())
120                .parse::<bool>()
121                .unwrap_or(false),
122            use_heartbeat_v2: env::var(Self::HEART_BEAT_V2)
123                .unwrap_or_else(|_| "false".to_string())
124                .parse::<bool>()
125                .unwrap_or(false),
126            enable_concurrent_heartbeat: env::var(Self::ENABLE_CONCURRENT_HEARTBEAT)
127                .unwrap_or_else(|_| "false".to_string())
128                .parse::<bool>()
129                .unwrap_or(false),
130            use_tls: false,
131            tls_config: TlsConfig::default(),
132            socks_proxy_config: env::var(Self::SOCKS_PROXY_CONFIG)
133                .unwrap_or_else(|_| "{}".to_string())
134                .into(),
135            mq_client_api_timeout: Duration::from_secs(3).as_millis() as u64,
136            detect_timeout: 200,
137            detect_interval: Duration::from_secs(2).as_millis() as u32,
138            language: LanguageCode::RUST,
139            enable_stream_request_type: false,
140            send_latency_enable: env::var(Self::SEND_LATENCY_ENABLE)
141                .unwrap_or_else(|_| "false".to_string())
142                .parse::<bool>()
143                .unwrap_or(false),
144            start_detector_enable: env::var(Self::START_DETECTOR_ENABLE)
145                .unwrap_or_else(|_| "false".to_string())
146                .parse::<bool>()
147                .unwrap_or(false),
148            enable_heartbeat_channel_event_listener: true,
149            enable_trace: false,
150            trace_topic: None,
151            trace_msg_batch_num: 10,
152            max_page_size_in_get_metadata: 2000,
153            concurrent_heartbeat_thread_pool_size: num_cpus::get(),
154        }
155    }
156}
157
158impl ClientConfig {
159    #[inline]
160    pub fn with_namespace(&mut self, resource: impl Into<CheetahString>) -> CheetahString {
161        let resource = resource.into();
162        let namespace = self.get_namespace().unwrap_or_default();
163
164        // Fast path: no namespace needed, return resource directly
165        if namespace.is_empty() {
166            return resource;
167        }
168
169        // Fast path: resource already has namespace, return directly
170        if NamespaceUtil::is_already_with_namespace(resource.as_str(), namespace.as_str()) {
171            return resource;
172        }
173
174        NamespaceUtil::wrap_namespace(namespace, resource)
175    }
176
177    #[inline]
178    pub fn queue_with_namespace(&mut self, mut queue: MessageQueue) -> MessageQueue {
179        if let Some(namespace) = self.get_namespace() {
180            if !namespace.is_empty() {
181                let topic = NamespaceUtil::wrap_namespace(namespace.as_str(), queue.topic_str());
182                queue.set_topic(topic);
183                return queue;
184            }
185        }
186        queue
187    }
188
189    #[inline]
190    pub fn queues_with_namespace<I>(&mut self, queues: I) -> Vec<MessageQueue>
191    where
192        I: IntoIterator<Item = MessageQueue>,
193    {
194        queues
195            .into_iter()
196            .map(|queue| self.queue_with_namespace(queue))
197            .collect()
198    }
199
200    #[inline]
201    pub fn get_namespace(&mut self) -> Option<CheetahString> {
202        let namespace_initialized = self.namespace_initialized.load(Ordering::Acquire);
203        if namespace_initialized {
204            return self.namespace.clone();
205        }
206
207        if let Some(ref namespace) = self.namespace {
208            return Some(namespace.clone());
209        }
210
211        if let Some(ref namesrv_addr) = self.namesrv_addr {
212            if NameServerAddressUtils::validate_instance_endpoint(namesrv_addr.as_ref()) {
213                self.namespace =
214                    NameServerAddressUtils::parse_instance_id_from_endpoint(namesrv_addr.as_ref()).map(|id| id.into());
215            }
216        }
217        self.namespace_initialized.store(true, Ordering::Release);
218        self.namespace.clone()
219    }
220
221    #[inline]
222    pub fn change_instance_name_to_pid(&mut self) {
223        if self.instance_name == "DEFAULT" {
224            self.instance_name = format!("{}#{}", std::process::id(), current_nano()).into();
225        }
226    }
227    #[inline]
228    pub fn set_instance_name(&mut self, instance_name: CheetahString) {
229        self.instance_name = instance_name;
230    }
231    #[inline]
232    pub fn set_namesrv_addr(&mut self, namesrv_addr: CheetahString) {
233        self.namesrv_addr = Some(namesrv_addr);
234        self.namespace_initialized.store(false, Ordering::Release);
235    }
236
237    #[inline]
238    pub fn build_mq_client_id(&self) -> String {
239        // Pre-allocate capacity to avoid reallocations
240        let estimated_capacity = self.client_ip.as_ref().map(|ip| ip.len()).unwrap_or(15)
241            + self.instance_name.len()
242            + self.unit_name.as_ref().map(|un| un.len() + 1).unwrap_or(0)
243            + if self.enable_stream_request_type { 8 } else { 0 }
244            + 3; // For '@' separators
245
246        let mut sb = String::with_capacity(estimated_capacity);
247        if let Some(ref client_ip) = self.client_ip {
248            sb.push_str(client_ip.as_str());
249        }
250
251        sb.push('@');
252        sb.push_str(self.instance_name.as_str());
253        if let Some(ref unit_name) = self.unit_name {
254            if !unit_name.is_empty() {
255                sb.push('@');
256                sb.push_str(unit_name.as_str());
257            }
258        }
259
260        if self.enable_stream_request_type {
261            sb.push('@');
262            sb.push_str(RequestType::Stream.to_string().as_str());
263        }
264
265        sb
266    }
267
268    #[inline]
269    pub fn get_namesrv_addr(&self) -> Option<CheetahString> {
270        if let Some(namesrv_addr) = self
271            .namesrv_addr
272            .as_ref()
273            .filter(|addr| !addr.is_empty() && NameServerAddressUtils::is_name_srv_endpoint(addr.as_str()))
274        {
275            NameServerAddressUtils::get_name_srv_addr_from_namesrv_endpoint(namesrv_addr.as_str())
276                .map(|addr| addr.into())
277        } else {
278            self.namesrv_addr.clone()
279        }
280    }
281
282    // ============ Comprehensive Getters and Setters ============
283
284    #[inline]
285    pub fn get_client_ip(&self) -> Option<&CheetahString> {
286        self.client_ip.as_ref()
287    }
288
289    #[inline]
290    pub fn set_client_ip(&mut self, client_ip: CheetahString) {
291        self.client_ip = Some(client_ip);
292    }
293
294    #[inline]
295    pub fn get_instance_name(&self) -> &CheetahString {
296        &self.instance_name
297    }
298
299    #[inline]
300    pub fn get_client_callback_executor_threads(&self) -> usize {
301        self.client_callback_executor_threads
302    }
303
304    #[inline]
305    pub fn set_client_callback_executor_threads(&mut self, threads: usize) {
306        self.client_callback_executor_threads = threads;
307    }
308
309    #[inline]
310    pub fn get_namespace_v2(&self) -> Option<&CheetahString> {
311        self.namespace_v2.as_ref()
312    }
313
314    #[inline]
315    pub fn set_namespace_v2(&mut self, namespace_v2: CheetahString) {
316        self.namespace_v2 = Some(namespace_v2);
317    }
318
319    #[inline]
320    pub fn set_namespace(&mut self, namespace: CheetahString) {
321        self.namespace = Some(namespace);
322        self.namespace_initialized.store(true, Ordering::Release);
323    }
324
325    #[inline]
326    pub fn get_access_channel(&self) -> AccessChannel {
327        self.access_channel
328    }
329
330    #[inline]
331    pub fn set_access_channel(&mut self, access_channel: AccessChannel) {
332        self.access_channel = access_channel;
333    }
334
335    #[inline]
336    pub fn get_poll_name_server_interval(&self) -> u32 {
337        self.poll_name_server_interval
338    }
339
340    #[inline]
341    pub fn set_poll_name_server_interval(&mut self, interval: u32) {
342        self.poll_name_server_interval = interval;
343    }
344
345    #[inline]
346    pub fn get_heartbeat_broker_interval(&self) -> u32 {
347        self.heartbeat_broker_interval
348    }
349
350    #[inline]
351    pub fn set_heartbeat_broker_interval(&mut self, interval: u32) {
352        self.heartbeat_broker_interval = interval;
353    }
354
355    #[inline]
356    pub fn get_persist_consumer_offset_interval(&self) -> u32 {
357        self.persist_consumer_offset_interval
358    }
359
360    #[inline]
361    pub fn set_persist_consumer_offset_interval(&mut self, interval: u32) {
362        self.persist_consumer_offset_interval = interval;
363    }
364
365    #[inline]
366    pub fn get_pull_time_delay_millis_when_exception(&self) -> u32 {
367        self.pull_time_delay_millis_when_exception
368    }
369
370    #[inline]
371    pub fn get_pull_time_delay_mills_when_exception(&self) -> u32 {
372        self.get_pull_time_delay_millis_when_exception()
373    }
374
375    #[inline]
376    pub fn set_pull_time_delay_millis_when_exception(&mut self, delay: u32) {
377        self.pull_time_delay_millis_when_exception = delay;
378    }
379
380    #[inline]
381    pub fn set_pull_time_delay_mills_when_exception(&mut self, delay: u32) {
382        self.set_pull_time_delay_millis_when_exception(delay);
383    }
384
385    #[inline]
386    pub fn get_unit_name(&self) -> Option<&CheetahString> {
387        self.unit_name.as_ref()
388    }
389
390    #[inline]
391    pub fn set_unit_name(&mut self, unit_name: CheetahString) {
392        self.unit_name = Some(unit_name);
393    }
394
395    #[inline]
396    pub fn is_unit_mode(&self) -> bool {
397        self.unit_mode
398    }
399
400    #[inline]
401    pub fn set_unit_mode(&mut self, unit_mode: bool) {
402        self.unit_mode = unit_mode;
403    }
404
405    #[inline]
406    pub fn is_decode_read_body(&self) -> bool {
407        self.decode_read_body
408    }
409
410    #[inline]
411    pub fn set_decode_read_body(&mut self, decode_read_body: bool) {
412        self.decode_read_body = decode_read_body;
413    }
414
415    #[inline]
416    pub fn is_decode_decompress_body(&self) -> bool {
417        self.decode_decompress_body
418    }
419
420    #[inline]
421    pub fn set_decode_decompress_body(&mut self, decode_decompress_body: bool) {
422        self.decode_decompress_body = decode_decompress_body;
423    }
424
425    #[inline]
426    pub fn is_vip_channel_enabled(&self) -> bool {
427        self.vip_channel_enabled
428    }
429
430    #[inline]
431    pub fn set_vip_channel_enabled(&mut self, enabled: bool) {
432        self.vip_channel_enabled = enabled;
433    }
434
435    #[inline]
436    pub fn is_use_heartbeat_v2(&self) -> bool {
437        self.use_heartbeat_v2
438    }
439
440    #[inline]
441    pub fn set_use_heartbeat_v2(&mut self, use_heartbeat_v2: bool) {
442        self.use_heartbeat_v2 = use_heartbeat_v2;
443    }
444
445    #[inline]
446    pub fn is_use_tls(&self) -> bool {
447        self.use_tls
448    }
449
450    #[inline]
451    pub fn set_use_tls(&mut self, use_tls: bool) {
452        self.use_tls = use_tls;
453        self.tls_config.enable = use_tls;
454    }
455
456    #[inline]
457    pub fn tls_config(&self) -> &TlsConfig {
458        &self.tls_config
459    }
460
461    #[inline]
462    pub fn set_tls_config(&mut self, mut tls_config: TlsConfig) {
463        tls_config.enable = self.use_tls;
464        self.tls_config = tls_config;
465    }
466
467    #[inline]
468    pub fn set_tls_test_mode_enable(&mut self, enabled: bool) {
469        self.tls_config.test_mode_enable = enabled;
470    }
471
472    #[inline]
473    pub fn set_tls_client_auth_server(&mut self, enabled: bool) {
474        self.tls_config.client.auth_server = enabled;
475    }
476
477    #[inline]
478    pub fn set_tls_client_trust_cert_path(&mut self, path: impl Into<String>) {
479        self.tls_config.client.trust_cert_path = Some(path.into());
480    }
481
482    #[inline]
483    pub fn set_tls_client_cert_path(&mut self, path: impl Into<String>) {
484        self.tls_config.client.cert_path = Some(path.into());
485    }
486
487    #[inline]
488    pub fn set_tls_client_key_path(&mut self, path: impl Into<String>) {
489        self.tls_config.client.key_path = Some(path.into());
490    }
491
492    #[inline]
493    pub fn get_socks_proxy_config(&self) -> &CheetahString {
494        &self.socks_proxy_config
495    }
496
497    #[inline]
498    pub fn set_socks_proxy_config(&mut self, config: CheetahString) {
499        self.socks_proxy_config = config;
500    }
501
502    #[inline]
503    pub fn get_language(&self) -> LanguageCode {
504        self.language
505    }
506
507    #[inline]
508    pub fn set_language(&mut self, language: LanguageCode) {
509        self.language = language;
510    }
511
512    #[inline]
513    pub fn get_mq_client_api_timeout(&self) -> u64 {
514        self.mq_client_api_timeout
515    }
516
517    #[inline]
518    pub fn set_mq_client_api_timeout(&mut self, timeout: u64) {
519        self.mq_client_api_timeout = timeout;
520    }
521
522    #[inline]
523    pub fn get_detect_timeout(&self) -> u32 {
524        self.detect_timeout
525    }
526
527    #[inline]
528    pub fn set_detect_timeout(&mut self, timeout: u32) {
529        self.detect_timeout = timeout;
530    }
531
532    #[inline]
533    pub fn get_detect_interval(&self) -> u32 {
534        self.detect_interval
535    }
536
537    #[inline]
538    pub fn set_detect_interval(&mut self, interval: u32) {
539        self.detect_interval = interval;
540    }
541
542    #[inline]
543    pub fn is_enable_stream_request_type(&self) -> bool {
544        self.enable_stream_request_type
545    }
546
547    #[inline]
548    pub fn set_enable_stream_request_type(&mut self, enabled: bool) {
549        self.enable_stream_request_type = enabled;
550    }
551
552    #[inline]
553    pub fn is_send_latency_enable(&self) -> bool {
554        self.send_latency_enable
555    }
556
557    #[inline]
558    pub fn set_send_latency_enable(&mut self, enabled: bool) {
559        self.send_latency_enable = enabled;
560    }
561
562    #[inline]
563    pub fn is_start_detector_enable(&self) -> bool {
564        self.start_detector_enable
565    }
566
567    #[inline]
568    pub fn set_start_detector_enable(&mut self, enabled: bool) {
569        self.start_detector_enable = enabled;
570    }
571
572    #[inline]
573    pub fn is_enable_heartbeat_channel_event_listener(&self) -> bool {
574        self.enable_heartbeat_channel_event_listener
575    }
576
577    #[inline]
578    pub fn set_enable_heartbeat_channel_event_listener(&mut self, enabled: bool) {
579        self.enable_heartbeat_channel_event_listener = enabled;
580    }
581
582    #[inline]
583    pub fn is_enable_trace(&self) -> bool {
584        self.enable_trace
585    }
586
587    #[inline]
588    pub fn set_enable_trace(&mut self, enabled: bool) {
589        self.enable_trace = enabled;
590    }
591
592    #[inline]
593    pub fn get_trace_topic(&self) -> Option<&CheetahString> {
594        self.trace_topic.as_ref()
595    }
596
597    #[inline]
598    pub fn set_trace_topic(&mut self, topic: CheetahString) {
599        self.trace_topic = Some(topic);
600    }
601
602    #[inline]
603    pub fn get_trace_msg_batch_num(&self) -> usize {
604        self.trace_msg_batch_num
605    }
606
607    #[inline]
608    pub fn set_trace_msg_batch_num(&mut self, num: usize) {
609        self.trace_msg_batch_num = num;
610    }
611
612    #[inline]
613    pub fn get_max_page_size_in_get_metadata(&self) -> usize {
614        self.max_page_size_in_get_metadata
615    }
616
617    #[inline]
618    pub fn set_max_page_size_in_get_metadata(&mut self, size: usize) {
619        self.max_page_size_in_get_metadata = size;
620    }
621
622    #[inline]
623    pub fn get_concurrent_heartbeat_thread_pool_size(&self) -> usize {
624        self.concurrent_heartbeat_thread_pool_size
625    }
626
627    #[inline]
628    pub fn is_enable_concurrent_heartbeat(&self) -> bool {
629        self.enable_concurrent_heartbeat
630    }
631
632    #[inline]
633    pub fn set_enable_concurrent_heartbeat(&mut self, enabled: bool) {
634        self.enable_concurrent_heartbeat = enabled;
635    }
636
637    #[inline]
638    pub fn set_concurrent_heartbeat_thread_pool_size(&mut self, size: usize) {
639        self.concurrent_heartbeat_thread_pool_size = size;
640    }
641
642    // ============ Utility Methods ============
643
644    /// Clones the configuration
645    #[inline]
646    pub fn clone_client_config(&self) -> Self {
647        self.clone()
648    }
649
650    /// Resets client config from another instance
651    pub fn reset_client_config(&mut self, other: &ClientConfig) {
652        self.namesrv_addr = other.namesrv_addr.clone();
653        self.client_ip = other.client_ip.clone();
654        self.instance_name = other.instance_name.clone();
655        self.client_callback_executor_threads = other.client_callback_executor_threads;
656        self.namespace = other.namespace.clone();
657        self.namespace_v2 = other.namespace_v2.clone();
658        self.access_channel = other.access_channel;
659        self.poll_name_server_interval = other.poll_name_server_interval;
660        self.heartbeat_broker_interval = other.heartbeat_broker_interval;
661        self.persist_consumer_offset_interval = other.persist_consumer_offset_interval;
662        self.pull_time_delay_millis_when_exception = other.pull_time_delay_millis_when_exception;
663        self.unit_mode = other.unit_mode;
664        self.unit_name = other.unit_name.clone();
665        self.decode_read_body = other.decode_read_body;
666        self.decode_decompress_body = other.decode_decompress_body;
667        self.vip_channel_enabled = other.vip_channel_enabled;
668        self.use_heartbeat_v2 = other.use_heartbeat_v2;
669        self.use_tls = other.use_tls;
670        self.tls_config = other.tls_config.clone();
671        self.socks_proxy_config = other.socks_proxy_config.clone();
672        self.language = other.language;
673        self.mq_client_api_timeout = other.mq_client_api_timeout;
674        self.detect_timeout = other.detect_timeout;
675        self.detect_interval = other.detect_interval;
676        self.enable_stream_request_type = other.enable_stream_request_type;
677        self.send_latency_enable = other.send_latency_enable;
678        self.start_detector_enable = other.start_detector_enable;
679        self.enable_heartbeat_channel_event_listener = other.enable_heartbeat_channel_event_listener;
680        self.enable_trace = other.enable_trace;
681        self.trace_topic = other.trace_topic.clone();
682        self.trace_msg_batch_num = other.trace_msg_batch_num;
683        self.max_page_size_in_get_metadata = other.max_page_size_in_get_metadata;
684        self.enable_concurrent_heartbeat = other.enable_concurrent_heartbeat;
685        self.concurrent_heartbeat_thread_pool_size = other.concurrent_heartbeat_thread_pool_size;
686    }
687
688    /// Deprecated: Use with_namespace instead
689    #[inline]
690    #[deprecated(note = "Use with_namespace for namespace wrapping")]
691    pub fn without_namespace(&mut self, resource: &str) -> CheetahString {
692        if let Some(namespace) = self.get_namespace().as_deref() {
693            NamespaceUtil::without_namespace_with_namespace(resource, namespace).into()
694        } else {
695            NamespaceUtil::without_namespace(resource).into()
696        }
697    }
698
699    /// Creates a new builder for constructing a ClientConfig with a fluent API
700    ///
701    /// # Example
702    ///
703    /// ```rust
704    /// use rocketmq_client_rust::base::client_config::ClientConfig;
705    ///
706    /// let config = ClientConfig::builder()
707    ///     .namesrv_addr("localhost:9876")
708    ///     .instance_name("my_producer")
709    ///     .enable_tls(true)
710    ///     .build()?;
711    /// # Ok::<(), rocketmq_error::RocketMQError>(())
712    /// ```
713    #[inline]
714    pub fn builder() -> crate::base::client_config_builder::ClientConfigBuilder {
715        crate::base::client_config_builder::ClientConfigBuilder::new()
716    }
717}
718
719impl std::fmt::Display for ClientConfig {
720    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
721        write!(
722            f,
723            "ClientConfig {{ namesrv_addr: {:?}, client_ip: {:?}, instance_name: {}, \
724             client_callback_executor_threads: {}, namespace: {:?}, namespace_v2: {:?}, access_channel: {}, \
725             poll_name_server_interval: {}, heartbeat_broker_interval: {}, persist_consumer_offset_interval: {}, \
726             pull_time_delay_millis_when_exception: {}, unit_mode: {}, unit_name: {:?}, decode_read_body: {}, \
727             decode_decompress_body: {}, vip_channel_enabled: {}, use_heartbeat_v2: {}, use_tls: {}, \
728             socks_proxy_config: {}, mq_client_api_timeout: {}, detect_timeout: {}, detect_interval: {}, language: \
729             {:?}, enable_stream_request_type: {}, send_latency_enable: {}, start_detector_enable: {}, \
730             enable_heartbeat_channel_event_listener: {}, enable_trace: {}, trace_topic: {:?}, trace_msg_batch_num: \
731             {}, max_page_size_in_get_metadata: {}, enable_concurrent_heartbeat: {}, \
732             concurrent_heartbeat_thread_pool_size: {} }}",
733            self.namesrv_addr,
734            self.client_ip,
735            self.instance_name,
736            self.client_callback_executor_threads,
737            self.namespace,
738            self.namespace_v2,
739            self.access_channel,
740            self.poll_name_server_interval,
741            self.heartbeat_broker_interval,
742            self.persist_consumer_offset_interval,
743            self.pull_time_delay_millis_when_exception,
744            self.unit_mode,
745            self.unit_name,
746            self.decode_read_body,
747            self.decode_decompress_body,
748            self.vip_channel_enabled,
749            self.use_heartbeat_v2,
750            self.use_tls,
751            self.socks_proxy_config,
752            self.mq_client_api_timeout,
753            self.detect_timeout,
754            self.detect_interval,
755            self.language,
756            self.enable_stream_request_type,
757            self.send_latency_enable,
758            self.start_detector_enable,
759            self.enable_heartbeat_channel_event_listener,
760            self.enable_trace,
761            self.trace_topic,
762            self.trace_msg_batch_num,
763            self.max_page_size_in_get_metadata,
764            self.enable_concurrent_heartbeat,
765            self.concurrent_heartbeat_thread_pool_size
766        )
767    }
768}
769
770#[cfg(test)]
771mod tests {
772    use super::*;
773
774    #[test]
775    fn java_compatible_aliases_delegate_to_client_config_fields() {
776        let mut config = ClientConfig::default();
777
778        config.set_pull_time_delay_mills_when_exception(2345);
779        assert_eq!(config.get_pull_time_delay_mills_when_exception(), 2345);
780        assert_eq!(config.get_pull_time_delay_millis_when_exception(), 2345);
781
782        config.set_enable_concurrent_heartbeat(false);
783        assert!(!config.is_enable_concurrent_heartbeat());
784        config.set_enable_concurrent_heartbeat(true);
785        assert!(config.is_enable_concurrent_heartbeat());
786    }
787
788    #[test]
789    fn reset_client_config_copies_modern_java_fields() {
790        let mut source = ClientConfig::default();
791        source.set_use_tls(true);
792        source.set_enable_trace(true);
793        source.set_trace_topic(CheetahString::from("TraceTopicA"));
794        source.set_trace_msg_batch_num(32);
795        source.set_max_page_size_in_get_metadata(4096);
796        source.set_enable_concurrent_heartbeat(true);
797        source.set_concurrent_heartbeat_thread_pool_size(8);
798
799        let mut target = ClientConfig::default();
800        target.set_use_tls(false);
801        target.set_enable_trace(false);
802        target.trace_topic = None;
803        target.set_trace_msg_batch_num(10);
804        target.set_max_page_size_in_get_metadata(2000);
805        target.set_enable_concurrent_heartbeat(false);
806        target.set_concurrent_heartbeat_thread_pool_size(1);
807
808        target.reset_client_config(&source);
809
810        assert!(target.is_use_tls());
811        assert!(target.tls_config().enable);
812        assert!(target.is_enable_trace());
813        assert_eq!(
814            target.get_trace_topic().map(|topic| topic.as_str()),
815            Some("TraceTopicA")
816        );
817        assert_eq!(target.get_trace_msg_batch_num(), 32);
818        assert_eq!(target.get_max_page_size_in_get_metadata(), 4096);
819        assert!(target.is_enable_concurrent_heartbeat());
820        assert_eq!(target.get_concurrent_heartbeat_thread_pool_size(), 8);
821    }
822
823    #[test]
824    fn display_uses_java_access_channel_name() {
825        let mut config = ClientConfig::default();
826        config.set_access_channel(AccessChannel::Cloud);
827
828        let rendered = config.to_string();
829
830        assert!(rendered.contains("access_channel: CLOUD"));
831        assert!(!rendered.contains("access_channel: Cloud"));
832    }
833
834    #[test]
835    fn queues_with_namespace_wraps_each_queue_like_java_collection_helper() {
836        let mut config = ClientConfig::default();
837        let queues = vec![
838            MessageQueue::from_parts("topic_a", "broker-a", 0),
839            MessageQueue::from_parts("ns%topic_b", "broker-a", 1),
840        ];
841
842        let unchanged = config.queues_with_namespace(queues.clone());
843        assert_eq!(unchanged[0].topic_str(), "topic_a");
844        assert_eq!(unchanged[1].topic_str(), "ns%topic_b");
845
846        config.namespace = Some(CheetahString::from("ns"));
847        config.namespace_initialized.store(false, Ordering::Release);
848
849        let wrapped = config.queues_with_namespace(queues);
850        assert_eq!(wrapped[0].topic_str(), "ns%topic_a");
851        assert_eq!(wrapped[1].topic_str(), "ns%topic_b");
852    }
853}