dynomite/conf/enums.rs
1//! Typed enums for configuration values that the C parser stored as
2//! free-form strings or small integer codes.
3
4use std::fmt;
5
6use serde::de::{self, Deserializer, Visitor};
7use serde::{Deserialize, Serialize};
8
9use super::error::ConfError;
10
11macro_rules! string_enum_serde {
12 ($t:ty) => {
13 impl Serialize for $t {
14 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
15 ser.serialize_str(self.as_str())
16 }
17 }
18
19 impl<'de> Deserialize<'de> for $t {
20 fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
21 struct V;
22 impl Visitor<'_> for V {
23 type Value = $t;
24 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 f.write_str(concat!("a string naming a ", stringify!($t)))
26 }
27 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
28 <$t>::parse(v).map_err(|e| E::custom(e.to_string()))
29 }
30 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
31 self.visit_str(&v)
32 }
33 }
34 de.deserialize_str(V)
35 }
36 }
37 };
38}
39
40string_enum_serde!(SecureServerOption);
41string_enum_serde!(HashType);
42string_enum_serde!(Distribution);
43string_enum_serde!(Transport);
44
45/// Transport selected by the pool's `transport:` directive.
46///
47/// Controls which network stack the proxy listener binds.
48/// `Tcp` is the historical default and the only option a build
49/// without the `quic` Cargo feature can satisfy.
50///
51/// # Examples
52///
53/// ```
54/// use dynomite::conf::Transport;
55/// assert_eq!(Transport::parse("tcp").unwrap(), Transport::Tcp);
56/// assert_eq!(Transport::parse("quic").unwrap(), Transport::Quic);
57/// assert_eq!(Transport::default(), Transport::Tcp);
58/// ```
59#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
60pub enum Transport {
61 /// TCP transport. The historical default and the only
62 /// option compiled in when the `quic` Cargo feature is
63 /// off.
64 Tcp,
65 /// QUIC transport. Requires the `quic` Cargo feature on
66 /// the engine and a server cert / key pair supplied via
67 /// the pool's `quic_cert_file:` and `quic_key_file:`
68 /// directives.
69 Quic,
70}
71
72impl Default for Transport {
73 fn default() -> Self {
74 Self::Tcp
75 }
76}
77
78impl Transport {
79 /// Parse a `transport:` value (case-insensitive).
80 ///
81 /// # Errors
82 /// Returns [`ConfError::BadServer`] when the value is
83 /// not a recognised transport.
84 ///
85 /// # Examples
86 ///
87 /// ```
88 /// use dynomite::conf::Transport;
89 /// assert_eq!(Transport::parse("TCP").unwrap(), Transport::Tcp);
90 /// assert!(Transport::parse("http").is_err());
91 /// ```
92 pub fn parse(s: &str) -> Result<Self, ConfError> {
93 match s.to_ascii_lowercase().as_str() {
94 "tcp" => Ok(Transport::Tcp),
95 "quic" => Ok(Transport::Quic),
96 other => Err(ConfError::BadServer {
97 field: "transport",
98 value: other.to_string(),
99 reason: "transport must be 'tcp' or 'quic'".to_string(),
100 }),
101 }
102 }
103
104 /// Render back to the canonical YAML name.
105 ///
106 /// # Examples
107 ///
108 /// ```
109 /// use dynomite::conf::Transport;
110 /// assert_eq!(Transport::Tcp.as_str(), "tcp");
111 /// assert_eq!(Transport::Quic.as_str(), "quic");
112 /// ```
113 #[must_use]
114 pub const fn as_str(self) -> &'static str {
115 match self {
116 Transport::Tcp => "tcp",
117 Transport::Quic => "quic",
118 }
119 }
120}
121
122impl fmt::Display for Transport {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.write_str(self.as_str())
125 }
126}
127
128/// Distribution algorithm selected by the pool's `distribution:`
129/// directive.
130///
131/// `Vnode` is the historical default and the only mode the C
132/// reference engine supported in the Rust port until
133/// `RandomSlicing` was added. `Ketama`, `Modula`, and `Random`
134/// are accepted for backward compatibility with the C
135/// configuration vocabulary; they collapse to `Vnode` at
136/// runtime and emit a deprecation warning at config-load time.
137///
138/// # Examples
139///
140/// ```
141/// use dynomite::conf::Distribution;
142/// assert_eq!(Distribution::parse("vnode").unwrap(), Distribution::Vnode);
143/// assert_eq!(
144/// Distribution::parse("random_slicing").unwrap(),
145/// Distribution::RandomSlicing
146/// );
147/// ```
148#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
149pub enum Distribution {
150 /// Per-rack continuum keyed by per-peer token lists. The
151 /// historical default.
152 Vnode,
153 /// Compatibility alias accepted by the C reference; collapsed
154 /// to [`Self::Vnode`] at runtime with a deprecation warning.
155 Ketama,
156 /// Compatibility alias accepted by the C reference; collapsed
157 /// to [`Self::Vnode`] at runtime with a deprecation warning.
158 Modula,
159 /// Compatibility alias accepted by the C reference; collapsed
160 /// to [`Self::Vnode`] at runtime with a deprecation warning.
161 Random,
162 /// Random-slicing distribution: a small, gap-free `(name,
163 /// size)` partition table over the 64-bit hash space. See
164 /// [`crate::hashkit::random_slicing`].
165 RandomSlicing,
166}
167
168impl Default for Distribution {
169 fn default() -> Self {
170 Self::Vnode
171 }
172}
173
174impl Distribution {
175 /// Parse a `distribution:` value (case-insensitive).
176 ///
177 /// # Errors
178 /// Returns [`ConfError::BadDistribution`] when the value is
179 /// not a recognised mode.
180 ///
181 /// # Examples
182 ///
183 /// ```
184 /// use dynomite::conf::Distribution;
185 /// assert_eq!(Distribution::parse("VNODE").unwrap(), Distribution::Vnode);
186 /// assert!(Distribution::parse("sphere").is_err());
187 /// ```
188 pub fn parse(s: &str) -> Result<Self, ConfError> {
189 Ok(match s.to_ascii_lowercase().as_str() {
190 "vnode" => Distribution::Vnode,
191 "ketama" => Distribution::Ketama,
192 "modula" => Distribution::Modula,
193 "random" => Distribution::Random,
194 "random_slicing" | "random-slicing" => Distribution::RandomSlicing,
195 _ => return Err(ConfError::BadDistribution(s.to_string())),
196 })
197 }
198
199 /// Render back to the canonical YAML name.
200 ///
201 /// # Examples
202 ///
203 /// ```
204 /// use dynomite::conf::Distribution;
205 /// assert_eq!(Distribution::Vnode.as_str(), "vnode");
206 /// assert_eq!(Distribution::RandomSlicing.as_str(), "random_slicing");
207 /// ```
208 #[must_use]
209 pub const fn as_str(self) -> &'static str {
210 match self {
211 Distribution::Vnode => "vnode",
212 Distribution::Ketama => "ketama",
213 Distribution::Modula => "modula",
214 Distribution::Random => "random",
215 Distribution::RandomSlicing => "random_slicing",
216 }
217 }
218
219 /// True for the modes that survived the C-to-Rust port
220 /// untouched; `Ketama`, `Modula`, and `Random` are accepted
221 /// for backward compatibility but collapse to `Vnode` at
222 /// runtime.
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// use dynomite::conf::Distribution;
228 /// assert!(Distribution::Vnode.is_supported());
229 /// assert!(Distribution::RandomSlicing.is_supported());
230 /// assert!(!Distribution::Ketama.is_supported());
231 /// ```
232 #[must_use]
233 pub const fn is_supported(self) -> bool {
234 matches!(self, Distribution::Vnode | Distribution::RandomSlicing)
235 }
236}
237
238impl fmt::Display for Distribution {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 f.write_str(self.as_str())
241 }
242}
243
244/// Datastore family selected by `data_store:`.
245///
246/// # Examples
247///
248/// ```
249/// use dynomite::conf::DataStore;
250/// assert_eq!(DataStore::from_int(0).unwrap(), DataStore::Redis);
251/// assert_eq!(DataStore::Redis.as_int(), 0);
252/// assert_eq!(DataStore::from_name("noxu").unwrap(), DataStore::Noxu);
253/// ```
254#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
255pub enum DataStore {
256 /// Redis (RESP) datastore. Encoded as `0` in YAML.
257 Redis,
258 /// Memcached ASCII datastore. Encoded as `1` in YAML.
259 Memcache,
260 /// In-process Noxu DB datastore (Riak-shaped). Encoded as
261 /// `2` in YAML, or as the string `noxu`. Selecting this
262 /// variant requires `dynomited` to be built with
263 /// `--features riak` and a sibling `noxu_path:` knob.
264 Noxu,
265}
266
267impl DataStore {
268 /// Parse a `data_store:` value as it appears in YAML.
269 ///
270 /// # Examples
271 ///
272 /// ```
273 /// use dynomite::conf::DataStore;
274 /// assert_eq!(DataStore::from_int(1).unwrap(), DataStore::Memcache);
275 /// assert_eq!(DataStore::from_int(2).unwrap(), DataStore::Noxu);
276 /// assert!(DataStore::from_int(7).is_err());
277 /// ```
278 pub fn from_int(v: i64) -> Result<Self, ConfError> {
279 match v {
280 0 => Ok(DataStore::Redis),
281 1 => Ok(DataStore::Memcache),
282 2 => Ok(DataStore::Noxu),
283 n => Err(ConfError::BadDataStore(n)),
284 }
285 }
286
287 /// Parse the textual form of a `data_store:` value, as
288 /// accepted in YAML alongside the integer form.
289 ///
290 /// Comparison is case-insensitive against `redis`,
291 /// `memcache`, `memcached`, and `noxu`.
292 ///
293 /// # Examples
294 ///
295 /// ```
296 /// use dynomite::conf::DataStore;
297 /// assert_eq!(DataStore::from_name("REDIS").unwrap(), DataStore::Redis);
298 /// assert!(DataStore::from_name("sql").is_err());
299 /// ```
300 pub fn from_name(s: &str) -> Result<Self, ConfError> {
301 if s.eq_ignore_ascii_case("redis") {
302 Ok(DataStore::Redis)
303 } else if s.eq_ignore_ascii_case("memcache") || s.eq_ignore_ascii_case("memcached") {
304 Ok(DataStore::Memcache)
305 } else if s.eq_ignore_ascii_case("noxu") {
306 Ok(DataStore::Noxu)
307 } else {
308 Err(ConfError::BadDataStore(-1))
309 }
310 }
311
312 /// Encode back to the small integer used in YAML.
313 ///
314 /// # Examples
315 ///
316 /// ```
317 /// use dynomite::conf::DataStore;
318 /// assert_eq!(DataStore::Memcache.as_int(), 1);
319 /// assert_eq!(DataStore::Noxu.as_int(), 2);
320 /// ```
321 pub fn as_int(self) -> i64 {
322 match self {
323 DataStore::Redis => 0,
324 DataStore::Memcache => 1,
325 DataStore::Noxu => 2,
326 }
327 }
328
329 /// Return the canonical lower-case textual name.
330 ///
331 /// # Examples
332 ///
333 /// ```
334 /// use dynomite::conf::DataStore;
335 /// assert_eq!(DataStore::Noxu.as_name(), "noxu");
336 /// ```
337 pub fn as_name(self) -> &'static str {
338 match self {
339 DataStore::Redis => "redis",
340 DataStore::Memcache => "memcache",
341 DataStore::Noxu => "noxu",
342 }
343 }
344}
345
346/// Inter-node security mode selected by `secure_server_option:`.
347///
348/// # Examples
349///
350/// ```
351/// use dynomite::conf::SecureServerOption;
352/// assert_eq!(
353/// SecureServerOption::parse("datacenter").unwrap(),
354/// SecureServerOption::Datacenter,
355/// );
356/// ```
357#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
358pub enum SecureServerOption {
359 /// No inter-node TLS.
360 None,
361 /// TLS only between racks (within a DC).
362 Rack,
363 /// TLS only between datacenters.
364 Datacenter,
365 /// TLS between all nodes.
366 All,
367}
368
369impl SecureServerOption {
370 /// Parse a `secure_server_option:` value, case-sensitively.
371 ///
372 /// # Examples
373 ///
374 /// ```
375 /// use dynomite::conf::SecureServerOption;
376 /// assert_eq!(SecureServerOption::parse("none").unwrap(), SecureServerOption::None);
377 /// assert!(SecureServerOption::parse("NONE").is_err());
378 /// ```
379 pub fn parse(s: &str) -> Result<Self, ConfError> {
380 match s {
381 "none" => Ok(SecureServerOption::None),
382 "rack" => Ok(SecureServerOption::Rack),
383 "datacenter" => Ok(SecureServerOption::Datacenter),
384 "all" => Ok(SecureServerOption::All),
385 other => Err(ConfError::BadSecure(other.to_string())),
386 }
387 }
388
389 /// Render back to the YAML string form.
390 ///
391 /// # Examples
392 ///
393 /// ```
394 /// use dynomite::conf::SecureServerOption;
395 /// assert_eq!(SecureServerOption::All.as_str(), "all");
396 /// ```
397 pub fn as_str(self) -> &'static str {
398 match self {
399 SecureServerOption::None => "none",
400 SecureServerOption::Rack => "rack",
401 SecureServerOption::Datacenter => "datacenter",
402 SecureServerOption::All => "all",
403 }
404 }
405}
406
407impl fmt::Display for SecureServerOption {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 f.write_str(self.as_str())
410 }
411}
412
413/// Quorum policy for read or write paths.
414///
415/// # Examples
416///
417/// ```
418/// use dynomite::conf::ConsistencyLevel;
419/// let lvl = ConsistencyLevel::parse("read_consistency", "DC_QUORUM").unwrap();
420/// assert_eq!(lvl, ConsistencyLevel::DcQuorum);
421/// ```
422#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
423pub enum ConsistencyLevel {
424 /// Single replica acknowledgement.
425 DcOne,
426 /// Majority within a single datacenter.
427 DcQuorum,
428 /// Majority within a single datacenter with checksum repair.
429 DcSafeQuorum,
430 /// Majority within every datacenter, with checksum repair.
431 DcEachSafeQuorum,
432}
433
434impl ConsistencyLevel {
435 /// Parse a `read_consistency` or `write_consistency` value.
436 ///
437 /// Comparison is case-insensitive against the canonical names
438 /// `DC_ONE`, `DC_QUORUM`, `DC_SAFE_QUORUM`, and
439 /// `DC_EACH_SAFE_QUORUM`.
440 ///
441 /// # Examples
442 ///
443 /// ```
444 /// use dynomite::conf::ConsistencyLevel;
445 /// assert_eq!(
446 /// ConsistencyLevel::parse("read_consistency", "dc_one").unwrap(),
447 /// ConsistencyLevel::DcOne,
448 /// );
449 /// assert!(ConsistencyLevel::parse("read_consistency", "nope").is_err());
450 /// ```
451 pub fn parse(field: &'static str, s: &str) -> Result<Self, ConfError> {
452 if s.eq_ignore_ascii_case("dc_one") {
453 Ok(ConsistencyLevel::DcOne)
454 } else if s.eq_ignore_ascii_case("dc_quorum") {
455 Ok(ConsistencyLevel::DcQuorum)
456 } else if s.eq_ignore_ascii_case("dc_safe_quorum") {
457 Ok(ConsistencyLevel::DcSafeQuorum)
458 } else if s.eq_ignore_ascii_case("dc_each_safe_quorum") {
459 Ok(ConsistencyLevel::DcEachSafeQuorum)
460 } else {
461 Err(ConfError::BadConsistency {
462 field,
463 value: s.to_string(),
464 })
465 }
466 }
467
468 /// Render back to the canonical YAML name.
469 ///
470 /// # Examples
471 ///
472 /// ```
473 /// use dynomite::conf::ConsistencyLevel;
474 /// assert_eq!(ConsistencyLevel::DcSafeQuorum.as_str(), "DC_SAFE_QUORUM");
475 /// ```
476 pub fn as_str(self) -> &'static str {
477 match self {
478 ConsistencyLevel::DcOne => "DC_ONE",
479 ConsistencyLevel::DcQuorum => "DC_QUORUM",
480 ConsistencyLevel::DcSafeQuorum => "DC_SAFE_QUORUM",
481 ConsistencyLevel::DcEachSafeQuorum => "DC_EACH_SAFE_QUORUM",
482 }
483 }
484}
485
486impl fmt::Display for ConsistencyLevel {
487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488 f.write_str(self.as_str())
489 }
490}
491
492/// Hash algorithm selected by `hash:`.
493///
494/// The names mirror the algorithm tags accepted by the YAML parser.
495/// Stage 3 owns the hashing math; this enum models only the configured
496/// choice so the parser can echo it back without depending on the
497/// hashkit module.
498///
499/// # Examples
500///
501/// ```
502/// use dynomite::conf::HashType;
503/// assert_eq!(HashType::parse("murmur3").unwrap(), HashType::Murmur3);
504/// assert_eq!(HashType::Md5.as_str(), "md5");
505/// ```
506#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
507pub enum HashType {
508 /// One-at-a-time hash.
509 OneAtATime,
510 /// MD5 (truncated for ketama).
511 Md5,
512 /// CRC-16.
513 Crc16,
514 /// CRC-32.
515 Crc32,
516 /// CRC-32 ARM.
517 Crc32a,
518 /// 64-bit FNV-1.
519 Fnv1_64,
520 /// 64-bit FNV-1a.
521 Fnv1a64,
522 /// 32-bit FNV-1.
523 Fnv1_32,
524 /// 32-bit FNV-1a.
525 Fnv1a32,
526 /// Paul Hsieh's hash.
527 Hsieh,
528 /// Murmur hash (32-bit, version 1).
529 Murmur,
530 /// Bob Jenkins's hash.
531 Jenkins,
532 /// Murmur hash 3 (128-bit).
533 Murmur3,
534 /// MurmurHash3 truncated to 64 bits (used by random
535 /// slicing).
536 #[allow(non_camel_case_types)]
537 Murmur3X64_64,
538}
539
540impl HashType {
541 /// Parse a `hash:` value (case-sensitive).
542 ///
543 /// # Examples
544 ///
545 /// ```
546 /// use dynomite::conf::HashType;
547 /// assert_eq!(HashType::parse("fnv1a_64").unwrap(), HashType::Fnv1a64);
548 /// assert!(HashType::parse("FNV1A_64").is_err());
549 /// ```
550 pub fn parse(s: &str) -> Result<Self, ConfError> {
551 Ok(match s {
552 "one_at_a_time" => HashType::OneAtATime,
553 "md5" => HashType::Md5,
554 "crc16" => HashType::Crc16,
555 "crc32" => HashType::Crc32,
556 "crc32a" => HashType::Crc32a,
557 "fnv1_64" => HashType::Fnv1_64,
558 "fnv1a_64" => HashType::Fnv1a64,
559 "fnv1_32" => HashType::Fnv1_32,
560 "fnv1a_32" => HashType::Fnv1a32,
561 "hsieh" => HashType::Hsieh,
562 "murmur" => HashType::Murmur,
563 "jenkins" => HashType::Jenkins,
564 "murmur3" => HashType::Murmur3,
565 "murmur3_x64_64" => HashType::Murmur3X64_64,
566 other => return Err(ConfError::BadHash(other.to_string())),
567 })
568 }
569
570 /// Render back to the canonical YAML name.
571 ///
572 /// # Examples
573 ///
574 /// ```
575 /// use dynomite::conf::HashType;
576 /// assert_eq!(HashType::Crc32a.as_str(), "crc32a");
577 /// ```
578 pub fn as_str(self) -> &'static str {
579 match self {
580 HashType::OneAtATime => "one_at_a_time",
581 HashType::Md5 => "md5",
582 HashType::Crc16 => "crc16",
583 HashType::Crc32 => "crc32",
584 HashType::Crc32a => "crc32a",
585 HashType::Fnv1_64 => "fnv1_64",
586 HashType::Fnv1a64 => "fnv1a_64",
587 HashType::Fnv1_32 => "fnv1_32",
588 HashType::Fnv1a32 => "fnv1a_32",
589 HashType::Hsieh => "hsieh",
590 HashType::Murmur => "murmur",
591 HashType::Jenkins => "jenkins",
592 HashType::Murmur3 => "murmur3",
593 HashType::Murmur3X64_64 => "murmur3_x64_64",
594 }
595 }
596}
597
598impl fmt::Display for HashType {
599 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
600 f.write_str(self.as_str())
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607
608 #[test]
609 fn data_store_round_trip() {
610 assert_eq!(DataStore::from_int(0).unwrap(), DataStore::Redis);
611 assert_eq!(DataStore::from_int(1).unwrap(), DataStore::Memcache);
612 assert_eq!(DataStore::from_int(2).unwrap(), DataStore::Noxu);
613 assert!(matches!(
614 DataStore::from_int(7),
615 Err(ConfError::BadDataStore(7))
616 ));
617 assert_eq!(DataStore::from_name("noxu").unwrap(), DataStore::Noxu);
618 assert_eq!(DataStore::from_name("REDIS").unwrap(), DataStore::Redis);
619 assert!(DataStore::from_name("sql").is_err());
620 assert_eq!(DataStore::Noxu.as_name(), "noxu");
621 }
622
623 #[test]
624 fn secure_round_trip() {
625 for s in ["none", "rack", "datacenter", "all"] {
626 assert_eq!(SecureServerOption::parse(s).unwrap().as_str(), s);
627 }
628 assert!(SecureServerOption::parse("nope").is_err());
629 }
630
631 #[test]
632 fn consistency_case_insensitive() {
633 assert_eq!(
634 ConsistencyLevel::parse("read_consistency", "dc_one").unwrap(),
635 ConsistencyLevel::DcOne
636 );
637 assert_eq!(
638 ConsistencyLevel::parse("read_consistency", "DC_SAFE_QUORUM").unwrap(),
639 ConsistencyLevel::DcSafeQuorum
640 );
641 assert!(ConsistencyLevel::parse("read_consistency", "garbage").is_err());
642 }
643
644 #[test]
645 fn hash_round_trip() {
646 for &name in &[
647 "one_at_a_time",
648 "md5",
649 "crc16",
650 "crc32",
651 "crc32a",
652 "fnv1_64",
653 "fnv1a_64",
654 "fnv1_32",
655 "fnv1a_32",
656 "hsieh",
657 "murmur",
658 "jenkins",
659 "murmur3",
660 "murmur3_x64_64",
661 ] {
662 assert_eq!(HashType::parse(name).unwrap().as_str(), name);
663 }
664 }
665
666 #[test]
667 fn distribution_round_trip() {
668 for &name in &["vnode", "ketama", "modula", "random", "random_slicing"] {
669 assert_eq!(Distribution::parse(name).unwrap().as_str(), name);
670 }
671 // Case-insensitive parse for back-compat with the C
672 // reference, which accepts upper-case.
673 assert_eq!(Distribution::parse("VNODE").unwrap(), Distribution::Vnode);
674 // Hyphenated alias accepted.
675 assert_eq!(
676 Distribution::parse("random-slicing").unwrap(),
677 Distribution::RandomSlicing
678 );
679 assert!(matches!(
680 Distribution::parse("sphere"),
681 Err(ConfError::BadDistribution(_))
682 ));
683 assert!(Distribution::Vnode.is_supported());
684 assert!(Distribution::RandomSlicing.is_supported());
685 assert!(!Distribution::Ketama.is_supported());
686 }
687
688 #[test]
689 fn distribution_default_is_vnode() {
690 assert_eq!(Distribution::default(), Distribution::Vnode);
691 }
692
693 #[test]
694 fn distribution_yaml_round_trip() {
695 // Serialise via serde, then parse back.
696 let raw = serde_yaml::to_string(&Distribution::RandomSlicing).unwrap();
697 let parsed: Distribution = serde_yaml::from_str(&raw).unwrap();
698 assert_eq!(parsed, Distribution::RandomSlicing);
699 }
700
701 #[test]
702 fn transport_round_trip() {
703 for &name in &["tcp", "quic"] {
704 assert_eq!(Transport::parse(name).unwrap().as_str(), name);
705 }
706 // Case-insensitive parse.
707 assert_eq!(Transport::parse("QUIC").unwrap(), Transport::Quic);
708 assert_eq!(Transport::parse("Tcp").unwrap(), Transport::Tcp);
709 assert!(Transport::parse("http").is_err());
710 }
711
712 #[test]
713 fn transport_default_is_tcp() {
714 assert_eq!(Transport::default(), Transport::Tcp);
715 }
716
717 #[test]
718 fn transport_yaml_round_trip() {
719 let raw = serde_yaml::to_string(&Transport::Quic).unwrap();
720 let parsed: Transport = serde_yaml::from_str(&raw).unwrap();
721 assert_eq!(parsed, Transport::Quic);
722 }
723}