fish-lib 0.2.3

A work-in-progress fishing game library containing the game/storage logic for a discord fishing game I'm working on.
Documentation
use crate::config::ConfigInterface;
use crate::game::errors::resource::GameResourceError;
use crate::game::errors::GameResult;
use crate::game::repositories::fishing_history_entry_repository::FishingHistoryEntryRepositoryInterface;
use crate::models::fishing_history_entry::{FishingHistoryEntry, NewFishingHistoryEntry};
use crate::models::specimen::Specimen;
use chrono::{DateTime, Utc};
use std::sync::Arc;

pub trait FishingHistoryServiceInterface: Send + Sync {
    fn register_catch(&self, fish: &Specimen) -> GameResult<FishingHistoryEntry>;
    fn register_sell(
        &self,
        fish: &Specimen,
        sell_time: DateTime<Utc>,
    ) -> GameResult<FishingHistoryEntry>;
}

pub struct FishingHistoryService {
    config: Arc<dyn ConfigInterface>,
    fishing_history_entry_repository: Arc<dyn FishingHistoryEntryRepositoryInterface>,
}

impl FishingHistoryService {
    pub fn new(
        config: Arc<dyn ConfigInterface>,
        fishing_history_entry_repository: Arc<dyn FishingHistoryEntryRepositoryInterface>,
    ) -> Self {
        Self {
            config,
            fishing_history_entry_repository,
        }
    }
}

impl FishingHistoryServiceInterface for FishingHistoryService {
    fn register_catch(&self, fish: &Specimen) -> GameResult<FishingHistoryEntry> {
        let config = self.config.clone();

        let existing_entry = self
            .fishing_history_entry_repository
            .find_by_user_and_species_id(fish.user_id, fish.species_id)?;

        let time_multiplier = self.config.settings().time_speed_multiplier;

        if let Some(mut entry) = existing_entry {
            let total_size_ratio = fish.get_total_size_ratio(config, time_multiplier)?;
            entry.register_catch(total_size_ratio, fish.created_at);
            let saved_entry = self.fishing_history_entry_repository.save(entry)?;
            Ok(saved_entry)
        } else {
            let total_size_ratio = fish.get_total_size_ratio(config, time_multiplier)?;
            let new_entry = NewFishingHistoryEntry {
                user_id: fish.user_id,
                species_id: fish.species_id,
                caught_count: 1,
                sold_count: 0,
                smallest_catch_size_ratio: total_size_ratio,
                largest_catch_size_ratio: total_size_ratio,
            };
            let saved_entry = self.fishing_history_entry_repository.create(new_entry)?;
            Ok(saved_entry)
        }
    }

    fn register_sell(
        &self,
        fish: &Specimen,
        sell_time: DateTime<Utc>,
    ) -> GameResult<FishingHistoryEntry> {
        let mut existing_entry = self
            .fishing_history_entry_repository
            .find_by_user_and_species_id(fish.user_id, fish.species_id)?
            .ok_or_else(|| {
                GameResourceError::fishing_history_not_found(fish.user_id, fish.species_id)
            })?;

        existing_entry.register_sell(sell_time);
        Ok(self.fishing_history_entry_repository.save(existing_entry)?)
    }
}