1use std::borrow::Borrow;
2use std::cmp::Ordering;
3use std::collections::{BTreeSet, HashMap};
4use std::error;
5use std::fmt;
6use std::io;
7use std::str::FromStr;
8use std::time::Duration;
9use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader, BufWriter};
10
11#[derive(Debug, Default, PartialEq, Clone)]
12pub struct Stats {
13 pub pid: i64,
15 pub uptime: u64,
16 pub server_time: i64,
17 pub threads: u64,
18 pub version: String,
19
20 pub rusage_user: f64,
22 pub rusage_system: f64,
23
24 pub max_connections: u64,
26 pub curr_connections: u64,
27 pub total_connections: u64,
28 pub rejected_connections: u64,
29
30 pub cmd_get: u64,
32 pub cmd_set: u64,
33 pub cmd_flush: u64,
34 pub cmd_touch: u64,
35 pub cmd_meta: u64,
36
37 pub get_hits: u64,
39 pub get_misses: u64,
40 pub get_expired: u64,
41 pub get_flushed: u64,
42
43 pub store_too_large: u64,
45 pub store_no_memory: u64,
46
47 pub delete_hits: u64,
49 pub delete_misses: u64,
50
51 pub incr_hits: u64,
53 pub incr_misses: u64,
54 pub decr_hits: u64,
55 pub decr_misses: u64,
56
57 pub touch_hits: u64,
59 pub touch_misses: u64,
60
61 pub bytes_read: u64,
63 pub bytes_written: u64,
64 pub bytes: u64,
65 pub max_bytes: u64,
66
67 pub curr_items: u64,
69 pub total_items: u64,
70 pub evictions: u64,
71}
72
73impl TryFrom<&HashMap<String, String>> for Stats {
74 type Error = MtopError;
75
76 fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
77 Ok(Stats {
78 pid: parse_field("pid", value)?,
79 uptime: parse_field("uptime", value)?,
80 server_time: parse_field("time", value)?,
81 version: parse_field("version", value)?,
82 threads: parse_field("threads", value)?,
83
84 rusage_user: parse_field("rusage_user", value)?,
85 rusage_system: parse_field("rusage_system", value)?,
86
87 max_connections: parse_field("max_connections", value)?,
88 curr_connections: parse_field("curr_connections", value)?,
89 total_connections: parse_field("total_connections", value)?,
90 rejected_connections: parse_field("rejected_connections", value)?,
91
92 cmd_get: parse_field("cmd_get", value)?,
93 cmd_set: parse_field("cmd_set", value)?,
94 cmd_flush: parse_field("cmd_flush", value)?,
95 cmd_touch: parse_field("cmd_touch", value)?,
96 cmd_meta: parse_field("cmd_meta", value)?,
97
98 get_hits: parse_field("get_hits", value)?,
99 get_misses: parse_field("get_misses", value)?,
100 get_expired: parse_field("get_expired", value)?,
101 get_flushed: parse_field("get_flushed", value)?,
102
103 store_too_large: parse_field("store_too_large", value)?,
104 store_no_memory: parse_field("store_no_memory", value)?,
105
106 delete_hits: parse_field("delete_hits", value)?,
107 delete_misses: parse_field("delete_misses", value)?,
108
109 incr_hits: parse_field("incr_hits", value)?,
110 incr_misses: parse_field("incr_misses", value)?,
111
112 decr_hits: parse_field("decr_hits", value)?,
113 decr_misses: parse_field("decr_misses", value)?,
114
115 touch_hits: parse_field("touch_hits", value)?,
116 touch_misses: parse_field("touch_misses", value)?,
117
118 bytes_read: parse_field("bytes_read", value)?,
119 bytes_written: parse_field("bytes_written", value)?,
120 bytes: parse_field("bytes", value)?,
121 max_bytes: parse_field("limit_maxbytes", value)?,
122
123 curr_items: parse_field("curr_items", value)?,
124 total_items: parse_field("total_items", value)?,
125 evictions: parse_field("evictions", value)?,
126 })
127 }
128}
129
130#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
131pub struct Slab {
132 pub id: u64,
133 pub chunk_size: u64,
134 pub chunks_per_page: u64,
135 pub total_pages: u64,
136 pub total_chunks: u64,
137 pub used_chunks: u64,
138 pub free_chunks: u64,
139 pub get_hits: u64,
140 pub cmd_set: u64,
141 pub delete_hits: u64,
142 pub incr_hits: u64,
143 pub decr_hits: u64,
144 pub cas_hits: u64,
145 pub cas_badval: u64,
146 pub touch_hits: u64,
147}
148
149impl PartialOrd for Slab {
150 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
151 Some(self.cmp(other))
152 }
153}
154
155impl Ord for Slab {
156 fn cmp(&self, other: &Self) -> Ordering {
157 self.id.cmp(&other.id)
158 }
159}
160
161#[derive(Debug, Default, PartialEq, Eq, Clone)]
162#[repr(transparent)]
163pub struct Slabs(Vec<Slab>);
164
165impl Slabs {
166 pub fn iter(&self) -> impl ExactSizeIterator<Item = &Slab> {
167 self.0.iter()
168 }
169
170 pub fn len(&self) -> usize {
171 self.0.len()
172 }
173
174 pub fn is_empty(&self) -> bool {
175 self.0.is_empty()
176 }
177}
178
179impl IntoIterator for Slabs {
180 type Item = Slab;
181 type IntoIter = std::vec::IntoIter<Slab>;
182
183 fn into_iter(self) -> Self::IntoIter {
184 self.0.into_iter()
185 }
186}
187
188impl TryFrom<&HashMap<String, String>> for Slabs {
189 type Error = MtopError;
190
191 fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
192 let mut ids = BTreeSet::new();
197 for k in value.keys() {
198 let key_id: Option<u64> = k.split_once(':').map(|(raw, _rest)| raw).and_then(|raw| raw.parse().ok());
199
200 if let Some(id) = key_id {
201 ids.insert(id);
202 }
203 }
204
205 let mut slabs = Vec::with_capacity(ids.len());
206
207 for id in ids {
208 slabs.push(Slab {
209 id,
210 chunk_size: parse_field(&format!("{}:chunk_size", id), value)?,
211 chunks_per_page: parse_field(&format!("{}:chunks_per_page", id), value)?,
212 total_pages: parse_field(&format!("{}:total_pages", id), value)?,
213 total_chunks: parse_field(&format!("{}:total_chunks", id), value)?,
214 used_chunks: parse_field(&format!("{}:used_chunks", id), value)?,
215 free_chunks: parse_field(&format!("{}:free_chunks", id), value)?,
216 get_hits: parse_field(&format!("{}:get_hits", id), value)?,
217 cmd_set: parse_field(&format!("{}:cmd_set", id), value)?,
218 delete_hits: parse_field(&format!("{}:delete_hits", id), value)?,
219 incr_hits: parse_field(&format!("{}:incr_hits", id), value)?,
220 decr_hits: parse_field(&format!("{}:decr_hits", id), value)?,
221 cas_hits: parse_field(&format!("{}:cas_hits", id), value)?,
222 cas_badval: parse_field(&format!("{}:cas_badval", id), value)?,
223 touch_hits: parse_field(&format!("{}:touch_hits", id), value)?,
224 });
225 }
226
227 Ok(Self(slabs))
228 }
229}
230
231#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
232pub struct SlabItem {
233 pub id: u64,
234 pub number: u64,
235 pub number_hot: u64,
236 pub number_warm: u64,
237 pub number_cold: u64,
238 pub age_hot: u64,
239 pub age_warm: u64,
240 pub age: u64,
241 pub mem_requested: u64,
242 pub evicted: u64,
243 pub evicted_nonzero: u64,
244 pub evicted_time: u64,
245 pub out_of_memory: u64,
246 pub tail_repairs: u64,
247 pub reclaimed: u64,
248 pub expired_unfetched: u64,
249 pub evicted_unfetched: u64,
250 pub evicted_active: u64,
251 pub crawler_reclaimed: u64,
252 pub crawler_items_checked: u64,
253 pub lrutail_reflocked: u64,
254 pub moves_to_cold: u64,
255 pub moves_to_warm: u64,
256 pub moves_within_lru: u64,
257 pub direct_reclaims: u64,
258 pub hits_to_hot: u64,
259 pub hits_to_warm: u64,
260 pub hits_to_cold: u64,
261 pub hits_to_temp: u64,
262}
263
264impl PartialOrd for SlabItem {
265 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
266 Some(self.cmp(other))
267 }
268}
269
270impl Ord for SlabItem {
271 fn cmp(&self, other: &Self) -> Ordering {
272 self.id.cmp(&other.id)
273 }
274}
275
276#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
277#[repr(transparent)]
278pub struct SlabItems(Vec<SlabItem>);
279
280impl SlabItems {
281 pub fn iter(&self) -> impl ExactSizeIterator<Item = &SlabItem> {
282 self.0.iter()
283 }
284
285 pub fn len(&self) -> usize {
286 self.0.len()
287 }
288
289 pub fn is_empty(&self) -> bool {
290 self.0.is_empty()
291 }
292}
293
294impl IntoIterator for SlabItems {
295 type Item = SlabItem;
296 type IntoIter = std::vec::IntoIter<SlabItem>;
297
298 fn into_iter(self) -> Self::IntoIter {
299 self.0.into_iter()
300 }
301}
302
303impl TryFrom<&HashMap<String, String>> for SlabItems {
304 type Error = MtopError;
305
306 fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
307 let mut ids = BTreeSet::new();
312 for k in value.keys() {
313 let key_id: Option<u64> = k
314 .trim_start_matches("items:")
315 .split_once(':')
316 .map(|(raw, _rest)| raw)
317 .and_then(|raw| raw.parse().ok());
318
319 if let Some(id) = key_id {
320 ids.insert(id);
321 }
322 }
323
324 let mut items = Vec::with_capacity(ids.len());
325
326 for id in ids {
327 items.push(SlabItem {
328 id,
329 number: parse_field(&format!("items:{}:number", id), value)?,
330 number_hot: parse_field(&format!("items:{}:number_hot", id), value)?,
331 number_warm: parse_field(&format!("items:{}:number_warm", id), value)?,
332 number_cold: parse_field(&format!("items:{}:number_cold", id), value)?,
333 age_hot: parse_field(&format!("items:{}:age_hot", id), value)?,
334 age_warm: parse_field(&format!("items:{}:age_warm", id), value)?,
335 age: parse_field(&format!("items:{}:age", id), value)?,
336 mem_requested: parse_field(&format!("items:{}:mem_requested", id), value)?,
337 evicted: parse_field(&format!("items:{}:evicted", id), value)?,
338 evicted_nonzero: parse_field(&format!("items:{}:evicted_nonzero", id), value)?,
339 evicted_time: parse_field(&format!("items:{}:evicted_time", id), value)?,
340 out_of_memory: parse_field(&format!("items:{}:outofmemory", id), value)?,
341 tail_repairs: parse_field(&format!("items:{}:tailrepairs", id), value)?,
342 reclaimed: parse_field(&format!("items:{}:reclaimed", id), value)?,
343 expired_unfetched: parse_field(&format!("items:{}:expired_unfetched", id), value)?,
344 evicted_unfetched: parse_field(&format!("items:{}:evicted_unfetched", id), value)?,
345 evicted_active: parse_field(&format!("items:{}:evicted_active", id), value)?,
346 crawler_reclaimed: parse_field(&format!("items:{}:crawler_reclaimed", id), value)?,
347 crawler_items_checked: parse_field(&format!("items:{}:crawler_items_checked", id), value)?,
348 lrutail_reflocked: parse_field(&format!("items:{}:lrutail_reflocked", id), value)?,
349 moves_to_cold: parse_field(&format!("items:{}:moves_to_cold", id), value)?,
350 moves_to_warm: parse_field(&format!("items:{}:moves_to_warm", id), value)?,
351 moves_within_lru: parse_field(&format!("items:{}:moves_within_lru", id), value)?,
352 direct_reclaims: parse_field(&format!("items:{}:direct_reclaims", id), value)?,
353 hits_to_hot: parse_field(&format!("items:{}:hits_to_hot", id), value)?,
354 hits_to_warm: parse_field(&format!("items:{}:hits_to_warm", id), value)?,
355 hits_to_cold: parse_field(&format!("items:{}:hits_to_cold", id), value)?,
356 hits_to_temp: parse_field(&format!("items:{}:hits_to_temp", id), value)?,
357 });
358 }
359
360 Ok(Self(items))
361 }
362}
363
364#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
365pub struct Meta {
366 pub key: String,
367 pub expires: i64,
368 pub size: u64,
369}
370
371impl Meta {
372 const KEYS: &'static [&'static str] = &["key", "exp", "size"];
376}
377
378impl TryFrom<&HashMap<String, String>> for Meta {
379 type Error = MtopError;
380
381 fn try_from(value: &HashMap<String, String>) -> Result<Self, Self::Error> {
382 Ok(Meta {
383 key: parse_field("key", value)?,
384 expires: parse_field("exp", value)?,
385 size: parse_field("size", value)?,
386 })
387 }
388}
389
390impl PartialOrd for Meta {
391 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
392 Some(self.cmp(other))
393 }
394}
395
396impl Ord for Meta {
397 fn cmp(&self, other: &Self) -> Ordering {
398 self.key.cmp(&other.key)
399 }
400}
401
402#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]
403pub struct Value {
404 pub key: String,
405 pub cas: u64,
406 pub flags: u64,
407 pub data: Vec<u8>,
408}
409
410impl PartialOrd for Value {
411 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
412 Some(self.cmp(other))
413 }
414}
415
416impl Ord for Value {
417 fn cmp(&self, other: &Self) -> Ordering {
418 self.key.cmp(&other.key)
419 }
420}
421
422fn parse_field<T>(key: &str, map: &HashMap<String, String>) -> Result<T, MtopError>
423where
424 T: FromStr,
425 <T as FromStr>::Err: fmt::Display + Send + Sync + error::Error + 'static,
426{
427 map.get(key)
428 .ok_or_else(|| MtopError::runtime(format!("field {} missing", key)))
429 .and_then(|v| {
430 v.parse()
431 .map_err(|e| MtopError::runtime_cause(format!("field {} value '{}'", key, v), e))
432 })
433}
434
435fn parse_value<T>(val: &str, line: &str) -> Result<T, MtopError>
436where
437 T: FromStr + fmt::Display,
438 <T as FromStr>::Err: fmt::Display + Send + Sync + error::Error + 'static,
439{
440 val.parse()
441 .map_err(|e| MtopError::runtime_cause(format!("parsing {} from '{}'", val, line), e))
442}
443
444#[derive(Debug, PartialOrd, PartialEq, Copy, Clone)]
445pub enum ErrorKind {
446 Runtime,
447 IO,
448 Protocol,
449 Configuration,
450}
451
452impl fmt::Display for ErrorKind {
453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454 match self {
455 Self::Runtime => write!(f, "runtime error"),
456 Self::IO => write!(f, "io error"),
457 Self::Protocol => write!(f, "protocol error"),
458 Self::Configuration => write!(f, "configuration error"),
459 }
460 }
461}
462
463#[derive(Debug)]
464enum ErrorRepr {
465 Message(String),
466 Cause(Box<dyn error::Error + Send + Sync + 'static>),
467 MessageCause(String, Box<dyn error::Error + Send + Sync + 'static>),
468}
469
470#[derive(Debug)]
471pub struct MtopError {
472 kind: ErrorKind,
473 repr: ErrorRepr,
474}
475
476impl MtopError {
477 pub fn runtime<S>(msg: S) -> MtopError
478 where
479 S: Into<String>,
480 {
481 MtopError {
482 kind: ErrorKind::Runtime,
483 repr: ErrorRepr::Message(msg.into()),
484 }
485 }
486
487 pub fn runtime_cause<S, E>(msg: S, e: E) -> MtopError
488 where
489 S: Into<String>,
490 E: error::Error + Send + Sync + 'static,
491 {
492 MtopError {
493 kind: ErrorKind::Runtime,
494 repr: ErrorRepr::MessageCause(msg.into(), Box::new(e)),
495 }
496 }
497
498 pub fn configuration<S>(msg: S) -> MtopError
499 where
500 S: Into<String>,
501 {
502 MtopError {
503 kind: ErrorKind::Configuration,
504 repr: ErrorRepr::Message(msg.into()),
505 }
506 }
507
508 pub fn configuration_cause<S, E>(msg: S, e: E) -> MtopError
509 where
510 S: Into<String>,
511 E: error::Error + Send + Sync + 'static,
512 {
513 MtopError {
514 kind: ErrorKind::Configuration,
515 repr: ErrorRepr::MessageCause(msg.into(), Box::new(e)),
516 }
517 }
518
519 pub fn timeout<D>(t: Duration, operation: D) -> MtopError
520 where
521 D: fmt::Display,
522 {
523 MtopError {
524 kind: ErrorKind::IO,
525 repr: ErrorRepr::Message(format!("operation {} timed out after {:?}", operation, t)),
526 }
527 }
528
529 pub fn kind(&self) -> ErrorKind {
530 self.kind
531 }
532}
533
534impl fmt::Display for MtopError {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 match &self.repr {
537 ErrorRepr::Message(msg) => write!(f, "{}: {}", self.kind, msg),
538 ErrorRepr::Cause(e) => write!(f, "{}: {}", self.kind, e),
539 ErrorRepr::MessageCause(msg, e) => write!(f, "{}: {}: {}", self.kind, msg, e),
540 }
541 }
542}
543
544impl error::Error for MtopError {
545 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
546 match &self.repr {
547 ErrorRepr::Message(_) => None,
548 ErrorRepr::Cause(e) | ErrorRepr::MessageCause(_, e) => Some(e.as_ref()),
549 }
550 }
551}
552
553impl From<(String, io::Error)> for MtopError {
554 fn from((s, e): (String, io::Error)) -> Self {
555 MtopError {
556 kind: ErrorKind::IO,
557 repr: ErrorRepr::MessageCause(s, Box::new(e)),
558 }
559 }
560}
561
562impl From<io::Error> for MtopError {
563 fn from(e: io::Error) -> Self {
564 MtopError {
565 kind: ErrorKind::IO,
566 repr: ErrorRepr::Cause(Box::new(e)),
567 }
568 }
569}
570
571impl From<ProtocolError> for MtopError {
572 fn from(e: ProtocolError) -> Self {
573 MtopError {
574 kind: ErrorKind::Protocol,
575 repr: ErrorRepr::Cause(Box::new(e)),
576 }
577 }
578}
579
580#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
581pub enum ProtocolErrorKind {
582 BadClass,
583 Busy,
584 Client,
585 NotFound,
586 NotStored,
587 Server,
588 Syntax,
589}
590
591impl fmt::Display for ProtocolErrorKind {
592 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
593 match self {
594 Self::BadClass => "BADCLASS".fmt(f),
595 Self::Busy => "BUSY".fmt(f),
596 Self::Client => "CLIENT_ERROR".fmt(f),
597 Self::NotFound => "NOT_FOUND".fmt(f),
598 Self::NotStored => "NOT_STORED".fmt(f),
599 Self::Server => "SERVER_ERROR".fmt(f),
600 Self::Syntax => "ERROR".fmt(f),
601 }
602 }
603}
604
605#[derive(Debug)]
606pub struct ProtocolError {
607 kind: ProtocolErrorKind,
608 message: Option<String>,
609}
610
611impl ProtocolError {
612 pub fn kind(&self) -> ProtocolErrorKind {
613 self.kind
614 }
615}
616
617impl fmt::Display for ProtocolError {
618 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619 if let Some(msg) = &self.message {
620 write!(f, "{} {}", self.kind, msg)
621 } else {
622 write!(f, "{}", self.kind)
623 }
624 }
625}
626
627impl error::Error for ProtocolError {}
628
629#[derive(Debug, Eq, PartialEq, Clone)]
630enum Command<'a> {
631 Add(&'a Key, u64, u32, &'a [u8]),
632 CrawlerMetadump,
633 Decr(&'a Key, u64),
634 Delete(&'a Key),
635 FlushAll(u64),
636 Gets(&'a [Key]),
637 Incr(&'a Key, u64),
638 MetaNoop,
639 Replace(&'a Key, u64, u32, &'a [u8]),
640 Stats,
641 StatsItems,
642 StatsSlabs,
643 Set(&'a Key, u64, u32, &'a [u8]),
644 Touch(&'a Key, u32),
645}
646
647impl<'a> From<Command<'a>> for Vec<u8> {
648 fn from(value: Command<'a>) -> Self {
649 match value {
650 Command::Add(key, flags, ttl, data) => storage_command("add", key, flags, ttl, data),
651 Command::CrawlerMetadump => "lru_crawler metadump hash\r\n".to_owned().into_bytes(),
652 Command::Decr(key, delta) => format!("decr {} {}\r\n", key, delta).into_bytes(),
653 Command::Delete(key) => format!("delete {}\r\n", key).into_bytes(),
654 Command::FlushAll(wait) => format!("flush_all {}\r\n", wait).into_bytes(),
655 Command::Gets(keys) => format!("gets {}\r\n", keys.join(" ")).into_bytes(),
656 Command::Incr(key, delta) => format!("incr {} {}\r\n", key, delta).into_bytes(),
657 Command::MetaNoop => "mn\r\n".to_owned().into_bytes(),
658 Command::Replace(key, flags, ttl, data) => storage_command("replace", key, flags, ttl, data),
659 Command::Stats => "stats\r\n".to_owned().into_bytes(),
660 Command::StatsItems => "stats items\r\n".to_owned().into_bytes(),
661 Command::StatsSlabs => "stats slabs\r\n".to_owned().into_bytes(),
662 Command::Set(key, flags, ttl, data) => storage_command("set", key, flags, ttl, data),
663 Command::Touch(key, ttl) => format!("touch {} {}\r\n", key, ttl).into_bytes(),
664 }
665 }
666}
667
668fn storage_command(verb: &str, key: &Key, flags: u64, ttl: u32, data: &[u8]) -> Vec<u8> {
669 let mut bytes = Vec::with_capacity(key.len() + data.len() + 32);
670 io::Write::write_all(
671 &mut bytes,
672 format!("{} {} {} {} {}\r\n", verb, key, flags, ttl, data.len()).as_bytes(),
673 )
674 .unwrap();
675 io::Write::write_all(&mut bytes, data).unwrap();
676 io::Write::write_all(&mut bytes, "\r\n".as_bytes()).unwrap();
677 bytes
678}
679
680pub struct Memcached {
681 read: BufReader<Box<dyn AsyncRead + Send + Sync + Unpin>>,
682 write: BufWriter<Box<dyn AsyncWrite + Send + Sync + Unpin>>,
683
684 buf_pairs: HashMap<String, String>,
685 buf_line: String,
686}
687
688impl Memcached {
689 const MAX_PAYLOAD_SIZE: u64 = 1024 * 1024 * 1024;
690
691 pub fn new<R, W>(read: R, write: W) -> Self
692 where
693 R: AsyncRead + Send + Sync + Unpin + 'static,
694 W: AsyncWrite + Send + Sync + Unpin + 'static,
695 {
696 Memcached {
697 read: BufReader::new(Box::new(read)),
698 write: BufWriter::new(Box::new(write)),
699 buf_pairs: HashMap::new(),
700 buf_line: String::new(),
701 }
702 }
703
704 pub async fn stats(&mut self) -> Result<Stats, MtopError> {
706 self.send(Command::Stats).await?;
707 self.read_stats_response().await
708 }
709
710 pub async fn slabs(&mut self) -> Result<Slabs, MtopError> {
715 self.send(Command::StatsSlabs).await?;
716 self.read_stats_response().await
717 }
718
719 pub async fn items(&mut self) -> Result<SlabItems, MtopError> {
724 self.send(Command::StatsItems).await?;
725 self.read_stats_response().await
726 }
727
728 pub async fn metas(&mut self) -> Result<Vec<Meta>, MtopError> {
732 self.send(Command::CrawlerMetadump).await?;
733
734 let mut out = Vec::new();
735 self.buf_line.clear();
736 self.buf_pairs.clear();
737
738 while self.read.read_line(&mut self.buf_line).await? != 0 {
739 let line = self.buf_line.trim_end();
740 if line == "END" {
741 break;
742 }
743
744 if let Some(err) = Self::parse_error(line) {
748 return Err(MtopError::from(err));
749 }
750
751 let item = Self::parse_crawler_meta(line, Meta::KEYS, &mut self.buf_pairs)?;
752 out.push(item);
753
754 self.buf_line.clear();
755 self.buf_pairs.clear();
756 }
757
758 Ok(out)
759 }
760
761 pub async fn ping(&mut self) -> Result<(), MtopError> {
763 self.send(Command::MetaNoop).await?;
764 self.read_simple_response("MN").await
765 }
766
767 pub async fn flush_all(&mut self, wait: Duration) -> Result<(), MtopError> {
770 self.send(Command::FlushAll(wait.as_secs())).await?;
771 self.read_simple_response("OK").await
772 }
773
774 pub async fn get(&mut self, keys: &[Key]) -> Result<HashMap<String, Value>, MtopError> {
777 self.send(Command::Gets(keys)).await?;
778
779 let mut out = HashMap::with_capacity(keys.len());
780 self.buf_line.clear();
781
782 while self.read.read_line(&mut self.buf_line).await? != 0 {
783 let line = self.buf_line.trim_end();
784 if line == "END" {
785 break;
786 }
787
788 let value = Self::parse_gets_value(line, &mut self.read).await?;
789 out.insert(value.key.clone(), value);
790 self.buf_line.clear();
791 }
792
793 Ok(out)
794 }
795
796 pub async fn incr(&mut self, key: &Key, delta: u64) -> Result<u64, MtopError> {
799 self.send(Command::Incr(key, delta)).await?;
800 self.buf_line.clear();
801
802 if self.read.read_line(&mut self.buf_line).await? != 0 {
803 Self::parse_numeric_response(self.buf_line.trim_end())
804 } else {
805 Err(MtopError::runtime("unexpected empty response"))
806 }
807 }
808
809 pub async fn decr(&mut self, key: &Key, delta: u64) -> Result<u64, MtopError> {
812 self.send(Command::Decr(key, delta)).await?;
813 self.buf_line.clear();
814
815 if self.read.read_line(&mut self.buf_line).await? != 0 {
816 Self::parse_numeric_response(self.buf_line.trim_end())
817 } else {
818 Err(MtopError::runtime("unexpected empty response"))
819 }
820 }
821
822 pub async fn set<V>(&mut self, key: &Key, flags: u64, ttl: u32, data: V) -> Result<(), MtopError>
824 where
825 V: AsRef<[u8]>,
826 {
827 self.send(Command::Set(key, flags, ttl, data.as_ref())).await?;
828 self.read_simple_response("STORED").await
829 }
830
831 pub async fn add<V>(&mut self, key: &Key, flags: u64, ttl: u32, data: V) -> Result<(), MtopError>
833 where
834 V: AsRef<[u8]>,
835 {
836 self.send(Command::Add(key, flags, ttl, data.as_ref())).await?;
837 self.read_simple_response("STORED").await
838 }
839
840 pub async fn replace<V>(&mut self, key: &Key, flags: u64, ttl: u32, data: V) -> Result<(), MtopError>
842 where
843 V: AsRef<[u8]>,
844 {
845 self.send(Command::Replace(key, flags, ttl, data.as_ref())).await?;
846 self.read_simple_response("STORED").await
847 }
848
849 pub async fn touch(&mut self, key: &Key, ttl: u32) -> Result<(), MtopError> {
851 self.send(Command::Touch(key, ttl)).await?;
852 self.read_simple_response("TOUCHED").await
853 }
854
855 pub async fn delete(&mut self, key: &Key) -> Result<(), MtopError> {
857 self.send(Command::Delete(key)).await?;
858 self.read_simple_response("DELETED").await
859 }
860
861 async fn read_stats_response<'this, 's, S>(&'this mut self) -> Result<S, MtopError>
862 where
863 'this: 's,
864 S: TryFrom<&'s HashMap<String, String>, Error = MtopError>,
865 {
866 self.buf_pairs.clear();
867 self.buf_line.clear();
868
869 while self.read.read_line(&mut self.buf_line).await? != 0 {
870 let line = self.buf_line.trim_end();
871 if line == "END" {
872 break;
873 }
874
875 let (key, val) = Self::parse_stat_line(line)?;
876 self.buf_pairs.insert(key.to_owned(), val.to_owned());
877 self.buf_line.clear();
878 }
879
880 S::try_from(&self.buf_pairs)
881 }
882
883 fn parse_stat_line(line: &str) -> Result<(&str, &str), MtopError> {
884 let mut parts = line.splitn(3, ' ');
885 match (parts.next(), parts.next(), parts.next()) {
886 (Some("STAT"), Some(key), Some(val)) => Ok((key, val)),
887 _ => {
888 if let Some(err) = Self::parse_error(line) {
889 Err(MtopError::from(err))
890 } else {
891 Err(MtopError::runtime(format!("unable to parse '{}'", line)))
892 }
893 }
894 }
895 }
896
897 #[inline]
898 fn parse_crawler_meta(line: &str, keys: &[&str], raw: &mut HashMap<String, String>) -> Result<Meta, MtopError> {
899 assert!(raw.is_empty(), "scratch HashMap should be cleared before being used");
900
901 for p in line.split(' ') {
902 let (key, val) = p
903 .split_once('=')
904 .ok_or_else(|| MtopError::runtime(format!("unexpected metadump format '{}'", line)))?;
905
906 if !keys.contains(&key) {
910 continue;
911 }
912
913 let decoded = crate::codec::url_decode(val)?;
914 raw.insert(key.to_owned(), decoded);
915 }
916
917 Meta::try_from(&*raw)
918 }
919
920 async fn parse_gets_value<R>(line: &str, reader: &mut BufReader<R>) -> Result<Value, MtopError>
921 where
922 R: AsyncRead + Send + Sync + Unpin + 'static,
923 {
924 let mut parts = line.splitn(5, ' ');
925
926 match (parts.next(), parts.next(), parts.next(), parts.next(), parts.next()) {
927 (Some("VALUE"), Some(k), Some(flags), Some(len), Some(cas)) => {
928 let flags: u64 = parse_value(flags, line)?;
929 let len: u64 = parse_value(len, line)?;
930 let cas: u64 = parse_value(cas, line)?;
931
932 if len > Self::MAX_PAYLOAD_SIZE {
937 return Err(MtopError::runtime(format!(
938 "server response of length {} exceeds client max of {}",
939 len,
940 Self::MAX_PAYLOAD_SIZE
941 )));
942 }
943
944 let data = {
945 let mut data = Vec::with_capacity(usize::try_from(len).unwrap() + 2);
947 reader.take(len + 2).read_to_end(&mut data).await?;
948 data.truncate(usize::try_from(len).unwrap());
949 data
950 };
951
952 Ok(Value {
953 key: k.to_owned(),
954 flags,
955 cas,
956 data,
957 })
958 }
959 _ => {
960 if let Some(err) = Self::parse_error(line) {
964 Err(MtopError::from(err))
965 } else {
966 Err(MtopError::runtime(format!("unable to parse '{}'", line)))
967 }
968 }
969 }
970 }
971
972 fn parse_numeric_response(line: &str) -> Result<u64, MtopError> {
973 if let Some(err) = Self::parse_error(line) {
974 Err(MtopError::from(err))
975 } else {
976 line.parse()
977 .map_err(|_e| MtopError::runtime(format!("unable to parse '{}'", line)))
978 }
979 }
980
981 async fn read_simple_response(&mut self, expected: &str) -> Result<(), MtopError> {
982 self.buf_line.clear();
983
984 if self.read.read_line(&mut self.buf_line).await? != 0 {
985 let line = self.buf_line.trim_end();
986 if line == expected {
987 Ok(())
988 } else if let Some(err) = Self::parse_error(line) {
989 Err(MtopError::from(err))
990 } else {
991 Err(MtopError::runtime(format!("unable to parse '{}'", line)))
992 }
993 } else {
994 Err(MtopError::runtime("unexpected empty response"))
995 }
996 }
997
998 fn parse_error(line: &str) -> Option<ProtocolError> {
999 let mut values = line.splitn(2, ' ');
1000 let (kind, message) = match (values.next(), values.next()) {
1001 (Some("BADCLASS"), Some(msg)) => (ProtocolErrorKind::BadClass, Some(msg.to_owned())),
1002 (Some("BUSY"), Some(msg)) => (ProtocolErrorKind::Busy, Some(msg.to_owned())),
1003 (Some("CLIENT_ERROR"), Some(msg)) => (ProtocolErrorKind::Client, Some(msg.to_owned())),
1004 (Some("ERROR"), None) => (ProtocolErrorKind::Syntax, None),
1005 (Some("ERROR"), Some(msg)) => (ProtocolErrorKind::Syntax, Some(msg.to_owned())),
1006 (Some("NOT_FOUND"), None) => (ProtocolErrorKind::NotFound, None),
1007 (Some("NOT_STORED"), None) => (ProtocolErrorKind::NotStored, None),
1008 (Some("SERVER_ERROR"), Some(msg)) => (ProtocolErrorKind::Server, Some(msg.to_owned())),
1009
1010 _ => return None,
1011 };
1012
1013 Some(ProtocolError { kind, message })
1014 }
1015
1016 async fn send(&mut self, cmd: Command<'_>) -> Result<(), MtopError> {
1017 let cmd_bytes: Vec<u8> = cmd.into();
1018 self.write.write_all(&cmd_bytes).await?;
1019 Ok(self.write.flush().await?)
1020 }
1021}
1022
1023impl fmt::Debug for Memcached {
1024 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1025 f.debug_struct("Memcached")
1026 .field("read", &"...")
1027 .field("write", &"...")
1028 .finish()
1029 }
1030}
1031
1032#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
1033#[repr(transparent)]
1034pub struct Key(String);
1035
1036impl Key {
1037 const MAX_LENGTH: usize = 250;
1038
1039 pub fn parse_all<I, T>(vals: I) -> Result<Vec<Self>, MtopError>
1040 where
1041 I: IntoIterator<Item = T>,
1042 T: TryInto<Key, Error = MtopError>,
1043 {
1044 let iter = vals.into_iter();
1045 let (sz, _) = iter.size_hint();
1046 let mut out = Vec::with_capacity(sz);
1047
1048 for val in iter {
1049 out.push(val.try_into()?);
1050 }
1051
1052 Ok(out)
1053 }
1054
1055 pub fn len(&self) -> usize {
1056 self.0.len()
1057 }
1058
1059 pub fn is_empty(&self) -> bool {
1060 self.0.is_empty()
1061 }
1062
1063 fn parse<T>(val: T) -> Result<Self, MtopError>
1064 where
1065 T: Into<String>,
1066 {
1067 let val = val.into();
1068 if Self::is_legal_val(&val) {
1069 Ok(Key(val))
1070 } else {
1071 Err(MtopError::runtime(format!("invalid key {}", val)))
1072 }
1073 }
1074
1075 fn is_legal_val(val: &str) -> bool {
1076 if val.len() > Self::MAX_LENGTH {
1077 return false;
1078 }
1079
1080 for c in val.chars() {
1081 if !c.is_ascii() || c.is_ascii_whitespace() || c.is_ascii_control() {
1082 return false;
1083 }
1084 }
1085
1086 true
1087 }
1088}
1089
1090impl fmt::Display for Key {
1091 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1092 self.0.fmt(f)
1093 }
1094}
1095
1096impl AsRef<str> for Key {
1097 fn as_ref(&self) -> &str {
1098 &self.0
1099 }
1100}
1101
1102impl Borrow<str> for Key {
1103 fn borrow(&self) -> &str {
1104 &self.0
1105 }
1106}
1107
1108impl TryFrom<String> for Key {
1109 type Error = MtopError;
1110
1111 fn try_from(value: String) -> Result<Self, Self::Error> {
1112 Key::parse(value)
1113 }
1114}
1115
1116impl TryFrom<&String> for Key {
1117 type Error = MtopError;
1118
1119 fn try_from(value: &String) -> Result<Self, Self::Error> {
1120 Key::parse(value)
1121 }
1122}
1123
1124impl TryFrom<&str> for Key {
1125 type Error = MtopError;
1126
1127 fn try_from(value: &str) -> Result<Self, Self::Error> {
1128 Key::parse(value)
1129 }
1130}
1131
1132#[cfg(test)]
1133mod test {
1134 use super::{ErrorKind, Key, Memcached, Meta, Slab, SlabItem, SlabItems};
1135 use std::io::{Cursor, Error};
1136 use std::pin::Pin;
1137 use std::task::{Context, Poll};
1138 use std::time::Duration;
1139 use tokio::io::AsyncWrite;
1140 use tokio::sync::mpsc::{self, UnboundedSender};
1141
1142 #[test]
1147 fn test_key_parse_length() {
1148 let val = "abc".repeat(Key::MAX_LENGTH);
1149 let res = Key::parse(val);
1150 assert!(res.is_err());
1151 }
1152
1153 #[test]
1154 fn test_key_parse_non_ascii() {
1155 let val = "🤦";
1156 let res = Key::parse(val);
1157 assert!(res.is_err());
1158 }
1159
1160 #[test]
1161 fn test_key_parse_whitespace() {
1162 let val = "some thing";
1163 let res = Key::parse(val);
1164 assert!(res.is_err());
1165 }
1166
1167 #[test]
1168 fn test_key_parse_control_char() {
1169 let val = "\x7F";
1170 let res = Key::parse(val);
1171 assert!(res.is_err());
1172 }
1173
1174 #[test]
1175 fn test_key_parse_success() {
1176 let val = "a-reasonable-key";
1177 let key = Key::parse(val).unwrap();
1178
1179 assert_eq!(Key("a-reasonable-key".to_owned()), key);
1180 }
1181
1182 #[test]
1183 fn test_key_parse_all_string() {
1184 let vals = vec![String::from("foo"), String::from("bar")];
1185 let keys = Key::parse_all(vals).unwrap();
1186
1187 assert_eq!(vec![Key("foo".to_owned()), Key("bar".to_owned())], keys,);
1188 }
1189
1190 #[test]
1191 fn test_key_parse_all_string_borrowed() {
1192 let vals = vec![String::from("foo"), String::from("bar")];
1193 let keys = Key::parse_all(&vals).unwrap();
1194
1195 assert_eq!(vec![Key("foo".to_owned()), Key("bar".to_owned())], keys,);
1196 }
1197
1198 #[test]
1199 fn test_key_parse_all_str() {
1200 let vals = ["foo", "bar"];
1201 let keys = Key::parse_all(vals).unwrap();
1202
1203 assert_eq!(vec![Key("foo".to_owned()), Key("bar".to_owned())], keys,);
1204 }
1205
1206 struct WriteAdapter {
1207 tx: UnboundedSender<Vec<u8>>,
1208 }
1209
1210 impl AsyncWrite for WriteAdapter {
1211 fn poll_write(self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize, Error>> {
1212 self.tx.send(buf.to_owned()).unwrap();
1213 Poll::Ready(Ok(buf.len()))
1214 }
1215
1216 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
1217 Poll::Ready(Ok(()))
1218 }
1219
1220 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
1221 Poll::Ready(Ok(()))
1222 }
1223 }
1224
1225 macro_rules! client {
1230 () => ({
1231 let (tx, rx) = mpsc::unbounded_channel();
1232 let reads = Vec::new();
1233 (rx, Memcached::new(Cursor::new(reads), WriteAdapter { tx }))
1234 });
1235 ($($line:expr),+ $(,)?) => ({
1236 let (tx, rx) = mpsc::unbounded_channel();
1237 let mut reads = Vec::new();
1238 $(reads.extend_from_slice($line.as_bytes());)+
1239 (rx, Memcached::new(Cursor::new(reads), WriteAdapter { tx }))
1240 })
1241 }
1242
1243 #[tokio::test]
1248 async fn test_memcached_get_no_key() {
1249 let (_rx, mut client) = client!();
1250 let vals: Vec<String> = vec![];
1251 let keys = Key::parse_all(vals).unwrap();
1252 let res = client.get(&keys).await.unwrap();
1253
1254 assert!(res.is_empty());
1255 }
1256
1257 #[tokio::test]
1258 async fn test_memcached_get_error() {
1259 let (_rx, mut client) = client!("SERVER_ERROR backend failure\r\n");
1260 let keys = Key::parse_all(vec!["foo", "baz"]).unwrap();
1261 let res = client.get(&keys).await;
1262
1263 assert!(res.is_err());
1264 let err = res.unwrap_err();
1265 assert_eq!(ErrorKind::Protocol, err.kind());
1266 }
1267
1268 #[tokio::test]
1269 async fn test_memcached_get_miss() {
1270 let (_rx, mut client) = client!("END\r\n");
1271 let keys = Key::parse_all(vec!["foo", "baz"]).unwrap();
1272 let res = client.get(&keys).await.unwrap();
1273
1274 assert!(res.is_empty());
1275 }
1276
1277 #[tokio::test]
1278 async fn test_memcached_get_hit() {
1279 let (_rx, mut client) = client!(
1280 "VALUE foo 32 3 1\r\n",
1281 "bar\r\n",
1282 "VALUE baz 64 3 2\r\n",
1283 "qux\r\n",
1284 "END\r\n",
1285 );
1286 let keys = Key::parse_all(vec!["foo", "baz"]).unwrap();
1287 let res = client.get(&keys).await.unwrap();
1288
1289 let val1 = res.get("foo").unwrap();
1290 assert_eq!("foo", val1.key);
1291 assert_eq!("bar".as_bytes(), val1.data);
1292 assert_eq!(32, val1.flags);
1293 assert_eq!(1, val1.cas);
1294
1295 let val2 = res.get("baz").unwrap();
1296 assert_eq!("baz", val2.key);
1297 assert_eq!("qux".as_bytes(), val2.data);
1298 assert_eq!(64, val2.flags);
1299 assert_eq!(2, val2.cas);
1300 }
1301
1302 #[tokio::test]
1307 async fn test_memcached_incr_bad_val() {
1308 let (mut rx, mut client) = client!("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n");
1309 let key = Key::parse("test").unwrap();
1310 let res = client.incr(&key, 2).await;
1311
1312 assert!(res.is_err());
1313 let err = res.unwrap_err();
1314 assert_eq!(ErrorKind::Protocol, err.kind());
1315
1316 let bytes = rx.recv().await.unwrap();
1317 let command = String::from_utf8(bytes).unwrap();
1318 assert_eq!("incr test 2\r\n", command);
1319 }
1320
1321 #[tokio::test]
1322 async fn test_memcached_incr_success() {
1323 let (mut rx, mut client) = client!("3\r\n");
1324 let key = Key::parse("test").unwrap();
1325 let res = client.incr(&key, 2).await.unwrap();
1326
1327 assert_eq!(3, res);
1328 let bytes = rx.recv().await.unwrap();
1329 let command = String::from_utf8(bytes).unwrap();
1330 assert_eq!("incr test 2\r\n", command);
1331 }
1332
1333 #[tokio::test]
1338 async fn test_memcached_decr_bad_val() {
1339 let (mut rx, mut client) = client!("CLIENT_ERROR cannot increment or decrement non-numeric value\r\n");
1340 let key = Key::parse("test").unwrap();
1341 let res = client.decr(&key, 1).await;
1342
1343 assert!(res.is_err());
1344 let err = res.unwrap_err();
1345 assert_eq!(ErrorKind::Protocol, err.kind());
1346
1347 let bytes = rx.recv().await.unwrap();
1348 let command = String::from_utf8(bytes).unwrap();
1349 assert_eq!("decr test 1\r\n", command);
1350 }
1351
1352 #[tokio::test]
1353 async fn test_memcached_decr_success() {
1354 let (mut rx, mut client) = client!("3\r\n");
1355 let key = Key::parse("test").unwrap();
1356 let res = client.decr(&key, 1).await.unwrap();
1357
1358 assert_eq!(3, res);
1359 let bytes = rx.recv().await.unwrap();
1360 let command = String::from_utf8(bytes).unwrap();
1361 assert_eq!("decr test 1\r\n", command);
1362 }
1363
1364 #[tokio::test]
1369 async fn test_memcached_ping_bad_val() {
1370 let (mut rx, mut client) = client!("220 localhost ESMTP Postfix\r\n");
1371 let res = client.ping().await;
1372
1373 assert!(res.is_err());
1374 let err = res.unwrap_err();
1375 assert_eq!(ErrorKind::Runtime, err.kind());
1376
1377 let bytes = rx.recv().await.unwrap();
1378 let command = String::from_utf8(bytes).unwrap();
1379 assert_eq!("mn\r\n", command);
1380 }
1381
1382 #[tokio::test]
1383 async fn test_memcached_ping_success() {
1384 let (mut rx, mut client) = client!("MN\r\n");
1385 client.ping().await.unwrap();
1386
1387 let bytes = rx.recv().await.unwrap();
1388 let command = String::from_utf8(bytes).unwrap();
1389 assert_eq!("mn\r\n", command);
1390 }
1391
1392 macro_rules! test_store_command_success {
1393 ($method:ident, $verb:expr) => {
1394 let (mut rx, mut client) = client!("STORED\r\n");
1395 let res = client.$method(&Key::parse("test").unwrap(), 0, 300, "val".as_bytes()).await;
1396
1397 assert!(res.is_ok());
1398 let bytes = rx.recv().await.unwrap();
1399 let command = String::from_utf8(bytes).unwrap();
1400 assert_eq!(concat!($verb, " test 0 300 3\r\nval\r\n"), command);
1401 };
1402 }
1403
1404 macro_rules! test_store_command_error {
1405 ($method:ident, $verb:expr) => {
1406 let (mut rx, mut client) = client!("NOT_STORED\r\n");
1407 let res = client.$method(&Key::parse("test").unwrap(), 0, 300, "val".as_bytes()).await;
1408
1409 assert!(res.is_err());
1410 let err = res.unwrap_err();
1411 assert_eq!(ErrorKind::Protocol, err.kind());
1412
1413 let bytes = rx.recv().await.unwrap();
1414 let command = String::from_utf8(bytes).unwrap();
1415 assert_eq!(concat!($verb, " test 0 300 3\r\nval\r\n"), command);
1416 };
1417 }
1418
1419 #[tokio::test]
1424 async fn test_memcached_set_success() {
1425 test_store_command_success!(set, "set");
1426 }
1427
1428 #[tokio::test]
1429 async fn test_memcached_set_error() {
1430 test_store_command_error!(set, "set");
1431 }
1432
1433 #[tokio::test]
1438 async fn test_memcached_add_success() {
1439 test_store_command_success!(add, "add");
1440 }
1441
1442 #[tokio::test]
1443 async fn test_memcached_add_error() {
1444 test_store_command_error!(add, "add");
1445 }
1446
1447 #[tokio::test]
1452 async fn test_memcached_replace_success() {
1453 test_store_command_success!(replace, "replace");
1454 }
1455
1456 #[tokio::test]
1457 async fn test_memcached_replace_error() {
1458 test_store_command_error!(replace, "replace");
1459 }
1460
1461 #[tokio::test]
1466 async fn test_memcached_touch_success() {
1467 let (mut rx, mut client) = client!("TOUCHED\r\n");
1468 let key = Key::parse("test").unwrap();
1469 let res = client.touch(&key, 300).await;
1470
1471 assert!(res.is_ok());
1472 let bytes = rx.recv().await.unwrap();
1473 let command = String::from_utf8(bytes).unwrap();
1474 assert_eq!("touch test 300\r\n", command);
1475 }
1476
1477 #[tokio::test]
1478 async fn test_memcached_touch_error() {
1479 let (mut rx, mut client) = client!("NOT_FOUND\r\n");
1480 let key = Key::parse("test").unwrap();
1481 let res = client.touch(&key, 300).await;
1482
1483 assert!(res.is_err());
1484 let err = res.unwrap_err();
1485 assert_eq!(ErrorKind::Protocol, err.kind());
1486
1487 let bytes = rx.recv().await.unwrap();
1488 let command = String::from_utf8(bytes).unwrap();
1489 assert_eq!("touch test 300\r\n", command);
1490 }
1491
1492 #[tokio::test]
1497 async fn test_memcached_delete_success() {
1498 let (mut rx, mut client) = client!("DELETED\r\n");
1499 let key = Key::parse("test").unwrap();
1500 let res = client.delete(&key).await;
1501
1502 assert!(res.is_ok());
1503 let bytes = rx.recv().await.unwrap();
1504 let command = String::from_utf8(bytes).unwrap();
1505 assert_eq!("delete test\r\n", command);
1506 }
1507
1508 #[tokio::test]
1509 async fn test_memcached_delete_error() {
1510 let (mut rx, mut client) = client!("NOT_FOUND\r\n");
1511 let key = Key::parse("test").unwrap();
1512 let res = client.delete(&key).await;
1513
1514 assert!(res.is_err());
1515 let err = res.unwrap_err();
1516 assert_eq!(ErrorKind::Protocol, err.kind());
1517
1518 let bytes = rx.recv().await.unwrap();
1519 let command = String::from_utf8(bytes).unwrap();
1520 assert_eq!("delete test\r\n", command);
1521 }
1522
1523 #[tokio::test]
1528 async fn test_memcached_flush_all_with_wait_success() {
1529 let (mut rx, mut client) = client!("OK\r\n");
1530 let res = client.flush_all(Duration::from_secs(25)).await;
1531
1532 assert!(res.is_ok());
1533 let bytes = rx.recv().await.unwrap();
1534 let command = String::from_utf8(bytes).unwrap();
1535 assert_eq!("flush_all 25\r\n", command);
1536 }
1537
1538 #[tokio::test]
1539 async fn test_memcached_flush_all_with_wait_error() {
1540 let (mut rx, mut client) = client!("ERROR\r\n");
1541 let res = client.flush_all(Duration::from_secs(25)).await;
1542
1543 assert!(res.is_err());
1544 let err = res.unwrap_err();
1545 assert_eq!(ErrorKind::Protocol, err.kind());
1546
1547 let bytes = rx.recv().await.unwrap();
1548 let command = String::from_utf8(bytes).unwrap();
1549 assert_eq!("flush_all 25\r\n", command);
1550 }
1551
1552 #[tokio::test]
1553 async fn test_memcached_flush_all_no_wait_success() {
1554 let (mut rx, mut client) = client!("OK\r\n");
1555 let res = client.flush_all(Duration::ZERO).await;
1556
1557 assert!(res.is_ok());
1558 let bytes = rx.recv().await.unwrap();
1559 let command = String::from_utf8(bytes).unwrap();
1560 assert_eq!("flush_all 0\r\n", command);
1561 }
1562
1563 #[tokio::test]
1564 async fn test_memcached_flush_all_no_wait_error() {
1565 let (mut rx, mut client) = client!("ERROR\r\n");
1566 let res = client.flush_all(Duration::ZERO).await;
1567
1568 assert!(res.is_err());
1569 let err = res.unwrap_err();
1570 assert_eq!(ErrorKind::Protocol, err.kind());
1571
1572 let bytes = rx.recv().await.unwrap();
1573 let command = String::from_utf8(bytes).unwrap();
1574 assert_eq!("flush_all 0\r\n", command);
1575 }
1576
1577 #[tokio::test]
1582 async fn test_memcached_stats_empty() {
1583 let (_rx, mut client) = client!("END\r\n");
1584 let res = client.stats().await;
1585
1586 assert!(res.is_err());
1587 let err = res.unwrap_err();
1588 assert_eq!(ErrorKind::Runtime, err.kind());
1589 }
1590
1591 #[tokio::test]
1592 async fn test_memcached_stats_error() {
1593 let (_rx, mut client) = client!("SERVER_ERROR backend failure\r\n");
1594 let res = client.stats().await;
1595
1596 assert!(res.is_err());
1597 let err = res.unwrap_err();
1598 assert_eq!(ErrorKind::Protocol, err.kind());
1599 }
1600
1601 #[tokio::test]
1602 async fn test_memcached_stats_success() {
1603 let (_rx, mut client) = client!(
1604 "STAT pid 1525\r\n",
1605 "STAT uptime 271984\r\n",
1606 "STAT time 1687212809\r\n",
1607 "STAT version 1.6.14\r\n",
1608 "STAT libevent 2.1.12-stable\r\n",
1609 "STAT pointer_size 64\r\n",
1610 "STAT rusage_user 17.544323\r\n",
1611 "STAT rusage_system 11.830461\r\n",
1612 "STAT max_connections 1024\r\n",
1613 "STAT curr_connections 1\r\n",
1614 "STAT total_connections 3\r\n",
1615 "STAT rejected_connections 0\r\n",
1616 "STAT connection_structures 2\r\n",
1617 "STAT response_obj_oom 0\r\n",
1618 "STAT response_obj_count 1\r\n",
1619 "STAT response_obj_bytes 32768\r\n",
1620 "STAT read_buf_count 4\r\n",
1621 "STAT read_buf_bytes 65536\r\n",
1622 "STAT read_buf_bytes_free 16384\r\n",
1623 "STAT read_buf_oom 0\r\n",
1624 "STAT reserved_fds 20\r\n",
1625 "STAT cmd_get 1\r\n",
1626 "STAT cmd_set 0\r\n",
1627 "STAT cmd_flush 0\r\n",
1628 "STAT cmd_touch 0\r\n",
1629 "STAT cmd_meta 0\r\n",
1630 "STAT get_hits 0\r\n",
1631 "STAT get_misses 1\r\n",
1632 "STAT get_expired 0\r\n",
1633 "STAT get_flushed 0\r\n",
1634 "STAT delete_misses 0\r\n",
1635 "STAT delete_hits 0\r\n",
1636 "STAT incr_misses 0\r\n",
1637 "STAT incr_hits 0\r\n",
1638 "STAT decr_misses 0\r\n",
1639 "STAT decr_hits 0\r\n",
1640 "STAT cas_misses 0\r\n",
1641 "STAT cas_hits 0\r\n",
1642 "STAT cas_badval 0\r\n",
1643 "STAT touch_hits 0\r\n",
1644 "STAT touch_misses 0\r\n",
1645 "STAT store_too_large 0\r\n",
1646 "STAT store_no_memory 0\r\n",
1647 "STAT auth_cmds 0\r\n",
1648 "STAT auth_errors 0\r\n",
1649 "STAT bytes_read 16\r\n",
1650 "STAT bytes_written 7\r\n",
1651 "STAT limit_maxbytes 67108864\r\n",
1652 "STAT accepting_conns 1\r\n",
1653 "STAT listen_disabled_num 0\r\n",
1654 "STAT time_in_listen_disabled_us 0\r\n",
1655 "STAT threads 4\r\n",
1656 "STAT conn_yields 0\r\n",
1657 "STAT hash_power_level 16\r\n",
1658 "STAT hash_bytes 524288\r\n",
1659 "STAT hash_is_expanding 0\r\n",
1660 "STAT slab_reassign_rescues 0\r\n",
1661 "STAT slab_reassign_chunk_rescues 0\r\n",
1662 "STAT slab_reassign_evictions_nomem 0\r\n",
1663 "STAT slab_reassign_inline_reclaim 0\r\n",
1664 "STAT slab_reassign_busy_items 0\r\n",
1665 "STAT slab_reassign_busy_deletes 0\r\n",
1666 "STAT slab_reassign_running 0\r\n",
1667 "STAT slabs_moved 0\r\n",
1668 "STAT lru_crawler_running 0\r\n",
1669 "STAT lru_crawler_starts 105\r\n",
1670 "STAT lru_maintainer_juggles 271976\r\n",
1671 "STAT malloc_fails 0\r\n",
1672 "STAT log_worker_dropped 0\r\n",
1673 "STAT log_worker_written 0\r\n",
1674 "STAT log_watcher_skipped 0\r\n",
1675 "STAT log_watcher_sent 0\r\n",
1676 "STAT log_watchers 0\r\n",
1677 "STAT unexpected_napi_ids 0\r\n",
1678 "STAT round_robin_fallback 0\r\n",
1679 "STAT bytes 0\r\n",
1680 "STAT curr_items 0\r\n",
1681 "STAT total_items 0\r\n",
1682 "STAT slab_global_page_pool 0\r\n",
1683 "STAT expired_unfetched 0\r\n",
1684 "STAT evicted_unfetched 0\r\n",
1685 "STAT evicted_active 0\r\n",
1686 "STAT evictions 0\r\n",
1687 "STAT reclaimed 0\r\n",
1688 "STAT crawler_reclaimed 0\r\n",
1689 "STAT crawler_items_checked 0\r\n",
1690 "STAT lrutail_reflocked 0\r\n",
1691 "STAT moves_to_cold 0\r\n",
1692 "STAT moves_to_warm 0\r\n",
1693 "STAT moves_within_lru 0\r\n",
1694 "STAT direct_reclaims 0\r\n",
1695 "STAT lru_bumps_dropped 0\r\n",
1696 "END\r\n",
1697 );
1698 let res = client.stats().await.unwrap();
1699
1700 assert_eq!(0, res.cmd_set);
1701 assert_eq!(1, res.cmd_get);
1702 assert_eq!(1, res.get_misses);
1703 assert_eq!(0, res.get_hits);
1704 }
1705
1706 #[tokio::test]
1711 async fn test_memcached_slabs_empty() {
1712 let (_rx, mut client) = client!("STAT active_slabs 0\r\n", "STAT total_malloced 0\r\n", "END\r\n");
1713 let res = client.slabs().await.unwrap();
1714
1715 assert!(res.0.is_empty());
1716 }
1717
1718 #[tokio::test]
1719 async fn test_memcached_slabs_error() {
1720 let (_rx, mut client) = client!("ERROR Too many open connections\r\n");
1721 let res = client.slabs().await;
1722
1723 assert!(res.is_err());
1724 let err = res.unwrap_err();
1725 assert_eq!(ErrorKind::Protocol, err.kind());
1726 }
1727
1728 #[tokio::test]
1729 async fn test_memcached_slabs_success() {
1730 let (_rx, mut client) = client!(
1731 "STAT 6:chunk_size 304\r\n",
1732 "STAT 6:chunks_per_page 3449\r\n",
1733 "STAT 6:total_pages 1\r\n",
1734 "STAT 6:total_chunks 3449\r\n",
1735 "STAT 6:used_chunks 1\r\n",
1736 "STAT 6:free_chunks 3448\r\n",
1737 "STAT 6:free_chunks_end 0\r\n",
1738 "STAT 6:get_hits 951\r\n",
1739 "STAT 6:cmd_set 100\r\n",
1740 "STAT 6:delete_hits 0\r\n",
1741 "STAT 6:incr_hits 0\r\n",
1742 "STAT 6:decr_hits 0\r\n",
1743 "STAT 6:cas_hits 0\r\n",
1744 "STAT 6:cas_badval 0\r\n",
1745 "STAT 6:touch_hits 0\r\n",
1746 "STAT 7:chunk_size 384\r\n",
1747 "STAT 7:chunks_per_page 2730\r\n",
1748 "STAT 7:total_pages 1\r\n",
1749 "STAT 7:total_chunks 2730\r\n",
1750 "STAT 7:used_chunks 5\r\n",
1751 "STAT 7:free_chunks 2725\r\n",
1752 "STAT 7:free_chunks_end 0\r\n",
1753 "STAT 7:get_hits 4792\r\n",
1754 "STAT 7:cmd_set 520\r\n",
1755 "STAT 7:delete_hits 0\r\n",
1756 "STAT 7:incr_hits 0\r\n",
1757 "STAT 7:decr_hits 0\r\n",
1758 "STAT 7:cas_hits 0\r\n",
1759 "STAT 7:cas_badval 0\r\n",
1760 "STAT 7:touch_hits 0\r\n",
1761 "STAT active_slabs 2\r\n",
1762 "STAT total_malloced 30408704\r\n",
1763 "END\r\n",
1764 );
1765 let res = client.slabs().await.unwrap();
1766
1767 let expected = vec![
1768 Slab {
1769 id: 6,
1770 chunk_size: 304,
1771 chunks_per_page: 3449,
1772 total_pages: 1,
1773 total_chunks: 3449,
1774 used_chunks: 1,
1775 free_chunks: 3448,
1776 get_hits: 951,
1777 cmd_set: 100,
1778 delete_hits: 0,
1779 incr_hits: 0,
1780 decr_hits: 0,
1781 cas_hits: 0,
1782 cas_badval: 0,
1783 touch_hits: 0,
1784 },
1785 Slab {
1786 id: 7,
1787 chunk_size: 384,
1788 chunks_per_page: 2730,
1789 total_pages: 1,
1790 total_chunks: 2730,
1791 used_chunks: 5,
1792 free_chunks: 2725,
1793 get_hits: 4792,
1794 cmd_set: 520,
1795 delete_hits: 0,
1796 incr_hits: 0,
1797 decr_hits: 0,
1798 cas_hits: 0,
1799 cas_badval: 0,
1800 touch_hits: 0,
1801 },
1802 ];
1803
1804 assert_eq!(expected, res.0);
1805 }
1806
1807 #[tokio::test]
1812 async fn test_memcached_items_empty() {
1813 let (_rx, mut client) = client!();
1814 let res = client.items().await.unwrap();
1815
1816 assert!(res.is_empty());
1817 }
1818
1819 #[tokio::test]
1820 async fn test_memcached_items_error() {
1821 let (_rx, mut client) = client!("ERROR Too many open connections\r\n");
1822 let res = client.items().await;
1823
1824 assert!(res.is_err());
1825 let err = res.unwrap_err();
1826 assert_eq!(ErrorKind::Protocol, err.kind());
1827 }
1828
1829 #[tokio::test]
1830 async fn test_memcached_items_success() {
1831 let (_rx, mut client) = client!(
1832 "STAT items:39:number 3\r\n",
1833 "STAT items:39:number_hot 0\r\n",
1834 "STAT items:39:number_warm 1\r\n",
1835 "STAT items:39:number_cold 2\r\n",
1836 "STAT items:39:age_hot 0\r\n",
1837 "STAT items:39:age_warm 7\r\n",
1838 "STAT items:39:age 8\r\n",
1839 "STAT items:39:mem_requested 1535788\r\n",
1840 "STAT items:39:evicted 1646\r\n",
1841 "STAT items:39:evicted_nonzero 1646\r\n",
1842 "STAT items:39:evicted_time 0\r\n",
1843 "STAT items:39:outofmemory 9\r\n",
1844 "STAT items:39:tailrepairs 0\r\n",
1845 "STAT items:39:reclaimed 13\r\n",
1846 "STAT items:39:expired_unfetched 4\r\n",
1847 "STAT items:39:evicted_unfetched 202\r\n",
1848 "STAT items:39:evicted_active 6\r\n",
1849 "STAT items:39:crawler_reclaimed 0\r\n",
1850 "STAT items:39:crawler_items_checked 40\r\n",
1851 "STAT items:39:lrutail_reflocked 17365\r\n",
1852 "STAT items:39:moves_to_cold 8703\r\n",
1853 "STAT items:39:moves_to_warm 7285\r\n",
1854 "STAT items:39:moves_within_lru 3651\r\n",
1855 "STAT items:39:direct_reclaims 1949\r\n",
1856 "STAT items:39:hits_to_hot 894\r\n",
1857 "STAT items:39:hits_to_warm 4079\r\n",
1858 "STAT items:39:hits_to_cold 8043\r\n",
1859 "STAT items:39:hits_to_temp 0\r\n",
1860 "END\r\n",
1861 );
1862 let res = client.items().await.unwrap();
1863
1864 let expected = SlabItems(vec![SlabItem {
1865 id: 39,
1866 number: 3,
1867 number_hot: 0,
1868 number_warm: 1,
1869 number_cold: 2,
1870 age_hot: 0,
1871 age_warm: 7,
1872 age: 8,
1873 mem_requested: 1_535_788,
1874 evicted: 1646,
1875 evicted_nonzero: 1646,
1876 evicted_time: 0,
1877 out_of_memory: 9,
1878 tail_repairs: 0,
1879 reclaimed: 13,
1880 expired_unfetched: 4,
1881 evicted_unfetched: 202,
1882 evicted_active: 6,
1883 crawler_reclaimed: 0,
1884 crawler_items_checked: 40,
1885 lrutail_reflocked: 17365,
1886 moves_to_cold: 8703,
1887 moves_to_warm: 7285,
1888 moves_within_lru: 3651,
1889 direct_reclaims: 1949,
1890 hits_to_hot: 894,
1891 hits_to_warm: 4079,
1892 hits_to_cold: 8043,
1893 hits_to_temp: 0,
1894 }]);
1895
1896 assert_eq!(expected, res);
1897 }
1898
1899 #[tokio::test]
1904 async fn test_memcached_metas_empty() {
1905 let (_rx, mut client) = client!();
1906 let res = client.metas().await.unwrap();
1907
1908 assert!(res.is_empty());
1909 }
1910
1911 #[tokio::test]
1912 async fn test_memcached_metas_error() {
1913 let (_rx, mut client) = client!("BUSY crawler is busy\r\n",);
1914 let res = client.metas().await;
1915
1916 assert!(res.is_err());
1917 let err = res.unwrap_err();
1918 assert_eq!(ErrorKind::Protocol, err.kind());
1919 }
1920
1921 #[tokio::test]
1922 async fn test_memcached_metas_success() {
1923 let (_rx, mut client) = client!(
1924 "key=memcached%2Fmurmur3_hash.c exp=1687216956 la=1687216656 cas=259502 fetch=yes cls=17 size=2912\r\n",
1925 "key=memcached%2Fmd5.h exp=1687216956 la=1687216656 cas=259731 fetch=yes cls=17 size=3593\r\n",
1926 "END\r\n",
1927 );
1928 let res = client.metas().await.unwrap();
1929
1930 let expected = vec![
1931 Meta {
1932 key: "memcached/murmur3_hash.c".to_string(),
1933 expires: 1_687_216_956,
1934 size: 2912,
1935 },
1936 Meta {
1937 key: "memcached/md5.h".to_string(),
1938 expires: 1_687_216_956,
1939 size: 3593,
1940 },
1941 ];
1942
1943 assert_eq!(expected, res);
1944 }
1945}