Skip to content

Commit c2ed98d

Browse files
Merge pull request #781 from tlyu/advanced-errs
feature: advanced errors
2 parents 0de45cc + abd6b70 commit c2ed98d

File tree

5 files changed

+475
-51
lines changed

5 files changed

+475
-51
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// advanced_errs1.rs
2+
3+
// Remember back in errors6, we had multiple mapping functions so that we
4+
// could translate lower-level errors into our custom error type using
5+
// `map_err()`? What if we could use the `?` operator directly instead?
6+
7+
// Make this code compile! Execute `rustlings hint advanced_errs1` for
8+
// hints :)
9+
10+
// I AM NOT DONE
11+
12+
use std::num::ParseIntError;
13+
use std::str::FromStr;
14+
15+
// This is a custom error type that we will be using in the `FromStr`
16+
// implementation.
17+
#[derive(PartialEq, Debug)]
18+
enum ParsePosNonzeroError {
19+
Creation(CreationError),
20+
ParseInt(ParseIntError),
21+
}
22+
23+
impl From<CreationError> for ParsePosNonzeroError {
24+
fn from(e: CreationError) -> Self {
25+
// TODO: complete this implementation so that the `?` operator will
26+
// work for `CreationError`
27+
}
28+
}
29+
30+
// TODO: implement another instance of the `From` trait here so that the
31+
// `?` operator will work in the other place in the `FromStr`
32+
// implementation below.
33+
34+
// Don't change anything below this line.
35+
36+
impl FromStr for PositiveNonzeroInteger {
37+
type Err = ParsePosNonzeroError;
38+
fn from_str(s: &str) -> Result<PositiveNonzeroInteger, Self::Err> {
39+
let x: i64 = s.parse()?;
40+
Ok(PositiveNonzeroInteger::new(x)?)
41+
}
42+
}
43+
44+
#[derive(PartialEq, Debug)]
45+
struct PositiveNonzeroInteger(u64);
46+
47+
#[derive(PartialEq, Debug)]
48+
enum CreationError {
49+
Negative,
50+
Zero,
51+
}
52+
53+
impl PositiveNonzeroInteger {
54+
fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
55+
match value {
56+
x if x < 0 => Err(CreationError::Negative),
57+
x if x == 0 => Err(CreationError::Zero),
58+
x => Ok(PositiveNonzeroInteger(x as u64)),
59+
}
60+
}
61+
}
62+
63+
#[cfg(test)]
64+
mod test {
65+
use super::*;
66+
67+
#[test]
68+
fn test_parse_error() {
69+
// We can't construct a ParseIntError, so we have to pattern match.
70+
assert!(matches!(
71+
PositiveNonzeroInteger::from_str("not a number"),
72+
Err(ParsePosNonzeroError::ParseInt(_))
73+
));
74+
}
75+
76+
#[test]
77+
fn test_negative() {
78+
assert_eq!(
79+
PositiveNonzeroInteger::from_str("-555"),
80+
Err(ParsePosNonzeroError::Creation(CreationError::Negative))
81+
);
82+
}
83+
84+
#[test]
85+
fn test_zero() {
86+
assert_eq!(
87+
PositiveNonzeroInteger::from_str("0"),
88+
Err(ParsePosNonzeroError::Creation(CreationError::Zero))
89+
);
90+
}
91+
92+
#[test]
93+
fn test_positive() {
94+
let x = PositiveNonzeroInteger::new(42);
95+
assert!(x.is_ok());
96+
assert_eq!(PositiveNonzeroInteger::from_str("42"), Ok(x.unwrap()));
97+
}
98+
}
+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// advanced_errs2.rs
2+
3+
// This exercise demonstrates a few traits that are useful for custom error
4+
// types to implement, especially so that other code can consume the custom
5+
// error type more usefully.
6+
7+
// Make this compile, and make the tests pass!
8+
// Execute `rustlings hint advanced_errs2` for hints.
9+
10+
// Steps:
11+
// 1. Implement a missing trait so that `main()` will compile.
12+
// 2. Complete the partial implementation of `From` for
13+
// `ParseClimateError`.
14+
// 3. Handle the missing error cases in the `FromStr` implementation for
15+
// `Climate`.
16+
// 4. Complete the partial implementation of `Display` for
17+
// `ParseClimateError`.
18+
19+
// I AM NOT DONE
20+
21+
use std::error::Error;
22+
use std::fmt::{self, Display, Formatter};
23+
use std::num::{ParseFloatError, ParseIntError};
24+
use std::str::FromStr;
25+
26+
// This is the custom error type that we will be using for the parser for
27+
// `Climate`.
28+
#[derive(Debug, PartialEq)]
29+
enum ParseClimateError {
30+
Empty,
31+
BadLen,
32+
NoCity,
33+
ParseInt(ParseIntError),
34+
ParseFloat(ParseFloatError),
35+
}
36+
37+
// This `From` implementation allows the `?` operator to work on
38+
// `ParseIntError` values.
39+
impl From<ParseIntError> for ParseClimateError {
40+
fn from(e: ParseIntError) -> Self {
41+
Self::ParseInt(e)
42+
}
43+
}
44+
45+
// This `From` implementation allows the `?` operator to work on
46+
// `ParseFloatError` values.
47+
impl From<ParseFloatError> for ParseClimateError {
48+
fn from(e: ParseFloatError) -> Self {
49+
// TODO: Complete this function
50+
}
51+
}
52+
53+
// TODO: Implement a missing trait so that `main()` below will compile. It
54+
// is not necessary to implement any methods inside the missing trait.
55+
56+
// The `Display` trait allows for other code to obtain the error formatted
57+
// as a user-visible string.
58+
impl Display for ParseClimateError {
59+
// TODO: Complete this function so that it produces the correct strings
60+
// for each error variant.
61+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
62+
// Imports the variants to make the following code more compact.
63+
use ParseClimateError::*;
64+
match self {
65+
NoCity => write!(f, "no city name"),
66+
ParseFloat(e) => write!(f, "error parsing temperature: {}", e),
67+
_ => write!(f, "unhandled error!"),
68+
}
69+
}
70+
}
71+
72+
#[derive(Debug, PartialEq)]
73+
struct Climate {
74+
city: String,
75+
year: u32,
76+
temp: f32,
77+
}
78+
79+
// Parser for `Climate`.
80+
// 1. Split the input string into 3 fields: city, year, temp.
81+
// 2. Return an error if the string is empty or has the wrong number of
82+
// fields.
83+
// 3. Return an error if the city name is empty.
84+
// 4. Parse the year as a `u32` and return an error if that fails.
85+
// 5. Parse the temp as a `f32` and return an error if that fails.
86+
// 6. Return an `Ok` value containing the completed `Climate` value.
87+
impl FromStr for Climate {
88+
type Err = ParseClimateError;
89+
// TODO: Complete this function by making it handle the missing error
90+
// cases.
91+
fn from_str(s: &str) -> Result<Self, Self::Err> {
92+
let v: Vec<_> = s.split(',').collect();
93+
let (city, year, temp) = match &v[..] {
94+
[city, year, temp] => (city.to_string(), year, temp),
95+
_ => return Err(ParseClimateError::BadLen),
96+
};
97+
let year: u32 = year.parse()?;
98+
let temp: f32 = temp.parse()?;
99+
Ok(Climate { city, year, temp })
100+
}
101+
}
102+
103+
// Don't change anything below this line (other than to enable ignored
104+
// tests).
105+
106+
fn main() -> Result<(), Box<dyn Error>> {
107+
println!("{:?}", "Hong Kong,1999,25.7".parse::<Climate>()?);
108+
println!("{:?}", "".parse::<Climate>()?);
109+
Ok(())
110+
}
111+
112+
#[cfg(test)]
113+
mod test {
114+
use super::*;
115+
#[test]
116+
fn test_empty() {
117+
let res = "".parse::<Climate>();
118+
assert_eq!(res, Err(ParseClimateError::Empty));
119+
assert_eq!(res.unwrap_err().to_string(), "empty input");
120+
}
121+
#[test]
122+
fn test_short() {
123+
let res = "Boston,1991".parse::<Climate>();
124+
assert_eq!(res, Err(ParseClimateError::BadLen));
125+
assert_eq!(res.unwrap_err().to_string(), "incorrect number of fields");
126+
}
127+
#[test]
128+
fn test_long() {
129+
let res = "Paris,1920,17.2,extra".parse::<Climate>();
130+
assert_eq!(res, Err(ParseClimateError::BadLen));
131+
assert_eq!(res.unwrap_err().to_string(), "incorrect number of fields");
132+
}
133+
#[test]
134+
fn test_no_city() {
135+
let res = ",1997,20.5".parse::<Climate>();
136+
assert_eq!(res, Err(ParseClimateError::NoCity));
137+
assert_eq!(res.unwrap_err().to_string(), "no city name");
138+
}
139+
#[test]
140+
fn test_parse_int_neg() {
141+
let res = "Barcelona,-25,22.3".parse::<Climate>();
142+
assert!(matches!(res, Err(ParseClimateError::ParseInt(_))));
143+
let err = res.unwrap_err();
144+
if let ParseClimateError::ParseInt(ref inner) = err {
145+
assert_eq!(
146+
err.to_string(),
147+
format!("error parsing year: {}", inner.to_string())
148+
);
149+
} else {
150+
unreachable!();
151+
};
152+
}
153+
#[test]
154+
fn test_parse_int_bad() {
155+
let res = "Beijing,foo,15.0".parse::<Climate>();
156+
assert!(matches!(res, Err(ParseClimateError::ParseInt(_))));
157+
let err = res.unwrap_err();
158+
if let ParseClimateError::ParseInt(ref inner) = err {
159+
assert_eq!(
160+
err.to_string(),
161+
format!("error parsing year: {}", inner.to_string())
162+
);
163+
} else {
164+
unreachable!();
165+
};
166+
}
167+
#[test]
168+
fn test_parse_float() {
169+
let res = "Manila,2001,bar".parse::<Climate>();
170+
assert!(matches!(res, Err(ParseClimateError::ParseFloat(_))));
171+
let err = res.unwrap_err();
172+
if let ParseClimateError::ParseFloat(ref inner) = err {
173+
assert_eq!(
174+
err.to_string(),
175+
format!("error parsing temperature: {}", inner.to_string())
176+
);
177+
} else {
178+
unreachable!();
179+
};
180+
}
181+
#[test]
182+
fn test_parse_good() {
183+
let res = "Munich,2015,23.1".parse::<Climate>();
184+
assert_eq!(
185+
res,
186+
Ok(Climate {
187+
city: "Munich".to_string(),
188+
year: 2015,
189+
temp: 23.1,
190+
})
191+
);
192+
}
193+
#[test]
194+
#[ignore]
195+
fn test_downcast() {
196+
let res = "São Paulo,-21,28.5".parse::<Climate>();
197+
assert!(matches!(res, Err(ParseClimateError::ParseInt(_))));
198+
let err = res.unwrap_err();
199+
let inner: Option<&(dyn Error + 'static)> = err.source();
200+
assert!(inner.is_some());
201+
assert!(inner.unwrap().is::<ParseIntError>());
202+
}
203+
}

0 commit comments

Comments
 (0)