From 849c090e28a2d7bf5b77351ea34390c4980992da Mon Sep 17 00:00:00 2001 From: WiseDev <83840010+wisedevik@users.noreply.github.com> Date: Fri, 28 Aug 2026 23:26:44 +0300 Subject: [PATCH] give the shop a typed error instead of string literals ShopError variants are matchable; the message is the Display, rendered at the log site, not baked into the error type. --- crates/game-service/src/home_mode.rs | 54 +++++++++++++++++++--------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/crates/game-service/src/home_mode.rs b/crates/game-service/src/home_mode.rs index 534b4a7..818280a 100644 --- a/crates/game-service/src/home_mode.rs +++ b/crates/game-service/src/home_mode.rs @@ -22,6 +22,28 @@ fn gold() -> LogicDataRef { fn diamonds() -> LogicDataRef { LogicDataRef::by_name(table::RESOURCES, RESOURCE_DIAMONDS) } +#[derive(Debug, thiserror::Error)] +pub enum ShopError { + #[error("not a card")] + NotACard, + #[error("not on sale")] + NotOnSale, + #[error("buy limit reached")] + BuyLimitReached, + #[error("nothing sells that")] + NotSold, + #[error("chest not sold")] + ChestNotSold, + #[error("no market price")] + NoMarketPrice, + #[error("price is not a resource")] + PriceNotResource, + #[error("not enough funds")] + CannotAfford, + #[error("unsupported currency")] + UnsupportedCurrency, +} + #[derive(Debug, Default)] pub struct TurnResult { pub claims: Vec, @@ -92,11 +114,11 @@ impl HomeMode { _ => LogicDataRef::treasure_chest_for_arena(CHEST_FREE, arena), } } - fn market_cost(&self, give: &ShopEntry) -> Result { + fn market_cost(&self, give: &ShopEntry) -> Result { if let Some(chest) = give.data.as_treasure_chest() { let price = chest.shop_price(); if price < 1 { - return Err("that chest is not sold in the shop"); + return Err(ShopError::ChestNotSold); } return Ok(ShopEntry::new(diamonds(), price)); } @@ -106,27 +128,27 @@ impl HomeMode { LogicGlobals::resource_diamond_cost(give.count), )); } - Err("that item has no market price") + Err(ShopError::NoMarketPrice) } - fn pay(&mut self, cost: &ShopEntry) -> Result<(), &'static str> { + fn pay(&mut self, cost: &ShopEntry) -> Result<(), ShopError> { if !cost.is_resource() { - return Err("the price must be a resource"); + return Err(ShopError::PriceNotResource); } match cost.data.name() { RESOURCE_GOLD if self.logic.spend_gold(cost.count) => Ok(()), RESOURCE_DIAMONDS if self.logic.spend_diamonds(cost.count) => Ok(()), - RESOURCE_GOLD | RESOURCE_DIAMONDS => Err("the player cannot afford it"), - _ => Err("only gold and diamonds are accepted"), + RESOURCE_GOLD | RESOURCE_DIAMONDS => Err(ShopError::CannotAfford), + _ => Err(ShopError::UnsupportedCurrency), } } - fn buy_card(&mut self, card: &LogicDataRef) -> Result { - let spell = card.as_spell().ok_or("that is not a card")?; + fn buy_card(&mut self, card: &LogicDataRef) -> Result { + let spell = card.as_spell().ok_or(ShopError::NotACard)?; let buy_times = self .logic .shop_buy_times(card) - .ok_or("that card is not on sale right now")?; + .ok_or(ShopError::NotOnSale)?; if buy_times >= spell.shop_buy_limit() { - return Err("the buy limit for that card is reached"); + return Err(ShopError::BuyLimitReached); } let cost = ShopEntry::new(gold(), spell.cost_in_shop(buy_times)); self.pay(&cost)?; @@ -143,14 +165,14 @@ impl HomeMode { data: &LogicDataRef, count: i32, shop: &ShopCatalog, - ) -> Result { + ) -> Result { if data.as_spell().is_some() { return self.buy_card(data); } let offer = shop .offer_for(data) .filter(|offer| offer.give.count == count) - .ok_or("nothing in the shop sells that")? + .ok_or(ShopError::NotSold)? .clone(); let cost = match &offer.cost { Some(cost) => cost.clone(), @@ -161,7 +183,7 @@ impl HomeMode { match offer.give.data.name() { RESOURCE_GOLD => self.logic.add_gold(offer.give.count), RESOURCE_DIAMONDS => self.logic.add_diamonds(offer.give.count), - _ => return Err("only gold and diamonds can be sold"), + _ => return Err(ShopError::UnsupportedCurrency), } } Ok(Purchase { @@ -221,11 +243,11 @@ impl HomeMode { result.changed = true; result.purchases.push(purchase); } - Err(reason) => { + Err(error) => { tracing::warn!( command = command.command_type(), item = %data, - reason, + reason = %error, "shop purchase rejected" ); }