diff --git a/src/lib.rs b/src/lib.rs index d73b240..d821b7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,9 @@ pub fn run(input: &str) -> Result { Err(ParseError::ExpectedUnit) => { return Err("Expected a unit".to_string()); } + Err(ParseError::AmbiguousUnit(unit, prefix1, prefix2)) => { + return Err(format!("Ambiguous unit '{unit}', use either '{prefix1} {unit}' or '{prefix2} {unit}'")); + } }; if non_metric.len() == 0 { @@ -110,6 +113,7 @@ mod test { assert_eq!(run("1 tf"), Err("Unknown unit: tf".to_string())); assert_eq!(run("1"), Err("Expected a unit".to_string())); assert_eq!(run(""), Err("Expected quantity or quantities to convert".to_string())); + assert_eq!(run("1 fl oz"), Err("Ambiguous unit 'fl oz', use either 'imp fl oz' or 'US fl oz'".to_string())); assert_eq!(run("6 ft 1 lbs"), Err("Incompatible units: feet, pounds".to_string())); assert_eq!(run("0 °F 0 °F"), Err("Cannot sum together temperatures".to_string())); } diff --git a/src/parse.rs b/src/parse.rs index 53ad3cd..18ded43 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -11,6 +11,7 @@ pub enum ParseError { UnexpectedUnit(String), UnknownUnit(String), ExpectedUnit, + AmbiguousUnit(String, &'static str, &'static str), } pub fn parse(input: &str) -> Result, ParseError> { @@ -280,6 +281,27 @@ fn parse_unit(input: String) -> Result { "us gallons" => Ok(NonMetric::USGallon), "us gal" => Ok(NonMetric::USGallon), + // Ambiguous units + "fluid ounce" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "fluid ounces" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "fl oz" => Err(ParseError::AmbiguousUnit(input, "imp", "US")), + "fl. oz." => Err(ParseError::AmbiguousUnit(input, "imp", "US")), + "oz. fl." => Err(ParseError::AmbiguousUnit(input, "imp", "US")), + + "pint" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "pints" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "pt" => Err(ParseError::AmbiguousUnit(input, "imp", "US")), + "p" => Err(ParseError::AmbiguousUnit(input, "imp", "US")), + + "quart" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "quarts" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "qt" => Err(ParseError::AmbiguousUnit(input, "imp", "US")), + + "gallon" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "gallons" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")), + "gal" => Err(ParseError::AmbiguousUnit(input, "imp", "US")), + + // Unknown unit _ => Err(ParseError::UnknownUnit(input)), } } @@ -374,6 +396,7 @@ mod test { assert_eq!(parse("ft"), Err(ParseError::UnexpectedUnit("ft".to_string()))); assert_eq!(parse("5 tf"), Err(ParseError::UnknownUnit("tf".to_string()))); assert_eq!(parse("12"), Err(ParseError::ExpectedUnit)); + assert_eq!(parse("1 gallon"), Err(ParseError::AmbiguousUnit("gallon".to_string(), "imperial", "US"))); } #[test] @@ -608,8 +631,113 @@ mod test { assert_eq!(parse_unit("us gallon".to_string()), Ok(NonMetric::USGallon)); assert_eq!(parse_unit("us gallons".to_string()), Ok(NonMetric::USGallon)); assert_eq!(parse_unit("us gal".to_string()), Ok(NonMetric::USGallon)); + } - // Unknown unit + #[test] + fn ambiguous_units() { + assert_eq!( + parse_unit("fluid ounce".to_string()), + Err(ParseError::AmbiguousUnit("fluid ounce".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("fluid ounces".to_string()), + Err(ParseError::AmbiguousUnit("fluid ounces".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("fl oz".to_string()), + Err(ParseError::AmbiguousUnit("fl oz".to_string(), "imp", "US")) + ); + assert_eq!( + parse_unit("fl. oz.".to_string()), + Err(ParseError::AmbiguousUnit("fl. oz.".to_string(), "imp", "US")) + ); + assert_eq!( + parse_unit("oz. fl.".to_string()), + Err(ParseError::AmbiguousUnit("oz. fl.".to_string(), "imp", "US")) + ); + + assert_eq!( + parse_unit("pint".to_string()), + Err(ParseError::AmbiguousUnit("pint".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("pints".to_string()), + Err(ParseError::AmbiguousUnit("pints".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("pt".to_string()), + Err(ParseError::AmbiguousUnit("pt".to_string(), "imp", "US")) + ); + assert_eq!( + parse_unit("p".to_string()), + Err(ParseError::AmbiguousUnit("p".to_string(), "imp", "US")) + ); + + assert_eq!( + parse_unit("quart".to_string()), + Err(ParseError::AmbiguousUnit("quart".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("quarts".to_string()), + Err(ParseError::AmbiguousUnit("quarts".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("qt".to_string()), + Err(ParseError::AmbiguousUnit("qt".to_string(), "imp", "US")) + ); + + assert_eq!( + parse_unit("gallon".to_string()), + Err(ParseError::AmbiguousUnit("gallon".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("gallons".to_string()), + Err(ParseError::AmbiguousUnit("gallons".to_string(), "imperial", "US")) + ); + assert_eq!( + parse_unit("gal".to_string()), + Err(ParseError::AmbiguousUnit("gal".to_string(), "imp", "US")) + ); + } + + #[test] + fn ambiguous_unit_suggestions() { + let ambiguous_units = [ + "fluid ounce", + "fluid ounces", + "fl oz", + "fl. oz.", + "oz. fl.", + + "pint", + "pints", + "pt", + "p", + + "quart", + "quarts", + "qt", + + "gallon", + "gallons", + "gal", + ]; + + for unit in ambiguous_units { + let parsed = parse_unit(unit.to_string()); + if let Err(ParseError::AmbiguousUnit(unit, prefix1, prefix2)) = parsed { + let suggestion1 = format!("{prefix1} {unit}"); + let suggestion2 = format!("{prefix2} {unit}"); + assert!(parse_unit(suggestion1).is_ok()); + assert!(parse_unit(suggestion2).is_ok()); + } else { + unreachable!(); + } + } + } + + #[test] + fn unknown_unit() { assert_eq!(parse_unit("hutenosa".to_string()), Err(ParseError::UnknownUnit("hutenosa".to_string()))); }