1use bamcensus_core::model::identifier::{fips, Geoid, GeoidType, HasGeoidString};
2use std::rc::Rc;
3
4use super::DeserializeGeoidFn;
5
6#[derive(Debug, Clone)]
12pub enum AcsGeoidQuery {
13 State(Option<fips::State>),
14 County(Option<fips::State>, Option<fips::County>),
15 CountySubdivision(
16 fips::State,
17 Option<fips::County>,
18 Option<fips::CountySubdivision>,
19 ),
20 Place(Option<fips::State>, Option<fips::Place>),
21 CensusTract(fips::State, Option<fips::County>, Option<fips::CensusTract>),
22 BlockGroup(
23 fips::State,
24 Option<fips::County>,
25 Option<fips::CensusTract>,
26 Option<fips::BlockGroup>,
27 ),
28}
29
30impl AcsGeoidQuery {
31 pub fn new(geoid: Option<Geoid>, wildcard: Option<GeoidType>) -> Result<AcsGeoidQuery, String> {
77 use Geoid as G;
78 use GeoidType as GT;
79
80 match (geoid, wildcard) {
81 (None, None) => Err(String::from(
84 "cannot create query without at least a geoid or wildcard",
85 )),
86 (None, Some(GT::CountySubdivision)) => Err(String::from(
87 "cannot create county subdivision query without State Geoid",
88 )),
89 (None, Some(GT::CensusTract)) => Err(String::from(
90 "cannot create census tract query without State Geoid",
91 )),
92 (None, Some(GT::BlockGroup)) => Err(String::from(
93 "cannot create block group query without State + County Geoids",
94 )),
95 (_, Some(GT::Block)) => Err(String::from("acs does not support block-level queries")),
96 (Some(G::Block(_, _, _, _)), _) => {
97 Err(String::from("acs does not support block-level queries"))
98 }
99
100 (Some(Geoid::State(_)), Some(GT::BlockGroup)) => Err(String::from(
101 "cannot create block group query without County Geoid",
102 )),
103 (Some(Geoid::County(_, _)), Some(GT::Place)) => Err(String::from(
104 "cannot append a 'Place' wildcard to a County Geoid",
105 )),
106 (Some(G::CountySubdivision(_, _, _)), Some(GT::Place)) => Err(String::from(
107 "cannot append a 'Place' wildcard to a CountySubdivision Geoid",
108 )),
109 (Some(G::CountySubdivision(_, _, _)), Some(GT::CensusTract)) => Err(String::from(
110 "cannot append a 'CensusTract' wildcard to a CountySubdivision Geoid",
111 )),
112 (Some(G::CountySubdivision(_, _, _)), Some(GT::BlockGroup)) => Err(String::from(
113 "cannot append a 'BlockGroup' wildcard to a CountySubdivision Geoid",
114 )),
115 (Some(Geoid::Place(_, _)), Some(GT::County)) => Err(String::from(
116 "cannot append a 'County' wildcard to a Place Geoid",
117 )),
118 (Some(Geoid::Place(_, _)), Some(GT::CountySubdivision)) => Err(String::from(
119 "cannot append a 'CountySubdivision' wildcard to a Place Geoid",
120 )),
121 (Some(Geoid::Place(_, _)), Some(GT::CensusTract)) => Err(String::from(
122 "cannot append a 'CensusTract' wildcard to a Place Geoid",
123 )),
124 (Some(Geoid::Place(_, _)), Some(GT::BlockGroup)) => Err(String::from(
125 "cannot append a 'BlockGroup' wildcard to a Place Geoid",
126 )),
127 (Some(Geoid::CensusTract(_, _, _)), Some(GT::State)) => Err(String::from(
128 "cannot append a 'State' wildcard to a CensusTract Geoid",
129 )),
130 (Some(Geoid::CensusTract(_, _, _)), Some(GT::CountySubdivision)) => Err(String::from(
131 "cannot append a 'CountySubdivision' wildcard to a CensusTract Geoid",
132 )),
133 (Some(Geoid::CensusTract(_, _, _)), Some(GT::Place)) => Err(String::from(
134 "cannot append a 'Place' wildcard to a CensusTract Geoid",
135 )),
136 (Some(Geoid::BlockGroup(_, _, _, _)), Some(GT::State)) => Err(String::from(
137 "cannot append a 'State' wildcard to a BlockGroup Geoid",
138 )),
139 (Some(Geoid::BlockGroup(_, _, _, _)), Some(GT::CountySubdivision)) => Err(
140 String::from("cannot append a 'CountySubdivision' wildcard to a BlockGroup Geoid"),
141 ),
142 (Some(Geoid::BlockGroup(_, _, _, _)), Some(GT::Place)) => Err(String::from(
143 "cannot append a 'Place' wildcard to a BlockGroup Geoid",
144 )),
145
146 (None, Some(GT::State)) => Ok(AcsGeoidQuery::State(None)),
148 (None, Some(GT::County)) => Ok(AcsGeoidQuery::County(None, None)),
149 (None, Some(GT::Place)) => Ok(AcsGeoidQuery::Place(None, None)),
150
151 (Some(Geoid::State(_)), Some(GT::State)) => Ok(AcsGeoidQuery::State(None)),
154 (Some(Geoid::State(s)), Some(GT::County)) => Ok(AcsGeoidQuery::County(Some(s), None)),
155 (Some(Geoid::State(s)), Some(GT::CountySubdivision)) => {
156 Ok(AcsGeoidQuery::CountySubdivision(s, None, None))
157 }
158 (Some(Geoid::State(s)), Some(GT::Place)) => Ok(AcsGeoidQuery::Place(Some(s), None)),
159 (Some(Geoid::State(s)), Some(GT::CensusTract)) => {
160 Ok(AcsGeoidQuery::CensusTract(s, None, None))
161 }
162
163 (Some(Geoid::County(_, c)), Some(GT::State)) => {
165 Ok(AcsGeoidQuery::County(None, Some(c)))
166 }
167 (Some(Geoid::County(s, _)), Some(GT::County)) => {
168 Ok(AcsGeoidQuery::County(Some(s), None))
169 }
170 (Some(Geoid::County(s, c)), Some(GT::CountySubdivision)) => {
171 Ok(AcsGeoidQuery::CountySubdivision(s, Some(c), None))
172 }
173 (Some(Geoid::County(s, c)), Some(GT::CensusTract)) => {
174 Ok(AcsGeoidQuery::CensusTract(s, Some(c), None))
175 }
176 (Some(Geoid::County(s, c)), Some(GT::BlockGroup)) => {
177 Ok(AcsGeoidQuery::BlockGroup(s, Some(c), None, None))
178 }
179
180 (Some(G::CountySubdivision(st, ct, cs)), Some(GT::State)) => {
182 Ok(AcsGeoidQuery::CountySubdivision(st, Some(ct), Some(cs)))
183 }
184 (Some(G::CountySubdivision(s, _, cs)), Some(GT::County)) => {
185 Ok(AcsGeoidQuery::CountySubdivision(s, None, Some(cs)))
186 }
187 (Some(G::CountySubdivision(s, ct, _)), Some(GT::CountySubdivision)) => {
188 Ok(AcsGeoidQuery::CountySubdivision(s, Some(ct), None))
189 }
190
191 (Some(Geoid::Place(_, p)), Some(GT::State)) => Ok(AcsGeoidQuery::Place(None, Some(p))),
193 (Some(Geoid::Place(s, _)), Some(GT::Place)) => Ok(AcsGeoidQuery::Place(Some(s), None)),
194
195 (Some(Geoid::CensusTract(s, _, t)), Some(GT::County)) => {
197 Ok(AcsGeoidQuery::CensusTract(s, None, Some(t)))
198 }
199 (Some(Geoid::CensusTract(s, c, _)), Some(GT::CensusTract)) => {
200 Ok(AcsGeoidQuery::CensusTract(s, Some(c), None))
201 }
202 (Some(Geoid::CensusTract(s, c, t)), Some(GT::BlockGroup)) => {
203 Ok(AcsGeoidQuery::BlockGroup(s, Some(c), Some(t), None))
204 }
205
206 (Some(Geoid::BlockGroup(s, _, t, b)), Some(GT::County)) => {
208 Ok(AcsGeoidQuery::BlockGroup(s, None, Some(t), Some(b)))
209 }
210 (Some(Geoid::BlockGroup(s, c, _, b)), Some(GT::CensusTract)) => {
211 Ok(AcsGeoidQuery::BlockGroup(s, Some(c), None, Some(b)))
212 }
213 (Some(Geoid::BlockGroup(s, c, t, _)), Some(GT::BlockGroup)) => {
214 Ok(AcsGeoidQuery::BlockGroup(s, Some(c), Some(t), None))
215 }
216
217 (Some(Geoid::State(s)), None) => Ok(AcsGeoidQuery::State(Some(s))),
219 (Some(Geoid::County(s, c)), None) => Ok(AcsGeoidQuery::County(Some(s), Some(c))),
220 (Some(Geoid::CountySubdivision(s, ct, cs)), None) => {
221 Ok(AcsGeoidQuery::CountySubdivision(s, Some(ct), Some(cs)))
222 }
223 (Some(Geoid::Place(s, p)), None) => Ok(AcsGeoidQuery::Place(Some(s), Some(p))),
224 (Some(Geoid::CensusTract(s, c, t)), None) => {
225 Ok(AcsGeoidQuery::CensusTract(s, Some(c), Some(t)))
226 }
227 (Some(Geoid::BlockGroup(s, c, t, b)), None) => {
228 Ok(AcsGeoidQuery::BlockGroup(s, Some(c), Some(t), Some(b)))
229 }
230 }
231 }
232
233 pub fn to_query_key(&self) -> String {
236 use AcsGeoidQuery as G;
237 match self {
238 G::State(state) => match state {
239 None => String::from("&for=state:*"),
240 Some(s) => format!("&for=state:{}", s.geoid_string()),
241 },
242 G::County(state, county) => match (state, county) {
243 (None, None) => String::from("&for=county:*"),
244 (None, Some(c)) => format!("&for=county:{}", c.geoid_string()),
245 (Some(s), None) => format!("&for=county:*&in=state:{}", s.geoid_string()),
246 (Some(s), Some(c)) => format!(
247 "&for=county:{}&in=state:{}",
248 c.geoid_string(),
249 s.geoid_string()
250 ),
251 },
252 G::CountySubdivision(state, county, cousub) => match (county, cousub) {
253 (None, None) => format!(
254 "&for=county%20subdivision:*&in=state:{}&in=county:*",
255 state.geoid_string(),
256 ),
257 (None, Some(cs)) => format!(
258 "&for=county%20subdivision:{}&in=state:{}&in=county:*",
259 cs.geoid_string(),
260 state.geoid_string(),
261 ),
262 (Some(co), None) => format!(
263 "&for=county%20subdivision:*&in=state:{}&in=county:{}",
264 state.geoid_string(),
265 co.geoid_string(),
266 ),
267 (Some(co), Some(cs)) => format!(
268 "&for=county%20subdivision:{}&in=state:{}&in=county:{}",
269 cs.geoid_string(),
270 state.geoid_string(),
271 co.geoid_string()
272 ),
273 },
274 G::Place(state, place) => match (state, place) {
275 (None, None) => String::from("&for=place:*"),
276 (None, Some(pl)) => format!("&for=place:{}&in=state:*", pl.geoid_string()),
277 (Some(st), None) => format!("&for=place:*&in=state:{}", st.geoid_string()),
278 (Some(st), Some(pl)) => format!(
279 "&for=place:{}&in=state:{}",
280 pl.geoid_string(),
281 st.geoid_string()
282 ),
283 },
284 G::CensusTract(state, county, tract) => match (county, tract) {
285 (None, None) => format!("&for=tract:*&in=state:{}", state.geoid_string()),
286 (None, Some(tr)) => format!(
287 "&for=tract:{}&in=state:{}",
288 tr.geoid_string(),
289 state.geoid_string()
290 ),
291 (Some(co), None) => format!(
292 "&for=tract:*&in=state:{}&in=county:{}",
293 state.geoid_string(),
294 co.geoid_string()
295 ),
296 (Some(co), Some(tr)) => format!(
297 "&for=tract:{}&in=state:{}&in=county:{}",
298 tr.geoid_string(),
299 state.geoid_string(),
300 co.geoid_string()
301 ),
302 },
303 G::BlockGroup(state, county, tract, block_group) => {
304 match (county, tract, block_group) {
305 (None, None, None) => format!(
306 "&for=block%20group:*&in=state:{}&in=county:*&in=tract:*",
307 state.geoid_string()
308 ),
309 (None, None, Some(b)) => format!(
310 "&for=block%20group:{}&in=state:{}&in=county:*&in=tract:*",
311 b.geoid_string(),
312 state.geoid_string()
313 ),
314 (None, Some(t), None) => format!(
315 "&for=block%20group:*&in=state:{}&in=county:*&in=tract:{}",
316 state.geoid_string(),
317 t.geoid_string(),
318 ),
319 (None, Some(t), Some(b)) => format!(
320 "&for=block%20group:{}&in=state:{}&in=county:*&in=tract:{}",
321 b.geoid_string(),
322 state.geoid_string(),
323 t.geoid_string(),
324 ),
325 (Some(c), None, None) => format!(
326 "&for=block%20group:*&in=state:{}&in=county:{}&in=tract:*",
327 state.geoid_string(),
328 c.geoid_string()
329 ),
330 (Some(c), None, Some(b)) => format!(
331 "&for=block%20group:{}&in=state:{}&in=county:{}&in=tract:*",
332 b.geoid_string(),
333 state.geoid_string(),
334 c.geoid_string(),
335 ),
336 (Some(c), Some(t), None) => format!(
337 "&for=block%20group:*&in=state:{}&in=county:{}&in=tract:{}",
338 state.geoid_string(),
339 c.geoid_string(),
340 t.geoid_string(),
341 ),
342 (Some(c), Some(t), Some(b)) => format!(
343 "&for=block%20group:{}&in=state:{}&in=county:{}&in=tract:{}",
344 b.geoid_string(),
345 state.geoid_string(),
346 c.geoid_string(),
347 t.geoid_string()
348 ),
349 }
350 }
351 }
352 }
353
354 pub fn response_geoid_type(&self) -> GeoidType {
355 use AcsGeoidQuery as G;
356 match self {
357 G::State(_) => GeoidType::State,
358 G::County(_, _) => GeoidType::County,
359 G::CountySubdivision(_, _, _) => GeoidType::CountySubdivision,
360 G::Place(_, _) => GeoidType::Place,
361 G::CensusTract(_, _, _) => GeoidType::CensusTract,
362 G::BlockGroup(_, _, _, _) => GeoidType::BlockGroup,
363 }
364 }
365
366 pub fn response_column_names(&self) -> Vec<String> {
367 use AcsGeoidQuery as G;
368 match self {
369 G::State(_) => vec![String::from("state")],
370 G::County(_, _) => vec![String::from("state"), String::from("county")],
371 G::CountySubdivision(_, _, _) => vec![
372 String::from("state"),
373 String::from("county"),
374 String::from("county subdivision"),
375 ],
376 G::Place(_, _) => vec![String::from("state"), String::from("place")],
377 G::CensusTract(_, _, _) => vec![
378 String::from("state"),
379 String::from("county"),
380 String::from("tract"),
381 ],
382 G::BlockGroup(_, _, _, _) => vec![
383 String::from("state"),
384 String::from("county"),
385 String::from("tract"),
386 String::from("block group"),
387 ],
388 }
389 }
390
391 pub fn response_column_count(&self) -> usize {
392 match self {
393 AcsGeoidQuery::State(_) => 1,
394 AcsGeoidQuery::County(_, _) => 2,
395 AcsGeoidQuery::CountySubdivision(_, _, _) => 3,
396 AcsGeoidQuery::Place(_, _) => 2,
397 AcsGeoidQuery::CensusTract(_, _, _) => 3,
398 AcsGeoidQuery::BlockGroup(_, _, _, _) => 4,
399 }
400 }
401
402 pub fn get_geoid_type(&self) -> GeoidType {
403 match self {
404 AcsGeoidQuery::State(_) => GeoidType::State,
405 AcsGeoidQuery::County(_, _) => GeoidType::County,
406 AcsGeoidQuery::CountySubdivision(_, _, _) => GeoidType::CountySubdivision,
407 AcsGeoidQuery::Place(_, _) => GeoidType::Place,
408 AcsGeoidQuery::CensusTract(_, _, _) => GeoidType::CensusTract,
409 AcsGeoidQuery::BlockGroup(_, _, _, _) => GeoidType::BlockGroup,
410 }
411 }
412
413 pub fn build_deserialize_geoid_fn(&self) -> DeserializeGeoidFn {
417 let geoid_type = self.get_geoid_type();
418 let f: DeserializeGeoidFn = Rc::new(move |vals| {
419 let strings = as_strings(&vals)?;
420 geoid_type.geoid_from_slice_of_strings(&strings)
421 });
422 f
423 }
424}
425
426fn as_strings(arr: &[serde_json::Value]) -> Result<Vec<String>, String> {
428 arr.iter()
429 .map(|v| {
430 v.as_str()
431 .ok_or_else(|| format!("raw geoid value should be string, found {v}"))
432 .map(String::from)
433 })
434 .collect::<Result<Vec<_>, String>>()
435}