Skip to main content

rocketmq_client_rust/hook/
namespace_rpc_hook.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::net::SocketAddr;
16
17use rocketmq_model::common::mix_all;
18use rocketmq_protocol::protocol::remoting_command::RemotingCommand;
19use rocketmq_transport::api::RPCHook;
20
21use crate::base::client_config::ClientConfig;
22
23/// RPC hook that injects namespace metadata into outgoing requests.
24///
25/// When the client configuration contains a `namespace_v2` value, this hook
26/// automatically adds namespace-related extension fields to all RPC requests.
27/// This enables multi-tenancy and namespace isolation in RocketMQ clusters.
28///
29/// Two extension fields are added to requests when a namespace is configured:
30/// - `nsd`: Marker field set to `"true"` indicating namespace presence
31/// - `ns`: The actual namespace identifier
32///
33/// # Examples
34///
35/// ```ignore
36/// use cheetah_string::CheetahString;
37/// use rocketmq_client_rust::ClientConfig;
38/// use rocketmq_client_rust::NamespaceRpcHook;
39///
40/// let mut config = ClientConfig::default();
41/// config.set_namespace_v2(CheetahString::from_static_str("my-namespace"));
42///
43/// let hook = NamespaceRpcHook::new(config);
44/// ```
45pub struct NamespaceRpcHook {
46    client_config: ClientConfig,
47}
48
49impl NamespaceRpcHook {
50    /// Creates a new namespace RPC hook.
51    ///
52    /// The hook uses the provided client configuration to determine whether
53    /// namespace fields should be injected into requests.
54    pub fn new(client_config: ClientConfig) -> Self {
55        Self { client_config }
56    }
57
58    /// Returns a reference to the underlying client configuration.
59    pub fn client_config(&self) -> &ClientConfig {
60        &self.client_config
61    }
62}
63
64impl RPCHook for NamespaceRpcHook {
65    /// Injects namespace extension fields into the request before transmission.
66    ///
67    /// If the client configuration contains a `namespace_v2` value, this method
68    /// initializes the request's extension fields map (if not already present)
69    /// and adds the namespace marker and identifier fields.
70    fn do_before_request(
71        &self,
72        _remote_addr: SocketAddr,
73        request: &mut RemotingCommand,
74    ) -> Result<(), rocketmq_error::SharedError> {
75        if let Some(namespace_v2) = self.client_config.get_namespace_v2() {
76            request.ensure_ext_fields_initialized();
77
78            request.add_ext_field(mix_all::RPC_REQUEST_HEADER_NAMESPACED_FIELD, "true");
79            request.add_ext_field(mix_all::RPC_REQUEST_HEADER_NAMESPACE_FIELD, namespace_v2.as_str());
80        }
81        Ok(())
82    }
83
84    /// Processes the response after reception.
85    ///
86    /// This implementation performs no modifications and unconditionally returns `Ok(())`.
87    fn do_after_response(
88        &self,
89        _remote_addr: SocketAddr,
90        _request: &RemotingCommand,
91        _response: &mut RemotingCommand,
92    ) -> Result<(), rocketmq_error::SharedError> {
93        Ok(())
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use std::net::SocketAddr;
100
101    use cheetah_string::CheetahString;
102    use rocketmq_protocol::protocol::remoting_command::RemotingCommand;
103    use rocketmq_transport::api::RPCHook;
104
105    use super::*;
106
107    #[test]
108    fn test_namespace_rpc_hook_adds_headers_when_namespace_configured() {
109        let mut config = ClientConfig::default();
110        config.set_namespace_v2(CheetahString::from_static_str("test-namespace"));
111
112        let hook = NamespaceRpcHook::new(config);
113        let mut request = RemotingCommand::create_remoting_command(1);
114        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
115
116        hook.do_before_request(addr, &mut request).unwrap();
117
118        assert_eq!(
119            request
120                .ext_fields()
121                .unwrap()
122                .get(mix_all::RPC_REQUEST_HEADER_NAMESPACED_FIELD),
123            Some(&CheetahString::from_static_str("true"))
124        );
125        assert_eq!(
126            request
127                .ext_fields()
128                .unwrap()
129                .get(mix_all::RPC_REQUEST_HEADER_NAMESPACE_FIELD),
130            Some(&CheetahString::from_static_str("test-namespace"))
131        );
132    }
133
134    #[test]
135    fn test_namespace_rpc_hook_no_headers_when_namespace_not_configured() {
136        let config = ClientConfig::default();
137        let hook = NamespaceRpcHook::new(config);
138        let mut request = RemotingCommand::create_remoting_command(1);
139        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
140
141        hook.do_before_request(addr, &mut request).unwrap();
142
143        assert_eq!(request.ext_fields(), None);
144    }
145
146    #[test]
147    fn test_do_after_response_does_nothing() {
148        let config = ClientConfig::default();
149        let hook = NamespaceRpcHook::new(config);
150        let request = RemotingCommand::create_remoting_command(1);
151        let mut response = RemotingCommand::create_success_response_command();
152        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
153
154        let result = hook.do_after_response(addr, &request, &mut response);
155        assert!(result.is_ok());
156    }
157
158    #[test]
159    fn test_client_config_accessor() {
160        let mut config = ClientConfig::default();
161        config.set_namespace_v2(CheetahString::from_static_str("test-namespace"));
162
163        let hook = NamespaceRpcHook::new(config.clone());
164
165        assert_eq!(
166            hook.client_config().get_namespace_v2(),
167            Some(&CheetahString::from_static_str("test-namespace"))
168        );
169    }
170
171    #[test]
172    fn test_ensure_ext_fields_preserves_existing_data() {
173        let mut config = ClientConfig::default();
174        config.set_namespace_v2(CheetahString::from_static_str("test-ns"));
175
176        let hook = NamespaceRpcHook::new(config);
177        let mut request = RemotingCommand::create_remoting_command(100);
178
179        // Store original opaque (request ID)
180        let original_opaque = request.opaque();
181
182        // Add an existing ext field
183        request.ensure_ext_fields_initialized();
184        request.add_ext_field("existing-key", "existing-value");
185
186        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
187
188        // Apply namespace hook
189        hook.do_before_request(addr, &mut request).unwrap();
190
191        // Verify original opaque is preserved
192        assert_eq!(request.opaque(), original_opaque);
193
194        // Verify existing field is preserved
195        assert_eq!(
196            request
197                .ext_fields()
198                .unwrap()
199                .get(&CheetahString::from_static_str("existing-key")),
200            Some(&CheetahString::from_static_str("existing-value"))
201        );
202
203        // Verify namespace fields are added
204        assert_eq!(
205            request
206                .ext_fields()
207                .unwrap()
208                .get(mix_all::RPC_REQUEST_HEADER_NAMESPACED_FIELD),
209            Some(&CheetahString::from_static_str("true"))
210        );
211        assert_eq!(
212            request
213                .ext_fields()
214                .unwrap()
215                .get(mix_all::RPC_REQUEST_HEADER_NAMESPACE_FIELD),
216            Some(&CheetahString::from_static_str("test-ns"))
217        );
218    }
219}