guessing_utils/
lib.rs

1//! # Guessing utilities
2//! This crate is ideal for writing programs for guessing numbers in range (0..101).
3//! It provides a handful of utilities made for various cases.
4
5use rand::Rng;
6
7/// Used for creating a guess. This is a good alternative to storing a guessed number,
8/// which is much safer and comfortable to use compared to a primitive number.
9/// 
10/// # Usage
11/// Use `Guess::new(val: i32)` to create a guess object. The object will store the number now.
12/// An error might be returned, make sure to handle in appropriate situations.
13/// Now, the object can be used to compare with other guesses, extract the value, etc.
14/// 
15/// # Safety
16/// The constructor automatically checks if a provided input is in correct range.
17/// The object provides other utilities, like parsing which provide a safe way to do error handling.
18/// 
19/// # Comfort
20/// The object implements equality and comparing checks to use with other guesses,
21/// therefore not having to rely on `value()` function every time.
22#[derive(Eq, Debug)]
23pub struct Guess {
24    val: i32, // i32 instead of u32 for future capabilities
25}
26
27/// Custom-written error handling.
28pub mod err {
29    use std::fmt;
30
31    /// Used when the provided argument is outside the required (0..101) range.
32    /// Usually returned by `Guess::new(val: i32)` function when an invalid input is provided.
33    #[derive(Debug, Clone)]
34    pub struct GuessRangeError;
35
36    impl fmt::Display for GuessRangeError {
37        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38            write!(f, "The guess value was out of 0-100 range")
39        }
40    }
41
42    impl std::error::Error for GuessRangeError {}
43}
44
45impl Guess {
46    /// Creates a new `Guess` object.
47    /// An error will be returned if the provided number was outside (0..101) range.
48    /// # Example
49    /// ```
50    /// use guessing_utils::Guess;
51    /// 
52    /// let value = 100; // our guess number
53    /// let guess = Guess::new(value);
54    /// 
55    /// let guess = match guess {
56    ///     Ok(val) => val,
57    ///     Err(err) => panic!("Provided value was outside the range!"),
58    /// };
59    /// ```
60    pub fn new(val: i32) -> Result<Guess, err::GuessRangeError> {
61        if val < 0 || val > 100 {
62            return Err(err::GuessRangeError);
63        }
64
65        Ok(Guess { val })
66    }
67
68    /// Creates a new object from parsing the provided string slice.
69    /// Invalid input will produce either a parsing or a range error.
70    /// # Example
71    /// ```
72    /// use guessing_utils::Guess;
73    /// 
74    /// let guess = "57"; // value to parse (input)
75    /// 
76    /// let guess = match Guess::parse(&guess) {
77    ///     Ok(val) => val,
78    ///     Err(err) => panic!("{}", err), // handle the input error
79    /// };
80    /// ```
81    pub fn parse(val: &str) -> Result<Guess, Box<dyn std::error::Error>> {
82        let parsed: i32 = match val.trim().parse() {
83            Ok(val) => val,
84            Err(err) => return Err(err)?,
85        };
86
87        match Guess::new(parsed) {
88            Ok(val) => Ok(val),
89            Err(err) => Err(err)?
90        }
91    }
92
93    /// Gets the value stored in the object.
94    /// # Example
95    /// ```
96    /// use guessing_utils::Guess;
97    /// 
98    /// let guess = Guess::new(15).unwrap();
99    /// println!("{}", guess.value()); // 15
100    /// ```
101    pub fn value(&self) -> &i32 {
102        &self.val
103    }
104}
105
106impl Ord for Guess {
107    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
108        self.value().cmp(other.value())
109    }
110}
111
112impl PartialOrd for Guess {
113    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
114        Some(self.cmp(other))
115    }
116}
117
118impl PartialEq for Guess {
119    fn eq(&self, other: &Self) -> bool {
120        self.value() == other.value()
121    }
122}
123
124/// Generates a `Guess` object together with a randomly generated number in (0..101) range put inside the object.
125/// # Example
126/// ```
127/// use guessing_utils::Guess;
128/// use guessing_utils::gen_random;
129/// 
130/// use std::cmp::Ordering;
131/// 
132/// let random_guess = gen_random();
133/// let my_guess = Guess::new(50).unwrap();
134/// 
135/// match random_guess.cmp(&my_guess) {
136///     Ordering::Equal => println!("I guessed the number!"),
137///     _ => (),
138/// }
139/// ```
140pub fn gen_random() -> Guess {
141    Guess::new(rand::thread_rng().gen_range(0..101)).unwrap()
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use std::cmp::Ordering;
148
149    #[test]
150    fn eq_test() {
151        let guess1 = Guess::new(13).unwrap();
152        let guess2 = Guess::new(13).unwrap();
153
154        assert_eq!(guess1, guess2);
155    }
156
157    #[test]
158    fn cmp_test() {
159        let guess1 = Guess::new(50).unwrap();
160        let guess2 = Guess::new(13).unwrap();
161
162        assert_eq!(guess1.cmp(&guess2), Ordering::Greater);
163    }
164
165    #[test]
166    fn parse_test() {
167        match Guess::parse("16") {
168            Ok(_) => (),
169            Err(err) => panic!("Should had no errors, got: {}", err),
170        }
171
172        match Guess::parse("val") {
173            Ok(_) => panic!("Should have panicked but didn't."),
174            Err(_) => (),
175        }
176    }
177}