1use std::fmt;
10use std::str::FromStr;
11
12use serde::{Deserialize, Serialize};
13
14#[derive(
17 Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default,
18)]
19#[serde(rename_all = "lowercase")]
20pub enum SyncMode {
21 #[default]
24 Full,
25 Metadata,
28 Off,
30}
31
32impl SyncMode {
33 const fn rank(self) -> u8 {
35 match self {
36 SyncMode::Full => 2,
37 SyncMode::Metadata => 1,
38 SyncMode::Off => 0,
39 }
40 }
41
42 pub fn most_restrictive(self, other: SyncMode) -> SyncMode {
46 if self.rank() <= other.rank() { self } else { other }
47 }
48
49 pub fn syncs_blob(self) -> bool {
51 matches!(self, SyncMode::Full)
52 }
53
54 pub fn syncs_state(self) -> bool {
57 matches!(self, SyncMode::Full | SyncMode::Metadata)
58 }
59
60 pub fn syncs_anything(self) -> bool {
62 !matches!(self, SyncMode::Off)
63 }
64
65 pub fn as_str(self) -> &'static str {
67 match self {
68 SyncMode::Full => "full",
69 SyncMode::Metadata => "metadata",
70 SyncMode::Off => "off",
71 }
72 }
73
74 pub fn from_token_or_default(s: &str) -> SyncMode {
78 s.parse().unwrap_or_default()
79 }
80}
81
82impl fmt::Display for SyncMode {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 f.write_str(self.as_str())
85 }
86}
87
88impl FromStr for SyncMode {
89 type Err = ();
90
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 match s.trim().to_ascii_lowercase().as_str() {
93 "full" => Ok(SyncMode::Full),
94 "metadata" | "meta" => Ok(SyncMode::Metadata),
95 "off" | "disabled" | "none" => Ok(SyncMode::Off),
96 _ => Err(()),
97 }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn default_is_full() {
107 assert_eq!(SyncMode::default(), SyncMode::Full);
108 }
109
110 #[test]
111 fn most_restrictive_takes_the_lower_rank() {
112 use SyncMode::*;
113 assert_eq!(Full.most_restrictive(Metadata), Metadata);
114 assert_eq!(Metadata.most_restrictive(Full), Metadata);
115 assert_eq!(Full.most_restrictive(Off), Off);
116 assert_eq!(Off.most_restrictive(Metadata), Off);
117 assert_eq!(Full.most_restrictive(Full), Full);
118 assert_eq!(Metadata.most_restrictive(Off), Off.most_restrictive(Metadata));
120 }
121
122 #[test]
123 fn capability_predicates() {
124 assert!(SyncMode::Full.syncs_blob());
125 assert!(!SyncMode::Metadata.syncs_blob());
126 assert!(!SyncMode::Off.syncs_blob());
127
128 assert!(SyncMode::Full.syncs_state());
129 assert!(SyncMode::Metadata.syncs_state());
130 assert!(!SyncMode::Off.syncs_state());
131
132 assert!(SyncMode::Full.syncs_anything());
133 assert!(SyncMode::Metadata.syncs_anything());
134 assert!(!SyncMode::Off.syncs_anything());
135 }
136
137 #[test]
138 fn parse_round_trips_and_accepts_aliases() {
139 for m in [SyncMode::Full, SyncMode::Metadata, SyncMode::Off] {
140 assert_eq!(m.as_str().parse::<SyncMode>(), Ok(m));
141 }
142 assert_eq!("meta".parse(), Ok(SyncMode::Metadata));
143 assert_eq!(" OFF ".parse(), Ok(SyncMode::Off));
144 assert_eq!("disabled".parse(), Ok(SyncMode::Off));
145 assert_eq!(SyncMode::from_token_or_default("nonsense"), SyncMode::Full);
146 }
147
148 #[test]
149 fn serde_uses_lowercase_tokens() {
150 let json = serde_json::to_string(&SyncMode::Metadata).unwrap();
151 assert_eq!(json, "\"metadata\"");
152 let back: SyncMode = serde_json::from_str("\"off\"").unwrap();
153 assert_eq!(back, SyncMode::Off);
154 }
155}