1#![allow(clippy::too_many_arguments, clippy::type_complexity)]
4
5use crate::GlideError;
6use crate::ValkeyResult;
7use crate::cmd::Cmd;
8use crate::executor::CommandExecutor;
9use crate::value::FromValkeyValue;
10use crate::value::ValkeyValue;
11use crate::value::to_glide_error;
12use crate::write::ToValkeyArgs;
13use crate::write::ValkeyWrite;
14use async_trait::async_trait;
15use bytes::Bytes;
16use std::collections::HashMap;
17
18pub type StreamEntry = (String, Vec<(Bytes, Bytes)>);
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum StreamTrimmingMode {
27 Exact,
29 Approx,
31}
32
33impl ToValkeyArgs for StreamTrimmingMode {
34 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
35 out.write_arg(match self {
36 Self::Exact => b"=",
37 Self::Approx => b"~",
38 });
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum StreamTrimStrategy {
48 MaxLen(StreamTrimmingMode, usize, Option<usize>),
50 MinId(StreamTrimmingMode, String, Option<usize>),
52}
53
54impl StreamTrimStrategy {
55 pub fn maxlen(trim: StreamTrimmingMode, max_entries: usize) -> Self {
57 Self::MaxLen(trim, max_entries, None)
58 }
59
60 pub fn minid(trim: StreamTrimmingMode, stream_id: impl Into<String>) -> Self {
62 Self::MinId(trim, stream_id.into(), None)
63 }
64
65 pub fn limit(self, limit: usize) -> Self {
67 match self {
68 Self::MaxLen(mode, threshold, _) => Self::MaxLen(mode, threshold, Some(limit)),
69 Self::MinId(mode, threshold, _) => Self::MinId(mode, threshold, Some(limit)),
70 }
71 }
72}
73
74impl ToValkeyArgs for StreamTrimStrategy {
75 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
76 let limit = match self {
77 Self::MaxLen(mode, threshold, limit) => {
78 out.write_arg(b"MAXLEN");
79 mode.write_valkey_args(out);
80 out.write_arg_fmt(threshold);
81 limit
82 }
83 Self::MinId(mode, threshold, limit) => {
84 out.write_arg(b"MINID");
85 mode.write_valkey_args(out);
86 out.write_arg(threshold.as_bytes());
87 limit
88 }
89 };
90 if let Some(limit) = limit {
91 out.write_arg(b"LIMIT");
92 out.write_arg_fmt(limit);
93 }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101#[non_exhaustive]
102pub enum StreamMaxlen {
103 Equals(usize),
105 Approx(usize),
107}
108
109impl ToValkeyArgs for StreamMaxlen {
110 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
111 let (mode, threshold): (&[u8], _) = match *self {
112 Self::Equals(threshold) => (b"=", threshold),
113 Self::Approx(threshold) => (b"~", threshold),
114 };
115 out.write_arg(b"MAXLEN");
116 out.write_arg(mode);
117 out.write_arg_fmt(threshold);
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct StreamTrimOptions {
127 strategy: StreamTrimStrategy,
128}
129
130impl StreamTrimOptions {
131 pub fn maxlen(mode: StreamTrimmingMode, max_entries: usize) -> Self {
133 Self {
134 strategy: StreamTrimStrategy::maxlen(mode, max_entries),
135 }
136 }
137
138 pub fn minid(mode: StreamTrimmingMode, stream_id: impl Into<String>) -> Self {
140 Self {
141 strategy: StreamTrimStrategy::minid(mode, stream_id),
142 }
143 }
144
145 pub fn limit(mut self, limit: usize) -> Self {
147 self.strategy = self.strategy.limit(limit);
148 self
149 }
150}
151
152impl ToValkeyArgs for StreamTrimOptions {
153 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
154 self.strategy.write_valkey_args(out);
155 }
156}
157
158#[derive(Debug, Clone, Default, PartialEq, Eq)]
162pub struct StreamAddOptions {
163 nomkstream: bool,
164 trim: Option<StreamTrimStrategy>,
165}
166
167impl StreamAddOptions {
168 pub fn nomkstream(mut self) -> Self {
170 self.nomkstream = true;
171 self
172 }
173
174 pub fn trim(mut self, trim: StreamTrimStrategy) -> Self {
176 self.trim = Some(trim);
177 self
178 }
179}
180
181impl ToValkeyArgs for StreamAddOptions {
182 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
183 if self.nomkstream {
184 out.write_arg(b"NOMKSTREAM");
185 }
186 self.trim.write_valkey_args(out);
187 }
188}
189
190#[derive(Debug, Clone, Default, PartialEq, Eq)]
195pub struct StreamReadOptions {
196 block: Option<usize>,
197 count: Option<usize>,
198}
199
200impl StreamReadOptions {
201 pub fn block(mut self, ms: usize) -> Self {
203 self.block = Some(ms);
204 self
205 }
206
207 pub fn count(mut self, n: usize) -> Self {
209 self.count = Some(n);
210 self
211 }
212}
213
214impl ToValkeyArgs for StreamReadOptions {
215 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
216 if let Some(block) = self.block {
217 out.write_arg(b"BLOCK");
218 out.write_arg_fmt(block);
219 }
220 if let Some(count) = self.count {
221 out.write_arg(b"COUNT");
222 out.write_arg_fmt(count);
223 }
224 }
225}
226
227#[derive(Debug, Clone, Copy, Default)]
229pub struct StreamReadGroupOptions {
230 pub block_ms: Option<i64>,
232 pub count: Option<i64>,
234 pub no_ack: bool,
236}
237
238impl ToValkeyArgs for StreamReadGroupOptions {
239 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
240 if let Some(block_ms) = self.block_ms {
241 out.write_arg(b"BLOCK");
242 out.write_arg_fmt(block_ms);
243 }
244 if let Some(count) = self.count {
245 out.write_arg(b"COUNT");
246 out.write_arg_fmt(count);
247 }
248 if self.no_ack {
249 out.write_arg(b"NOACK");
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, Default)]
258pub struct StreamGroupCreateOptions {
259 pub make_stream: bool,
261 pub entries_read: Option<i64>,
263}
264
265impl ToValkeyArgs for StreamGroupCreateOptions {
266 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
267 if self.make_stream {
268 out.write_arg(b"MKSTREAM");
269 }
270 if let Some(entries_read) = self.entries_read {
271 out.write_arg(b"ENTRIESREAD");
272 out.write_arg_fmt(entries_read);
273 }
274 }
275}
276
277#[derive(Debug, Clone, Default, PartialEq, Eq)]
281pub struct StreamClaimOptions {
282 idle: Option<usize>,
283 time: Option<usize>,
284 retry: Option<usize>,
285 force: bool,
286 justid: bool,
287 lastid: Option<String>,
288}
289
290impl StreamClaimOptions {
291 pub fn idle(mut self, ms: usize) -> Self {
293 self.idle = Some(ms);
294 self
295 }
296
297 pub fn time(mut self, ms_time: usize) -> Self {
299 self.time = Some(ms_time);
300 self
301 }
302
303 pub fn retry(mut self, count: usize) -> Self {
305 self.retry = Some(count);
306 self
307 }
308
309 pub fn with_force(mut self) -> Self {
311 self.force = true;
312 self
313 }
314
315 pub fn with_justid(mut self) -> Self {
317 self.justid = true;
318 self
319 }
320
321 pub fn with_lastid(mut self, lastid: impl Into<String>) -> Self {
323 self.lastid = Some(lastid.into());
324 self
325 }
326}
327
328impl ToValkeyArgs for StreamClaimOptions {
329 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
330 if let Some(idle) = self.idle {
331 out.write_arg(b"IDLE");
332 out.write_arg_fmt(idle);
333 }
334 if let Some(time) = self.time {
335 out.write_arg(b"TIME");
336 out.write_arg_fmt(time);
337 }
338 if let Some(retry) = self.retry {
339 out.write_arg(b"RETRYCOUNT");
340 out.write_arg_fmt(retry);
341 }
342 if self.force {
343 out.write_arg(b"FORCE");
344 }
345 if self.justid {
346 out.write_arg(b"JUSTID");
347 }
348 if let Some(lastid) = &self.lastid {
349 out.write_arg(b"LASTID");
350 out.write_arg(lastid.as_bytes());
351 }
352 }
353}
354
355#[derive(Debug, Clone, Default, PartialEq, Eq)]
359pub struct StreamAutoClaimOptions {
360 count: Option<usize>,
361 justid: bool,
362}
363
364impl StreamAutoClaimOptions {
365 pub fn count(mut self, n: usize) -> Self {
367 self.count = Some(n);
368 self
369 }
370
371 pub fn with_justid(mut self) -> Self {
373 self.justid = true;
374 self
375 }
376}
377
378impl ToValkeyArgs for StreamAutoClaimOptions {
379 fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
380 if let Some(count) = self.count {
381 out.write_arg(b"COUNT");
382 out.write_arg_fmt(count);
383 }
384 if self.justid {
385 out.write_arg(b"JUSTID");
386 }
387 }
388}
389
390#[derive(Debug, Clone)]
392pub struct XPendingEntry {
393 pub id: Bytes,
395 pub consumer: Bytes,
397 pub idle_ms: i64,
399 pub delivery_count: i64,
401}
402
403impl FromValkeyValue for XPendingEntry {
404 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
406 match value {
407 ValkeyValue::Array(items) if items.len() == 4 => {
408 let mut items = items.into_iter();
409 let mut next = || items.next().expect("checked length");
410 Ok(Self {
411 id: Bytes::from_owned_valkey_value(next())?,
412 consumer: Bytes::from_owned_valkey_value(next())?,
413 idle_ms: i64::from_owned_valkey_value(next())?,
414 delivery_count: i64::from_owned_valkey_value(next())?,
415 })
416 }
417 other => Err(to_glide_error(other, "Unexpected XPENDING entry.")),
418 }
419 }
420}
421
422#[derive(Debug, Clone, Default, PartialEq)]
426pub struct StreamId {
427 pub id: String,
429 pub map: HashMap<String, ValkeyValue>,
431 pub milliseconds_elapsed_from_delivery: Option<usize>,
433 pub delivered_count: Option<usize>,
435}
436
437impl StreamId {
438 pub fn get<T: FromValkeyValue>(&self, key: &str) -> Option<T> {
441 self.map
442 .get(key)
443 .and_then(|value| T::from_owned_valkey_value(value.clone()).ok())
444 }
445
446 pub fn contains_key(&self, key: &str) -> bool {
448 self.map.contains_key(key)
449 }
450
451 pub fn len(&self) -> usize {
453 self.map.len()
454 }
455
456 pub fn is_empty(&self) -> bool {
458 self.map.is_empty()
459 }
460}
461
462#[derive(Debug, Clone, Default)]
466pub struct StreamKey {
467 pub key: String,
469 pub ids: Vec<StreamId>,
471}
472
473#[derive(Debug, Clone, Default)]
477pub struct StreamReadReply {
478 pub keys: Vec<StreamKey>,
480}
481
482impl FromValkeyValue for StreamReadReply {
483 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
484 let keys = map_pairs(value, "Unexpected XREAD reply.")?
485 .into_iter()
486 .map(|(key, entries)| {
487 Ok(StreamKey {
488 key: String::from_owned_valkey_value(key)?,
489 ids: parse_stream_ids(entries)?,
490 })
491 })
492 .collect::<ValkeyResult<_>>()?;
493 Ok(Self { keys })
494 }
495}
496
497#[derive(Debug, Clone, Default)]
501pub struct StreamRangeReply {
502 pub ids: Vec<StreamId>,
504}
505
506impl FromValkeyValue for StreamRangeReply {
507 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
508 Ok(Self {
509 ids: parse_stream_ids(value)?,
510 })
511 }
512}
513
514#[derive(Debug, Clone, Default)]
518pub struct StreamClaimReply {
519 pub ids: Vec<StreamId>,
521}
522
523impl FromValkeyValue for StreamClaimReply {
524 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
525 Ok(Self {
526 ids: parse_stream_ids(value)?,
527 })
528 }
529}
530
531#[derive(Debug, Clone, Default)]
535pub struct StreamAutoClaimReply {
536 pub next_stream_id: String,
538 pub claimed: Vec<StreamId>,
540 pub deleted_ids: Vec<String>,
542 pub invalid_entries: bool,
545}
546
547impl FromValkeyValue for StreamAutoClaimReply {
548 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
551 match value {
552 ValkeyValue::Array(mut items) if items.len() == 2 || items.len() == 3 => {
553 let deleted_ids = if items.len() == 3 {
554 Vec::<String>::from_owned_valkey_value(items.pop().expect("checked length"))?
555 } else {
556 Vec::new()
557 };
558 let claimed = items.pop().expect("checked length");
559 let next_stream_id =
560 String::from_owned_valkey_value(items.pop().expect("checked length"))?;
561 let (claimed, invalid_entries) = match claimed {
562 ValkeyValue::Array(ids) => {
564 let count = ids.len();
565 let claimed = ids
566 .into_iter()
567 .filter(|id| !matches!(id, ValkeyValue::Nil))
568 .map(|id| {
569 Ok(StreamId {
570 id: String::from_owned_valkey_value(id)?,
571 ..Default::default()
572 })
573 })
574 .collect::<ValkeyResult<Vec<_>>>()?;
575 let invalid_entries = claimed.len() < count;
576 (claimed, invalid_entries)
577 }
578 other => (parse_stream_ids(other)?, false),
579 };
580 Ok(Self {
581 next_stream_id,
582 claimed,
583 deleted_ids,
584 invalid_entries,
585 })
586 }
587 other => Err(to_glide_error(other, "Unexpected XAUTOCLAIM reply.")),
588 }
589 }
590}
591
592#[derive(Debug, Clone, Default)]
596#[non_exhaustive]
597pub enum StreamPendingReply {
598 #[default]
600 Empty,
601 Data(StreamPendingData),
603}
604
605impl StreamPendingReply {
606 pub fn count(&self) -> usize {
608 match self {
609 Self::Empty => 0,
610 Self::Data(data) => data.count,
611 }
612 }
613}
614
615impl FromValkeyValue for StreamPendingReply {
616 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
618 type Summary = (
619 usize,
620 Option<String>,
621 Option<String>,
622 Option<Vec<(String, usize)>>,
623 );
624 let (count, start_id, end_id, consumers) = Summary::from_owned_valkey_value(value)?;
625 if count == 0 {
626 return Ok(Self::Empty);
627 }
628 let (Some(start_id), Some(end_id)) = (start_id, end_id) else {
629 return Err(GlideError::Request(
630 "Non-empty XPENDING reply without start and end IDs.".into(),
631 ));
632 };
633 let consumers = consumers
634 .unwrap_or_default()
635 .into_iter()
636 .map(|(name, pending)| StreamInfoConsumer {
637 name,
638 pending,
639 idle: 0,
640 })
641 .collect();
642 Ok(Self::Data(StreamPendingData {
643 count,
644 start_id,
645 end_id,
646 consumers,
647 }))
648 }
649}
650
651#[derive(Debug, Clone, Default)]
655pub struct StreamPendingData {
656 pub count: usize,
658 pub start_id: String,
660 pub end_id: String,
662 pub consumers: Vec<StreamInfoConsumer>,
664}
665
666#[derive(Debug, Clone, Default)]
670pub struct StreamPendingCountReply {
671 pub ids: Vec<StreamPendingId>,
673}
674
675impl FromValkeyValue for StreamPendingCountReply {
676 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
677 let ids = Vec::<(String, String, usize, usize)>::from_owned_valkey_value(value)?
678 .into_iter()
679 .map(
680 |(id, consumer, last_delivered_ms, times_delivered)| StreamPendingId {
681 id,
682 consumer,
683 last_delivered_ms,
684 times_delivered,
685 },
686 )
687 .collect();
688 Ok(Self { ids })
689 }
690}
691
692#[derive(Debug, Clone, Default)]
696pub struct StreamPendingId {
697 pub id: String,
699 pub consumer: String,
701 pub last_delivered_ms: usize,
703 pub times_delivered: usize,
705}
706
707#[derive(Debug, Clone, Default)]
711pub struct StreamInfoStreamReply {
712 pub last_generated_id: String,
714 pub radix_tree_keys: usize,
716 pub groups: usize,
718 pub length: usize,
720 pub first_entry: StreamId,
722 pub last_entry: StreamId,
724}
725
726impl FromValkeyValue for StreamInfoStreamReply {
727 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
728 let mut reply = Self::default();
729 for (field, value) in map_pairs(value, "Unexpected XINFO STREAM reply.")? {
730 match String::from_owned_valkey_value(field)?.as_str() {
731 "last-generated-id" => {
732 reply.last_generated_id = FromValkeyValue::from_owned_valkey_value(value)?
733 }
734 "radix-tree-nodes" => {
735 reply.radix_tree_keys = FromValkeyValue::from_owned_valkey_value(value)?
736 }
737 "groups" => reply.groups = FromValkeyValue::from_owned_valkey_value(value)?,
738 "length" => reply.length = FromValkeyValue::from_owned_valkey_value(value)?,
739 "first-entry" => reply.first_entry = parse_optional_stream_id(value)?,
740 "last-entry" => reply.last_entry = parse_optional_stream_id(value)?,
741 _ => {}
742 }
743 }
744 Ok(reply)
745 }
746}
747
748#[derive(Debug, Clone, Default)]
752pub struct StreamInfoConsumersReply {
753 pub consumers: Vec<StreamInfoConsumer>,
755}
756
757impl FromValkeyValue for StreamInfoConsumersReply {
758 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
759 let consumers = list_of_map_pairs(value, "Unexpected XINFO CONSUMERS reply.")?
760 .into_iter()
761 .map(|fields| {
762 let mut consumer = StreamInfoConsumer::default();
763 for (field, value) in fields {
764 match String::from_owned_valkey_value(field)?.as_str() {
765 "name" => consumer.name = FromValkeyValue::from_owned_valkey_value(value)?,
766 "pending" => {
767 consumer.pending = FromValkeyValue::from_owned_valkey_value(value)?
768 }
769 "idle" => consumer.idle = FromValkeyValue::from_owned_valkey_value(value)?,
770 _ => {}
771 }
772 }
773 Ok(consumer)
774 })
775 .collect::<ValkeyResult<_>>()?;
776 Ok(Self { consumers })
777 }
778}
779
780#[derive(Debug, Clone, Default)]
784pub struct StreamInfoGroupsReply {
785 pub groups: Vec<StreamInfoGroup>,
787}
788
789impl FromValkeyValue for StreamInfoGroupsReply {
790 fn from_owned_valkey_value(value: ValkeyValue) -> ValkeyResult<Self> {
791 let groups = list_of_map_pairs(value, "Unexpected XINFO GROUPS reply.")?
792 .into_iter()
793 .map(|fields| {
794 let mut group = StreamInfoGroup::default();
795 for (field, value) in fields {
796 match String::from_owned_valkey_value(field)?.as_str() {
797 "name" => group.name = FromValkeyValue::from_owned_valkey_value(value)?,
798 "consumers" => {
799 group.consumers = FromValkeyValue::from_owned_valkey_value(value)?
800 }
801 "pending" => {
802 group.pending = FromValkeyValue::from_owned_valkey_value(value)?
803 }
804 "last-delivered-id" => {
805 group.last_delivered_id =
806 FromValkeyValue::from_owned_valkey_value(value)?
807 }
808 "entries-read" => {
809 group.entries_read = FromValkeyValue::from_owned_valkey_value(value)?
810 }
811 "lag" => group.lag = FromValkeyValue::from_owned_valkey_value(value)?,
812 _ => {}
813 }
814 }
815 Ok(group)
816 })
817 .collect::<ValkeyResult<_>>()?;
818 Ok(Self { groups })
819 }
820}
821
822#[derive(Debug, Clone, Default)]
826pub struct StreamInfoConsumer {
827 pub name: String,
829 pub pending: usize,
831 pub idle: usize,
833}
834
835#[derive(Debug, Clone, Default)]
839pub struct StreamInfoGroup {
840 pub name: String,
842 pub consumers: usize,
844 pub pending: usize,
846 pub last_delivered_id: String,
848 pub entries_read: Option<usize>,
850 pub lag: Option<usize>,
852}
853
854fn map_pairs(value: ValkeyValue, error: &str) -> ValkeyResult<Vec<(ValkeyValue, ValkeyValue)>> {
856 match value {
857 ValkeyValue::Nil => Ok(Vec::new()),
858 ValkeyValue::Map(pairs) => Ok(pairs),
859 other => Err(to_glide_error(other, error)),
860 }
861}
862
863fn list_of_map_pairs(
865 value: ValkeyValue,
866 error: &str,
867) -> ValkeyResult<Vec<Vec<(ValkeyValue, ValkeyValue)>>> {
868 match value {
869 ValkeyValue::Nil => Ok(Vec::new()),
870 ValkeyValue::Array(items) => items
871 .into_iter()
872 .map(|item| map_pairs(item, error))
873 .collect(),
874 other => Err(to_glide_error(other, error)),
875 }
876}
877
878fn parse_stream_ids(value: ValkeyValue) -> ValkeyResult<Vec<StreamId>> {
880 map_pairs(value, "Unexpected stream entries reply.")?
881 .into_iter()
882 .map(|(id, fields)| {
883 let fields = match fields {
884 ValkeyValue::Array(fields) => fields
885 .into_iter()
886 .map(|pair| match pair {
887 ValkeyValue::Array(pair) if pair.len() == 2 => {
888 let [field, value]: [ValkeyValue; 2] =
889 pair.try_into().expect("checked length");
890 Ok((field, value))
891 }
892 other => Err(to_glide_error(other, "Unexpected stream entry field.")),
893 })
894 .collect::<ValkeyResult<_>>()?,
895 other => return Err(to_glide_error(other, "Unexpected stream entry fields.")),
896 };
897 stream_id(id, fields)
898 })
899 .collect()
900}
901
902fn parse_optional_stream_id(value: ValkeyValue) -> ValkeyResult<StreamId> {
904 match value {
905 ValkeyValue::Nil => Ok(StreamId::default()),
906 ValkeyValue::Array(items) if items.len() == 2 => {
907 let [id, fields]: [ValkeyValue; 2] = items.try_into().expect("checked length");
908 let fields = match fields {
909 ValkeyValue::Array(fields) if fields.len() % 2 == 0 => fields,
910 other => return Err(to_glide_error(other, "Unexpected stream entry fields.")),
911 };
912 let mut fields = fields.into_iter();
913 let mut pairs = Vec::with_capacity(fields.len() / 2);
914 while let (Some(field), Some(value)) = (fields.next(), fields.next()) {
915 pairs.push((field, value));
916 }
917 stream_id(id, pairs)
918 }
919 other => Err(to_glide_error(other, "Unexpected stream entry reply.")),
920 }
921}
922
923fn stream_id(id: ValkeyValue, fields: Vec<(ValkeyValue, ValkeyValue)>) -> ValkeyResult<StreamId> {
925 let map = fields
926 .into_iter()
927 .map(|(field, value)| Ok((String::from_owned_valkey_value(field)?, value)))
928 .collect::<ValkeyResult<_>>()?;
929 Ok(StreamId {
930 id: String::from_owned_valkey_value(id)?,
931 map,
932 milliseconds_elapsed_from_delivery: None,
933 delivered_count: None,
934 })
935}
936
937#[async_trait]
939pub trait StreamCommands: CommandExecutor {
940 async fn xreadgroup<K: ToValkeyArgs + Send + Sync>(
942 &self,
943 group: &str,
944 consumer: &str,
945 keys_ids: &[(K, &str)],
946 options: Option<StreamReadGroupOptions>,
947 ) -> ValkeyResult<Vec<(Bytes, Vec<StreamEntry>)>> {
948 let mut cmd = Cmd::new();
949 cmd.arg("XREADGROUP").arg("GROUP").arg(group).arg(consumer);
950 cmd.arg(options);
951 cmd.arg("STREAMS");
952 for (k, _) in keys_ids {
953 cmd.arg(k);
954 }
955 for (_, id) in keys_ids {
956 cmd.arg(*id);
957 }
958 parse_stream_read(self.execute_command(cmd, None).await?)
959 }
960
961 async fn xpending_range<K: ToValkeyArgs + Send>(
964 &self,
965 key: K,
966 group: &str,
967 start: &str,
968 end: &str,
969 count: i64,
970 min_idle_time_ms: Option<i64>,
971 consumer: Option<&str>,
972 ) -> ValkeyResult<Vec<XPendingEntry>> {
973 let mut cmd = Cmd::new();
974 cmd.arg("XPENDING").arg(key).arg(group);
975 if let Some(idle) = min_idle_time_ms {
976 cmd.arg("IDLE").arg(idle);
977 }
978 cmd.arg(start).arg(end).arg(count);
979 if let Some(c) = consumer {
980 cmd.arg(c);
981 }
982 Vec::<XPendingEntry>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
983 }
984
985 async fn xinfo_stream_full<K: ToValkeyArgs + Send>(
989 &self,
990 key: K,
991 count: Option<i64>,
992 ) -> ValkeyResult<Vec<(Bytes, ValkeyValue)>> {
993 let mut cmd = Cmd::new();
994 cmd.arg("XINFO").arg("STREAM").arg(key).arg("FULL");
995 if let Some(c) = count {
996 cmd.arg("COUNT").arg(c);
997 }
998 parse_field_value_map(self.execute_command(cmd, None).await?)
999 }
1000
1001 async fn xsetid<K: ToValkeyArgs + Send>(
1003 &self,
1004 key: K,
1005 last_id: &str,
1006 entries_added: Option<i64>,
1007 max_deleted_id: Option<&str>,
1008 ) -> ValkeyResult<()> {
1009 let mut cmd = Cmd::new();
1010 cmd.arg("XSETID").arg(key).arg(last_id);
1011 if let Some(e) = entries_added {
1012 cmd.arg("ENTRIESADDED").arg(e);
1013 }
1014 if let Some(m) = max_deleted_id {
1015 cmd.arg("MAXDELETEDID").arg(m);
1016 }
1017 <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
1018 }
1019
1020 async fn xgroup_create_options<K: ToValkeyArgs + Send>(
1023 &self,
1024 key: K,
1025 group: &str,
1026 id: &str,
1027 options: &StreamGroupCreateOptions,
1028 ) -> ValkeyResult<()> {
1029 let mut cmd = Cmd::new();
1030 cmd.arg("XGROUP")
1031 .arg("CREATE")
1032 .arg(key)
1033 .arg(group)
1034 .arg(id)
1035 .arg(options);
1036 <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
1037 }
1038
1039 async fn xgroup_set_id<K: ToValkeyArgs + Send>(
1041 &self,
1042 key: K,
1043 group: &str,
1044 id: &str,
1045 entries_read: Option<i64>,
1046 ) -> ValkeyResult<()> {
1047 let mut cmd = Cmd::new();
1048 cmd.arg("XGROUP").arg("SETID").arg(key).arg(group).arg(id);
1049 if let Some(e) = entries_read {
1050 cmd.arg("ENTRIESREAD").arg(e);
1051 }
1052 <()>::from_owned_valkey_value(self.execute_command(cmd, None).await?)
1053 }
1054}
1055
1056impl<T: CommandExecutor + ?Sized> StreamCommands for T {}
1057
1058fn parse_entries(v: ValkeyValue) -> ValkeyResult<Vec<StreamEntry>> {
1062 let pairs: Vec<(ValkeyValue, ValkeyValue)> = match v {
1063 ValkeyValue::Nil => return Ok(Vec::new()),
1064 ValkeyValue::Map(pairs) => pairs,
1065 ValkeyValue::Array(items) => {
1066 let mut out = Vec::with_capacity(items.len());
1068 for entry in items {
1069 if let ValkeyValue::Array(mut parts) = entry
1070 && parts.len() == 2
1071 {
1072 let fields = parts.pop().unwrap();
1073 let id = parts.pop().unwrap();
1074 out.push((id, fields));
1075 }
1076 }
1077 out
1078 }
1079 other => {
1080 return Err(to_glide_error(other, "Unexpected stream reply."));
1081 }
1082 };
1083
1084 let mut out = Vec::with_capacity(pairs.len());
1085 for (id_val, fields_val) in pairs {
1086 let id = String::from_owned_valkey_value(id_val)?;
1087 let fv = parse_fields(fields_val)?;
1088 out.push((id, fv));
1089 }
1090 Ok(out)
1091}
1092
1093fn parse_fields(v: ValkeyValue) -> ValkeyResult<Vec<(Bytes, Bytes)>> {
1096 let items = match v {
1097 ValkeyValue::Array(items) => items,
1098 ValkeyValue::Nil => return Ok(Vec::new()),
1099 other => return Ok(vec![(Bytes::from_owned_valkey_value(other)?, Bytes::new())]),
1100 };
1101 if items
1103 .iter()
1104 .all(|it| matches!(it, ValkeyValue::Array(inner) if inner.len() == 2))
1105 {
1106 let mut out = Vec::with_capacity(items.len());
1107 for it in items {
1108 if let ValkeyValue::Array(mut pair) = it {
1109 let val = Bytes::from_owned_valkey_value(pair.pop().unwrap())?;
1110 let field = Bytes::from_owned_valkey_value(pair.pop().unwrap())?;
1111 out.push((field, val));
1112 }
1113 }
1114 return Ok(out);
1115 }
1116 let mut out = Vec::with_capacity(items.len() / 2);
1118 let mut iter = items.into_iter();
1119 while let (Some(f), Some(val)) = (iter.next(), iter.next()) {
1120 out.push((
1121 Bytes::from_owned_valkey_value(f)?,
1122 Bytes::from_owned_valkey_value(val)?,
1123 ));
1124 }
1125 Ok(out)
1126}
1127
1128fn parse_stream_read(v: ValkeyValue) -> ValkeyResult<Vec<(Bytes, Vec<StreamEntry>)>> {
1131 let pairs: Vec<(ValkeyValue, ValkeyValue)> = match v {
1132 ValkeyValue::Nil => return Ok(Vec::new()),
1133 ValkeyValue::Map(pairs) => pairs,
1134 ValkeyValue::Array(items) => {
1135 let mut out = Vec::with_capacity(items.len());
1136 for entry in items {
1137 if let ValkeyValue::Array(mut parts) = entry
1138 && parts.len() == 2
1139 {
1140 let entries = parts.pop().unwrap();
1141 let key = parts.pop().unwrap();
1142 out.push((key, entries));
1143 }
1144 }
1145 out
1146 }
1147 other => {
1148 return Err(to_glide_error(other, "Unexpected XREAD reply."));
1149 }
1150 };
1151 let mut out = Vec::with_capacity(pairs.len());
1152 for (key_val, entries_val) in pairs {
1153 let key = Bytes::from_owned_valkey_value(key_val)?;
1154 let entries = parse_entries(entries_val)?;
1155 out.push((key, entries));
1156 }
1157 Ok(out)
1158}
1159
1160fn parse_field_value_map(v: ValkeyValue) -> ValkeyResult<Vec<(Bytes, ValkeyValue)>> {
1163 match v {
1164 ValkeyValue::Nil => Ok(Vec::new()),
1165 ValkeyValue::Map(pairs) => pairs
1166 .into_iter()
1167 .map(|(k, val)| Ok((Bytes::from_owned_valkey_value(k)?, val)))
1168 .collect(),
1169 ValkeyValue::Array(items) => {
1170 let mut out = Vec::with_capacity(items.len() / 2);
1171 let mut iter = items.into_iter();
1172 while let (Some(k), Some(val)) = (iter.next(), iter.next()) {
1173 out.push((Bytes::from_owned_valkey_value(k)?, val));
1174 }
1175 Ok(out)
1176 }
1177 other => Err(to_glide_error(other, "Unexpected XINFO reply.")),
1178 }
1179}
1180
1181#[cfg(test)]
1182mod tests {
1183 use super::*;
1184 use crate::test_utils::assert_args;
1185 use crate::test_utils::assert_args_empty;
1186
1187 #[test]
1188 fn trim_strategy_args() {
1189 assert_args(
1190 StreamTrimStrategy::maxlen(StreamTrimmingMode::Exact, 100),
1191 &["MAXLEN", "=", "100"],
1192 );
1193 assert_args(
1194 StreamTrimStrategy::maxlen(StreamTrimmingMode::Approx, 100).limit(10),
1195 &["MAXLEN", "~", "100", "LIMIT", "10"],
1196 );
1197 assert_args(
1198 StreamTrimStrategy::minid(StreamTrimmingMode::Approx, "1526985054069-0"),
1199 &["MINID", "~", "1526985054069-0"],
1200 );
1201 }
1202
1203 #[test]
1204 fn add_options_args() {
1205 assert_args_empty(StreamAddOptions::default());
1206 assert_args(
1207 StreamAddOptions::default()
1208 .nomkstream()
1209 .trim(StreamTrimStrategy::maxlen(StreamTrimmingMode::Exact, 5)),
1210 &["NOMKSTREAM", "MAXLEN", "=", "5"],
1211 );
1212 }
1213
1214 #[test]
1215 fn read_group_options_args() {
1216 assert_args_empty(StreamReadGroupOptions::default());
1217 assert_args(
1218 StreamReadGroupOptions {
1219 block_ms: Some(500),
1220 count: Some(10),
1221 no_ack: true,
1222 },
1223 &["BLOCK", "500", "COUNT", "10", "NOACK"],
1224 );
1225 }
1226
1227 #[test]
1228 fn group_create_options_args() {
1229 assert_args_empty(StreamGroupCreateOptions::default());
1230 assert_args(
1231 StreamGroupCreateOptions {
1232 make_stream: true,
1233 entries_read: Some(7),
1234 },
1235 &["MKSTREAM", "ENTRIESREAD", "7"],
1236 );
1237 }
1238
1239 #[test]
1240 fn claim_options_args() {
1241 assert_args_empty(StreamClaimOptions::default());
1242 assert_args(
1243 StreamClaimOptions::default()
1244 .idle(100)
1245 .retry(3)
1246 .with_force()
1247 .with_justid()
1248 .with_lastid("5-0"),
1249 &[
1250 "IDLE",
1251 "100",
1252 "RETRYCOUNT",
1253 "3",
1254 "FORCE",
1255 "JUSTID",
1256 "LASTID",
1257 "5-0",
1258 ],
1259 );
1260 assert_args(
1261 StreamClaimOptions::default().time(1_700_000_000_000),
1262 &["TIME", "1700000000000"],
1263 );
1264 }
1265
1266 #[test]
1267 fn maxlen_args() {
1268 assert_args(StreamMaxlen::Equals(5), &["MAXLEN", "=", "5"]);
1269 assert_args(StreamMaxlen::Approx(5), &["MAXLEN", "~", "5"]);
1270 }
1271
1272 #[test]
1273 fn trim_options_args() {
1274 assert_args(
1275 StreamTrimOptions::maxlen(StreamTrimmingMode::Exact, 100),
1276 &["MAXLEN", "=", "100"],
1277 );
1278 assert_args(
1279 StreamTrimOptions::minid(StreamTrimmingMode::Approx, "1-0").limit(10),
1280 &["MINID", "~", "1-0", "LIMIT", "10"],
1281 );
1282 }
1283
1284 #[test]
1285 fn autoclaim_options_args() {
1286 assert_args_empty(StreamAutoClaimOptions::default());
1287 assert_args(
1288 StreamAutoClaimOptions::default().count(10).with_justid(),
1289 &["COUNT", "10", "JUSTID"],
1290 );
1291 }
1292
1293 #[test]
1294 fn read_options_args() {
1295 assert_args_empty(StreamReadOptions::default());
1296 assert_args(
1297 StreamReadOptions::default().count(10).block(1000),
1298 &["BLOCK", "1000", "COUNT", "10"],
1299 );
1300 }
1301
1302 fn bulk(s: &str) -> ValkeyValue {
1303 ValkeyValue::BulkString(Bytes::from(s.to_string()))
1304 }
1305
1306 fn array(items: Vec<ValkeyValue>) -> ValkeyValue {
1307 ValkeyValue::Array(items)
1308 }
1309
1310 fn entry(id: &str, field: &str, value: &str) -> StreamId {
1311 StreamId {
1312 id: id.to_string(),
1313 map: HashMap::from([(field.to_string(), bulk(value))]),
1314 ..Default::default()
1315 }
1316 }
1317
1318 #[test]
1319 fn range_reply_decoding() {
1320 let range = ValkeyValue::Map(vec![
1322 (bulk("1-0"), array(vec![array(vec![bulk("f"), bulk("a")])])),
1323 (bulk("2-0"), array(vec![array(vec![bulk("f"), bulk("b")])])),
1324 ]);
1325 let reply = StreamRangeReply::from_owned_valkey_value(range).unwrap();
1326 assert_eq!(
1327 reply.ids,
1328 vec![entry("1-0", "f", "a"), entry("2-0", "f", "b")]
1329 );
1330 assert!(
1331 StreamRangeReply::from_owned_valkey_value(ValkeyValue::Nil)
1332 .unwrap()
1333 .ids
1334 .is_empty()
1335 );
1336
1337 let raw = array(vec![array(vec![
1339 bulk("1-0"),
1340 array(vec![bulk("f"), bulk("a")]),
1341 ])]);
1342 assert!(StreamRangeReply::from_owned_valkey_value(raw).is_err());
1343
1344 let unpaired = ValkeyValue::Map(vec![(bulk("1-0"), array(vec![bulk("f")]))]);
1346 assert!(StreamRangeReply::from_owned_valkey_value(unpaired).is_err());
1347
1348 let claim = StreamClaimReply::from_owned_valkey_value(ValkeyValue::Map(vec![(
1349 bulk("1-0"),
1350 array(vec![array(vec![bulk("f"), bulk("a")])]),
1351 )]))
1352 .unwrap();
1353 assert_eq!(claim.ids, vec![entry("1-0", "f", "a")]);
1354
1355 let id = entry("1-0", "f", "7");
1356 assert_eq!(id.get::<i64>("f"), Some(7));
1357 assert_eq!(id.get::<i64>("missing"), None);
1358 assert!(id.contains_key("f"));
1359 assert_eq!((id.len(), id.is_empty()), (1, false));
1360 }
1361
1362 #[test]
1363 fn read_reply_decoding() {
1364 let entries = ValkeyValue::Map(vec![(
1366 bulk("1-0"),
1367 array(vec![array(vec![bulk("f"), bulk("a")])]),
1368 )]);
1369 let reply =
1370 StreamReadReply::from_owned_valkey_value(ValkeyValue::Map(vec![(bulk("s"), entries)]))
1371 .unwrap();
1372 assert_eq!(reply.keys.len(), 1);
1373 assert_eq!(reply.keys[0].key, "s");
1374 assert_eq!(reply.keys[0].ids, vec![entry("1-0", "f", "a")]);
1375
1376 assert!(
1377 Option::<StreamReadReply>::from_owned_valkey_value(ValkeyValue::Nil)
1378 .unwrap()
1379 .is_none()
1380 );
1381 }
1382
1383 #[test]
1384 fn autoclaim_reply_decoding() {
1385 let full = array(vec![
1386 bulk("2-0"),
1387 ValkeyValue::Map(vec![(
1388 bulk("1-0"),
1389 array(vec![array(vec![bulk("f"), bulk("a")])]),
1390 )]),
1391 array(vec![bulk("0-1")]),
1392 ]);
1393 let reply = StreamAutoClaimReply::from_owned_valkey_value(full).unwrap();
1394 assert_eq!(reply.next_stream_id, "2-0");
1395 assert_eq!(reply.claimed, vec![entry("1-0", "f", "a")]);
1396 assert_eq!(reply.deleted_ids, vec!["0-1"]);
1397 assert!(!reply.invalid_entries);
1398
1399 let justid = array(vec![
1401 bulk("0-0"),
1402 array(vec![bulk("1-0"), ValkeyValue::Nil]),
1403 ]);
1404 let reply = StreamAutoClaimReply::from_owned_valkey_value(justid).unwrap();
1405 assert_eq!(reply.next_stream_id, "0-0");
1406 assert_eq!(reply.claimed.len(), 1);
1407 assert_eq!(reply.claimed[0].id, "1-0");
1408 assert!(reply.claimed[0].is_empty());
1409 assert!(reply.deleted_ids.is_empty());
1410 assert!(reply.invalid_entries);
1411
1412 assert!(StreamAutoClaimReply::from_owned_valkey_value(array(vec![bulk("0-0")])).is_err());
1413 }
1414
1415 #[test]
1416 fn pending_count_reply_decoding() {
1417 let reply = StreamPendingCountReply::from_owned_valkey_value(array(vec![array(vec![
1418 bulk("1-0"),
1419 bulk("c1"),
1420 ValkeyValue::Int(100),
1421 ValkeyValue::Int(2),
1422 ])]))
1423 .unwrap();
1424 assert_eq!(reply.ids.len(), 1);
1425 let pending = &reply.ids[0];
1426 assert_eq!(
1427 (pending.id.as_str(), pending.consumer.as_str()),
1428 ("1-0", "c1")
1429 );
1430 assert_eq!(
1431 (pending.last_delivered_ms, pending.times_delivered),
1432 (100, 2)
1433 );
1434
1435 assert!(
1436 StreamPendingCountReply::from_owned_valkey_value(array(vec![]))
1437 .unwrap()
1438 .ids
1439 .is_empty()
1440 );
1441 }
1442
1443 #[test]
1444 fn pending_reply_decoding() {
1445 let empty = array(vec![
1446 ValkeyValue::Int(0),
1447 ValkeyValue::Nil,
1448 ValkeyValue::Nil,
1449 ValkeyValue::Nil,
1450 ]);
1451 let reply = StreamPendingReply::from_owned_valkey_value(empty).unwrap();
1452 assert!(matches!(reply, StreamPendingReply::Empty));
1453 assert_eq!(reply.count(), 0);
1454
1455 let data = array(vec![
1456 ValkeyValue::Int(3),
1457 bulk("1-0"),
1458 bulk("3-0"),
1459 array(vec![array(vec![bulk("c1"), bulk("3")])]),
1460 ]);
1461 let reply = StreamPendingReply::from_owned_valkey_value(data).unwrap();
1462 assert_eq!(reply.count(), 3);
1463 let StreamPendingReply::Data(data) = reply else {
1464 panic!("expected pending data");
1465 };
1466 assert_eq!(
1467 (data.start_id.as_str(), data.end_id.as_str()),
1468 ("1-0", "3-0")
1469 );
1470 assert_eq!(data.consumers.len(), 1);
1471 assert_eq!(
1472 (data.consumers[0].name.as_str(), data.consumers[0].pending),
1473 ("c1", 3)
1474 );
1475
1476 let missing_ids = array(vec![
1477 ValkeyValue::Int(1),
1478 ValkeyValue::Nil,
1479 ValkeyValue::Nil,
1480 ValkeyValue::Nil,
1481 ]);
1482 assert!(StreamPendingReply::from_owned_valkey_value(missing_ids).is_err());
1483 }
1484
1485 #[test]
1486 fn info_stream_reply_decoding() {
1487 let info = ValkeyValue::Map(vec![
1489 (bulk("length"), ValkeyValue::Int(2)),
1490 (bulk("radix-tree-nodes"), ValkeyValue::Int(1)),
1491 (bulk("groups"), ValkeyValue::Int(1)),
1492 (bulk("last-generated-id"), bulk("2-0")),
1493 (
1494 bulk("first-entry"),
1495 array(vec![bulk("1-0"), array(vec![bulk("f"), bulk("a")])]),
1496 ),
1497 (bulk("last-entry"), ValkeyValue::Nil),
1498 (bulk("entries-added"), ValkeyValue::Int(2)),
1499 ]);
1500 let reply = StreamInfoStreamReply::from_owned_valkey_value(info).unwrap();
1501 assert_eq!(
1502 (reply.length, reply.radix_tree_keys, reply.groups),
1503 (2, 1, 1)
1504 );
1505 assert_eq!(reply.last_generated_id, "2-0");
1506 assert_eq!(reply.first_entry, entry("1-0", "f", "a"));
1507 assert_eq!(reply.last_entry, StreamId::default());
1508
1509 let odd = ValkeyValue::Map(vec![(
1511 bulk("first-entry"),
1512 array(vec![
1513 bulk("1-0"),
1514 array(vec![bulk("f"), bulk("a"), bulk("g")]),
1515 ]),
1516 )]);
1517 assert!(StreamInfoStreamReply::from_owned_valkey_value(odd).is_err());
1518 }
1519
1520 #[test]
1521 fn info_groups_and_consumers_reply_decoding() {
1522 let groups = array(vec![ValkeyValue::Map(vec![
1524 (bulk("name"), bulk("g")),
1525 (bulk("consumers"), ValkeyValue::Int(2)),
1526 (bulk("pending"), ValkeyValue::Int(3)),
1527 (bulk("last-delivered-id"), bulk("3-0")),
1528 (bulk("entries-read"), ValkeyValue::Int(3)),
1529 (bulk("lag"), ValkeyValue::Nil),
1530 ])]);
1531 let reply = StreamInfoGroupsReply::from_owned_valkey_value(groups).unwrap();
1532 let group = &reply.groups[0];
1533 assert_eq!(
1534 (group.name.as_str(), group.consumers, group.pending),
1535 ("g", 2, 3)
1536 );
1537 assert_eq!(group.last_delivered_id, "3-0");
1538 assert_eq!((group.entries_read, group.lag), (Some(3), None));
1539
1540 let consumers = array(vec![ValkeyValue::Map(vec![
1541 (bulk("name"), bulk("c1")),
1542 (bulk("pending"), ValkeyValue::Int(1)),
1543 (bulk("idle"), ValkeyValue::Int(42)),
1544 (bulk("inactive"), ValkeyValue::Int(42)),
1545 ])]);
1546 let reply = StreamInfoConsumersReply::from_owned_valkey_value(consumers).unwrap();
1547 let consumer = &reply.consumers[0];
1548 assert_eq!(
1549 (consumer.name.as_str(), consumer.pending, consumer.idle),
1550 ("c1", 1, 42)
1551 );
1552 }
1553}