My (hacky) solution to 2020 Advent of Code Challenge in Rust
use regex::Regex;
use std::collections::HashMap;
use std::io::{self, Read};

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

fn main() -> Result<()> {
    let mut input = String::new();
    io::stdin().read_to_string(&mut input)?;
    let parsed: Vec<HashMap<String, String>> = parse(input);
    part1(&parsed);
    part2(&parsed);
    Ok(())
}

fn part1(input: &Vec<HashMap<String, String>>) {
    let mut valid = 0;
    // Removed `cid` as it is optional
    let all_key = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"];
    for passport in input {
        if all_key.iter().all(|&key| passport.contains_key(key)) {
            valid += 1;
        }
    }
    println!("{}", valid);
}

fn part2(input: &Vec<HashMap<String, String>>) {
    let mut valid = 0;
    // Removed `cid` as it is optional
    let all_key = ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"];
    for passport in input {
        let mut local = 0;
        for key in &all_key {
            if !passport.contains_key(*key) {
                break;
            }
            if validate(key, passport.get(*key).unwrap()) {
                local += 1;
            }
        }
        if local == 7 {
            valid += 1;
        }
    }
    println!("{}", valid);
}

fn validate(key: &str, value: &String) -> bool {
    match key {
        "byr" => {
            let year = value.parse::<u32>().unwrap();
            if year >= 1920 && year <= 2002 {
                true
            } else {
                false
            }
        }
        "iyr" => {
            let year = value.parse::<u32>().unwrap();
            if year >= 2010 && year <= 2020 {
                true
            } else {
                false
            }
        }
        "eyr" => {
            let year = value.parse::<u32>().unwrap();
            if year >= 2020 && year <= 2030 {
                true
            } else {
                false
            }
        }
        "hgt" => {
            let is_cm = value.contains("cm");
            if is_cm {
                let value = value.replace("cm", "").parse::<u32>().unwrap();
                if value >= 150 && value <= 193 {
                    true
                } else {
                    false
                }
            } else {
                let value = value.replace("in", "").parse::<u32>().unwrap();
                if value >= 59 && value <= 76 {
                    true
                } else {
                    false
                }
            }
        }
        "hcl" => {
            let re = Regex::new(r"#[a-f0-9]{6}").unwrap();
            if re.is_match(value) {
                true
            } else {
                false
            }
        }
        "ecl" => {
            let valid = ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"];
            if valid.iter().any(|key| key == value) {
                true
            } else {
                false
            }
        }
        "pid" => {
            let re = Regex::new(r"^[0-9]{9}$").unwrap();
            if re.is_match(value) {
                true
            } else {
                false
            }
        }
        _ => false,
    }
}

fn parse(input: String) -> Vec<HashMap<String, String>> {
    let mut vec: Vec<HashMap<String, String>> = Vec::new();
    // Split on newline
    let parsed = input.trim().split("\n\n");
    for para in parsed {
        let mut map: HashMap<String, String> = HashMap::new();
        // Split on space to separate all the parts of a given passport.
        let para_intermidiate = para.replace("\n", " ");
        let para_parsed = para_intermidiate.split(" ");
        for part in para_parsed {
            // Split on : to separate key and value
            let mut split = part.split(":");
            let key = split.next().unwrap();
            let value = split.next().unwrap();
            map.insert(key.into(), value.into());
        }
        vec.push(map);
    }
    vec
}