use crate::StateCode;
use std::fmt::{Debug, Display};
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum FIPSParserError {
InvalidDigit { found: char },
InvalidLength { expected: u32, found: u32 },
ValueExceedsCapacity { value: u64, capacity: u64 },
}
impl Display for FIPSParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FIPSParserError::InvalidDigit { found } => write!(f, "Invalid digit: {}", found),
FIPSParserError::InvalidLength { expected, found } => {
write!(f, "Expected {} characters, found {}", expected, found)
}
FIPSParserError::ValueExceedsCapacity { value, capacity } => {
write!(f, "Value {} exceeds max capacity {}", value, capacity)
}
}
}
}
impl Debug for FIPSParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self, f)
}
}
impl std::error::Error for FIPSParserError {}
pub type IResult<I, O, E = (I, FIPSParserError)> = Result<(I, O), E>;
pub type FIPSParseResult<'a, T> = IResult<&'a str, T>;
pub fn parse_decimal_digits_to_bits(
digit_count: u32,
bit_count: u8,
input: &str,
) -> IResult<&str, u64> {
let maximum_allowed_value = (1u64 << bit_count) - 1;
let mut input_bytes = input.as_bytes().iter();
let mut computed_value: u64 = 0;
for idx in 0..digit_count {
match input_bytes.next() {
Some(c) => {
if c.is_ascii_digit() {
computed_value = 10 * computed_value + (c - b'0') as u64;
} else {
return Err((
input,
FIPSParserError::InvalidDigit {
found: input.chars().nth(idx as usize).unwrap(),
},
));
}
}
None => {
return Err((
input,
FIPSParserError::InvalidLength {
expected: digit_count,
found: idx,
},
));
}
} }
if computed_value > maximum_allowed_value {
return Err((
input,
FIPSParserError::ValueExceedsCapacity {
value: computed_value,
capacity: maximum_allowed_value,
},
));
}
let remaining = &input[digit_count as usize..];
Ok((remaining, computed_value))
}
pub fn parse_state_code(input: &str) -> FIPSParseResult<StateCode> {
parse_decimal_digits_to_bits(2, 7, input).map(|(rest, value)| (rest, value as StateCode))
}
pub fn parse_county_code(input: &str) -> FIPSParseResult<u16> {
parse_decimal_digits_to_bits(3, 10, input).map(|(rest, value)| {
(rest, value as u16)
})
}
pub fn parse_tract_code(input: &str) -> FIPSParseResult<u32> {
parse_decimal_digits_to_bits(6, 20, input).map(|(rest, value)| {
(rest, value as u32)
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::USState;
#[test]
fn test_parse_decimal_digits_to_bits_valid_cases() {
assert_eq!(
parse_decimal_digits_to_bits(2, 6, "42rest"),
Ok(("rest", 42))
);
assert_eq!(
parse_decimal_digits_to_bits(3, 10, "123more"),
Ok(("more", 123))
);
assert_eq!(parse_decimal_digits_to_bits(1, 4, "7end"), Ok(("end", 7)));
assert_eq!(
parse_decimal_digits_to_bits(6, 20, "123456extra"),
Ok(("extra", 123456))
);
assert_eq!(
parse_decimal_digits_to_bits(2, 6, "63text"),
Ok(("text", 63))
);
assert_eq!(
parse_decimal_digits_to_bits(3, 10, "999text"),
Ok(("text", 999))
);
assert_eq!(
parse_decimal_digits_to_bits(6, 20, "999999text"),
Ok(("text", 999999))
);
}
#[test]
fn test_parse_decimal_digits_to_bits_invalid_cases() {
assert!(parse_decimal_digits_to_bits(2, 6, "4").is_err());
assert!(parse_decimal_digits_to_bits(3, 10, "12").is_err());
assert!(parse_decimal_digits_to_bits(2, 6, "a4rest").is_err());
assert!(parse_decimal_digits_to_bits(3, 10, "1x3more").is_err());
assert!(parse_decimal_digits_to_bits(2, 6, "64text").is_err()); assert_eq!(
parse_decimal_digits_to_bits(3, 8, "256text"),
Err((
"256text",
FIPSParserError::ValueExceedsCapacity {
value: 256,
capacity: 255
}
))
); assert_eq!(
parse_decimal_digits_to_bits(7, 20, "1048576text"),
Err((
"1048576text",
FIPSParserError::ValueExceedsCapacity {
value: 1048576,
capacity: 1048575
}
))
); }
#[test]
fn test_parse_state_code_valid_cases() {
assert!(parse_state_code("01rest").is_ok()); assert!(parse_state_code("06rest").is_ok()); assert!(parse_state_code("48rest").is_ok()); assert!(parse_state_code("36rest").is_ok());
let (remainder, _) = parse_state_code("42Pennsylvania").unwrap();
assert_eq!(remainder, "Pennsylvania");
}
#[test]
fn test_parse_state_code_invalid_cases() {
assert!(parse_state_code("A1rest").is_err());
assert!(parse_state_code("4").is_err());
assert!(parse_state_code("").is_err());
}
#[test]
fn test_parse_county_code_valid_cases() {
assert_eq!(parse_county_code("001rest").unwrap().1, 1);
assert_eq!(parse_county_code("123rest").unwrap().1, 123);
assert_eq!(parse_county_code("999rest").unwrap().1, 999);
let (remainder, _) = parse_county_code("001CountyName").unwrap();
assert_eq!(remainder, "CountyName");
}
#[test]
fn test_parse_county_code_invalid_cases() {
assert_eq!(
parse_county_code("x01rest"),
Err(("x01rest", FIPSParserError::InvalidDigit { found: 'x' }))
);
assert_eq!(
parse_county_code("12"),
Err((
"12",
FIPSParserError::InvalidLength {
expected: 3,
found: 2
}
))
);
assert_eq!(
parse_county_code(""),
Err((
"",
FIPSParserError::InvalidLength {
expected: 3,
found: 0
}
))
);
}
#[test]
fn test_parse_tract_code_valid_cases() {
assert_eq!(parse_tract_code("000001rest").unwrap().1, 1);
assert_eq!(parse_tract_code("123456rest").unwrap().1, 123456);
assert_eq!(parse_tract_code("999999rest").unwrap().1, 999999);
let (remainder, _) = parse_tract_code("123456TractInfo").unwrap();
assert_eq!(remainder, "TractInfo");
}
#[test]
fn test_parse_tract_code_invalid_cases() {
assert!(parse_tract_code("12345xrest").is_err());
assert!(parse_tract_code("12345").is_err());
assert!(parse_tract_code("").is_err());
}
#[test]
fn test_integration_fips_parsing() {
let input = "01001020100RestOfData";
let (remainder1, state) = parse_state_code(input).unwrap();
assert_eq!(remainder1, "001020100RestOfData");
let (remainder2, county) = parse_county_code(remainder1).unwrap();
assert_eq!(remainder2, "020100RestOfData");
let (remainder3, tract) = parse_tract_code(remainder2).unwrap();
assert_eq!(remainder3, "RestOfData");
assert_eq!(state, USState::AL.into());
assert_eq!(county, 1);
assert_eq!(tract, 20100);
}
}