Skip to main content

ferric_fred/
region_type.rs

1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5/// A geographic region granularity for a GeoFRED / Maps request (the
6/// `region_type` parameter of `geofred/regional/data`) — the level the data is
7/// broken down to.
8///
9/// Carried on the wire as a lowercase token (e.g. `"state"`). Tokens this
10/// version does not name — e.g. Federal Reserve districts (`"frb"`) or census
11/// divisions — round-trip verbatim through [`RegionType::Other`] rather than
12/// failing (ADR-0005: forward-compatibility over strictness), and the enum is
13/// `#[non_exhaustive]` so new variants can be promoted out of `Other` later
14/// without breaking callers' `match` arms.
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum RegionType {
18    /// U.S. state.
19    State,
20    /// U.S. county.
21    County,
22    /// Metropolitan Statistical Area.
23    Msa,
24    /// Country.
25    Country,
26    /// Bureau of Economic Analysis region.
27    Bea,
28    /// A region type FRED accepts that this version does not name; holds the raw
29    /// token verbatim.
30    Other(String),
31}
32
33impl RegionType {
34    /// Map a GeoFRED region-type token to a [`RegionType`].
35    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    /// The GeoFRED query token for this region type (the `region_type`
47    /// parameter): `state`, `county`, `msa`, `country`, `bea`. For
48    /// [`RegionType::Other`] this returns the raw token.
49    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    /// Serializes as the GeoFRED token — symmetric with [`Deserialize`].
79    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}