ruststream_fred/
context.rs1use ruststream::{BuildContext, Field};
43
44use crate::message::RedisMessage;
45use crate::pubsub::RedisPubSubMessage;
46
47#[derive(Debug, Clone, Default, PartialEq, Eq)]
52pub struct StreamContext {
53 entry_id: Option<String>,
54 consumer_group: Option<String>,
55}
56
57impl StreamContext {
58 #[must_use]
60 pub fn new(entry_id: Option<String>, consumer_group: Option<String>) -> Self {
61 Self {
62 entry_id,
63 consumer_group,
64 }
65 }
66
67 #[must_use]
69 pub fn entry_id(&self) -> Option<&str> {
70 self.entry_id.as_deref()
71 }
72
73 #[must_use]
75 pub fn consumer_group(&self) -> Option<&str> {
76 self.consumer_group.as_deref()
77 }
78}
79
80impl BuildContext<RedisMessage> for StreamContext {
81 fn build(msg: &RedisMessage) -> Self {
82 Self {
83 entry_id: msg.id().map(str::to_owned),
84 consumer_group: msg.group().map(str::to_owned),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Default, PartialEq, Eq)]
91pub struct PubSubContext {
92 channel: String,
93 from_pattern: bool,
94}
95
96impl PubSubContext {
97 #[must_use]
99 pub fn new(channel: impl Into<String>, from_pattern: bool) -> Self {
100 Self {
101 channel: channel.into(),
102 from_pattern,
103 }
104 }
105
106 #[must_use]
109 pub fn channel(&self) -> &str {
110 &self.channel
111 }
112
113 #[must_use]
115 pub fn from_pattern(&self) -> bool {
116 self.from_pattern
117 }
118}
119
120impl BuildContext<RedisPubSubMessage> for PubSubContext {
121 fn build(msg: &RedisPubSubMessage) -> Self {
122 Self {
123 channel: msg.channel().to_owned(),
124 from_pattern: msg.from_pattern(),
125 }
126 }
127}
128
129pub mod keys {
134 use ruststream::ContextField;
135
136 use super::{Field, PubSubContext, StreamContext};
137
138 #[derive(Debug, Clone, Copy, Default)]
140 pub struct EntryId;
141
142 impl Field<StreamContext> for EntryId {
143 type Value<'a> = Option<&'a str>;
144 fn get(self, src: &StreamContext) -> Option<&str> {
145 src.entry_id()
146 }
147 }
148
149 impl ContextField for EntryId {
150 type Context = StreamContext;
151 type Value = Option<String>;
152 fn read(self, src: &StreamContext) -> Option<String> {
153 src.entry_id().map(str::to_owned)
154 }
155 }
156
157 #[derive(Debug, Clone, Copy, Default)]
159 pub struct ConsumerGroup;
160
161 impl Field<StreamContext> for ConsumerGroup {
162 type Value<'a> = Option<&'a str>;
163 fn get(self, src: &StreamContext) -> Option<&str> {
164 src.consumer_group()
165 }
166 }
167
168 impl ContextField for ConsumerGroup {
169 type Context = StreamContext;
170 type Value = Option<String>;
171 fn read(self, src: &StreamContext) -> Option<String> {
172 src.consumer_group().map(str::to_owned)
173 }
174 }
175
176 #[derive(Debug, Clone, Copy, Default)]
178 pub struct Channel;
179
180 impl Field<PubSubContext> for Channel {
181 type Value<'a> = &'a str;
182 fn get(self, src: &PubSubContext) -> &str {
183 src.channel()
184 }
185 }
186
187 impl ContextField for Channel {
188 type Context = PubSubContext;
189 type Value = String;
190 fn read(self, src: &PubSubContext) -> String {
191 src.channel().to_owned()
192 }
193 }
194
195 #[derive(Debug, Clone, Copy, Default)]
197 pub struct FromPattern;
198
199 impl Field<PubSubContext> for FromPattern {
200 type Value<'a> = bool;
201 fn get(self, src: &PubSubContext) -> bool {
202 src.from_pattern()
203 }
204 }
205
206 impl ContextField for FromPattern {
207 type Context = PubSubContext;
208 type Value = bool;
209 fn read(self, src: &PubSubContext) -> bool {
210 src.from_pattern()
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::keys::{Channel, ConsumerGroup, EntryId, FromPattern};
218 use super::{PubSubContext, StreamContext};
219 use ruststream::{ContextField, Field};
220
221 #[test]
222 fn stream_keys_read_native_fields() {
223 let cx = StreamContext::new(
224 Some("1700000000000-0".to_owned()),
225 Some("workers".to_owned()),
226 );
227 assert_eq!(EntryId.get(&cx), Some("1700000000000-0"));
228 assert_eq!(ConsumerGroup.get(&cx), Some("workers"));
229 }
230
231 #[test]
232 fn stream_keys_absent_when_settled() {
233 let cx = StreamContext::new(None, None);
234 assert_eq!(EntryId.get(&cx), None);
235 assert_eq!(ConsumerGroup.get(&cx), None);
236 }
237
238 #[test]
239 fn pubsub_keys_read_channel_and_pattern_flag() {
240 let exact = PubSubContext::new("events", false);
241 assert_eq!(Channel.get(&exact), "events");
242 assert!(!FromPattern.get(&exact));
243
244 let matched = PubSubContext::new("events.user", true);
245 assert_eq!(Channel.get(&matched), "events.user");
246 assert!(FromPattern.get(&matched));
247 }
248
249 #[test]
250 fn context_field_keys_yield_owned_values() {
251 let stream = StreamContext::new(
252 Some("1700000000000-0".to_owned()),
253 Some("workers".to_owned()),
254 );
255 assert_eq!(
256 <EntryId as ContextField>::read(EntryId, &stream),
257 Some("1700000000000-0".to_owned())
258 );
259 assert_eq!(
260 <ConsumerGroup as ContextField>::read(ConsumerGroup, &stream),
261 Some("workers".to_owned())
262 );
263
264 let pubsub = PubSubContext::new("orders.eu", true);
265 assert_eq!(
266 <Channel as ContextField>::read(Channel, &pubsub),
267 "orders.eu".to_owned()
268 );
269 assert!(<FromPattern as ContextField>::read(FromPattern, &pubsub));
270 }
271}