use std::fmt;
use std::sync::OnceLock;
use bytes::Bytes;
use cheetah_string::CheetahString;
use crate::code::response_code::RemotingSysResponseCode;
use crate::protocol::command_custom_header::CommandCustomHeader;
use crate::protocol::remoting_command::RemotingCommand;
use crate::protocol::SerializeType;
const JAVA_DEFAULT_RESPONSE_REMARK: &str = "not set any response code";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RemotingCommandDefaults {
version: i32,
serialize_type: SerializeType,
}
impl RemotingCommandDefaults {
pub const fn new(version: i32, serialize_type: SerializeType) -> Self {
Self {
version,
serialize_type,
}
}
pub const fn version(self) -> i32 {
self.version
}
pub const fn serialize_type(self) -> SerializeType {
self.serialize_type
}
}
impl Default for RemotingCommandDefaults {
fn default() -> Self {
Self::new(crate::version::CURRENT_VERSION as i32, SerializeType::JSON)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RemotingCommandFactory {
defaults: RemotingCommandDefaults,
}
impl RemotingCommandFactory {
pub const fn new(defaults: RemotingCommandDefaults) -> Self {
Self { defaults }
}
pub const fn defaults(&self) -> RemotingCommandDefaults {
self.defaults
}
pub fn create_remoting_command(&self, code: impl Into<i32>) -> RemotingCommand {
RemotingCommand::with_resolved_defaults(self.defaults.version(), self.defaults.serialize_type()).set_code(code)
}
pub fn create_request(&self, code: impl Into<i32>, body: impl Into<Bytes>) -> RemotingCommand {
self.create_remoting_command(code).set_body(body)
}
pub fn create_request_command<T>(&self, code: impl Into<i32>, header: T) -> RemotingCommand
where
T: CommandCustomHeader + Sync + Send + 'static,
{
self.create_remoting_command(code).set_command_custom_header(header)
}
pub fn create_response_command_with_code(&self, code: impl Into<i32>) -> RemotingCommand {
self.create_remoting_command(code).mark_response_type()
}
pub fn create_response_command_with_code_and_header(
&self,
code: impl Into<i32>,
header: impl CommandCustomHeader + Sync + Send + 'static,
) -> RemotingCommand {
self.create_response_command_with_code(code)
.set_command_custom_header(header)
}
pub fn create_response_command_with_code_remark(
&self,
code: impl Into<i32>,
remark: impl Into<CheetahString>,
) -> RemotingCommand {
self.create_remoting_command(code)
.set_remark_option(Some(remark.into()))
.mark_response_type()
}
pub fn create_success_response_command(&self) -> RemotingCommand {
self.create_response_command_with_code(RemotingSysResponseCode::Success)
}
pub fn create_success_response_command_with_header(
&self,
header: impl CommandCustomHeader + Sync + Send + 'static,
) -> RemotingCommand {
self.create_response_command_with_code_and_header(RemotingSysResponseCode::Success, header)
}
pub fn create_java_default_error_response_command(&self) -> RemotingCommand {
self.create_response_command_with_code_remark(
RemotingSysResponseCode::SystemError,
JAVA_DEFAULT_RESPONSE_REMARK,
)
}
pub fn create_java_default_error_response_command_with_header(
&self,
header: impl CommandCustomHeader + Sync + Send + 'static,
) -> RemotingCommand {
self.create_java_default_error_response_command()
.set_command_custom_header(header)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RemotingCommandDefaultsConflict {
initialized: RemotingCommandDefaults,
requested: RemotingCommandDefaults,
}
impl RemotingCommandDefaultsConflict {
pub const fn initialized(self) -> RemotingCommandDefaults {
self.initialized
}
pub const fn requested(self) -> RemotingCommandDefaults {
self.requested
}
}
impl fmt::Display for RemotingCommandDefaultsConflict {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"remoting command defaults are already initialized to {:?}, requested {:?}",
self.initialized, self.requested
)
}
}
impl std::error::Error for RemotingCommandDefaultsConflict {}
static APPLICATION_DEFAULTS: OnceLock<RemotingCommandDefaults> = OnceLock::new();
fn initialize_defaults(
cell: &OnceLock<RemotingCommandDefaults>,
defaults: RemotingCommandDefaults,
) -> Result<(), RemotingCommandDefaultsConflict> {
match cell.set(defaults) {
Ok(()) => Ok(()),
Err(requested) => {
let initialized = *cell
.get()
.expect("failed OnceLock::set always retains an initialized value");
if initialized == requested {
Ok(())
} else {
Err(RemotingCommandDefaultsConflict { initialized, requested })
}
}
}
}
pub fn initialize_remoting_command_defaults(
defaults: RemotingCommandDefaults,
) -> Result<(), RemotingCommandDefaultsConflict> {
initialize_defaults(&APPLICATION_DEFAULTS, defaults)
}
pub(crate) fn application_remoting_command_defaults() -> RemotingCommandDefaults {
*APPLICATION_DEFAULTS.get_or_init(RemotingCommandDefaults::default)
}
pub fn application_remoting_command_factory() -> RemotingCommandFactory {
RemotingCommandFactory::new(application_remoting_command_defaults())
}
#[cfg(test)]
mod tests {
use rocketmq_error::CanonicalCondition;
use super::*;
#[test]
fn initialization_is_idempotent_and_rejects_conflicts() {
let cell = OnceLock::new();
let first = RemotingCommandDefaults::new(1, SerializeType::JSON);
let second = RemotingCommandDefaults::new(2, SerializeType::ROCKETMQ);
assert_eq!(initialize_defaults(&cell, first), Ok(()));
assert_eq!(initialize_defaults(&cell, first), Ok(()));
assert_eq!(
initialize_defaults(&cell, second),
Err(RemotingCommandDefaultsConflict {
initialized: first,
requested: second,
})
);
}
#[test]
fn initialized_defaults_conflict_is_a_failed_precondition() {
let cell = OnceLock::new();
let initialized = RemotingCommandDefaults::new(1, SerializeType::JSON);
let requested = RemotingCommandDefaults::new(2, SerializeType::ROCKETMQ);
initialize_defaults(&cell, initialized).expect("first defaults initialize");
let source = initialize_defaults(&cell, requested).expect_err("different defaults conflict");
let error = crate::ProtocolContractViolation::from(source);
assert_eq!(error.condition(), CanonicalCondition::FailedPrecondition);
assert_eq!(
error.to_string(),
"remoting command defaults conflict with the initialized value"
);
}
}