Skip to content

Commit

Permalink
Merge pull request #781 from tlyu/advanced-errs
Browse files Browse the repository at this point in the history
feature: advanced errors
  • Loading branch information
shadows-withal authored Sep 25, 2021
2 parents 0de45cc + abd6b70 commit c2ed98d
Show file tree
Hide file tree
Showing 5 changed files with 475 additions and 51 deletions.
98 changes: 98 additions & 0 deletions exercises/advanced_errors/advanced_errs1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// advanced_errs1.rs

// Remember back in errors6, we had multiple mapping functions so that we
// could translate lower-level errors into our custom error type using
// `map_err()`? What if we could use the `?` operator directly instead?

// Make this code compile! Execute `rustlings hint advanced_errs1` for
// hints :)

// I AM NOT DONE

use std::num::ParseIntError;
use std::str::FromStr;

// This is a custom error type that we will be using in the `FromStr`
// implementation.
#[derive(PartialEq, Debug)]
enum ParsePosNonzeroError {
Creation(CreationError),
ParseInt(ParseIntError),
}

impl From<CreationError> for ParsePosNonzeroError {
fn from(e: CreationError) -> Self {
// TODO: complete this implementation so that the `?` operator will
// work for `CreationError`
}
}

// TODO: implement another instance of the `From` trait here so that the
// `?` operator will work in the other place in the `FromStr`
// implementation below.

// Don't change anything below this line.

impl FromStr for PositiveNonzeroInteger {
type Err = ParsePosNonzeroError;
fn from_str(s: &str) -> Result<PositiveNonzeroInteger, Self::Err> {
let x: i64 = s.parse()?;
Ok(PositiveNonzeroInteger::new(x)?)
}
}

#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);

#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
}

impl PositiveNonzeroInteger {
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
match value {
x if x < 0 => Err(CreationError::Negative),
x if x == 0 => Err(CreationError::Zero),
x => Ok(PositiveNonzeroInteger(x as u64)),
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_parse_error() {
// We can't construct a ParseIntError, so we have to pattern match.
assert!(matches!(
PositiveNonzeroInteger::from_str("not a number"),
Err(ParsePosNonzeroError::ParseInt(_))
));
}

#[test]
fn test_negative() {
assert_eq!(
PositiveNonzeroInteger::from_str("-555"),
Err(ParsePosNonzeroError::Creation(CreationError::Negative))
);
}

#[test]
fn test_zero() {
assert_eq!(
PositiveNonzeroInteger::from_str("0"),
Err(ParsePosNonzeroError::Creation(CreationError::Zero))
);
}

#[test]
fn test_positive() {
let x = PositiveNonzeroInteger::new(42);
assert!(x.is_ok());
assert_eq!(PositiveNonzeroInteger::from_str("42"), Ok(x.unwrap()));
}
}
203 changes: 203 additions & 0 deletions exercises/advanced_errors/advanced_errs2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// advanced_errs2.rs

// This exercise demonstrates a few traits that are useful for custom error
// types to implement, especially so that other code can consume the custom
// error type more usefully.

// Make this compile, and make the tests pass!
// Execute `rustlings hint advanced_errs2` for hints.

// Steps:
// 1. Implement a missing trait so that `main()` will compile.
// 2. Complete the partial implementation of `From` for
// `ParseClimateError`.
// 3. Handle the missing error cases in the `FromStr` implementation for
// `Climate`.
// 4. Complete the partial implementation of `Display` for
// `ParseClimateError`.

// I AM NOT DONE

use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::num::{ParseFloatError, ParseIntError};
use std::str::FromStr;

// This is the custom error type that we will be using for the parser for
// `Climate`.
#[derive(Debug, PartialEq)]
enum ParseClimateError {
Empty,
BadLen,
NoCity,
ParseInt(ParseIntError),
ParseFloat(ParseFloatError),
}

// This `From` implementation allows the `?` operator to work on
// `ParseIntError` values.
impl From<ParseIntError> for ParseClimateError {
fn from(e: ParseIntError) -> Self {
Self::ParseInt(e)
}
}

// This `From` implementation allows the `?` operator to work on
// `ParseFloatError` values.
impl From<ParseFloatError> for ParseClimateError {
fn from(e: ParseFloatError) -> Self {
// TODO: Complete this function
}
}

// TODO: Implement a missing trait so that `main()` below will compile. It
// is not necessary to implement any methods inside the missing trait.

// The `Display` trait allows for other code to obtain the error formatted
// as a user-visible string.
impl Display for ParseClimateError {
// TODO: Complete this function so that it produces the correct strings
// for each error variant.
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// Imports the variants to make the following code more compact.
use ParseClimateError::*;
match self {
NoCity => write!(f, "no city name"),
ParseFloat(e) => write!(f, "error parsing temperature: {}", e),
_ => write!(f, "unhandled error!"),
}
}
}

#[derive(Debug, PartialEq)]
struct Climate {
city: String,
year: u32,
temp: f32,
}

// Parser for `Climate`.
// 1. Split the input string into 3 fields: city, year, temp.
// 2. Return an error if the string is empty or has the wrong number of
// fields.
// 3. Return an error if the city name is empty.
// 4. Parse the year as a `u32` and return an error if that fails.
// 5. Parse the temp as a `f32` and return an error if that fails.
// 6. Return an `Ok` value containing the completed `Climate` value.
impl FromStr for Climate {
type Err = ParseClimateError;
// TODO: Complete this function by making it handle the missing error
// cases.
fn from_str(s: &str) -> Result<Self, Self::Err> {
let v: Vec<_> = s.split(',').collect();
let (city, year, temp) = match &v[..] {
[city, year, temp] => (city.to_string(), year, temp),
_ => return Err(ParseClimateError::BadLen),
};
let year: u32 = year.parse()?;
let temp: f32 = temp.parse()?;
Ok(Climate { city, year, temp })
}
}

// Don't change anything below this line (other than to enable ignored
// tests).

fn main() -> Result<(), Box<dyn Error>> {
println!("{:?}", "Hong Kong,1999,25.7".parse::<Climate>()?);
println!("{:?}", "".parse::<Climate>()?);
Ok(())
}

#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_empty() {
let res = "".parse::<Climate>();
assert_eq!(res, Err(ParseClimateError::Empty));
assert_eq!(res.unwrap_err().to_string(), "empty input");
}
#[test]
fn test_short() {
let res = "Boston,1991".parse::<Climate>();
assert_eq!(res, Err(ParseClimateError::BadLen));
assert_eq!(res.unwrap_err().to_string(), "incorrect number of fields");
}
#[test]
fn test_long() {
let res = "Paris,1920,17.2,extra".parse::<Climate>();
assert_eq!(res, Err(ParseClimateError::BadLen));
assert_eq!(res.unwrap_err().to_string(), "incorrect number of fields");
}
#[test]
fn test_no_city() {
let res = ",1997,20.5".parse::<Climate>();
assert_eq!(res, Err(ParseClimateError::NoCity));
assert_eq!(res.unwrap_err().to_string(), "no city name");
}
#[test]
fn test_parse_int_neg() {
let res = "Barcelona,-25,22.3".parse::<Climate>();
assert!(matches!(res, Err(ParseClimateError::ParseInt(_))));
let err = res.unwrap_err();
if let ParseClimateError::ParseInt(ref inner) = err {
assert_eq!(
err.to_string(),
format!("error parsing year: {}", inner.to_string())
);
} else {
unreachable!();
};
}
#[test]
fn test_parse_int_bad() {
let res = "Beijing,foo,15.0".parse::<Climate>();
assert!(matches!(res, Err(ParseClimateError::ParseInt(_))));
let err = res.unwrap_err();
if let ParseClimateError::ParseInt(ref inner) = err {
assert_eq!(
err.to_string(),
format!("error parsing year: {}", inner.to_string())
);
} else {
unreachable!();
};
}
#[test]
fn test_parse_float() {
let res = "Manila,2001,bar".parse::<Climate>();
assert!(matches!(res, Err(ParseClimateError::ParseFloat(_))));
let err = res.unwrap_err();
if let ParseClimateError::ParseFloat(ref inner) = err {
assert_eq!(
err.to_string(),
format!("error parsing temperature: {}", inner.to_string())
);
} else {
unreachable!();
};
}
#[test]
fn test_parse_good() {
let res = "Munich,2015,23.1".parse::<Climate>();
assert_eq!(
res,
Ok(Climate {
city: "Munich".to_string(),
year: 2015,
temp: 23.1,
})
);
}
#[test]
#[ignore]
fn test_downcast() {
let res = "São Paulo,-21,28.5".parse::<Climate>();
assert!(matches!(res, Err(ParseClimateError::ParseInt(_))));
let err = res.unwrap_err();
let inner: Option<&(dyn Error + 'static)> = err.source();
assert!(inner.is_some());
assert!(inner.unwrap().is::<ParseIntError>());
}
}
Loading

0 comments on commit c2ed98d

Please sign in to comment.