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::data::item_data::ItemData;
use crate::dto::inventory::Inventory;
use crate::game::errors::resource::GameResourceError;
use crate::game::errors::GameResult;
use crate::game::repositories::item_repository::ItemRepositoryInterface;
use crate::models::item::properties_container::ItemPropertiesContainerInterface;
use crate::models::item::{Item, ItemEventResult, ItemEventSuccess, NewItem};
use crate::models::user::User;
use std::sync::Arc;

pub trait ItemServiceInterface: Send + Sync {
    fn get_item_data(&self, type_id: i32) -> GameResult<Arc<ItemData>>;
    fn add_new_item(&self, item: NewItem, user: &User) -> GameResult<Item>;
    fn create_and_save_item(&self, item_data: Arc<ItemData>, user: &User) -> GameResult<Item>;
    fn create_and_save_item_with_count(
        &self,
        item_data: Arc<ItemData>,
        user: &User,
        count: u64,
    ) -> GameResult<Item>;
    fn manipulate(
        &self,
        item: Item,
        function: Box<dyn Fn(&mut Item) -> ItemEventResult>,
    ) -> GameResult<ItemEventSuccess>;
    fn get_inventory(&self, user: &User) -> GameResult<Inventory>;
}

pub struct ItemService {
    config: Arc<dyn ConfigInterface>,
    item_repository: Arc<dyn ItemRepositoryInterface>,
}

impl ItemService {
    pub fn new(
        config: Arc<dyn ConfigInterface>,
        item_repository: Arc<dyn ItemRepositoryInterface>,
    ) -> ItemService {
        ItemService {
            config,
            item_repository,
        }
    }
}

impl ItemServiceInterface for ItemService {
    fn get_item_data(&self, type_id: i32) -> GameResult<Arc<ItemData>> {
        self.config
            .get_item_data(type_id)
            .ok_or(GameResourceError::item_not_found(type_id).into())
    }

    fn add_new_item(&self, new_item: NewItem, user: &User) -> GameResult<Item> {
        let item_data = self.get_item_data(new_item.type_id)?;
        let existing_items = self
            .item_repository
            .find_by_type_and_user(new_item.type_id, user.id)?;

        let max_count = item_data.max_count;
        let has_count = existing_items.len() as u32;

        let count_max_exceeded = max_count > 1 && has_count >= max_count;
        let count_unique_exceeded = max_count == 1 && has_count > 0 && !item_data.is_stackable();
        if count_max_exceeded || count_unique_exceeded {
            return Err(GameResourceError::item_max_count_exceeded(
                new_item.type_id,
                user.external_id,
            )
            .into());
        };

        let item = if max_count > 1 || has_count == 0 || !item_data.is_stackable() {
            self.item_repository.create(new_item)?
        } else {
            let amount =
                new_item
                    .properties
                    .get_count()
                    .ok_or(GameResourceError::item_unstackable(
                        new_item.type_id,
                        "New item has no count property",
                    ))?;

            let mut item_to_edit =
                existing_items
                    .first()
                    .cloned()
                    .ok_or(GameResourceError::item_unstackable(
                        new_item.type_id,
                        "Did not find any item to add the new item's amount to",
                    ))?;

            item_to_edit.add(amount)?;
            self.item_repository.save(item_to_edit)?
        };

        Ok(item)
    }

    fn create_and_save_item(&self, item_data: Arc<ItemData>, user: &User) -> GameResult<Item> {
        let new_item = NewItem::new(user.id, item_data.clone());
        self.add_new_item(new_item, user)
    }

    fn create_and_save_item_with_count(
        &self,
        item_data: Arc<ItemData>,
        user: &User,
        count: u64,
    ) -> GameResult<Item> {
        let mut new_item = NewItem::new(user.id, item_data.clone());
        if !item_data.is_stackable() {
            return Err(GameResourceError::item_unstackable(
                item_data.id,
                "count provided on item creation, but item is unstackable",
            )
            .into());
        } else {
            new_item.properties.set_count(count);
        }
        self.add_new_item(new_item, user)
    }

    fn manipulate(
        &self,
        mut item: Item,
        function: Box<dyn Fn(&mut Item) -> ItemEventResult>,
    ) -> GameResult<ItemEventSuccess> {
        let success = function(&mut item)?;

        if success.consume {
            self.item_repository.delete(item)?;
        } else {
            self.item_repository.save(item)?;
        }

        Ok(success)
    }

    fn get_inventory(&self, user: &User) -> GameResult<Inventory> {
        let items = self.item_repository.find_by_user(user.id)?;
        Ok(Inventory::new(self.config.clone(), items))
    }
}