feat: add better error handling for cards (following C-GOOD-ERR)
authorMA Beaudet <ma@beaudet.xyz>
Wed, 10 Nov 2021 13:24:16 +0000 (14:24 +0100)
committerMA Beaudet <ma@beaudet.xyz>
Wed, 10 Nov 2021 13:24:16 +0000 (14:24 +0100)
src/card.rs
src/errors.rs [new file with mode: 0644]
src/lib.rs

index 47a8a2c..5595bf4 100644 (file)
@@ -1,12 +1,12 @@
 use std::str::FromStr;
 
-use crate::{constants::PRIMES, MyError, Rules};
+use crate::{constants::PRIMES, errors::ParseCardError, Rules};
 
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(Debug, Default, Clone, Hash)]
+#[derive(Debug, Default, Clone, Hash, PartialEq)]
 pub struct Cards(Vec<Card>);
 
 impl Cards {
@@ -49,14 +49,14 @@ impl Extend<Card> for Cards {
 }
 
 impl FromStr for Cards {
-    type Err = MyError;
+    type Err = ParseCardError;
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Ok(Self(
-            s.trim()
-                .split_whitespace()
-                .map(|c| Card::from_str(c).expect("Could not parse card from string"))
-                .collect::<Vec<_>>(),
-        ))
+        let mut cards = Cards::new();
+        for c in s.trim().split_whitespace() {
+            let c = Card::from_str(c)?;
+            cards.0.push(c);
+        }
+        Ok(cards)
     }
 }
 
@@ -93,7 +93,7 @@ impl Deck {
         &self.0
     }
 
-    pub fn deal_with_rules(&mut self, rules: &Rules) -> Result<Cards, MyError> {
+    pub fn deal_with_rules(&mut self, rules: &Rules) -> Result<Cards, ParseCardError> {
         let v = match rules {
             Rules::Classic => self.deal(5)?,
 
@@ -106,16 +106,16 @@ impl Deck {
         &mut self,
         rules: &Rules,
         player_nb: usize,
-    ) -> Result<Vec<Cards>, MyError> {
+    ) -> Result<Vec<Cards>, ParseCardError> {
         (0..player_nb)
             .into_iter()
             .map(|_| Ok(self.deal_with_rules(rules)?))
             .collect()
     }
 
-    pub fn deal(&mut self, n: usize) -> Result<Cards, MyError> {
+    pub fn deal(&mut self, n: usize) -> Result<Cards, ParseCardError> {
         if self.0 .0.len() < n {
-            Err(MyError::IndexError)
+            Err(ParseCardError::Index)
         } else {
             Ok(Cards(self.0 .0.split_off(self.0 .0.len() - n)))
         }
@@ -147,14 +147,6 @@ impl Default for Deck {
     }
 }
 
-/// ```
-/// use poker_eval::{Card};
-/// use std::str::FromStr;
-///
-/// assert_eq!(
-///     Card::from_str("Kd").unwrap().get(),
-///     0b00001000_00000000_01001011_00100101_u32
-/// );
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, Copy, Hash)]
 #[repr(transparent)]
@@ -175,13 +167,6 @@ impl Card {
         }
     }
 
-    /// ```
-    /// use poker_eval::{Card};
-    /// use std::str::FromStr;
-    ///
-    /// let card = Card::from_str("Kd").unwrap();
-    /// assert_eq!(card.rank(), 'K');
-    /// ```
     pub fn rank(&self) -> char {
         match (self.0 >> 8) & 0xF {
             0 => '2',
@@ -209,32 +194,22 @@ impl AsRef<u32> for Card {
 }
 
 impl TryFrom<u32> for Card {
-    type Error = MyError;
+    type Error = ParseCardError;
 
     fn try_from(value: u32) -> Result<Self, Self::Error> {
         let _ = match (value >> 8) & 0xF {
             0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 => Ok(()),
-            _ => Err(MyError::InvalidRank),
+            _ => Err(ParseCardError::U32),
         };
 
         let _ = match (value >> 12) & 0xF {
             0b1000 | 0b0100 | 0b0010 | 0b0001 => Ok(()),
-            _ => Err(MyError::InvalidSuit),
+            _ => Err(ParseCardError::U32),
         };
         Ok(Self(value))
     }
 }
 
-/// ```
-/// use poker_eval::{Card};
-/// use std::str::FromStr;
-///
-/// let card = Card::from_str("Kd").unwrap();
-/// let other = Card::from_str("Qd").unwrap();
-/// let other2 = Card::from_str("Ks").unwrap();
-/// assert!(card > other);
-/// assert!(card <= other2);
-/// ```
 impl PartialOrd for Card {
     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
         ((self.0 >> 8) & 0xF).partial_cmp(&((other.0 >> 8) & 0xF))
@@ -247,14 +222,6 @@ impl Ord for Card {
     }
 }
 
-/// ```
-/// use poker_eval::{Card};
-/// use std::str::FromStr;
-///
-/// let card = Card::from_str("Kd").unwrap();
-/// let other = Card::from_str("Ks").unwrap();
-/// assert!(card == other);
-/// ```
 impl PartialEq for Card {
     fn eq(&self, other: &Self) -> bool {
         (self.0 >> 8) & 0xF == (other.0 >> 8) & 0xF
@@ -264,15 +231,15 @@ impl PartialEq for Card {
 impl Eq for Card {}
 
 impl FromStr for Card {
-    type Err = MyError;
+    type Err = ParseCardError;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         let mut input = s.chars();
         let (r, s) = (input.next(), input.next());
         match (r, s) {
-            (None, None) => Err(MyError::InvalidLength),
-            (None, Some(_)) => Err(MyError::InvalidLength),
-            (Some(_), None) => Err(MyError::InvalidLength),
+            (None, None) => Err(ParseCardError::String),
+            (None, Some(_)) => Err(ParseCardError::String),
+            (Some(_), None) => Err(ParseCardError::String),
             (Some(r), Some(s)) => {
                 let r = match r {
                     '2' => Some(0),
@@ -298,9 +265,9 @@ impl FromStr for Card {
                     _ => None,
                 };
                 match (r, s) {
-                    (None, None) => Err(MyError::InvalidLength),
-                    (None, Some(_)) => Err(MyError::InvalidRank),
-                    (Some(_), None) => Err(MyError::InvalidSuit),
+                    (None, None) => Err(ParseCardError::Chars),
+                    (None, Some(_)) => Err(ParseCardError::Rank),
+                    (Some(_), None) => Err(ParseCardError::Suit),
                     (Some(r), Some(s)) => Ok(Self(
                         PRIMES[r as usize] as u32 | (r << 8) | s | (1 << (16 + r)),
                     )),
@@ -310,13 +277,6 @@ impl FromStr for Card {
     }
 }
 
-/// ```
-/// use poker_eval::{Card};
-/// use std::str::FromStr;
-///
-/// let card = Card::from_str("Kd").unwrap();
-/// assert_eq!(card.to_string(), "Kd");
-/// ```
 impl std::fmt::Display for Card {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{}{}", self.rank(), self.suit())
@@ -338,3 +298,33 @@ fn test_sync() {
     assert_sync::<Cards>();
     assert_sync::<Deck>();
 }
+
+#[test]
+fn test_fromstr() {
+    assert!(Cards::from_str("7s 3v").is_err());
+    assert_eq!(Cards::from_str("7s 3v").unwrap_err(), ParseCardError::Suit);
+    assert_eq!(Cards::from_str("7s fv").unwrap_err(), ParseCardError::Chars);
+    assert_eq!(Cards::from_str("7s fd").unwrap_err(), ParseCardError::Rank);
+    assert!(Cards::from_str("7s 3d").is_ok());
+    assert_eq!(
+        Cards::from_str("7s 3d").unwrap(),
+        Cards(vec![
+            Card::from_str("7s").unwrap(),
+            Card::from_str("3d").unwrap()
+        ])
+    );
+    assert!(Card::from_str("3v").is_err());
+    assert_eq!(Card::from_str("3v").unwrap_err(), ParseCardError::Suit);
+    assert_eq!(Card::from_str("fv").unwrap_err(), ParseCardError::Chars);
+    assert_eq!(Card::from_str("fd").unwrap_err(), ParseCardError::Rank);
+    assert_eq!(
+        *Card::from_str("Kd").unwrap().as_ref(),
+        0b00001000_00000000_01001011_00100101_u32
+    );
+}
+
+#[test]
+fn test_tostr() {
+    let card = Card::from_str("Kd").unwrap();
+    assert_eq!(card.to_string(), "Kd");
+}
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644 (file)
index 0000000..c4332dd
--- /dev/null
@@ -0,0 +1,37 @@
+#[derive(Debug, PartialEq, Eq)]
+pub enum ParseCardError {
+    U32,
+    Rank,
+    Suit,
+    Chars,
+    String,
+    Index,
+}
+
+impl std::error::Error for ParseCardError {}
+
+impl std::fmt::Display for ParseCardError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ParseCardError::U32 => write!(f, "Invalid u32 provided"),
+            ParseCardError::Rank => write!(f, "Invalid rank char provided"),
+            ParseCardError::Suit => write!(f, "Invalid suit char provided"),
+            ParseCardError::Chars => write!(f, "Invalid chars provided"),
+            ParseCardError::String => write!(f, "Unexpected string size"),
+            ParseCardError::Index => write!(f, "Index out of bounds"),
+        }
+        // write!(f, "Invalid number of cards")
+    }
+}
+
+#[test]
+fn test_send() {
+    fn assert_send<T: Send>() {}
+    assert_send::<ParseCardError>();
+}
+
+#[test]
+fn test_sync() {
+    fn assert_sync<T: Sync>() {}
+    assert_sync::<ParseCardError>();
+}
index 34e6895..ed13f07 100644 (file)
@@ -4,6 +4,7 @@
 //! <http://suffe.cool/poker/evaluator.html>
 pub mod card;
 pub mod constants;
+pub mod errors;
 pub mod evaluator;
 
 use std::{
@@ -460,7 +461,9 @@ mod tests {
     #[test]
     fn evaluate_without_cards() {
         let mut deck = Deck::default();
-        let player_hands = deck.deal_with_rules_and_player_nb(&Rules::Holdem, 4).unwrap();
+        let player_hands = deck
+            .deal_with_rules_and_player_nb(&Rules::Holdem, 4)
+            .unwrap();
         let mut evaluator = Evaluator::new();
         assert!(evaluator.eval().is_err());
         assert!(evaluator.hands(&player_hands).eval().is_err());