use std::collections::HashMap;
use cheetah_string::CheetahString;
use crate::common::message::MessageConst;
#[derive(Clone, Debug, Default)]
pub struct MessageProperties {
inner: HashMap<CheetahString, CheetahString>,
}
impl MessageProperties {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn get(&self, key: MessagePropertyKey) -> Option<&str> {
self.inner.get(key.as_str()).map(|s| s.as_str())
}
pub(crate) fn insert(&mut self, key: MessagePropertyKey, value: impl Into<CheetahString>) {
self.inner.insert(key.to_cheetah_string(), value.into());
}
#[inline]
pub fn as_map(&self) -> &HashMap<CheetahString, CheetahString> {
&self.inner
}
#[doc(hidden)]
#[inline]
pub fn as_map_mut(&mut self) -> &mut HashMap<CheetahString, CheetahString> {
&mut self.inner
}
#[inline]
pub fn from_map(map: HashMap<CheetahString, CheetahString>) -> Self {
Self { inner: map }
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn tags(&self) -> Option<&str> {
self.get(MessagePropertyKey::Tags)
}
pub fn keys(&self) -> Option<Vec<String>> {
self.get(MessagePropertyKey::Keys).map(|s| {
s.split(MessageConst::KEY_SEPARATOR)
.filter(|k| !k.is_empty())
.map(|k| k.to_string())
.collect()
})
}
pub fn delay_level(&self) -> Option<i32> {
self.get(MessagePropertyKey::DelayTimeLevel)
.and_then(|s| s.parse().ok())
}
pub fn delay_time_sec(&self) -> Option<u64> {
self.get(MessagePropertyKey::DelayTimeSec).and_then(|s| s.parse().ok())
}
pub fn delay_time_ms(&self) -> Option<u64> {
self.get(MessagePropertyKey::DelayTimeMs).and_then(|s| s.parse().ok())
}
pub fn deliver_time_ms(&self) -> Option<u64> {
self.get(MessagePropertyKey::DeliverTimeMs).and_then(|s| s.parse().ok())
}
pub fn buyer_id(&self) -> Option<&str> {
self.get(MessagePropertyKey::BuyerId)
}
pub fn wait_store_msg_ok(&self) -> bool {
self.get(MessagePropertyKey::WaitStoreMsgOk)
.map(|s| s.eq_ignore_ascii_case("true"))
.unwrap_or(true)
}
pub fn origin_message_id(&self) -> Option<&str> {
self.get(MessagePropertyKey::OriginMessageId)
}
pub fn retry_topic(&self) -> Option<&str> {
self.get(MessagePropertyKey::RetryTopic)
}
pub fn real_topic(&self) -> Option<&str> {
self.get(MessagePropertyKey::RealTopic)
}
pub fn real_queue_id(&self) -> Option<i32> {
self.get(MessagePropertyKey::RealQueueId).and_then(|s| s.parse().ok())
}
pub fn unique_client_msg_id(&self) -> Option<&str> {
self.get(MessagePropertyKey::UniqueClientMsgId)
}
pub fn producer_group(&self) -> Option<&str> {
self.get(MessagePropertyKey::ProducerGroup)
}
pub fn instance_id(&self) -> Option<&str> {
self.get(MessagePropertyKey::InstanceId)
}
pub fn correlation_id(&self) -> Option<&str> {
self.get(MessagePropertyKey::CorrelationId)
}
pub fn message_type(&self) -> Option<&str> {
self.get(MessagePropertyKey::MessageType)
}
pub fn trace_switch(&self) -> bool {
self.get(MessagePropertyKey::TraceSwitch)
.map(|s| s == "true")
.unwrap_or(false)
}
pub fn priority(&self) -> i32 {
self.get(MessagePropertyKey::Priority)
.and_then(|s| s.parse().ok())
.unwrap_or(-1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MessagePropertyKey {
Tags,
Keys,
WaitStoreMsgOk,
DelayTimeLevel,
DelayTimeSec,
DelayTimeMs,
DeliverTimeMs,
RetryTopic,
RealTopic,
RealQueueId,
TransactionPrepared,
ProducerGroup,
MinOffset,
MaxOffset,
BuyerId,
OriginGroup,
OriginMessageId,
TransferFlag,
CorrectionFlag,
MqReplyTopic,
MqReplyQueueId,
UniqueClientMsgId,
ReconsumeTime,
MsgRegion,
TraceSwitch,
UniqueKey,
MaxReconsumeTimes,
ConsumeStartTimestamp,
PopCk,
PopCkOffset,
FirstPopTime,
Priority,
TransactionPreparedQueueOffset,
DupInfo,
ExtendUniqInfo,
InstanceId,
CorrelationId,
MessageReplyToClient,
MessageTtl,
ReplyMessageArriveTime,
PushReplyTime,
Cluster,
MessageType,
InnerMultiQueueOffset,
TimerDelayLevel,
TimerEnqueueMs,
TimerDequeueMs,
TimerRollTimes,
TimerRollLabel,
TimerOutMs,
TimerDelUniqkey,
TimerOriginalDeliverMs,
TimerDeliveryToken,
TimerId,
TimerOwnerEpoch,
TimerLane,
TimerGeneration,
TimerEngineType,
TimerFormatVersion,
TimerPolicyFingerprint,
BornHost,
BornTimestamp,
DlqOriginTopic,
DlqOriginMessageId,
Crc32,
Redirect,
ForwardQueueId,
InnerBase,
InnerMultiDispatch,
InnerNum,
Mq2Flag,
TransOffset,
ShardingKey,
TransactionId,
TransactionCheckTimes,
TransientGroupConfig,
TransientTopicConfig,
TraceContext,
}
impl MessagePropertyKey {
pub fn as_str(&self) -> &'static str {
match self {
Self::Tags => MessageConst::PROPERTY_TAGS,
Self::Keys => MessageConst::PROPERTY_KEYS,
Self::WaitStoreMsgOk => MessageConst::PROPERTY_WAIT_STORE_MSG_OK,
Self::DelayTimeLevel => MessageConst::PROPERTY_DELAY_TIME_LEVEL,
Self::DelayTimeSec => MessageConst::PROPERTY_TIMER_DELAY_SEC,
Self::DelayTimeMs => MessageConst::PROPERTY_TIMER_DELAY_MS,
Self::DeliverTimeMs => MessageConst::PROPERTY_TIMER_DELIVER_MS,
Self::RetryTopic => MessageConst::PROPERTY_RETRY_TOPIC,
Self::RealTopic => MessageConst::PROPERTY_REAL_TOPIC,
Self::RealQueueId => MessageConst::PROPERTY_REAL_QUEUE_ID,
Self::TransactionPrepared => MessageConst::PROPERTY_TRANSACTION_PREPARED,
Self::ProducerGroup => MessageConst::PROPERTY_PRODUCER_GROUP,
Self::MinOffset => MessageConst::PROPERTY_MIN_OFFSET,
Self::MaxOffset => MessageConst::PROPERTY_MAX_OFFSET,
Self::BuyerId => MessageConst::PROPERTY_BUYER_ID,
Self::OriginGroup => MessageConst::PROPERTY_ORIGIN_GROUP,
Self::OriginMessageId => MessageConst::PROPERTY_ORIGIN_MESSAGE_ID,
Self::TransferFlag => MessageConst::PROPERTY_TRANSFER_FLAG,
Self::CorrectionFlag => MessageConst::PROPERTY_CORRECTION_FLAG,
Self::MqReplyTopic => MessageConst::PROPERTY_MESSAGE_REPLY_TO_CLIENT,
Self::MqReplyQueueId => MessageConst::PROPERTY_MESSAGE_REPLY_TO_CLIENT,
Self::UniqueClientMsgId => MessageConst::PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX,
Self::ReconsumeTime => MessageConst::PROPERTY_RECONSUME_TIME,
Self::MsgRegion => MessageConst::PROPERTY_MSG_REGION,
Self::TraceSwitch => MessageConst::PROPERTY_TRACE_SWITCH,
Self::UniqueKey => MessageConst::PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX,
Self::MaxReconsumeTimes => MessageConst::PROPERTY_MAX_RECONSUME_TIMES,
Self::ConsumeStartTimestamp => MessageConst::PROPERTY_CONSUME_START_TIMESTAMP,
Self::PopCk => MessageConst::PROPERTY_POP_CK,
Self::PopCkOffset => MessageConst::PROPERTY_POP_CK_OFFSET,
Self::FirstPopTime => MessageConst::PROPERTY_FIRST_POP_TIME,
Self::Priority => MessageConst::PROPERTY_PRIORITY,
Self::TransactionPreparedQueueOffset => MessageConst::PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET,
Self::DupInfo => MessageConst::DUP_INFO,
Self::ExtendUniqInfo => MessageConst::PROPERTY_EXTEND_UNIQ_INFO,
Self::InstanceId => MessageConst::PROPERTY_INSTANCE_ID,
Self::CorrelationId => MessageConst::PROPERTY_CORRELATION_ID,
Self::MessageReplyToClient => MessageConst::PROPERTY_MESSAGE_REPLY_TO_CLIENT,
Self::MessageTtl => MessageConst::PROPERTY_MESSAGE_TTL,
Self::ReplyMessageArriveTime => MessageConst::PROPERTY_REPLY_MESSAGE_ARRIVE_TIME,
Self::PushReplyTime => MessageConst::PROPERTY_PUSH_REPLY_TIME,
Self::Cluster => MessageConst::PROPERTY_CLUSTER,
Self::MessageType => MessageConst::PROPERTY_MESSAGE_TYPE,
Self::InnerMultiQueueOffset => MessageConst::PROPERTY_INNER_MULTI_QUEUE_OFFSET,
Self::TimerDelayLevel => MessageConst::PROPERTY_TIMER_DELAY_LEVEL,
Self::TimerEnqueueMs => MessageConst::PROPERTY_TIMER_ENQUEUE_MS,
Self::TimerDequeueMs => MessageConst::PROPERTY_TIMER_DEQUEUE_MS,
Self::TimerRollTimes => MessageConst::PROPERTY_TIMER_ROLL_TIMES,
Self::TimerRollLabel => MessageConst::PROPERTY_TIMER_ROLL_LABEL,
Self::TimerOutMs => MessageConst::PROPERTY_TIMER_OUT_MS,
Self::TimerDelUniqkey => MessageConst::PROPERTY_TIMER_DEL_UNIQKEY,
Self::TimerOriginalDeliverMs => MessageConst::PROPERTY_TIMER_ORIGINAL_DELIVER_MS,
Self::TimerDeliveryToken => MessageConst::PROPERTY_TIMER_DELIVERY_TOKEN,
Self::TimerId => MessageConst::PROPERTY_TIMER_ID,
Self::TimerOwnerEpoch => MessageConst::PROPERTY_TIMER_OWNER_EPOCH,
Self::TimerLane => MessageConst::PROPERTY_TIMER_LANE,
Self::TimerGeneration => MessageConst::PROPERTY_TIMER_GENERATION,
Self::TimerEngineType => MessageConst::TIMER_ENGINE_TYPE,
Self::TimerFormatVersion => MessageConst::PROPERTY_TIMER_FORMAT_VERSION,
Self::TimerPolicyFingerprint => MessageConst::PROPERTY_TIMER_POLICY_FINGERPRINT,
Self::BornHost => MessageConst::PROPERTY_BORN_HOST,
Self::BornTimestamp => MessageConst::PROPERTY_BORN_TIMESTAMP,
Self::DlqOriginTopic => MessageConst::PROPERTY_DLQ_ORIGIN_TOPIC,
Self::DlqOriginMessageId => MessageConst::PROPERTY_DLQ_ORIGIN_MESSAGE_ID,
Self::Crc32 => MessageConst::PROPERTY_CRC32,
Self::Redirect => MessageConst::PROPERTY_REDIRECT,
Self::ForwardQueueId => MessageConst::PROPERTY_FORWARD_QUEUE_ID,
Self::InnerBase => MessageConst::PROPERTY_INNER_BASE,
Self::InnerMultiDispatch => MessageConst::PROPERTY_INNER_MULTI_DISPATCH,
Self::InnerNum => MessageConst::PROPERTY_INNER_NUM,
Self::Mq2Flag => MessageConst::PROPERTY_MQ2_FLAG,
Self::TransOffset => MessageConst::PROPERTY_TRANS_OFFSET,
Self::ShardingKey => MessageConst::PROPERTY_SHARDING_KEY,
Self::TransactionId => MessageConst::PROPERTY_TRANSACTION_ID,
Self::TransactionCheckTimes => MessageConst::PROPERTY_TRANSACTION_CHECK_TIMES,
Self::TransientGroupConfig => MessageConst::PROPERTY_TRANSIENT_GROUP_CONFIG,
Self::TransientTopicConfig => MessageConst::PROPERTY_TRANSIENT_TOPIC_CONFIG,
Self::TraceContext => MessageConst::PROPERTY_TRACE_CONTEXT,
}
}
pub(crate) fn to_cheetah_string(self) -> CheetahString {
CheetahString::from_static_str(self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wait_store_msg_ok_matches_java_boolean_parse_semantics() {
let mut properties = MessageProperties::new();
assert!(properties.wait_store_msg_ok());
properties.insert(MessagePropertyKey::WaitStoreMsgOk, "true");
assert!(properties.wait_store_msg_ok());
properties.insert(MessagePropertyKey::WaitStoreMsgOk, "TRUE");
assert!(properties.wait_store_msg_ok());
properties.insert(MessagePropertyKey::WaitStoreMsgOk, "false");
assert!(!properties.wait_store_msg_ok());
properties.insert(MessagePropertyKey::WaitStoreMsgOk, "yes");
assert!(!properties.wait_store_msg_ok());
}
#[test]
fn priority_matches_java_default_and_parse_semantics() {
let mut properties = MessageProperties::new();
assert_eq!(properties.priority(), -1);
properties.insert(MessagePropertyKey::Priority, "7");
assert_eq!(properties.priority(), 7);
properties.insert(MessagePropertyKey::Priority, "invalid");
assert_eq!(properties.priority(), -1);
}
#[test]
fn protocol_property_keys_include_java_message_const_gaps() {
assert_eq!(MessagePropertyKey::OriginGroup.as_str(), "ORIGIN_GROUP");
assert_eq!(MessagePropertyKey::TransOffset.as_str(), "TRANS_OFFSET");
assert_eq!(MessagePropertyKey::TimerRollLabel.as_str(), "TIMER_ROLL_LABEL");
}
}