1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum Frequency {
17 Daily,
19 Weekly,
21 Biweekly,
23 Monthly,
25 Quarterly,
27 Semiannual,
29 Annual,
31 Other(String),
34}
35
36impl Frequency {
37 fn from_label(label: &str) -> Self {
39 match label {
40 "Daily" => Self::Daily,
41 "Weekly" => Self::Weekly,
42 "Biweekly" => Self::Biweekly,
43 "Monthly" => Self::Monthly,
44 "Quarterly" => Self::Quarterly,
45 "Semiannual" => Self::Semiannual,
46 "Annual" => Self::Annual,
47 other => Self::Other(other.to_owned()),
48 }
49 }
50
51 pub fn label(&self) -> &str {
53 match self {
54 Self::Daily => "Daily",
55 Self::Weekly => "Weekly",
56 Self::Biweekly => "Biweekly",
57 Self::Monthly => "Monthly",
58 Self::Quarterly => "Quarterly",
59 Self::Semiannual => "Semiannual",
60 Self::Annual => "Annual",
61 Self::Other(label) => label,
62 }
63 }
64
65 pub fn query_code(&self) -> &str {
70 match self {
71 Self::Daily => "d",
72 Self::Weekly => "w",
73 Self::Biweekly => "bw",
74 Self::Monthly => "m",
75 Self::Quarterly => "q",
76 Self::Semiannual => "sa",
77 Self::Annual => "a",
78 Self::Other(label) => label,
79 }
80 }
81}
82
83impl fmt::Display for Frequency {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 f.write_str(self.label())
86 }
87}
88
89impl<'de> Deserialize<'de> for Frequency {
90 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91 where
92 D: Deserializer<'de>,
93 {
94 let label = String::deserialize(deserializer)?;
95 Ok(Self::from_label(&label))
96 }
97}
98
99impl Serialize for Frequency {
100 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103 where
104 S: Serializer,
105 {
106 serializer.serialize_str(self.label())
107 }
108}
109
110#[cfg(feature = "schemars")]
114impl schemars::JsonSchema for Frequency {
115 fn schema_name() -> std::borrow::Cow<'static, str> {
116 "Frequency".into()
117 }
118
119 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
120 <String as schemars::JsonSchema>::json_schema(generator)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn known_label_maps_to_variant() {
130 assert_eq!(
131 serde_json::from_str::<Frequency>("\"Monthly\"").unwrap(),
132 Frequency::Monthly
133 );
134 }
135
136 #[test]
137 fn unknown_label_is_preserved_verbatim() {
138 assert_eq!(
139 serde_json::from_str::<Frequency>("\"Weekly, Ending Friday\"").unwrap(),
140 Frequency::Other("Weekly, Ending Friday".to_owned())
141 );
142 }
143
144 #[test]
145 fn display_round_trips_the_label() {
146 assert_eq!(Frequency::Annual.to_string(), "Annual");
147 assert_eq!(
148 Frequency::Other("Weekly, Ending Friday".to_owned()).to_string(),
149 "Weekly, Ending Friday"
150 );
151 }
152
153 #[test]
154 fn query_codes_match_fred() {
155 assert_eq!(Frequency::Monthly.query_code(), "m");
156 assert_eq!(Frequency::Semiannual.query_code(), "sa");
157 assert_eq!(Frequency::Daily.query_code(), "d");
158 }
159
160 #[test]
161 fn serializes_to_its_label_and_round_trips() {
162 assert_eq!(
163 serde_json::to_string(&Frequency::Monthly).unwrap(),
164 "\"Monthly\""
165 );
166 let other = Frequency::Other("Weekly, Ending Friday".to_owned());
167 let json = serde_json::to_string(&other).unwrap();
168 assert_eq!(json, "\"Weekly, Ending Friday\"");
169 assert_eq!(serde_json::from_str::<Frequency>(&json).unwrap(), other);
170 }
171}