#[derive(Clone, Debug, Default)] pub struct Passport { // "byr": birth_year pub birth_year: Option, // "iyr": issue_year pub issue_year: Option, // "eyr": expiration_year pub expiration_year: Option, // "hgt": height pub height: Option, // "hcl": hair_color pub hair_color: Option, // "ecl": eye_color pub eye_color: Option, // "pid": passport_id pub passport_id: Option, // "cid": country_id pub country_id: Option, } 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 { 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::().ok()?; if birth_year < 1920 || birth_year > 2002 { return None; } let issue_year = self.issue_year.as_ref()?.parse::().ok()?; if issue_year < 2010 || issue_year > 2020 { return None; } let expiration_year = self.expiration_year.as_ref()?.parse::().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::().ok()?; if height < 150 || height > 193 { return None; } } else if height.ends_with("in") { let height = height[..height.len() - 2].parse::().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() } }