use std::fmt;
use std::sync::LazyLock;
use std::sync::OnceLock;
use crate::protocol::command_custom_header::CommandCustomHeader;
use crate::protocol::remoting_command::RemotingCommand;
use crate::protocol::remoting_command_defaults::initialize_remoting_command_defaults;
use crate::protocol::remoting_command_defaults::RemotingCommandDefaults;
use crate::protocol::remoting_command_defaults::RemotingCommandFactory;
use crate::protocol::SerializeType;
use crate::ProtocolContractViolation;
use super::remoting_command::REMOTING_VERSION_KEY;
use super::remoting_command::SERIALIZE_TYPE_ENV;
use super::remoting_command::SERIALIZE_TYPE_PROPERTY;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RemotingVersionConflict {
initialized: i32,
requested: i32,
}
impl RemotingVersionConflict {
pub fn initialized(self) -> i32 {
self.initialized
}
pub fn requested(self) -> i32 {
self.requested
}
}
impl fmt::Display for RemotingVersionConflict {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"remoting version is already initialized to {}, requested {}",
self.initialized, self.requested
)
}
}
impl std::error::Error for RemotingVersionConflict {}
static REMOTING_VERSION: OnceLock<i32> = OnceLock::new();
static SERIALIZE_TYPE: LazyLock<SerializeType> = LazyLock::new(|| {
std::env::var(SERIALIZE_TYPE_PROPERTY)
.or_else(|_| std::env::var(SERIALIZE_TYPE_ENV))
.ok()
.and_then(|value| match value.as_str() {
"JSON" => Some(SerializeType::JSON),
"ROCKETMQ" => Some(SerializeType::ROCKETMQ),
_ => None,
})
.unwrap_or(SerializeType::JSON)
});
#[derive(Clone, PartialEq, Eq)]
pub struct InvalidRemotingSerializeType {
key: &'static str,
value: String,
}
impl InvalidRemotingSerializeType {
pub const fn key(&self) -> &'static str {
self.key
}
pub fn value(&self) -> &str {
&self.value
}
}
impl fmt::Display for InvalidRemotingSerializeType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"invalid remoting serialization type for {}: {:?}; expected JSON or ROCKETMQ",
self.key, self.value
)
}
}
impl std::error::Error for InvalidRemotingSerializeType {}
pub fn resolve_remoting_serialize_type(
property_value: Option<&str>,
environment_value: Option<&str>,
) -> Result<SerializeType, ProtocolContractViolation> {
let (key, value) = property_value
.map(|value| (SERIALIZE_TYPE_PROPERTY, value))
.or_else(|| environment_value.map(|value| (SERIALIZE_TYPE_ENV, value)))
.unwrap_or((SERIALIZE_TYPE_PROPERTY, "JSON"));
match value {
"JSON" => Ok(SerializeType::JSON),
"ROCKETMQ" => Ok(SerializeType::ROCKETMQ),
_ => Err(InvalidRemotingSerializeType {
key,
value: value.to_string(),
}
.into()),
}
}
impl fmt::Debug for InvalidRemotingSerializeType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("InvalidRemotingSerializeType")
.field("key", &self.key)
.field("value_present", &true)
.finish()
}
}
fn read_environment_value(key: &'static str) -> Result<Option<String>, ProtocolContractViolation> {
match std::env::var(key) {
Ok(value) => Ok(Some(value)),
Err(std::env::VarError::NotPresent) => Ok(None),
Err(std::env::VarError::NotUnicode(value)) => Err(InvalidRemotingSerializeType {
key,
value: value.to_string_lossy().into_owned(),
}
.into()),
}
}
pub fn initialize_remoting_command_factory(version: i32) -> Result<RemotingCommandFactory, ProtocolContractViolation> {
let property_value = read_environment_value(SERIALIZE_TYPE_PROPERTY)?;
let environment_value = if property_value.is_none() {
read_environment_value(SERIALIZE_TYPE_ENV)?
} else {
None
};
let serialize_type = resolve_remoting_serialize_type(property_value.as_deref(), environment_value.as_deref())?;
let defaults = RemotingCommandDefaults::new(version, serialize_type);
initialize_remoting_command_defaults(defaults)?;
Ok(RemotingCommandFactory::new(defaults))
}
pub fn initialize_remoting_defaults(version: i32) -> Result<(), ProtocolContractViolation> {
initialize_remoting_command_factory(version)?;
Ok(())
}
fn resolve_remoting_version(cell: &OnceLock<i32>) -> i32 {
*cell.get_or_init(|| {
std::env::var(REMOTING_VERSION_KEY)
.ok()
.and_then(|value| value.parse::<i32>().ok())
.unwrap_or(crate::version::CURRENT_VERSION as i32)
})
}
fn initialize_version(cell: &OnceLock<i32>, version: i32) -> Result<(), RemotingVersionConflict> {
match cell.set(version) {
Ok(()) => Ok(()),
Err(requested) => {
let initialized = resolve_remoting_version(cell);
if initialized == requested {
Ok(())
} else {
Err(RemotingVersionConflict { initialized, requested })
}
}
}
}
pub fn initialize_remoting_version(version: i32) -> Result<(), RemotingVersionConflict> {
initialize_version(&REMOTING_VERSION, version)
}
fn compatibility_factory() -> RemotingCommandFactory {
RemotingCommandFactory::new(RemotingCommandDefaults::new(
resolve_remoting_version(&REMOTING_VERSION),
*SERIALIZE_TYPE,
))
}
pub fn create_remoting_command(code: impl Into<i32>) -> RemotingCommand {
compatibility_factory().create_remoting_command(code)
}
pub fn create_request_command<T>(code: impl Into<i32>, header: T) -> RemotingCommand
where
T: CommandCustomHeader + Sync + Send + 'static,
{
compatibility_factory().create_request_command(code, header)
}
pub fn create_response_command() -> RemotingCommand {
compatibility_factory().create_success_response_command()
}
#[cfg(test)]
mod tests {
use std::sync::OnceLock;
use crate::protocol::header::empty_header::EmptyHeader;
use super::compatibility_factory;
use super::create_remoting_command;
use super::create_request_command;
use super::create_response_command;
use super::initialize_version;
use super::RemotingVersionConflict;
#[test]
fn remoting_version_initialization_is_idempotent() {
let version = OnceLock::new();
assert_eq!(initialize_version(&version, 321), Ok(()));
assert_eq!(initialize_version(&version, 321), Ok(()));
assert_eq!(version.get(), Some(&321));
}
#[test]
fn remoting_version_initialization_rejects_conflicts() {
let version = OnceLock::new();
assert_eq!(initialize_version(&version, 321), Ok(()));
assert_eq!(
initialize_version(&version, 654),
Err(RemotingVersionConflict {
initialized: 321,
requested: 654,
})
);
assert_eq!(version.get(), Some(&321));
}
#[test]
fn compatibility_constructors_share_factory_defaults() {
let expected_defaults = compatibility_factory().defaults();
let commands = [
create_remoting_command(10),
create_request_command(11, EmptyHeader {}),
create_response_command(),
];
for command in commands {
assert_eq!(command.version(), expected_defaults.version());
assert_eq!(command.serialize_type(), expected_defaults.serialize_type());
}
}
}