Skip to main content

rocketmq_client_rust/hook/
consume_message_context.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::any::Any;
16use std::collections::HashMap;
17use std::fmt;
18use std::sync::Arc;
19
20use cheetah_string::CheetahString;
21use rocketmq_model::common::message::message_ext::MessageExt;
22use rocketmq_model::common::message::message_queue::MessageQueue;
23
24use crate::base::access_channel::AccessChannel;
25
26/// Context information for message consumption operations.
27///
28/// Contains metadata about a message consumption attempt, including the consumer group,
29/// message list, target queue, and consumption result. This context is passed to
30/// consumption hooks for monitoring and tracing purposes.
31///
32/// # Examples
33///
34/// ```ignore
35/// use rocketmq_client::ConsumeMessageContext;
36/// use cheetah_string::CheetahString;
37///
38/// let context = ConsumeMessageContext::new(
39///     CheetahString::from("test_group"),
40///     &messages
41/// ).with_success(true)
42///   .with_status("CONSUME_SUCCESS");
43/// ```
44#[derive(Default)]
45pub struct ConsumeMessageContext<'a> {
46    /// Consumer group name
47    pub consumer_group: CheetahString,
48    /// List of messages being consumed
49    pub msg_list: &'a [Arc<MessageExt>],
50    /// Target message queue
51    pub mq: Option<MessageQueue>,
52    /// Whether consumption succeeded
53    pub success: bool,
54    /// Consumption status description
55    pub status: CheetahString,
56    /// Trace context for distributed tracing
57    pub mq_trace_context: Option<Arc<dyn Any + Send + Sync>>,
58    /// Additional properties
59    pub props: HashMap<CheetahString, CheetahString>,
60    /// Namespace
61    pub namespace: CheetahString,
62    /// Access channel
63    pub access_channel: Option<AccessChannel>,
64}
65
66impl<'a> ConsumeMessageContext<'a> {
67    /// Creates a new consumption context.
68    #[inline]
69    pub fn new(consumer_group: CheetahString, msg_list: &'a [Arc<MessageExt>]) -> Self {
70        Self {
71            consumer_group,
72            msg_list,
73            ..Default::default()
74        }
75    }
76
77    /// Sets the message queue.
78    #[inline]
79    pub fn with_mq(mut self, mq: MessageQueue) -> Self {
80        self.mq = Some(mq);
81        self
82    }
83
84    /// Sets the success flag.
85    #[inline]
86    pub fn with_success(mut self, success: bool) -> Self {
87        self.success = success;
88        self
89    }
90
91    /// Sets the status description.
92    #[inline]
93    pub fn with_status(mut self, status: impl Into<CheetahString>) -> Self {
94        self.status = status.into();
95        self
96    }
97
98    /// Sets the trace context.
99    #[inline]
100    pub fn with_trace_context(mut self, context: Arc<dyn Any + Send + Sync>) -> Self {
101        self.mq_trace_context = Some(context);
102        self
103    }
104
105    /// Sets the namespace.
106    #[inline]
107    pub fn with_namespace(mut self, namespace: impl Into<CheetahString>) -> Self {
108        self.namespace = namespace.into();
109        self
110    }
111
112    /// Sets the access channel.
113    #[inline]
114    pub fn with_access_channel(mut self, channel: AccessChannel) -> Self {
115        self.access_channel = Some(channel);
116        self
117    }
118
119    /// Adds a property.
120    #[inline]
121    pub fn add_prop(&mut self, key: impl Into<CheetahString>, value: impl Into<CheetahString>) {
122        self.props.insert(key.into(), value.into());
123    }
124
125    /// Gets the number of messages in this context.
126    #[inline]
127    pub fn message_count(&self) -> usize {
128        self.msg_list.len()
129    }
130}
131
132impl<'a> fmt::Display for ConsumeMessageContext<'a> {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(
135            f,
136            "ConsumeMessageContext {{ consumer_group: {}, msg_count: {}, ",
137            self.consumer_group,
138            self.msg_list.len()
139        )?;
140
141        if let Some(ref mq) = self.mq {
142            write!(f, "mq: {}, ", mq)?;
143        }
144
145        write!(f, "success: {}, status: {}", self.success, self.status)?;
146
147        if !self.namespace.is_empty() {
148            write!(f, ", namespace: {}", self.namespace)?;
149        }
150
151        if let Some(ref channel) = self.access_channel {
152            write!(f, ", access_channel: {:?}", channel)?;
153        }
154
155        write!(f, " }}")
156    }
157}
158
159impl<'a> fmt::Debug for ConsumeMessageContext<'a> {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        f.debug_struct("ConsumeMessageContext")
162            .field("consumer_group", &self.consumer_group)
163            .field("msg_list_len", &self.msg_list.len())
164            .field("mq", &self.mq)
165            .field("success", &self.success)
166            .field("status", &self.status)
167            .field("mq_trace_context", &self.mq_trace_context.as_ref().map(|_| "Some(_)"))
168            .field("props_count", &self.props.len())
169            .field("namespace", &self.namespace)
170            .field("access_channel", &self.access_channel)
171            .finish()
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn consume_message_context_new() {
181        let group = CheetahString::from("test_group");
182        let msg_list = vec![Arc::new(MessageExt::default())];
183        let context = ConsumeMessageContext::new(group.clone(), &msg_list);
184
185        assert_eq!(context.consumer_group, group);
186        assert_eq!(context.message_count(), 1);
187        assert!(context.mq.is_none());
188        assert!(!context.success);
189    }
190
191    #[test]
192    fn consume_message_context_builder_methods() {
193        let group = CheetahString::from("test_group");
194        let msg_list = vec![];
195        let mq = MessageQueue::from_parts("topic", "broker", 1);
196
197        let context = ConsumeMessageContext::new(group, &msg_list)
198            .with_mq(mq.clone())
199            .with_success(true)
200            .with_status("SUCCESS")
201            .with_namespace("test_ns")
202            .with_access_channel(AccessChannel::Cloud);
203
204        assert_eq!(context.mq.unwrap(), mq);
205        assert!(context.success);
206        assert_eq!(context.status, "SUCCESS");
207        assert_eq!(context.namespace, "test_ns");
208        assert_eq!(context.access_channel.unwrap(), AccessChannel::Cloud);
209    }
210
211    #[test]
212    fn consume_message_context_add_prop() {
213        let mut context = ConsumeMessageContext::<'static>::default();
214        context.add_prop("key1", "value1");
215        assert_eq!(context.props.get(&CheetahString::from("key1")).unwrap(), "value1");
216    }
217
218    #[test]
219    fn consume_message_context_display() {
220        let group = CheetahString::from("test_group");
221        let msg_list = vec![];
222        let context = ConsumeMessageContext::new(group, &msg_list)
223            .with_success(true)
224            .with_status("OK");
225
226        let display = format!("{}", context);
227        assert!(display.contains("consumer_group: test_group"));
228        assert!(display.contains("msg_count: 0"));
229        assert!(display.contains("success: true"));
230        assert!(display.contains("status: OK"));
231    }
232
233    #[test]
234    fn consume_message_context_debug() {
235        let context = ConsumeMessageContext::<'static>::default();
236        let debug = format!("{:?}", context);
237        assert!(debug.contains("ConsumeMessageContext"));
238        assert!(debug.contains("consumer_group"));
239    }
240
241    #[test]
242    fn consume_message_context_with_trace_context() {
243        let context =
244            ConsumeMessageContext::<'static>::default().with_trace_context(Arc::new("trace_data".to_string()));
245        assert!(context.mq_trace_context.is_some());
246    }
247}