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
23pub use cross_shard_tx::{
25 ShardParticipant, ShardTransactionCoordinator, ShardTxError, ShardTxResult,
26};
27pub use routing::{CompositeKeyExtractor, FieldExtractor, ShardKeyExtractor};
28pub use scatter::ScatterGather;
29
30fn fnv1a_hash(data: &str) -> u64 {
39 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
40 const FNV_PRIME: u64 = 0x100000001b3;
41 let mut hash = FNV_OFFSET_BASIS;
42 for &byte in data.as_bytes() {
43 hash ^= byte as u64;
44 hash = hash.wrapping_mul(FNV_PRIME);
45 }
46 hash ^= hash >> 33;
48 hash = hash.wrapping_mul(0xff51afd7ed558ccd);
49 hash ^= hash >> 33;
50 hash = hash.wrapping_mul(0xc4ceb9fe1a85ec53);
51 hash ^= hash >> 33;
52 hash
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60pub enum ShardingStrategy {
61 Hash,
63 Range,
65 Date,
67 Enum {
69 mapping: HashMap<String, String>,
71 default: Option<String>,
73 },
74 List {
76 keys: HashSet<String>,
78 target: String,
80 default: Option<String>,
82 },
83 Directory {
85 table: HashMap<String, String>,
87 },
88 Composite {
90 primary: Box<ShardingStrategy>,
92 primary_shards: Vec<String>,
94 secondary: Box<ShardingStrategy>,
96 secondary_shards: Vec<String>,
98 },
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
103pub enum ShardingError {
104 NoShardsConfigured,
106 NoMappingForKey(String),
108 ThreadPanic,
110}
111
112impl fmt::Display for ShardingError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 ShardingError::NoShardsConfigured => {
116 write!(f, "ShardingRouter has no shards configured")
117 }
118 ShardingError::NoMappingForKey(key) => write!(f, "no mapping for key: {}", key),
119 ShardingError::ThreadPanic => write!(f, "worker thread panicked"),
120 }
121 }
122}
123
124impl Error for ShardingError {}
125
126pub struct ShardingRouter {
131 strategy: ShardingStrategy,
132 shards: Vec<String>,
134}
135
136impl ShardingRouter {
137 pub fn new(strategy: ShardingStrategy, shards: Vec<&str>) -> Self {
139 Self {
140 strategy,
141 shards: shards.into_iter().map(|s| s.to_string()).collect(),
142 }
143 }
144
145 pub fn new_enum(mapping: HashMap<String, String>, default: Option<String>) -> Self {
147 Self {
148 strategy: ShardingStrategy::Enum { mapping, default },
149 shards: vec![],
150 }
151 }
152
153 pub fn new_list(keys: HashSet<String>, target: String, default: Option<String>) -> Self {
155 Self {
156 strategy: ShardingStrategy::List {
157 keys,
158 target,
159 default,
160 },
161 shards: vec![],
162 }
163 }
164
165 pub fn new_directory(table: HashMap<String, String>) -> Self {
167 Self {
168 strategy: ShardingStrategy::Directory { table },
169 shards: vec![],
170 }
171 }
172
173 pub fn new_composite(
175 primary: ShardingStrategy,
176 primary_shards: Vec<String>,
177 secondary: ShardingStrategy,
178 secondary_shards: Vec<String>,
179 ) -> Self {
180 Self {
181 strategy: ShardingStrategy::Composite {
182 primary: Box::new(primary),
183 primary_shards,
184 secondary: Box::new(secondary),
185 secondary_shards,
186 },
187 shards: vec![],
188 }
189 }
190
191 pub fn route(&self, key: &str) -> Result<&str, ShardingError> {
198 route_strategy(&self.strategy, &self.shards, key)
199 }
200
201 pub fn route_by_data(
207 &self,
208 data: &dyn std::any::Any,
209 extractor: &dyn ShardKeyExtractor,
210 ) -> Result<&str, ShardingError> {
211 let key = extractor.extract(data)?;
212 self.route(&key)
213 }
214
215 pub fn query_all(&self) -> &[String] {
217 &self.shards
218 }
219
220 pub fn strategy(&self) -> ShardingStrategy {
222 self.strategy.clone()
223 }
224
225 pub fn shard_count(&self) -> usize {
227 self.shards.len()
228 }
229}
230
231fn route_strategy<'a>(
237 strategy: &'a ShardingStrategy,
238 shards: &'a [String],
239 key: &str,
240) -> Result<&'a str, ShardingError> {
241 match strategy {
242 ShardingStrategy::Hash => {
243 if shards.is_empty() {
244 return Err(ShardingError::NoShardsConfigured);
245 }
246 Ok(route_hash(shards, key))
247 }
248 ShardingStrategy::Range => {
249 if shards.is_empty() {
250 return Err(ShardingError::NoShardsConfigured);
251 }
252 Ok(route_range(shards, key))
253 }
254 ShardingStrategy::Date => {
255 if shards.is_empty() {
256 return Err(ShardingError::NoShardsConfigured);
257 }
258 Ok(route_date(shards, key))
259 }
260 ShardingStrategy::Enum { mapping, default } => {
261 if let Some(shard) = mapping.get(key) {
262 Ok(shard.as_str())
263 } else if let Some(d) = default {
264 Ok(d.as_str())
265 } else {
266 Err(ShardingError::NoMappingForKey(key.to_string()))
267 }
268 }
269 ShardingStrategy::List {
270 keys,
271 target,
272 default,
273 } => {
274 if keys.contains(key) {
275 Ok(target.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::Directory { table } => table
283 .get(key)
284 .map(|s| s.as_str())
285 .ok_or_else(|| ShardingError::NoMappingForKey(key.to_string())),
286 ShardingStrategy::Composite {
287 primary,
288 primary_shards,
289 secondary,
290 secondary_shards,
291 } => {
292 let group = route_strategy(primary, primary_shards, key)?;
294 let composite_key = format!("{}:{}", group, key);
296 route_strategy(secondary, secondary_shards, &composite_key)
297 }
298 }
299}
300
301fn route_hash<'a>(shards: &'a [String], key: &str) -> &'a str {
305 let hash = fnv1a_hash(key);
306 let idx = (hash as usize) % shards.len();
307 &shards[idx]
308}
309
310fn route_range<'a>(shards: &'a [String], key: &str) -> &'a str {
314 let first_byte = key.bytes().next().unwrap_or(0) as usize;
315 let idx = (first_byte * shards.len()) / 256;
316 &shards[idx.min(shards.len() - 1)]
317}
318
319fn route_date<'a>(shards: &'a [String], key: &str) -> &'a str {
323 if let Some(date) = extract_date(key) {
324 if let Some(day) = date.get(8..10).and_then(|s| s.parse::<usize>().ok()) {
326 if day >= 1 {
327 let idx = (day - 1) % shards.len();
328 return &shards[idx];
329 }
330 }
331 let hash = fnv1a_hash(&date);
333 let idx = (hash as usize) % shards.len();
334 return &shards[idx];
335 }
336 let hash = fnv1a_hash(key);
338 let idx = (hash as usize) % shards.len();
339 &shards[idx]
340}
341
342fn extract_date(key: &str) -> Option<String> {
344 let bytes = key.as_bytes();
345 if bytes.len() < 10 {
346 return None;
347 }
348 for i in 0..=bytes.len() - 10 {
349 if is_digit(bytes[i])
350 && is_digit(bytes[i + 1])
351 && is_digit(bytes[i + 2])
352 && is_digit(bytes[i + 3])
353 && bytes[i + 4] == b'-'
354 && is_digit(bytes[i + 5])
355 && is_digit(bytes[i + 6])
356 && bytes[i + 7] == b'-'
357 && is_digit(bytes[i + 8])
358 && is_digit(bytes[i + 9])
359 {
360 return String::from_utf8(bytes[i..i + 10].to_vec()).ok();
361 }
362 }
363 None
364}
365
366fn is_digit(b: u8) -> bool {
367 b.is_ascii_digit()
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use std::collections::HashSet;
374
375 #[test]
378 fn test_router_creation() {
379 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["shard0", "shard1"]);
380 assert_eq!(router.shard_count(), 2);
381 assert_eq!(router.strategy(), ShardingStrategy::Hash);
382 }
383
384 #[test]
385 fn test_query_all() {
386 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s1", "s2", "s3"]);
387 assert_eq!(router.query_all().len(), 3);
388 assert_eq!(router.query_all()[0], "s1");
389 assert_eq!(router.query_all()[2], "s3");
390 }
391
392 #[test]
393 fn test_empty_shards_returns_error() {
394 let router = ShardingRouter::new(ShardingStrategy::Hash, vec![]);
395 let result = router.route("any_key");
396 assert!(matches!(result, Err(ShardingError::NoShardsConfigured)));
397 if let Err(err) = result {
398 let msg = format!("{}", err);
399 assert!(
400 msg.contains("no shards configured"),
401 "error message should mention empty shards, got: {}",
402 msg
403 );
404 }
405 }
406
407 #[test]
408 fn test_single_shard_always_returns_it() {
409 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["only"]);
410 assert_eq!(router.route("any_key").unwrap(), "only");
411 assert_eq!(router.route("different").unwrap(), "only");
412
413 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["only"]);
414 assert_eq!(router.route("any_key").unwrap(), "only");
415
416 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["only"]);
417 assert_eq!(router.route("2026-07-18").unwrap(), "only");
418 }
419
420 #[test]
423 fn test_hash_deterministic() {
424 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2"]);
425 let first = router.route("user:123").unwrap();
427 for _ in 0..5 {
428 assert_eq!(
429 router.route("user:123").unwrap(),
430 first,
431 "Hash 路由应确定性"
432 );
433 }
434 }
435
436 #[test]
437 fn test_hash_different_keys_distribute() {
438 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2", "s3"]);
439 let mut shards_hit = HashSet::new();
441 for i in 0..100 {
442 let key = format!("key_{}", i);
443 shards_hit.insert(router.route(&key).unwrap().to_string());
444 }
445 assert!(
446 shards_hit.len() >= 2,
447 "Hash 策略在 100 个不同 key 上应至少命中 2 个 shard,实际: {}",
448 shards_hit.len()
449 );
450 }
451
452 #[test]
453 fn test_hash_same_key_same_shard() {
454 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2"]);
455 let r1 = router.route("consistent_key").unwrap();
456 let r2 = router.route("consistent_key").unwrap();
457 let r3 = router.route("consistent_key").unwrap();
458 assert_eq!(r1, r2);
459 assert_eq!(r2, r3);
460 }
461
462 #[test]
463 fn test_hash_empty_key() {
464 let router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1"]);
465 let shard = router.route("").unwrap();
466 assert!(shard == "s0" || shard == "s1");
468 }
469
470 #[test]
473 fn test_range_ascii_vs_non_ascii() {
474 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1"]);
476 assert_eq!(router.route("Hello").unwrap(), "s0");
478 assert_eq!(router.route("world").unwrap(), "s0");
479 assert_eq!(router.route("A").unwrap(), "s0");
480 assert_eq!(router.route("a").unwrap(), "s0");
481 assert_eq!(router.route("你好").unwrap(), "s1");
483 assert_eq!(router.route("é").unwrap(), "s1");
484 }
485
486 #[test]
487 fn test_range_different_keys_hit_different_shards() {
488 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1", "s2"]);
490 let mut shards_hit = HashSet::new();
491 shards_hit.insert(router.route("A").unwrap().to_string());
493 shards_hit.insert(router.route("a").unwrap().to_string());
495 shards_hit.insert(router.route("é").unwrap().to_string());
497 assert_eq!(
498 shards_hit.len(),
499 3,
500 "Range 策略应能命中所有 3 个 shard,实际: {:?}",
501 shards_hit
502 );
503 }
504
505 #[test]
506 fn test_range_deterministic() {
507 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1", "s2"]);
508 let first = router.route("hello").unwrap();
509 assert_eq!(router.route("hello").unwrap(), first);
510 assert_eq!(router.route("hello").unwrap(), first);
511 }
512
513 #[test]
514 fn test_range_empty_key_uses_zero_byte() {
515 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1"]);
516 assert_eq!(router.route("").unwrap(), "s0");
518 }
519
520 #[test]
521 fn test_range_keys_with_similar_prefixes_cluster() {
522 let router = ShardingRouter::new(ShardingStrategy::Range, vec!["s0", "s1"]);
524 let shard1 = router.route("user:123").unwrap();
525 let shard2 = router.route("user:456").unwrap();
526 let shard3 = router.route("user:789").unwrap();
527 assert_eq!(shard1, shard2);
528 assert_eq!(shard2, shard3);
529 }
530
531 #[test]
534 fn test_date_day_based_routing() {
535 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
537 assert_eq!(router.route("2026-07-01").unwrap(), "s0");
539 assert_eq!(router.route("2026-07-02").unwrap(), "s1");
541 assert_eq!(router.route("2026-07-03").unwrap(), "s2");
543 assert_eq!(router.route("2026-07-04").unwrap(), "s0");
545 }
546
547 #[test]
548 fn test_date_different_days_distribute() {
549 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2", "s3"]);
550 let mut shards_hit = HashSet::new();
551 for day in 1..=28 {
552 let key = format!("2026-07-{:02}", day);
553 shards_hit.insert(router.route(&key).unwrap().to_string());
554 }
555 assert_eq!(
557 shards_hit.len(),
558 4,
559 "Date 策略 28 天应命中所有 4 个 shard,实际: {}",
560 shards_hit.len()
561 );
562 }
563
564 #[test]
565 fn test_date_extract_from_longer_key() {
566 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
567 let shard1 = router.route("log:2026-07-15:entry1").unwrap();
569 let shard2 = router.route("2026-07-15").unwrap();
570 assert_eq!(shard1, shard2, "包含相同日期的 key 应路由到相同 shard");
571 }
572
573 #[test]
574 fn test_date_deterministic() {
575 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
576 let first = router.route("2026-07-18").unwrap();
577 assert_eq!(router.route("2026-07-18").unwrap(), first);
578 }
579
580 #[test]
581 fn test_date_no_date_falls_back_to_hash() {
582 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
583 let shard = router.route("plain_key_without_date").unwrap();
585 assert!(shard == "s0" || shard == "s1" || shard == "s2");
586 assert_eq!(router.route("plain_key_without_date").unwrap(), shard);
588 }
589
590 #[test]
591 fn test_date_different_months_same_day_same_shard() {
592 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
593 let july_15 = router.route("2026-07-15").unwrap();
595 let aug_15 = router.route("2026-08-15").unwrap();
596 assert_eq!(july_15, aug_15);
597 }
598
599 #[test]
600 fn test_date_invalid_date_falls_back() {
601 let router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1"]);
602 let shard = router.route("2026-00-00").unwrap();
605 assert!(shard == "s0" || shard == "s1");
606 }
607
608 #[test]
611 fn test_different_strategies_may_route_differently() {
612 let key = "2026-07-15";
613 let hash_router = ShardingRouter::new(ShardingStrategy::Hash, vec!["s0", "s1", "s2"]);
614 let date_router = ShardingRouter::new(ShardingStrategy::Date, vec!["s0", "s1", "s2"]);
615
616 let hash_shard = hash_router.route(key).unwrap();
618 let date_shard = date_router.route(key).unwrap();
619 assert!(!hash_shard.is_empty());
620 assert!(!date_shard.is_empty());
621 }
622
623 #[test]
626 fn test_extract_date_pure_date() {
627 assert_eq!(extract_date("2026-07-18"), Some("2026-07-18".to_string()));
628 assert_eq!(extract_date("2025-01-01"), Some("2025-01-01".to_string()));
629 }
630
631 #[test]
632 fn test_extract_date_embedded() {
633 assert_eq!(
634 extract_date("log:2026-07-18:entry"),
635 Some("2026-07-18".to_string())
636 );
637 }
638
639 #[test]
640 fn test_extract_date_no_date() {
641 assert_eq!(extract_date("no date here"), None);
642 assert_eq!(extract_date("2026/07/18"), None);
643 assert_eq!(extract_date(""), None);
644 assert_eq!(extract_date("short"), None);
645 }
646
647 #[test]
648 fn test_extract_date_invalid_format() {
649 assert_eq!(extract_date("2026-7-18"), None); assert_eq!(extract_date("2026-07-8"), None); assert_eq!(extract_date("abcd-07-18"), None); }
653
654 #[test]
659 fn test_enum_route_hit() {
660 let mut mapping = HashMap::new();
661 mapping.insert("cn".to_string(), "shard_cn".to_string());
662 mapping.insert("us".to_string(), "shard_us".to_string());
663 mapping.insert("eu".to_string(), "shard_eu".to_string());
664 let router = ShardingRouter::new_enum(mapping, None);
665 assert_eq!(router.route("cn").unwrap(), "shard_cn");
666 assert_eq!(router.route("us").unwrap(), "shard_us");
667 assert_eq!(router.route("eu").unwrap(), "shard_eu");
668 }
669
670 #[test]
671 fn test_enum_route_miss_with_default() {
672 let mut mapping = HashMap::new();
673 mapping.insert("cn".to_string(), "shard_cn".to_string());
674 let router = ShardingRouter::new_enum(mapping, Some("shard_default".to_string()));
675 assert_eq!(router.route("unknown").unwrap(), "shard_default");
676 assert_eq!(router.route("cn").unwrap(), "shard_cn");
678 }
679
680 #[test]
681 fn test_enum_route_miss_no_default_errors() {
682 let router = ShardingRouter::new_enum(HashMap::new(), None);
683 let result = router.route("unknown");
684 assert!(matches!(result, Err(ShardingError::NoMappingForKey(_))));
685 if let Err(ShardingError::NoMappingForKey(key)) = result {
686 assert_eq!(key, "unknown");
687 } else {
688 panic!("expected NoMappingForKey");
689 }
690 }
691
692 #[test]
693 fn test_enum_route_deterministic() {
694 let mut mapping = HashMap::new();
695 mapping.insert("k1".to_string(), "s_a".to_string());
696 let router = ShardingRouter::new_enum(mapping, Some("s_def".to_string()));
697 let r1 = router.route("k1").unwrap();
698 let r2 = router.route("k1").unwrap();
699 assert_eq!(r1, r2);
700 assert_eq!(router.route("k2").unwrap(), "s_def");
701 }
702
703 #[test]
706 fn test_list_route_hit() {
707 let mut keys = HashSet::new();
708 keys.insert("vip1".to_string());
709 keys.insert("vip2".to_string());
710 keys.insert("vip3".to_string());
711 let router = ShardingRouter::new_list(keys, "vip_shard".to_string(), None);
712 assert_eq!(router.route("vip1").unwrap(), "vip_shard");
713 assert_eq!(router.route("vip2").unwrap(), "vip_shard");
714 assert_eq!(router.route("vip3").unwrap(), "vip_shard");
715 }
716
717 #[test]
718 fn test_list_route_miss_with_default() {
719 let keys = HashSet::new();
720 let router = ShardingRouter::new_list(
721 keys,
722 "vip_shard".to_string(),
723 Some("normal_shard".to_string()),
724 );
725 assert_eq!(router.route("any_non_listed").unwrap(), "normal_shard");
727 }
728
729 #[test]
730 fn test_list_route_miss_no_default_errors() {
731 let router = ShardingRouter::new_list(HashSet::new(), "vip_shard".to_string(), None);
732 let result = router.route("unknown");
733 assert!(matches!(result, Err(ShardingError::NoMappingForKey(_))));
734 }
735
736 #[test]
737 fn test_list_route_with_members_and_default() {
738 let mut keys = HashSet::new();
739 keys.insert("gold".to_string());
740 let router = ShardingRouter::new_list(
741 keys,
742 "premium_shard".to_string(),
743 Some("standard_shard".to_string()),
744 );
745 assert_eq!(router.route("gold").unwrap(), "premium_shard");
746 assert_eq!(router.route("silver").unwrap(), "standard_shard");
747 }
748
749 #[test]
752 fn test_directory_route_hit() {
753 let mut table = HashMap::new();
754 table.insert("user:1".to_string(), "dir_shard_a".to_string());
755 table.insert("user:2".to_string(), "dir_shard_b".to_string());
756 let router = ShardingRouter::new_directory(table);
757 assert_eq!(router.route("user:1").unwrap(), "dir_shard_a");
758 assert_eq!(router.route("user:2").unwrap(), "dir_shard_b");
759 }
760
761 #[test]
762 fn test_directory_route_miss_errors() {
763 let router = ShardingRouter::new_directory(HashMap::new());
764 assert!(matches!(
765 router.route("missing"),
766 Err(ShardingError::NoMappingForKey(_))
767 ));
768 }
769
770 #[test]
771 fn test_directory_route_deterministic() {
772 let mut table = HashMap::new();
773 table.insert("k".to_string(), "v".to_string());
774 let router = ShardingRouter::new_directory(table);
775 let r1 = router.route("k").unwrap();
776 let r2 = router.route("k").unwrap();
777 assert_eq!(r1, r2);
778 assert_eq!(r1, "v");
779 }
780
781 #[test]
784 fn test_composite_route_basic() {
785 let router = ShardingRouter::new_composite(
788 ShardingStrategy::Hash,
789 vec!["g0".to_string(), "g1".to_string()],
790 ShardingStrategy::Hash,
791 vec!["s0".to_string(), "s1".to_string(), "s2".to_string()],
792 );
793 let result = router.route("user:123").unwrap();
794 assert!(
795 result == "s0" || result == "s1" || result == "s2",
796 "composite result should be in secondary shards, got {}",
797 result
798 );
799 }
800
801 #[test]
802 fn test_composite_route_deterministic() {
803 let router = ShardingRouter::new_composite(
804 ShardingStrategy::Hash,
805 vec!["g0".to_string(), "g1".to_string()],
806 ShardingStrategy::Hash,
807 vec!["s0".to_string(), "s1".to_string()],
808 );
809 let r1 = router.route("user:123").unwrap();
810 for _ in 0..5 {
811 assert_eq!(router.route("user:123").unwrap(), r1);
812 }
813 }
814
815 #[test]
816 fn test_composite_uses_group_in_secondary_key() {
817 let mut mapping = HashMap::new();
821 mapping.insert("a".to_string(), "grpA".to_string());
822 mapping.insert("b".to_string(), "grpB".to_string());
823 let router = ShardingRouter::new_composite(
824 ShardingStrategy::Enum {
825 mapping,
826 default: None,
827 },
828 vec!["grpA".to_string(), "grpB".to_string()], ShardingStrategy::Hash,
830 vec!["s0".to_string(), "s1".to_string()],
831 );
832 let ra = router.route("a").unwrap();
834 let rb = router.route("b").unwrap();
835 assert!(ra == "s0" || ra == "s1");
837 assert!(rb == "s0" || rb == "s1");
838 }
839
840 #[test]
841 fn test_composite_primary_empty_shards_errors() {
842 let router = ShardingRouter::new_composite(
844 ShardingStrategy::Hash,
845 vec![],
846 ShardingStrategy::Hash,
847 vec!["s0".to_string()],
848 );
849 assert!(matches!(
850 router.route("k"),
851 Err(ShardingError::NoShardsConfigured)
852 ));
853 }
854
855 #[test]
856 fn test_composite_secondary_empty_shards_errors() {
857 let router = ShardingRouter::new_composite(
859 ShardingStrategy::Hash,
860 vec!["g0".to_string()],
861 ShardingStrategy::Hash,
862 vec![],
863 );
864 assert!(matches!(
865 router.route("k"),
866 Err(ShardingError::NoShardsConfigured)
867 ));
868 }
869
870 #[test]
873 fn test_sharding_error_no_mapping_display() {
874 let err = ShardingError::NoMappingForKey("k1".to_string());
875 let msg = format!("{}", err);
876 assert!(msg.contains("no mapping for key: k1"));
877 }
878
879 #[test]
880 fn test_sharding_error_thread_panic_display() {
881 let err = ShardingError::ThreadPanic;
882 let msg = format!("{}", err);
883 assert!(msg.contains("panicked"));
884 }
885}