ferric_fred/
region_type.rs1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum RegionType {
18 State,
20 County,
22 Msa,
24 Country,
26 Bea,
28 Other(String),
31}
32
33impl RegionType {
34 fn from_token(token: &str) -> Self {
36 match token {
37 "state" => Self::State,
38 "county" => Self::County,
39 "msa" => Self::Msa,
40 "country" => Self::Country,
41 "bea" => Self::Bea,
42 other => Self::Other(other.to_owned()),
43 }
44 }
45
46 pub fn query_code(&self) -> &str {
50 match self {
51 Self::State => "state",
52 Self::County => "county",
53 Self::Msa => "msa",
54 Self::Country => "country",
55 Self::Bea => "bea",
56 Self::Other(token) => token,
57 }
58 }
59}
60
61impl fmt::Display for RegionType {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.write_str(self.query_code())
64 }
65}
66
67impl<'de> Deserialize<'de> for RegionType {
68 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
69 where
70 D: Deserializer<'de>,
71 {
72 let token = String::deserialize(deserializer)?;
73 Ok(Self::from_token(&token))
74 }
75}
76
77impl Serialize for RegionType {
78 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
80 where
81 S: Serializer,
82 {
83 serializer.serialize_str(self.query_code())
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn known_token_maps_to_variant() {
93 assert_eq!(
94 serde_json::from_str::<RegionType>("\"state\"").unwrap(),
95 RegionType::State
96 );
97 }
98
99 #[test]
100 fn unknown_token_is_preserved_verbatim() {
101 assert_eq!(
102 serde_json::from_str::<RegionType>("\"frb\"").unwrap(),
103 RegionType::Other("frb".to_owned())
104 );
105 }
106
107 #[test]
108 fn query_codes_match_fred() {
109 assert_eq!(RegionType::State.query_code(), "state");
110 assert_eq!(RegionType::Msa.query_code(), "msa");
111 assert_eq!(RegionType::Other("frb".to_owned()).query_code(), "frb");
112 }
113
114 #[test]
115 fn serializes_to_its_token_and_round_trips() {
116 assert_eq!(
117 serde_json::to_string(&RegionType::Country).unwrap(),
118 "\"country\""
119 );
120 let other = RegionType::Other("censusregion".to_owned());
121 let json = serde_json::to_string(&other).unwrap();
122 assert_eq!(serde_json::from_str::<RegionType>(&json).unwrap(), other);
123 }
124
125 const KNOWN: &[RegionType] = &[
129 RegionType::State,
130 RegionType::County,
131 RegionType::Msa,
132 RegionType::Country,
133 RegionType::Bea,
134 ];
135
136 #[test]
137 fn every_known_variant_round_trips_through_serde() {
138 for variant in KNOWN {
139 let json = serde_json::to_string(variant).unwrap();
140 let back: RegionType = serde_json::from_str(&json).unwrap();
141 assert_eq!(
142 &back, variant,
143 "{variant:?} serialized to {json} but deserialized back to {back:?} — a \
144 named variant whose token is not wired into `from_token` is silently \
145 swallowed by `Other`, the exact bug ADR-0030 pins"
146 );
147 }
148 }
149
150 #[test]
151 fn known_list_is_exhaustive() {
152 fn account_for_every_variant(region_type: &RegionType) {
156 match region_type {
157 RegionType::State
158 | RegionType::County
159 | RegionType::Msa
160 | RegionType::Country
161 | RegionType::Bea
162 | RegionType::Other(_) => {}
163 }
164 }
165 account_for_every_variant(&RegionType::State);
166 assert!(
167 KNOWN.iter().all(|v| !matches!(v, RegionType::Other(_))),
168 "`KNOWN` must list only named variants, never `Other`"
169 );
170 }
171}