107 lines
3.0 KiB
Rust
107 lines
3.0 KiB
Rust
#[derive(Clone, Debug, Default)]
|
|
pub struct Passport {
|
|
// "byr": birth_year
|
|
pub birth_year: Option<String>,
|
|
// "iyr": issue_year
|
|
pub issue_year: Option<String>,
|
|
// "eyr": expiration_year
|
|
pub expiration_year: Option<String>,
|
|
// "hgt": height
|
|
pub height: Option<String>,
|
|
// "hcl": hair_color
|
|
pub hair_color: Option<String>,
|
|
// "ecl": eye_color
|
|
pub eye_color: Option<String>,
|
|
// "pid": passport_id
|
|
pub passport_id: Option<String>,
|
|
// "cid": country_id
|
|
pub country_id: Option<String>,
|
|
}
|
|
|
|
impl Passport {
|
|
pub fn parse(text: &str) -> Self {
|
|
let mut result = Self::default();
|
|
for token in text.split_whitespace() {
|
|
let colon = token.find(':').expect("missing separator");
|
|
let key = &token[..colon];
|
|
let value = &token[colon+1..];
|
|
match key {
|
|
"byr" => result.birth_year = Some(value.into()),
|
|
"iyr" => result.issue_year = Some(value.into()),
|
|
"eyr" => result.expiration_year = Some(value.into()),
|
|
"hgt" => result.height = Some(value.into()),
|
|
"hcl" => result.hair_color = Some(value.into()),
|
|
"ecl" => result.eye_color = Some(value.into()),
|
|
"pid" => result.passport_id = Some(value.into()),
|
|
"cid" => result.country_id = Some(value.into()),
|
|
_ => panic!("unknown field {:?}", key),
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
pub fn parse_list(text: &str) -> Vec<Self> {
|
|
text.split("\n\n").map(Self::parse).collect()
|
|
}
|
|
|
|
pub fn require_all_but_country_id(&self) -> bool {
|
|
self.birth_year.is_some() &
|
|
self.issue_year.is_some() &
|
|
self.expiration_year.is_some() &
|
|
self.height.is_some() &
|
|
self.hair_color.is_some() &
|
|
self.eye_color.is_some() &
|
|
self.passport_id.is_some()
|
|
}
|
|
|
|
fn _valid(&self) -> Option<()> {
|
|
let birth_year = self.birth_year.as_ref()?.parse::<u16>().ok()?;
|
|
if birth_year < 1920 || birth_year > 2002 {
|
|
return None;
|
|
}
|
|
let issue_year = self.issue_year.as_ref()?.parse::<u16>().ok()?;
|
|
if issue_year < 2010 || issue_year > 2020 {
|
|
return None;
|
|
}
|
|
let expiration_year = self.expiration_year.as_ref()?.parse::<u16>().ok()?;
|
|
if expiration_year < 2020 || expiration_year > 2030 {
|
|
return None;
|
|
}
|
|
let height = self.height.as_ref()?;
|
|
if height.ends_with("cm") {
|
|
let height = height[..height.len() - 2].parse::<u16>().ok()?;
|
|
if height < 150 || height > 193 {
|
|
return None;
|
|
}
|
|
} else if height.ends_with("in") {
|
|
let height = height[..height.len() - 2].parse::<u16>().ok()?;
|
|
if height < 59 || height > 76 {
|
|
return None;
|
|
}
|
|
} else {
|
|
return None;
|
|
}
|
|
let hair_color = self.hair_color.as_ref()?;
|
|
if !hair_color.starts_with("#") || hair_color.len() != 7 {
|
|
return None;
|
|
}
|
|
if !hair_color[1..].chars().all(|c| (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
|
return None;
|
|
}
|
|
let eye_color = self.eye_color.as_ref()?;
|
|
match eye_color.as_str() {
|
|
"amb"|"blu"|"brn"|"gry"|"grn"|"hzl"|"oth" => (),
|
|
_ => return None,
|
|
}
|
|
let passport_id = self.passport_id.as_ref()?;
|
|
if passport_id.len() != 9 || !passport_id.chars().all(|c| c.is_ascii_digit()) {
|
|
return None;
|
|
}
|
|
Some(())
|
|
}
|
|
|
|
pub fn valid(&self) -> bool {
|
|
self._valid().is_some()
|
|
}
|
|
}
|