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