Skip to main content

rocketmq_client_rust/base/
client_options.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 crate::ClientError;
16use crate::ClientResult;
17use cheetah_string::CheetahString;
18use rocketmq_protocol::protocol::remoting_command_defaults::RemotingCommandFactory;
19
20use super::client_config::ClientConfig;
21use crate::nameserver_discovery::NameServerDiscoveryConfig;
22
23/// Client configuration plus an optional typed NameServer discovery source.
24#[derive(Clone)]
25pub struct ClientOptions {
26    client: ClientConfig,
27    nameserver_discovery: Option<NameServerDiscoveryConfig>,
28    remoting_command_factory: Option<RemotingCommandFactory>,
29}
30
31impl ClientOptions {
32    /// Wraps an existing client configuration with legacy discovery behavior.
33    #[must_use]
34    pub fn legacy(client: ClientConfig) -> Self {
35        Self {
36            client,
37            nameserver_discovery: None,
38            remoting_command_factory: None,
39        }
40    }
41
42    /// Adds a typed NameServer discovery source.
43    #[must_use]
44    pub fn with_nameserver_discovery(mut self, discovery: NameServerDiscoveryConfig) -> Self {
45        self.nameserver_discovery = Some(discovery);
46        self
47    }
48
49    /// Overrides the runtime's remoting command factory for this client owner.
50    #[must_use]
51    pub fn with_remoting_command_factory(mut self, factory: RemotingCommandFactory) -> Self {
52        self.remoting_command_factory = Some(factory);
53        self
54    }
55
56    /// Returns the underlying legacy-compatible client configuration.
57    #[must_use]
58    pub fn client_config(&self) -> &ClientConfig {
59        &self.client
60    }
61
62    /// Returns the typed discovery configuration when present.
63    #[must_use]
64    pub fn nameserver_discovery(&self) -> Option<&NameServerDiscoveryConfig> {
65        self.nameserver_discovery.as_ref()
66    }
67
68    /// Returns the explicitly configured command factory, if present.
69    #[must_use]
70    pub fn remoting_command_factory(&self) -> Option<RemotingCommandFactory> {
71        self.remoting_command_factory
72    }
73
74    pub(crate) fn from_parts(client: ClientConfig, nameserver_discovery: Option<NameServerDiscoveryConfig>) -> Self {
75        Self {
76            client,
77            nameserver_discovery,
78            remoting_command_factory: None,
79        }
80    }
81
82    pub(crate) fn into_normalized_parts(
83        mut self,
84    ) -> ClientResult<(
85        ClientConfig,
86        Option<NameServerDiscoveryConfig>,
87        Option<RemotingCommandFactory>,
88    )> {
89        let remoting_command_factory = self.remoting_command_factory;
90        let Some(discovery) = self.nameserver_discovery else {
91            self.client.normalize_namesrv_addr()?;
92            return Ok((self.client, None, remoting_command_factory));
93        };
94
95        if let Some(legacy) = self.client.namesrv_addr.as_ref() {
96            return Err(ClientError::config_invalid(
97                "nameserver_discovery",
98                discovery.fingerprint(),
99                format!("typed NameServer discovery cannot be combined with legacy namesrv_addr={legacy}"),
100            ));
101        }
102        discovery.validate()?;
103        if let Some(canonical) = discovery.static_canonical() {
104            self.client.namesrv_addr = Some(CheetahString::from_string(canonical));
105        }
106        Ok((self.client, Some(discovery), remoting_command_factory))
107    }
108}
109
110impl Default for ClientOptions {
111    fn default() -> Self {
112        Self::legacy(ClientConfig::default())
113    }
114}
115
116impl From<ClientConfig> for ClientOptions {
117    fn from(value: ClientConfig) -> Self {
118        Self::legacy(value)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::nameserver_discovery::NameServerSource;
126
127    #[test]
128    fn legacy_options_preserve_existing_configuration() {
129        let options = ClientOptions::legacy(ClientConfig::default());
130        assert!(options.nameserver_discovery().is_none());
131    }
132
133    #[test]
134    fn typed_discovery_conflicts_with_legacy_address() {
135        let client = ClientConfig {
136            namesrv_addr: Some(CheetahString::from_static_str("ns-a:9876")),
137            ..Default::default()
138        };
139        let discovery = NameServerDiscoveryConfig::new(NameServerSource::dns("namesrv.default.svc", 9876).unwrap());
140        assert!(ClientOptions::legacy(client)
141            .with_nameserver_discovery(discovery)
142            .into_normalized_parts()
143            .is_err());
144    }
145}