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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::io::prelude::*;
use bzip2::read::{BzDecoder};
use debug_print::debug_println;
const ZIPCODE_LENGTH: usize = 5;
static ZIPCODE_BYTES_BZIP: &'static [u8] = include_bytes!("zips.json.bz2");
lazy_static! {
static ref ZIPCODES: Vec<Zipcode> = {
let mut decompressor = BzDecoder::new(ZIPCODE_BYTES_BZIP);
let mut zipcode_json_bytes = String::new();
decompressor.read_to_string(&mut zipcode_json_bytes).unwrap();
match serde_json::from_str::<Vec<Zipcode>>(zipcode_json_bytes.as_str()) {
Ok(o) => o,
Err(e) => { panic!("failed to deserialize zipcode database: {}", e); }
}
};
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Invalid format, zipcode must be of the format: \"#####\" or \"#####-####\"")]
InvalidFormat,
#[error("Invalid characters, zipcode may only contain digits and \"-\".")]
InvalidCharacters,
}
pub type Result<T> = std::result::Result<T, Error>;
pub fn matching(zipcode: &str, zipcodes: Option<Vec<Zipcode>>) -> Result<Vec<Zipcode>> {
let zipcode = clean_zipcode(zipcode)?;
let zipcodes = zipcodes.as_ref().unwrap_or(&ZIPCODES);
let matching_zipcodes = zipcodes.iter().filter(|z| z.zip_code == zipcode).cloned().collect::<Vec<_>>();
debug_println!("is_real matched {:?} zipcodes", matching_zipcodes.len());
Ok(matching_zipcodes)
}
pub fn is_real(zipcode: &str) -> Result<bool> {
let zipcode = clean_zipcode(zipcode)?;
let matching_zipcodes = matching(zipcode, None)?;
Ok(!matching_zipcodes.is_empty())
}
pub fn filter_by<F>(filters: Vec<F>, zipcodes: Option<Vec<Zipcode>>) -> Result<Vec<Zipcode>>
where F: Fn(&Zipcode) -> bool {
let zipcodes = zipcodes.as_ref().unwrap_or(&ZIPCODES);
Ok(zipcodes.iter().filter(|z| {
for filter in &filters {
if !filter(z) {
return false;
}
}
true
}).cloned().collect::<Vec<_>>())
}
pub fn list_all() -> Vec<Zipcode> {
ZIPCODES.clone()
}
fn clean_zipcode(zipcode: &str) -> Result<&str> {
let split_zipcode = zipcode.split("-").collect::<Vec<_>>();
let zipcode = split_zipcode.first().ok_or(Error::InvalidFormat)?;
if zipcode.len() != ZIPCODE_LENGTH {
return Err(Error::InvalidFormat);
}
if !zipcode.chars().all(|c| c.is_numeric()) {
return Err(Error::InvalidCharacters);
}
Ok(zipcode)
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Zipcode {
pub acceptable_cities: Vec<String>,
pub active: bool,
pub area_codes: Vec<String>,
pub city: String,
pub country: String,
pub lat: String,
pub long: String,
pub state: String,
pub timezone: String,
pub unacceptable_cities: Vec<String>,
pub world_region: String,
pub zip_code: String,
pub zip_code_type: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_find_real_zipcodes() {
match is_real("06902") {
Ok(o) => {
assert!(o)
}
Err(e) => {
panic!("{}", e)
}
}
}
}