1use serde::{Deserialize, Serialize};
14use std::collections::{HashMap, HashSet};
15use std::error::Error;
16use std::fmt;
17
18pub mod cross_shard_tx;
19pub mod enhanced;
20pub mod routing;
21pub mod scatter;
22
23#[cfg(feature = "shard-rebalance")]
24pub mod rebalancer;
25
26pub use cross_shard_tx::{
28 ShardParticipant, ShardTransactionCoordinator, ShardTxError, ShardTxResult,
29};
30pub use routing::{CompositeKeyExtractor, FieldExtractor, ShardKeyExtractor};
31pub use scatter::ScatterGather;
32
33fn fnv1a_hash(data: &str) -> u64 {
45 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
46 const FNV_PRIME: u64 = 0x100000001b3;
47 let mut hash = FNV_OFFSET_BASIS;
48 for &byte in data.as_bytes() {
49 hash ^= byte as u64;
50 hash = hash.wrapping_mul(FNV_PRIME);
51 }
52 hash ^= hash >> 33;
54 hash = hash.wrapping_mul(0xff51afd7ed558ccd);
55 hash ^= hash >> 33;
56 hash = hash.wrapping_mul(0xc4ceb9fe1a85ec53);
57 hash ^= hash >> 33;
58 hash
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68pub enum ShardingStrategy {
69 Hash,
71 Range,
73 Date,
75 Enum {
77 mapping: HashMap<String, String>,
79 default: Option<String>,
81 },
82 List {
84 keys: HashSet<String>,
86 target: String,
88 default: Option<String>,
90 },
91 Directory {
93 table: HashMap<String, String>,
95 },
96 Composite {
99 primary: Box<ShardingStrategy>,
101 primary_shards: Vec<String>,
103 secondary: Box<ShardingStrategy>,
105 secondary_shards: Vec<String>,
107 },
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum ShardingError {
113 NoShardsConfigured,
115 NoMappingForKey(String),
117 ThreadPanic,
119}
120
121impl fmt::Display for ShardingError {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 match self {
124 ShardingError::NoShardsConfigured => {
125 write!(f, "ShardingRouter has no shards configured")
126 }
127 ShardingError::NoMappingForKey(key) => write!(f, "no mapping for key: {}", key),
128 ShardingError::ThreadPanic => write!(f, "worker thread panicked"),
129 }
130 }
131}
132
133impl Error for ShardingError {}
134
135pub struct ShardingRouter {
140 strategy: ShardingStrategy,
141 shards: Vec<String>,
143}
144
145impl ShardingRouter {
146 pub fn new(strategy: ShardingStrategy, shards: Vec<&str>) -> Self {
148 Self {
149 strategy,
150 shards: shards.into_iter().map(|s| s.to_string()).collect(),
151 }
152 }
153
154 pub fn new_enum(mapping: HashMap<String, String>, default: Option<String>) -> Self {
156 Self {
157 strategy: ShardingStrategy::Enum { mapping, default },
158 shards: vec![],
159 }
160 }
161
162 pub fn new_list(keys: HashSet<String>, target: String, default: Option<String>) -> Self {
164 Self {
165 strategy: ShardingStrategy::List {
166 keys,
167 target,
168 default,
169 },
170 shards: vec![],
171 }
172 }
173
174 pub fn new_directory(table: HashMap<String, String>) -> Self {
176 Self {
177 strategy: ShardingStrategy::Directory { table },
178 shards: vec![],
179 }
180 }
181
182 pub fn new_composite(
184 primary: ShardingStrategy,
185 primary_shards: Vec<String>,
186 secondary: ShardingStrategy,
187 secondary_shards: Vec<String>,
188 ) -> Self {
189 Self {
190 strategy: ShardingStrategy::Composite {
191 primary: Box::new(primary),
192 primary_shards,
193 secondary: Box::new(secondary),
194 secondary_shards,
195 },
196 shards: vec![],
197 }
198 }
199
200 pub fn route(&self, key: &str) -> Result<&str, ShardingError> {
209 route_strategy(&self.strategy, &self.shards, key)
210 }
211
212 pub fn route_by_data(
219 &self,
220 data: &dyn std::any::Any,
221 extractor: &dyn ShardKeyExtractor,
222 ) -> Result<&str, ShardingError> {
223 let key = extractor.extract(data)?;
224 self.route(&key)
225 }
226
227 pub fn query_all(&self) -> &[String] {
229 &self.shards
230 }
231
232 pub fn strategy(&self) -> ShardingStrategy {
234 self.strategy.clone()
235 }
236
237 pub fn shard_count(&self) -> usize {
239 self.shards.len()
240 }
241}
242
243fn route_strategy<'a>(
250 strategy: &'a ShardingStrategy,
251 shards: &'a [String],
252 key: &str,
253) -> Result<&'a str, ShardingError> {
254 match strategy {
255 ShardingStrategy::Hash => {
256 if shards.is_empty() {
257 return Err(ShardingError::NoShardsConfigured);
258 }
259 Ok(route_hash(shards, key))
260 }
261 ShardingStrategy::Range => {
262 if shards.is_empty() {
263 return Err(ShardingError::NoShardsConfigured);
264 }
265 Ok(route_range(shards, key))
266 }
267 ShardingStrategy::Date => {
268 if shards.is_empty() {
269 return Err(ShardingError::NoShardsConfigured);
270 }
271 Ok(route_date(shards, key))
272 }
273 ShardingStrategy::Enum { mapping, default } => {
274 if let Some(shard) = mapping.get(key) {
275 Ok(shard.as_str())
276 } else if let Some(d) = default {
277 Ok(d.as_str())
278 } else {
279 Err(ShardingError::NoMappingForKey(key.to_string()))
280 }
281 }
282 ShardingStrategy::List {
283 keys,
284 target,
285 default,
286 } => {
287 if keys.contains(key) {
288 Ok(target.as_str())
289 } else if let Some(d) = default {
290 Ok(d.as_str())
291 } else {
292 Err(ShardingError::NoMappingForKey(key.to_string()))
293 }
294 }
295 ShardingStrategy::Directory { table } => table
296 .get(key)
297 .map(|s| s.as_str())
298 .ok_or_else(|| ShardingError::NoMappingForKey(key.to_string())),
299 ShardingStrategy::Composite {
300 primary,
301 primary_shards,
302 secondary,
303 secondary_shards,
304 } => {
305 let group = route_strategy(primary, primary_shards, key)?;
307 let composite_key = format!("{}:{}", group, key);
309 route_strategy(secondary, secondary_shards, &composite_key)
310 }
311 }
312}
313
314fn route_hash<'a>(shards: &'a [String], key: &str) -> &'a str {
319 let hash = fnv1a_hash(key);
320 let idx = (hash as usize) % shards.len();
321 &shards[idx]
322}
323
324fn route_range<'a>(shards: &'a [String], key: &str) -> &'a str {
330 let first_byte = key.bytes().next().unwrap_or(0) as usize;
331 let idx = (first_byte * shards.len()) / 256;
332 &shards[idx.min(shards.len() - 1)]
333}
334
335fn route_date<'a>(shards: &'a [String], key: &str) -> &'a str {
341 if let Some(date) = extract_date(key) {
342 if let Some(day) = date.get(8..10).and_then(|s| s.parse::<usize>().ok()) {
344 if day >= 1 {
345 let idx = (day - 1) % shards.len();
346 return &shards[idx];
347 }
348 }
349 let hash = fnv1a_hash(&date);
351 let idx = (hash as usize) % shards.len();
352 return &shards[idx];
353 }
354 let hash = fnv1a_hash(key);
356 let idx = (hash as usize) % shards.len();
357 &shards[idx]
358}
359
360fn extract_date(key: &str) -> Option<String> {
362 let bytes = key.as_bytes();
363 if bytes.len() < 10 {
364 return None;
365 }
366 for i in 0..=bytes.len() - 10 {
367 if is_digit(bytes[i])
368 && is_digit(bytes[i + 1])
369 && is_digit(bytes[i + 2])
370 && is_digit(bytes[i + 3])
371 && bytes[i + 4] == b'-'
372 && is_digit(bytes[i + 5])
373 && is_digit(bytes[i + 6])
374 && bytes[i + 7] == b'-'
375 && is_digit(bytes[i + 8])
376 && is_digit(bytes[i + 9])
377 {
378 return String::from_utf8(bytes[i..i + 10].to_vec()).ok();
379 }
380 }
381 None
382}
383
384fn is_digit(b: u8) -> bool {
385 b.is_ascii_digit()
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use std::collections::HashSet;
392
393 #[test]
396 fn test_router_creation() {
397 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["shard0", "shard1"]);
398 assert_eq!(router.shard_count(), 2);
399 assert_eq!(router.strategy(), ShardingStrategy::Hash);
400 }
401
402 #[test]
403 fn test_query_all() {
404 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s1", "s2", "s3"]);
405 assert_eq!(router.query_all().len(), 3);
406 assert_eq!(router.query_all()[0], "s1");
407 assert_eq!(router.query_all()[2], "s3");
408 }
409
410 #[test]
411 fn test_empty_shards_returns_error() {
412 let router = ShardingRouter::new(ShardingStrategy::Hash, vec![]);
413 let result = router.route("any_key");
414 assert!(matches!(result, Err(ShardingError::NoShardsConfigured)));
415 if let Err(err) = result {
416 let msg = format!("{}", err);
417 assert!(
418 msg.contains("no shards configured"),
419 "error message should mention empty shards, got: {}",
420 msg
421 );
422 }
423 }
424
425 #[test]
426 fn test_single_shard_always_returns_it() {
427 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["only"]);
428 assert_eq!(router.route("any_key").unwrap(), "only");
429 assert_eq!(router.route("different").unwrap(), "only");
430
431 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["only"]);
432 assert_eq!(router.route("any_key").unwrap(), "only");
433
434 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["only"]);
435 assert_eq!(router.route("2026-07-18").unwrap(), "only");
436 }
437
438 #[test]
441 fn test_hash_deterministic() {
442 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2"]);
443 let first = router.route("user:123").unwrap();
445 for _ in 0..5 {
446 assert_eq!(
447 router.route("user:123").unwrap(),
448 first,
449 "Hash 路由应确定性"
450 );
451 }
452 }
453
454 #[test]
455 fn test_hash_different_keys_distribute() {
456 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2", "s3"]);
457 let mut shards_hit = HashSet::new();
459 for i in 0..100 {
460 let key = format!("key_{}", i);
461 shards_hit.insert(router.route(&key).unwrap().to_string());
462 }
463 assert!(
464 shards_hit.len() >= 2,
465 "Hash 策略在 100 个不同 key 上应至少命中 2 个 shard,实际: {}",
466 shards_hit.len()
467 );
468 }
469
470 #[test]
471 fn test_hash_same_key_same_shard() {
472 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2"]);
473 let r1 = router.route("consistent_key").unwrap();
474 let r2 = router.route("consistent_key").unwrap();
475 let r3 = router.route("consistent_key").unwrap();
476 assert_eq!(r1, r2);
477 assert_eq!(r2, r3);
478 }
479
480 #[test]
481 fn test_hash_empty_key() {
482 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1"]);
483 let shard = router.route("").unwrap();
484 assert!(shard == "s0" || shard == "s1");
486 }
487
488 #[test]
491 fn test_range_ascii_vs_non_ascii() {
492 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1"]);
494 assert_eq!(router.route("Hello").unwrap(), "s0");
496 assert_eq!(router.route("world").unwrap(), "s0");
497 assert_eq!(router.route("A").unwrap(), "s0");
498 assert_eq!(router.route("a").unwrap(), "s0");
499 assert_eq!(router.route("你好").unwrap(), "s1");
501 assert_eq!(router.route("é").unwrap(), "s1");
502 }
503
504 #[test]
505 fn test_range_different_keys_hit_different_shards() {
506 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1", "s2"]);
508 let mut shards_hit = HashSet::new();
509 shards_hit.insert(router.route("A").unwrap().to_string());
511 shards_hit.insert(router.route("a").unwrap().to_string());
513 shards_hit.insert(router.route("é").unwrap().to_string());
515 assert_eq!(
516 shards_hit.len(),
517 3,
518 "Range 策略应能命中所有 3 个 shard,实际: {:?}",
519 shards_hit
520 );
521 }
522
523 #[test]
524 fn test_range_deterministic() {
525 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1", "s2"]);
526 let first = router.route("hello").unwrap();
527 assert_eq!(router.route("hello").unwrap(), first);
528 assert_eq!(router.route("hello").unwrap(), first);
529 }
530
531 #[test]
532 fn test_range_empty_key_uses_zero_byte() {
533 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1"]);
534 assert_eq!(router.route("").unwrap(), "s0");
536 }
537
538 #[test]
539 fn test_range_keys_with_similar_prefixes_cluster() {
540 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1"]);
542 let shard1 = router.route("user:123").unwrap();
543 let shard2 = router.route("user:456").unwrap();
544 let shard3 = router.route("user:789").unwrap();
545 assert_eq!(shard1, shard2);
546 assert_eq!(shard2, shard3);
547 }
548
549 #[test]
552 fn test_date_day_based_routing() {
553 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
555 assert_eq!(router.route("2026-07-01").unwrap(), "s0");
557 assert_eq!(router.route("2026-07-02").unwrap(), "s1");
559 assert_eq!(router.route("2026-07-03").unwrap(), "s2");
561 assert_eq!(router.route("2026-07-04").unwrap(), "s0");
563 }
564
565 #[test]
566 fn test_date_different_days_distribute() {
567 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2", "s3"]);
568 let mut shards_hit = HashSet::new();
569 for day in 1..=28 {
570 let key = format!("2026-07-{:02}", day);
571 shards_hit.insert(router.route(&key).unwrap().to_string());
572 }
573 assert_eq!(
575 shards_hit.len(),
576 4,
577 "Date 策略 28 天应命中所有 4 个 shard,实际: {}",
578 shards_hit.len()
579 );
580 }
581
582 #[test]
583 fn test_date_extract_from_longer_key() {
584 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
585 let shard1 = router.route("log:2026-07-15:entry1").unwrap();
587 let shard2 = router.route("2026-07-15").unwrap();
588 assert_eq!(shard1, shard2, "包含相同日期的 key 应路由到相同 shard");
589 }
590
591 #[test]
592 fn test_date_deterministic() {
593 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
594 let first = router.route("2026-07-18").unwrap();
595 assert_eq!(router.route("2026-07-18").unwrap(), first);
596 }
597
598 #[test]
599 fn test_date_no_date_falls_back_to_hash() {
600 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
601 let shard = router.route("plain_key_without_date").unwrap();
603 assert!(shard == "s0" || shard == "s1" || shard == "s2");
604 assert_eq!(router.route("plain_key_without_date").unwrap(), shard);
606 }
607
608 #[test]
609 fn test_date_different_months_same_day_same_shard() {
610 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
611 let july_15 = router.route("2026-07-15").unwrap();
613 let aug_15 = router.route("2026-08-15").unwrap();
614 assert_eq!(july_15, aug_15);
615 }
616
617 #[test]
618 fn test_date_invalid_date_falls_back() {
619 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1"]);
620 let shard = router.route("2026-00-00").unwrap();
623 assert!(shard == "s0" || shard == "s1");
624 }
625
626 #[test]
629 fn test_different_strategies_may_route_differently() {
630 let key = "2026-07-15";
631 let hash_router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2"]);
632 let date_router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
633
634 let hash_shard = hash_router.route(key).unwrap();
636 let date_shard = date_router.route(key).unwrap();
637 assert!(!hash_shard.is_empty());
638 assert!(!date_shard.is_empty());
639 }
640
641 #[test]
644 fn test_extract_date_pure_date() {
645 assert_eq!(extract_date("2026-07-18"), Some("2026-07-18".to_string()));
646 assert_eq!(extract_date("2025-01-01"), Some("2025-01-01".to_string()));
647 }
648
649 #[test]
650 fn test_extract_date_embedded() {
651 assert_eq!(
652 extract_date("log:2026-07-18:entry"),
653 Some("2026-07-18".to_string())
654 );
655 }
656
657 #[test]
658 fn test_extract_date_no_date() {
659 assert_eq!(extract_date("no date here"), None);
660 assert_eq!(extract_date("2026/07/18"), None);
661 assert_eq!(extract_date(""), None);
662 assert_eq!(extract_date("short"), None);
663 }
664
665 #[test]
666 fn test_extract_date_invalid_format() {
667 assert_eq!(extract_date("2026-7-18"), None); assert_eq!(extract_date("2026-07-8"), None); assert_eq!(extract_date("abcd-07-18"), None); }
671
672 #[test]
677 fn test_enum_route_hit() {
678 let mut mapping = HashMap::new();
679 mapping.insert("cn".to_string(), "shard_cn".to_string());
680 mapping.insert("us".to_string(), "shard_us".to_string());
681 mapping.insert("eu".to_string(), "shard_eu".to_string());
682 let router = ShardingRouter::new_enum(mapping, None);
683 assert_eq!(router.route("cn").unwrap(), "shard_cn");
684 assert_eq!(router.route("us").unwrap(), "shard_us");
685 assert_eq!(router.route("eu").unwrap(), "shard_eu");
686 }
687
688 #[test]
689 fn test_enum_route_miss_with_default() {
690 let mut mapping = HashMap::new();
691 mapping.insert("cn".to_string(), "shard_cn".to_string());
692 let router = ShardingRouter::new_enum(mapping, Some("shard_default".to_string()));
693 assert_eq!(router.route("unknown").unwrap(), "shard_default");
694 assert_eq!(router.route("cn").unwrap(), "shard_cn");
696 }
697
698 #[test]
699 fn test_enum_route_miss_no_default_errors() {
700 let router = ShardingRouter::new_enum(HashMap::new(), None);
701 let result = router.route("unknown");
702 assert!(matches!(result, Err(ShardingError::NoMappingForKey(_))));
703 if let Err(ShardingError::NoMappingForKey(key)) = result {
704 assert_eq!(key, "unknown");
705 } else {
706 panic!("expected NoMappingForKey");
707 }
708 }
709
710 #[test]
711 fn test_enum_route_deterministic() {
712 let mut mapping = HashMap::new();
713 mapping.insert("k1".to_string(), "s_a".to_string());
714 let router = ShardingRouter::new_enum(mapping, Some("s_def".to_string()));
715 let r1 = router.route("k1").unwrap();
716 let r2 = router.route("k1").unwrap();
717 assert_eq!(r1, r2);
718 assert_eq!(router.route("k2").unwrap(), "s_def");
719 }
720
721 #[test]
724 fn test_list_route_hit() {
725 let mut keys = HashSet::new();
726 keys.insert("vip1".to_string());
727 keys.insert("vip2".to_string());
728 keys.insert("vip3".to_string());
729 let router = ShardingRouter::new_list(keys, "vip_shard".to_string(), None);
730 assert_eq!(router.route("vip1").unwrap(), "vip_shard");
731 assert_eq!(router.route("vip2").unwrap(), "vip_shard");
732 assert_eq!(router.route("vip3").unwrap(), "vip_shard");
733 }
734
735 #[test]
736 fn test_list_route_miss_with_default() {
737 let keys = HashSet::new();
738 let router = ShardingRouter::new_list(
739 keys,
740 "vip_shard".to_string(),
741 Some("normal_shard".to_string()),
742 );
743 assert_eq!(router.route("any_non_listed").unwrap(), "normal_shard");
745 }
746
747 #[test]
748 fn test_list_route_miss_no_default_errors() {
749 let router = ShardingRouter::new_list(HashSet::new(), "vip_shard".to_string(), None);
750 let result = router.route("unknown");
751 assert!(matches!(result, Err(ShardingError::NoMappingForKey(_))));
752 }
753
754 #[test]
755 fn test_list_route_with_members_and_default() {
756 let mut keys = HashSet::new();
757 keys.insert("gold".to_string());
758 let router = ShardingRouter::new_list(
759 keys,
760 "premium_shard".to_string(),
761 Some("standard_shard".to_string()),
762 );
763 assert_eq!(router.route("gold").unwrap(), "premium_shard");
764 assert_eq!(router.route("silver").unwrap(), "standard_shard");
765 }
766
767 #[test]
770 fn test_directory_route_hit() {
771 let mut table = HashMap::new();
772 table.insert("user:1".to_string(), "dir_shard_a".to_string());
773 table.insert("user:2".to_string(), "dir_shard_b".to_string());
774 let router = ShardingRouter::new_directory(table);
775 assert_eq!(router.route("user:1").unwrap(), "dir_shard_a");
776 assert_eq!(router.route("user:2").unwrap(), "dir_shard_b");
777 }
778
779 #[test]
780 fn test_directory_route_miss_errors() {
781 let router = ShardingRouter::new_directory(HashMap::new());
782 assert!(matches!(
783 router.route("missing"),
784 Err(ShardingError::NoMappingForKey(_))
785 ));
786 }
787
788 #[test]
789 fn test_directory_route_deterministic() {
790 let mut table = HashMap::new();
791 table.insert("k".to_string(), "v".to_string());
792 let router = ShardingRouter::new_directory(table);
793 let r1 = router.route("k").unwrap();
794 let r2 = router.route("k").unwrap();
795 assert_eq!(r1, r2);
796 assert_eq!(r1, "v");
797 }
798
799 #[test]
802 fn test_composite_route_basic() {
803 let router = ShardingRouter::new_composite(
806 ShardingStrategy::Hash,
807 vec!["g0".to_string(), "g1".to_string()],
808 ShardingStrategy::Hash,
809 vec!["s0".to_string(), "s1".to_string(), "s2".to_string()],
810 );
811 let result = router.route("user:123").unwrap();
812 assert!(
813 result == "s0" || result == "s1" || result == "s2",
814 "composite result should be in secondary shards, got {}",
815 result
816 );
817 }
818
819 #[test]
820 fn test_composite_route_deterministic() {
821 let router = ShardingRouter::new_composite(
822 ShardingStrategy::Hash,
823 vec!["g0".to_string(), "g1".to_string()],
824 ShardingStrategy::Hash,
825 vec!["s0".to_string(), "s1".to_string()],
826 );
827 let r1 = router.route("user:123").unwrap();
828 for _ in 0..5 {
829 assert_eq!(router.route("user:123").unwrap(), r1);
830 }
831 }
832
833 #[test]
834 fn test_composite_uses_group_in_secondary_key() {
835 let mut mapping = HashMap::new();
839 mapping.insert("a".to_string(), "grpA".to_string());
840 mapping.insert("b".to_string(), "grpB".to_string());
841 let router = ShardingRouter::new_composite(
842 ShardingStrategy::Enum {
843 mapping,
844 default: None,
845 },
846 vec!["grpA".to_string(), "grpB".to_string()], ShardingStrategy::Hash,
848 vec!["s0".to_string(), "s1".to_string()],
849 );
850 let ra = router.route("a").unwrap();
852 let rb = router.route("b").unwrap();
853 assert!(ra == "s0" || ra == "s1");
855 assert!(rb == "s0" || rb == "s1");
856 }
857
858 #[test]
859 fn test_composite_primary_empty_shards_errors() {
860 let router = ShardingRouter::new_composite(
862 ShardingStrategy::Hash,
863 vec![],
864 ShardingStrategy::Hash,
865 vec!["s0".to_string()],
866 );
867 assert!(matches!(
868 router.route("k"),
869 Err(ShardingError::NoShardsConfigured)
870 ));
871 }
872
873 #[test]
874 fn test_composite_secondary_empty_shards_errors() {
875 let router = ShardingRouter::new_composite(
877 ShardingStrategy::Hash,
878 vec!["g0".to_string()],
879 ShardingStrategy::Hash,
880 vec![],
881 );
882 assert!(matches!(
883 router.route("k"),
884 Err(ShardingError::NoShardsConfigured)
885 ));
886 }
887
888 #[test]
891 fn test_sharding_error_no_mapping_display() {
892 let err = ShardingError::NoMappingForKey("k1".to_string());
893 let msg = format!("{}", err);
894 assert!(msg.contains("no mapping for key: k1"));
895 }
896
897 #[test]
898 fn test_sharding_error_thread_panic_display() {
899 let err = ShardingError::ThreadPanic;
900 let msg = format!("{}", err);
901 assert!(msg.contains("panicked"));
902 }
903}