rocketmq_protocol/protocol/
remoting_command_compat.rs1use std::fmt;
22use std::sync::LazyLock;
23use std::sync::OnceLock;
24
25use crate::protocol::command_custom_header::CommandCustomHeader;
26use crate::protocol::remoting_command::RemotingCommand;
27use crate::protocol::remoting_command_defaults::initialize_remoting_command_defaults;
28use crate::protocol::remoting_command_defaults::RemotingCommandDefaults;
29use crate::protocol::remoting_command_defaults::RemotingCommandFactory;
30use crate::protocol::SerializeType;
31use crate::ProtocolContractViolation;
32
33use super::remoting_command::REMOTING_VERSION_KEY;
34use super::remoting_command::SERIALIZE_TYPE_ENV;
35use super::remoting_command::SERIALIZE_TYPE_PROPERTY;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct RemotingVersionConflict {
41 initialized: i32,
42 requested: i32,
43}
44
45impl RemotingVersionConflict {
46 pub fn initialized(self) -> i32 {
48 self.initialized
49 }
50
51 pub fn requested(self) -> i32 {
53 self.requested
54 }
55}
56
57impl fmt::Display for RemotingVersionConflict {
58 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59 write!(
60 formatter,
61 "remoting version is already initialized to {}, requested {}",
62 self.initialized, self.requested
63 )
64 }
65}
66
67impl std::error::Error for RemotingVersionConflict {}
68
69static REMOTING_VERSION: OnceLock<i32> = OnceLock::new();
70
71static SERIALIZE_TYPE: LazyLock<SerializeType> = LazyLock::new(|| {
72 std::env::var(SERIALIZE_TYPE_PROPERTY)
73 .or_else(|_| std::env::var(SERIALIZE_TYPE_ENV))
74 .ok()
75 .and_then(|value| match value.as_str() {
76 "JSON" => Some(SerializeType::JSON),
77 "ROCKETMQ" => Some(SerializeType::ROCKETMQ),
78 _ => None,
79 })
80 .unwrap_or(SerializeType::JSON)
81});
82
83#[derive(Clone, PartialEq, Eq)]
85pub struct InvalidRemotingSerializeType {
86 key: &'static str,
87 value: String,
88}
89
90impl InvalidRemotingSerializeType {
91 pub const fn key(&self) -> &'static str {
92 self.key
93 }
94
95 pub fn value(&self) -> &str {
96 &self.value
97 }
98}
99
100impl fmt::Display for InvalidRemotingSerializeType {
101 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(
103 formatter,
104 "invalid remoting serialization type for {}: {:?}; expected JSON or ROCKETMQ",
105 self.key, self.value
106 )
107 }
108}
109
110impl std::error::Error for InvalidRemotingSerializeType {}
111
112pub fn resolve_remoting_serialize_type(
122 property_value: Option<&str>,
123 environment_value: Option<&str>,
124) -> Result<SerializeType, ProtocolContractViolation> {
125 let (key, value) = property_value
126 .map(|value| (SERIALIZE_TYPE_PROPERTY, value))
127 .or_else(|| environment_value.map(|value| (SERIALIZE_TYPE_ENV, value)))
128 .unwrap_or((SERIALIZE_TYPE_PROPERTY, "JSON"));
129
130 match value {
131 "JSON" => Ok(SerializeType::JSON),
132 "ROCKETMQ" => Ok(SerializeType::ROCKETMQ),
133 _ => Err(InvalidRemotingSerializeType {
134 key,
135 value: value.to_string(),
136 }
137 .into()),
138 }
139}
140
141impl fmt::Debug for InvalidRemotingSerializeType {
142 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143 formatter
144 .debug_struct("InvalidRemotingSerializeType")
145 .field("key", &self.key)
146 .field("value_present", &true)
147 .finish()
148 }
149}
150
151fn read_environment_value(key: &'static str) -> Result<Option<String>, ProtocolContractViolation> {
152 match std::env::var(key) {
153 Ok(value) => Ok(Some(value)),
154 Err(std::env::VarError::NotPresent) => Ok(None),
155 Err(std::env::VarError::NotUnicode(value)) => Err(InvalidRemotingSerializeType {
156 key,
157 value: value.to_string_lossy().into_owned(),
158 }
159 .into()),
160 }
161}
162
163pub fn initialize_remoting_command_factory(version: i32) -> Result<RemotingCommandFactory, ProtocolContractViolation> {
174 let property_value = read_environment_value(SERIALIZE_TYPE_PROPERTY)?;
175 let environment_value = if property_value.is_none() {
176 read_environment_value(SERIALIZE_TYPE_ENV)?
177 } else {
178 None
179 };
180 let serialize_type = resolve_remoting_serialize_type(property_value.as_deref(), environment_value.as_deref())?;
181 let defaults = RemotingCommandDefaults::new(version, serialize_type);
182 initialize_remoting_command_defaults(defaults)?;
183 Ok(RemotingCommandFactory::new(defaults))
184}
185
186pub fn initialize_remoting_defaults(version: i32) -> Result<(), ProtocolContractViolation> {
198 initialize_remoting_command_factory(version)?;
199 Ok(())
200}
201
202fn resolve_remoting_version(cell: &OnceLock<i32>) -> i32 {
203 *cell.get_or_init(|| {
204 std::env::var(REMOTING_VERSION_KEY)
205 .ok()
206 .and_then(|value| value.parse::<i32>().ok())
207 .unwrap_or(crate::version::CURRENT_VERSION as i32)
208 })
209}
210
211fn initialize_version(cell: &OnceLock<i32>, version: i32) -> Result<(), RemotingVersionConflict> {
212 match cell.set(version) {
213 Ok(()) => Ok(()),
214 Err(requested) => {
215 let initialized = resolve_remoting_version(cell);
216 if initialized == requested {
217 Ok(())
218 } else {
219 Err(RemotingVersionConflict { initialized, requested })
220 }
221 }
222 }
223}
224
225pub fn initialize_remoting_version(version: i32) -> Result<(), RemotingVersionConflict> {
236 initialize_version(&REMOTING_VERSION, version)
237}
238
239fn compatibility_factory() -> RemotingCommandFactory {
240 RemotingCommandFactory::new(RemotingCommandDefaults::new(
241 resolve_remoting_version(&REMOTING_VERSION),
242 *SERIALIZE_TYPE,
243 ))
244}
245
246pub fn create_remoting_command(code: impl Into<i32>) -> RemotingCommand {
247 compatibility_factory().create_remoting_command(code)
248}
249
250pub fn create_request_command<T>(code: impl Into<i32>, header: T) -> RemotingCommand
251where
252 T: CommandCustomHeader + Sync + Send + 'static,
253{
254 compatibility_factory().create_request_command(code, header)
255}
256
257pub fn create_response_command() -> RemotingCommand {
258 compatibility_factory().create_success_response_command()
259}
260
261#[cfg(test)]
262mod tests {
263 use std::sync::OnceLock;
264
265 use crate::protocol::header::empty_header::EmptyHeader;
266
267 use super::compatibility_factory;
268 use super::create_remoting_command;
269 use super::create_request_command;
270 use super::create_response_command;
271 use super::initialize_version;
272 use super::RemotingVersionConflict;
273
274 #[test]
275 fn remoting_version_initialization_is_idempotent() {
276 let version = OnceLock::new();
277
278 assert_eq!(initialize_version(&version, 321), Ok(()));
279 assert_eq!(initialize_version(&version, 321), Ok(()));
280 assert_eq!(version.get(), Some(&321));
281 }
282
283 #[test]
284 fn remoting_version_initialization_rejects_conflicts() {
285 let version = OnceLock::new();
286
287 assert_eq!(initialize_version(&version, 321), Ok(()));
288 assert_eq!(
289 initialize_version(&version, 654),
290 Err(RemotingVersionConflict {
291 initialized: 321,
292 requested: 654,
293 })
294 );
295 assert_eq!(version.get(), Some(&321));
296 }
297
298 #[test]
299 fn compatibility_constructors_share_factory_defaults() {
300 let expected_defaults = compatibility_factory().defaults();
301 let commands = [
302 create_remoting_command(10),
303 create_request_command(11, EmptyHeader {}),
304 create_response_command(),
305 ];
306
307 for command in commands {
308 assert_eq!(command.version(), expected_defaults.version());
309 assert_eq!(command.serialize_type(), expected_defaults.serialize_type());
310 }
311 }
312}