2dc93cadda
Remove the use of trait objects as errors from `from_str` and `try_from_into`; they seem to have caused a lot of confusion in practice. (Also, it's considered best practice to use custom error types instead of boxed errors in library code.) Instead, use custom error enums, and update hints accordingly. Hints also provide some guidance about converting errors, which could be covered more completely in a future advanced errors section. Also move from_str to directly after the similar exercise `from_into`, for the sake of familiarity when solving.
123 lines
3.3 KiB
Rust
123 lines
3.3 KiB
Rust
// from_str.rs
|
|
// This is similar to from_into.rs, but this time we'll implement `FromStr`
|
|
// and return errors instead of falling back to a default value.
|
|
// Additionally, upon implementing FromStr, you can use the `parse` method
|
|
// on strings to generate an object of the implementor type.
|
|
// You can read more about it at https://doc.rust-lang.org/std/str/trait.FromStr.html
|
|
use std::num::ParseIntError;
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
struct Person {
|
|
name: String,
|
|
age: usize,
|
|
}
|
|
|
|
// We will use this error type for the `FromStr` implementation.
|
|
#[derive(Debug, PartialEq)]
|
|
enum ParsePersonError {
|
|
// Empty input string
|
|
Empty,
|
|
// Incorrect number of fields
|
|
BadLen,
|
|
// Empty name field
|
|
NoName,
|
|
// Wrapped error from parse::<usize>()
|
|
ParseInt(ParseIntError),
|
|
}
|
|
|
|
// I AM NOT DONE
|
|
|
|
// Steps:
|
|
// 1. If the length of the provided string is 0, an error should be returned
|
|
// 2. Split the given string on the commas present in it
|
|
// 3. Only 2 elements should be returned from the split, otherwise return an error
|
|
// 4. Extract the first element from the split operation and use it as the name
|
|
// 5. Extract the other element from the split operation and parse it into a `usize` as the age
|
|
// with something like `"4".parse::<usize>()`
|
|
// 5. If while extracting the name and the age something goes wrong, an error should be returned
|
|
// If everything goes well, then return a Result of a Person object
|
|
|
|
impl FromStr for Person {
|
|
type Err = ParsePersonError;
|
|
fn from_str(s: &str) -> Result<Person, Self::Err> {
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let p = "Mark,20".parse::<Person>().unwrap();
|
|
println!("{:?}", p);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn empty_input() {
|
|
assert_eq!("".parse::<Person>(), Err(ParsePersonError::Empty));
|
|
}
|
|
#[test]
|
|
fn good_input() {
|
|
let p = "John,32".parse::<Person>();
|
|
assert!(p.is_ok());
|
|
let p = p.unwrap();
|
|
assert_eq!(p.name, "John");
|
|
assert_eq!(p.age, 32);
|
|
}
|
|
#[test]
|
|
fn missing_age() {
|
|
assert!(matches!(
|
|
"John,".parse::<Person>(),
|
|
Err(ParsePersonError::ParseInt(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_age() {
|
|
assert!(matches!(
|
|
"John,twenty".parse::<Person>(),
|
|
Err(ParsePersonError::ParseInt(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn missing_comma_and_age() {
|
|
assert_eq!("John".parse::<Person>(), Err(ParsePersonError::BadLen));
|
|
}
|
|
|
|
#[test]
|
|
fn missing_name() {
|
|
assert_eq!(",1".parse::<Person>(), Err(ParsePersonError::NoName));
|
|
}
|
|
|
|
#[test]
|
|
fn missing_name_and_age() {
|
|
assert!(matches!(
|
|
",".parse::<Person>(),
|
|
Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn missing_name_and_invalid_age() {
|
|
assert!(matches!(
|
|
",one".parse::<Person>(),
|
|
Err(ParsePersonError::NoName | ParsePersonError::ParseInt(_))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn trailing_comma() {
|
|
assert_eq!("John,32,".parse::<Person>(), Err(ParsePersonError::BadLen));
|
|
}
|
|
|
|
#[test]
|
|
fn trailing_comma_and_some_string() {
|
|
assert_eq!(
|
|
"John,32,man".parse::<Person>(),
|
|
Err(ParsePersonError::BadLen)
|
|
);
|
|
}
|
|
}
|