Skip to main content

ferric_fred/
shape_type.rs

1use std::fmt;
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5/// The kind of geographic boundary set to fetch from GeoFRED / Maps (the
6/// `shape` parameter of `geofred/shapes/file`) — which regions' polygons the
7/// returned [`ShapeFile`](crate::ShapeFile) covers.
8///
9/// Carried on the wire as a lowercase token (e.g. `"bea"`). Tokens this version
10/// does not name round-trip verbatim through [`ShapeType::Other`] rather than
11/// failing (ADR-0005), and the enum is `#[non_exhaustive]` so new variants can
12/// be promoted out of `Other` later without breaking callers' `match` arms.
13#[derive(Debug, Clone, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum ShapeType {
16    /// U.S. state boundaries.
17    State,
18    /// U.S. county boundaries.
19    County,
20    /// Metropolitan Statistical Area boundaries.
21    Msa,
22    /// Country boundaries.
23    Country,
24    /// Bureau of Economic Analysis region boundaries.
25    Bea,
26    /// A shape type FRED accepts that this version does not name; holds the raw
27    /// token verbatim.
28    Other(String),
29}
30
31impl ShapeType {
32    /// Map a GeoFRED shape token to a [`ShapeType`].
33    fn from_token(token: &str) -> Self {
34        match token {
35            "state" => Self::State,
36            "county" => Self::County,
37            "msa" => Self::Msa,
38            "country" => Self::Country,
39            "bea" => Self::Bea,
40            other => Self::Other(other.to_owned()),
41        }
42    }
43
44    /// The GeoFRED query token for this shape (the `shape` parameter): `state`,
45    /// `county`, `msa`, `country`, `bea`. For [`ShapeType::Other`] this returns
46    /// the raw token.
47    pub fn query_code(&self) -> &str {
48        match self {
49            Self::State => "state",
50            Self::County => "county",
51            Self::Msa => "msa",
52            Self::Country => "country",
53            Self::Bea => "bea",
54            Self::Other(token) => token,
55        }
56    }
57}
58
59impl fmt::Display for ShapeType {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.write_str(self.query_code())
62    }
63}
64
65impl<'de> Deserialize<'de> for ShapeType {
66    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67    where
68        D: Deserializer<'de>,
69    {
70        let token = String::deserialize(deserializer)?;
71        Ok(Self::from_token(&token))
72    }
73}
74
75impl Serialize for ShapeType {
76    /// Serializes as the GeoFRED token — symmetric with [`Deserialize`].
77    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78    where
79        S: Serializer,
80    {
81        serializer.serialize_str(self.query_code())
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn known_token_maps_to_variant() {
91        assert_eq!(
92            serde_json::from_str::<ShapeType>("\"bea\"").unwrap(),
93            ShapeType::Bea
94        );
95    }
96
97    #[test]
98    fn unknown_token_is_preserved_verbatim() {
99        assert_eq!(
100            serde_json::from_str::<ShapeType>("\"necta\"").unwrap(),
101            ShapeType::Other("necta".to_owned())
102        );
103    }
104
105    #[test]
106    fn query_codes_match_fred() {
107        assert_eq!(ShapeType::Bea.query_code(), "bea");
108        assert_eq!(ShapeType::State.query_code(), "state");
109        assert_eq!(ShapeType::Other("necta".to_owned()).query_code(), "necta");
110    }
111}