betting 0.3.1

A crate to manage twitch-style bets (aka 'Parimutuel betting')
Documentation
use rusqlite::Connection;
use crate::{BetError, Outcome, BetInfo};

pub(crate) trait BetConnection {
    fn outcomes_of_bet(&self, bet: u64) -> Result<Vec<u64>, BetError>;

    fn outcome_status(
        &self, bet: u64, outcome: u64,
    ) -> Result<Outcome, BetError>;

    fn outcomes_statuses(
        &self, bet: u64
    ) -> Result<Vec<Outcome>, BetError>;

    fn assert_bet_not_deleted(&self, bet: u64) -> Result<(), BetError>;

    fn bet_info(&self, bet: u64) -> Result<BetInfo, BetError>;

    fn balance(&self, server: u64, user: u64) -> Result<u64, BetError>;
}

impl BetConnection for Connection {
    fn outcomes_of_bet(&self, bet: u64) -> Result<Vec<u64>, BetError> {
        Ok(self.prepare(
                "SELECT number 
                FROM Outcome
                WHERE bet = ?1",
            )
            .unwrap()
            .query_map([bet], |row| row.get::<usize, u64>(0))?
            .collect::<Result<Vec<_>, _>>()?)
    }

    fn outcome_status(
        &self, bet: u64, outcome: u64,
    ) -> Result<Outcome, BetError> {
        let desc = self.prepare(
            "SELECT desc
            FROM Outcome
            WHERE bet = ?1 AND number = ?2",
            )
            .unwrap()
            .query_row([bet, outcome], |row| row.get::<usize, String>(0))?;
        let mut stmt = self
            .prepare(
                "SELECT user, amount
                FROM Wager
                WHERE bet = ?1 AND outcome = ?2",
            )
            .unwrap();
        let mut rows = stmt.query([bet, outcome])?;
        let mut wagers = Vec::new();
        while let Some(row) = rows.next()? {
            wagers.push((row.get::<usize, u64>(0)?, row.get::<usize, u64>(1)?));
        }
        Ok(Outcome {
            desc: desc,
            wagers: wagers,
        })
    }

    fn outcomes_statuses(
        &self, bet: u64
    ) -> Result<Vec<Outcome>, BetError> {
        let outcomes = self.outcomes_of_bet(bet)?;
        outcomes
            .into_iter()
            .map(|opt| self.outcome_status(bet, opt))
            .collect::<Result<Vec<_>, _>>()
    }

    fn assert_bet_not_deleted(&self, bet: u64) -> Result<(), BetError> {
        if self.prepare(
            "SELECT * 
            FROM ToDelete
            WHERE bet = ?1
            ",
        )
        .unwrap()
        .exists([bet])?
        {
            Err(BetError::NotFound)
        } else {
            Ok(())
        }
    }

    fn bet_info(&self, bet: u64) -> Result<BetInfo, BetError> {
        let (desc, server, author, is_open) = self.prepare(
            "SELECT desc, server, author, is_open 
            FROM Bet
            WHERE uuid = ?1
            ",
        )
        .unwrap()
        .query_row(
            [bet], 
            |row| 
            Ok((
                row.get::<usize, String>(0)?,
                row.get::<usize, u64>(1)?, 
                row.get::<usize, u64>(2)?, 
                row.get::<usize, u32>(3)? != 0
            ))

        )?;
        Ok(BetInfo { desc, server, author, is_open })
    }

    fn balance(&self, server: u64, user: u64) -> Result<u64, BetError> {
        Ok(self.prepare(
                "SELECT balance 
                    FROM Account
                    WHERE server = ?1 AND user = ?2",
            )
            .unwrap()
            .query_row([server, user], |row| row.get::<usize, u64>(0))?)
    }
}