cargo clippy

This commit is contained in:
Dylan Knutson
2025-08-29 17:17:42 +00:00
parent cf02bfd6d7
commit db04de9442
18 changed files with 106 additions and 112 deletions

View File

@@ -2,9 +2,7 @@ use crate::{
case,
db::{Listing, ListingDAO, ListingId, User, UserDAO},
keyboard_buttons,
message_utils::{
extract_callback_data, pluralize_with_count, send_message, HandleAndId, MessageTarget,
},
message_utils::{extract_callback_data, pluralize_with_count, send_message, MessageTarget},
Command, DialogueRootState, HandlerResult, RootDialogue,
};
use serde::{Deserialize, Serialize};
@@ -12,7 +10,7 @@ use sqlx::SqlitePool;
use teloxide::{
dispatching::{DpHandlerDescription, UpdateFilterExt},
prelude::*,
types::{InlineKeyboardButton, Message, MessageId, ParseMode},
types::{InlineKeyboardButton, Message},
Bot,
};
@@ -183,7 +181,7 @@ async fn show_listing_details(
);
send_message(
&bot,
bot,
target,
response,
Some(ManageListingButtons::to_keyboard()),
@@ -233,7 +231,7 @@ async fn get_user_and_listing(
listing_id: ListingId,
target: impl Into<MessageTarget>,
) -> HandlerResult<(User, Listing)> {
let user = match UserDAO::find_by_telegram_id(&db_pool, user_id).await? {
let user = match UserDAO::find_by_telegram_id(db_pool, user_id).await? {
Some(user) => user,
None => {
send_message(
@@ -247,7 +245,7 @@ async fn get_user_and_listing(
}
};
let listing = match ListingDAO::find_by_id(&db_pool, listing_id).await? {
let listing = match ListingDAO::find_by_id(db_pool, listing_id).await? {
Some(listing) => listing,
None => {
send_message(bot, target, "❌ Listing not found.", None).await?;

View File

@@ -9,7 +9,6 @@ use crate::{
models::new_listing::{NewListing, NewListingBase, NewListingFields},
ListingDuration, NewUser, UserDAO,
},
keyboard_buttons,
message_utils::*,
DialogueRootState, HandlerResult, RootDialogue,
};
@@ -35,8 +34,8 @@ fn create_back_button_keyboard_with_clear(field: &str) -> InlineKeyboardMarkup {
create_single_row_keyboard(&[
("🔙 Back", "edit_back"),
(
&format!("🧹 Clear {}", field),
&format!("edit_clear_{}", field),
&format!("🧹 Clear {field}"),
&format!("edit_clear_{field}"),
),
])
}
@@ -190,7 +189,7 @@ async fn handle_description_callback(
send_message(&bot, target, response, None).await?;
}
_ => {
error!("Unknown callback data: {}", data);
error!("Unknown callback data: {data}");
dialogue.exit().await?;
}
}
@@ -205,7 +204,7 @@ async fn handle_awaiting_draft_field_callback(
callback_query: CallbackQuery,
) -> HandlerResult {
let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?;
info!("User {:?} selected callback: {:?}", from, data);
info!("User {from:?} selected callback: {data:?}");
let target = (from, message_id);
if data == "cancel" {
@@ -214,17 +213,17 @@ async fn handle_awaiting_draft_field_callback(
match field {
ListingField::Title => {
error!("Unknown callback data: {}", data);
error!("Unknown callback data: {data}");
dialogue.exit().await?;
return Ok(());
Ok(())
}
ListingField::Description => {
handle_description_callback(bot, dialogue, draft, data.as_str(), target).await
}
ListingField::Price => {
error!("Unknown callback data: {}", data);
error!("Unknown callback data: {data}");
dialogue.exit().await?;
return Ok(());
Ok(())
}
ListingField::Slots => {
handle_slots_callback(bot, dialogue, draft, data.as_str(), target).await
@@ -303,7 +302,7 @@ async fn process_slots_and_respond(
);
send_message(
&bot,
bot,
target,
&response,
Some(StartTimeKeyboardButtons::to_keyboard()),
@@ -328,12 +327,12 @@ async fn handle_viewing_draft_callback(
match button {
ConfirmationKeyboardButtons::Create => {
info!("User {:?} confirmed listing creation", target);
info!("User {target:?} confirmed listing creation");
dialogue.exit().await?;
create_listing(db_pool, bot, dialogue, from, message_id, draft.clone()).await?;
}
ConfirmationKeyboardButtons::Discard => {
info!("User {:?} discarded listing creation", from);
info!("User {from:?} discarded listing creation");
// Exit dialogue and send cancellation message
dialogue.exit().await?;
@@ -345,7 +344,7 @@ async fn handle_viewing_draft_callback(
send_message(&bot, target, &response, None).await?;
}
ConfirmationKeyboardButtons::Edit => {
info!("User {:?} chose to edit listing", from);
info!("User {from:?} chose to edit listing");
// Go to editing state to allow user to modify specific fields
dialogue
@@ -379,18 +378,17 @@ async fn process_start_time_and_respond(
.await?;
// Generate response message
let start_msg = format!("in {}", duration);
let start_msg = format!("in {duration}");
let response = format!(
"✅ Listing will start: <b>{}</b>\n\n\
"✅ Listing will start: <b>{start_msg}</b>\n\n\
<i>Step 6 of 6: Duration</i>\n\
How long should your listing run?\n\
Enter duration in hours (minimum 1 hour, maximum 720 hours / 30 days):",
start_msg
Enter duration in hours (minimum 1 hour, maximum 720 hours / 30 days):"
);
send_message(
&bot,
bot,
target,
&response,
Some(DurationKeyboardButtons::to_keyboard()),
@@ -606,7 +604,7 @@ async fn show_edit_screen(
);
if let Some(flash_message) = flash_message {
response = format!("{}\n\n{}", flash_message, response);
response = format!("{flash_message}\n\n{response}");
}
send_message(
@@ -629,7 +627,7 @@ async fn handle_editing_field_input(
let chat = msg.chat.clone();
let text = msg.text().unwrap_or("").trim();
info!("User {:?} editing field {:?}", chat, field);
info!("User {chat:?} editing field {field:?}");
match field {
ListingField::Title => {
@@ -728,10 +726,9 @@ async fn handle_editing_draft_callback(
// update the message to show the edit screen
let response = format!(
"Editing {:?}\n\n\
Previous value: {}\
",
field, value
"Editing {field:?}\n\n\
Previous value: {value}\
"
);
send_message(&bot, target, response, Some(keyboard)).await?;
@@ -751,7 +748,7 @@ async fn create_listing(
let starts_at = now + Into::<Duration>::into(draft.start_delay);
let ends_at = starts_at + Into::<Duration>::into(draft.duration);
let user = match UserDAO::find_by_telegram_id(&db_pool, from.id.clone()).await? {
let user = match UserDAO::find_by_telegram_id(&db_pool, from.id).await? {
Some(user) => user,
None => {
UserDAO::insert_user(
@@ -803,7 +800,7 @@ async fn create_listing(
);
}
Err(e) => {
log::error!("Failed to create listing for user {:?}: {}", from, e);
log::error!("Failed to create listing for user {from:?}: {e}");
send_message(
&bot,
(from, message_id),
@@ -823,7 +820,7 @@ async fn cancel_wizard(
target: impl Into<MessageTarget>,
) -> HandlerResult {
let target = target.into();
info!("{:?} cancelled new listing wizard", target);
info!("{target:?} cancelled new listing wizard");
dialogue.exit().await?;
send_message(&bot, target, "❌ Listing creation cancelled.", None).await?;
Ok(())
@@ -838,7 +835,7 @@ async fn handle_edit_title(
target: impl Into<MessageTarget>,
) -> HandlerResult {
let target = target.into();
info!("User {:?} editing title: '{}'", target, text);
info!("User {target:?} editing title: '{text}'");
draft.title = match validate_title(text) {
Ok(title) => title,
@@ -873,7 +870,7 @@ async fn handle_edit_description(
target: impl Into<MessageTarget>,
) -> HandlerResult {
let target = target.into();
info!("User {:?} editing description: '{}'", target, text);
info!("User {target:?} editing description: '{text}'");
state.description = match validate_description(text) {
Ok(description) => Some(description),
@@ -903,7 +900,7 @@ async fn handle_edit_price(
target: impl Into<MessageTarget>,
) -> HandlerResult {
let target = target.into();
info!("User {:?} editing price: '{}'", target, text);
info!("User {target:?} editing price: '{text}'");
state.buy_now_price = match validate_price(text) {
Ok(price) => price,
@@ -932,7 +929,7 @@ async fn handle_edit_slots(
target: impl Into<MessageTarget>,
) -> HandlerResult {
let target = target.into();
info!("User {:?} editing slots: '{}'", target, text);
info!("User {target:?} editing slots: '{text}'");
state.slots_available = match validate_slots(text) {
Ok(s) => s,
@@ -962,7 +959,7 @@ async fn handle_edit_start_time(
target: impl Into<MessageTarget>,
) -> HandlerResult {
let target = target.into();
info!("User {:?} editing start time: '{}'", target, text);
info!("User {target:?} editing start time: '{text}'");
state.start_delay = match validate_start_time(text) {
Ok(h) => h,
@@ -998,7 +995,7 @@ async fn handle_edit_duration(
target: impl Into<MessageTarget>,
) -> HandlerResult {
let target = target.into();
info!("User {:?} editing duration: '{}'", target, text);
info!("User {target:?} editing duration: '{text}'");
state.duration = match validate_duration(text) {
Ok(d) => d,

View File

@@ -41,7 +41,7 @@ pub fn validate_price(text: &str) -> Result<MoneyAmount, String> {
pub fn validate_slots(text: &str) -> Result<i32, String> {
match text.parse::<i32>() {
Ok(slots) if slots >= 1 && slots <= 1000 => Ok(slots),
Ok(slots) if (1..=1000).contains(&slots) => Ok(slots),
Ok(_) => Err(
"❌ Number of slots must be between 1 and 1000. Please enter a valid number:"
.to_string(),
@@ -52,7 +52,7 @@ pub fn validate_slots(text: &str) -> Result<i32, String> {
pub fn validate_duration(text: &str) -> Result<ListingDuration, String> {
match text.parse::<i32>() {
Ok(hours) if hours >= 1 && hours <= 720 => Ok(ListingDuration::hours(hours)), // 1 hour to 30 days
Ok(hours) if (1..=720).contains(&hours) => Ok(ListingDuration::hours(hours)), // 1 hour to 30 days
Ok(_) => Err(
"❌ Duration must be between 1 and 720 hours. Please enter a valid number:".to_string(),
),
@@ -62,7 +62,7 @@ pub fn validate_duration(text: &str) -> Result<ListingDuration, String> {
pub fn validate_start_time(text: &str) -> Result<ListingDuration, String> {
match text.parse::<i32>() {
Ok(hours) if hours >= 0 && hours <= 168 => Ok(ListingDuration::hours(hours)), // Max 1 week delay
Ok(hours) if (0..=168).contains(&hours) => Ok(ListingDuration::hours(hours)), // Max 1 week delay
Ok(_) => Err(
"❌ Start time must be between 0 and 168 hours. Please enter a valid number:"
.to_string(),

View File

@@ -76,7 +76,7 @@ impl Config {
log::info!(" Web Port: {}", self.web_port);
if let Some(admin_id) = self.admin_user_id {
log::info!(" Admin User ID: {}", admin_id);
log::info!(" Admin User ID: {admin_id}");
} else {
log::info!(" Admin User ID: Not set");
}

View File

@@ -114,7 +114,7 @@ impl ListingDAO {
.fetch_optional(pool)
.await?;
Ok(result.map(Self::row_to_listing).transpose()?)
result.map(Self::row_to_listing).transpose()
}
/// Find all listings by a seller
@@ -125,10 +125,10 @@ impl ListingDAO {
.fetch_all(pool)
.await?;
Ok(rows
rows
.into_iter()
.map(Self::row_to_listing)
.collect::<Result<Vec<_>>>()?)
.collect::<Result<Vec<_>>>()
}
/// Delete a listing

View File

@@ -13,6 +13,7 @@ use crate::db::{
/// Data Access Object for User operations
pub struct UserDAO;
#[allow(unused)]
impl UserDAO {
/// Insert a new user into the database
pub async fn insert_user(pool: &SqlitePool, new_user: &NewUser) -> Result<User> {
@@ -131,7 +132,7 @@ impl UserDAO {
#[cfg(test)]
mod tests {
use super::*;
use crate::db::models::user::{NewUser, User};
use crate::db::models::user::NewUser;
use rstest::rstest;
use sqlx::SqlitePool;
use teloxide::types::UserId;

View File

@@ -15,6 +15,7 @@ use chrono::{DateTime, Utc};
/// Main listing/auction entity
#[derive(Debug, Clone)]
#[allow(unused)]
pub struct Listing {
pub base: ListingBase,
pub fields: ListingFields,
@@ -22,6 +23,7 @@ pub struct Listing {
/// Common fields shared by all listing types
#[derive(Debug, Clone)]
#[allow(unused)]
pub struct ListingBase {
pub id: ListingId,
pub seller_id: UserRowId,
@@ -34,6 +36,7 @@ pub struct ListingBase {
}
#[derive(Debug, Clone)]
#[allow(unused)]
pub enum ListingFields {
BasicAuction {
starting_bid: MoneyAmount,
@@ -57,6 +60,7 @@ pub enum ListingFields {
},
}
#[allow(unused)]
impl Listing {
/// Get the listing type as an enum value
pub fn listing_type(&self) -> ListingType {

View File

@@ -8,10 +8,6 @@ pub mod user;
pub mod user_settings;
// Re-export all types for easy access
pub use bid::*;
pub use listing::*;
pub use listing_media::*;
pub use listing_type::*;
pub use proxy_bid::*;
pub use user::*;
pub use user_settings::*;

View File

@@ -29,6 +29,7 @@ pub struct NewListingBase {
}
#[derive(Debug, Clone)]
#[allow(unused)]
pub enum NewListingFields {
BasicAuction {
starting_bid: MoneyAmount,
@@ -52,6 +53,7 @@ pub enum NewListingFields {
},
}
#[allow(unused)]
impl NewListingBase {
pub fn new(
seller_id: UserRowId,

View File

@@ -5,6 +5,7 @@ use crate::db::MoneyAmount;
/// Proxy bid strategies (automatic bidding settings)
#[derive(Debug, Clone, FromRow)]
#[allow(unused)]
pub struct ProxyBid {
pub id: i64,
pub listing_id: i64,
@@ -17,6 +18,7 @@ pub struct ProxyBid {
/// New proxy bid data for insertion
#[derive(Debug, Clone)]
#[allow(unused)]
pub struct NewProxyBid {
pub listing_id: i64,
pub buyer_id: i64,

View File

@@ -5,6 +5,7 @@ use crate::db::{TelegramUserId, UserRowId};
/// Core user information
#[derive(Debug, Clone, FromRow)]
#[allow(unused)]
pub struct User {
pub id: UserRowId,
pub telegram_id: TelegramUserId,

View File

@@ -3,41 +3,38 @@ use sqlx::{
};
/// Currency types supported by the platform
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CurrencyType {
USD,
#[default]
Usd,
}
#[allow(unused)]
impl CurrencyType {
/// Get the currency code as a string
pub fn as_str(&self) -> &'static str {
match self {
CurrencyType::USD => "USD",
CurrencyType::Usd => "USD",
}
}
/// Get the currency symbol
pub fn symbol(&self) -> &'static str {
match self {
CurrencyType::USD => "$",
CurrencyType::Usd => "$",
}
}
/// Parse currency from string
pub fn from_str(s: &str) -> Result<Self, String> {
match s.to_uppercase().as_str() {
"USD" => Ok(CurrencyType::USD),
_ => Err(format!("Unsupported currency: {}", s)),
"USD" => Ok(CurrencyType::Usd),
_ => Err(format!("Unsupported currency: {s}")),
}
}
}
// Implement Default for CurrencyType (defaults to USD)
impl Default for CurrencyType {
fn default() -> Self {
CurrencyType::USD
}
}
// Implement Display for CurrencyType
impl std::fmt::Display for CurrencyType {
@@ -83,7 +80,7 @@ mod tests {
#[test]
fn test_currency_type_display() {
let usd = CurrencyType::USD;
let usd = CurrencyType::Usd;
assert_eq!(usd.to_string(), "USD");
assert_eq!(usd.symbol(), "$");
assert_eq!(usd.as_str(), "USD");
@@ -92,16 +89,16 @@ mod tests {
#[test]
fn test_currency_type_default() {
let default_currency = CurrencyType::default();
assert_eq!(default_currency, CurrencyType::USD);
assert_eq!(default_currency, CurrencyType::Usd);
}
#[test]
fn test_currency_type_parsing() {
let parsed_currency = CurrencyType::from_str("usd").unwrap(); // Case insensitive
assert_eq!(parsed_currency, CurrencyType::USD);
assert_eq!(parsed_currency, CurrencyType::Usd);
let parsed_upper = CurrencyType::from_str("USD").unwrap();
assert_eq!(parsed_upper, CurrencyType::USD);
assert_eq!(parsed_upper, CurrencyType::Usd);
let invalid = CurrencyType::from_str("EUR");
assert!(invalid.is_err());

View File

@@ -11,6 +11,7 @@ use std::fmt::{self, Display};
use crate::message_utils::pluralize_with_count;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Default)]
pub struct ListingDuration(i32);
impl ListingDuration {
pub fn hours(hours: i32) -> Self {
@@ -23,11 +24,6 @@ impl ListingDuration {
Default::default()
}
}
impl Default for ListingDuration {
fn default() -> Self {
Self(0)
}
}
impl From<ListingDuration> for Duration {
fn from(duration: ListingDuration) -> Self {
Duration::hours(duration.0 as i64)

View File

@@ -6,6 +6,7 @@ mod telegram_user_id;
mod user_row_id;
// Re-export all types for easy access
#[allow(unused)]
pub use currency_type::*;
pub use listing_duration::*;
pub use listing_id::*;

View File

@@ -35,12 +35,12 @@ impl MoneyAmount {
}
/// Get the value in cents
pub fn cents(&self) -> i64 {
pub fn cents(self) -> i64 {
self.0
}
/// Get the value as a Decimal (for display/calculation purposes)
pub fn to_decimal(&self) -> Decimal {
pub fn to_decimal(self) -> Decimal {
Decimal::new(self.0, 2) // 2 decimal places for cents
}
}
@@ -204,7 +204,7 @@ mod tests {
// Insert test data
sqlx::query("INSERT INTO test_money (amount, currency) VALUES (?, ?)")
.bind(&amount)
.bind(CurrencyType::USD)
.bind(CurrencyType::Usd)
.execute(&pool)
.await
.expect("Failed to insert test data");
@@ -219,7 +219,7 @@ mod tests {
let retrieved_currency: CurrencyType = row.get("currency");
assert_eq!(retrieved_amount, amount);
assert_eq!(retrieved_currency, CurrencyType::USD);
assert_eq!(retrieved_currency, CurrencyType::Usd);
// Verify string representation matches expected format (cent-based precision)
assert_eq!(retrieved_amount.to_string(), expected_str);
@@ -234,7 +234,7 @@ mod tests {
// Test with NULL (None) value
sqlx::query("INSERT INTO test_money (amount, currency, optional_amount) VALUES (?, ?, ?)")
.bind(MoneyAmount::from_str("50.00").unwrap())
.bind(CurrencyType::USD)
.bind(CurrencyType::Usd)
.bind(None::<MoneyAmount>)
.execute(&pool)
.await
@@ -244,7 +244,7 @@ mod tests {
let optional_amount = Some(MoneyAmount::from_str("25.75").unwrap());
sqlx::query("INSERT INTO test_money (amount, currency, optional_amount) VALUES (?, ?, ?)")
.bind(MoneyAmount::from_str("100.00").unwrap())
.bind(CurrencyType::USD)
.bind(CurrencyType::Usd)
.bind(&optional_amount)
.execute(&pool)
.await
@@ -291,7 +291,7 @@ mod tests {
// Insert into database
sqlx::query("INSERT INTO test_money (amount, currency) VALUES (?, ?)")
.bind(&amount)
.bind(CurrencyType::USD)
.bind(CurrencyType::Usd)
.execute(&pool)
.await
.expect("Failed to insert precision test data");

View File

@@ -7,7 +7,6 @@ use sqlx::{
encode::IsNull, error::BoxDynError, sqlite::SqliteTypeInfo, Decode, Encode, Sqlite, Type,
};
use std::fmt;
use teloxide::types::ChatId;
/// Type-safe wrapper for user IDs
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]

View File

@@ -35,10 +35,10 @@ macro_rules! keyboard_buttons {
markup
}
}
impl Into<teloxide::types::InlineKeyboardButton> for $name {
fn into(self) -> teloxide::types::InlineKeyboardButton {
match self {
$($(Self::$variant => teloxide::types::InlineKeyboardButton::callback($text, $callback_data)),*),*
impl From<$name> for teloxide::types::InlineKeyboardButton {
fn from(value: $name) -> Self {
match value {
$($($name::$variant => teloxide::types::InlineKeyboardButton::callback($text, $callback_data)),*),*
}
}
}

View File

@@ -6,8 +6,8 @@ use teloxide::{
payloads::{EditMessageTextSetters as _, SendMessageSetters as _},
prelude::Requester as _,
types::{
CallbackQuery, Chat, ChatId, InlineKeyboardButton, InlineKeyboardMarkup, Message,
MessageId, ParseMode, User,
CallbackQuery, Chat, ChatId, InlineKeyboardButton, InlineKeyboardMarkup, MessageId,
ParseMode, User,
},
Bot,
};
@@ -39,14 +39,14 @@ impl<'s> HandleAndId<'s> {
}
}
impl<'s> Into<HandleAndId<'s>> for &'s User {
fn into(self) -> HandleAndId<'s> {
HandleAndId::from_user(self)
impl<'s> From<&'s User> for HandleAndId<'s> {
fn from(val: &'s User) -> Self {
HandleAndId::from_user(val)
}
}
impl<'s> Into<HandleAndId<'s>> for &'s Chat {
fn into(self) -> HandleAndId<'s> {
HandleAndId::from_chat(self)
impl<'s> From<&'s Chat> for HandleAndId<'s> {
fn from(val: &'s Chat) -> Self {
HandleAndId::from_chat(val)
}
}
@@ -60,47 +60,47 @@ pub struct MessageTarget {
pub message_id: Option<MessageId>,
}
impl Into<MessageTarget> for ChatId {
fn into(self) -> MessageTarget {
impl From<ChatId> for MessageTarget {
fn from(val: ChatId) -> Self {
MessageTarget {
chat_id: self,
chat_id: val,
message_id: None,
}
}
}
impl Into<MessageTarget> for Chat {
fn into(self) -> MessageTarget {
impl From<Chat> for MessageTarget {
fn from(val: Chat) -> Self {
MessageTarget {
chat_id: self.id,
chat_id: val.id,
message_id: None,
}
}
}
impl Into<MessageTarget> for User {
fn into(self) -> MessageTarget {
impl From<User> for MessageTarget {
fn from(val: User) -> Self {
MessageTarget {
chat_id: self.id.into(),
chat_id: val.id.into(),
message_id: None,
}
}
}
impl Into<MessageTarget> for (User, MessageId) {
fn into(self) -> MessageTarget {
impl From<(User, MessageId)> for MessageTarget {
fn from(val: (User, MessageId)) -> Self {
MessageTarget {
chat_id: self.0.id.into(),
message_id: Some(self.1),
chat_id: val.0.id.into(),
message_id: Some(val.1),
}
}
}
impl Into<MessageTarget> for (Chat, MessageId) {
fn into(self) -> MessageTarget {
impl From<(Chat, MessageId)> for MessageTarget {
fn from(val: (Chat, MessageId)) -> Self {
MessageTarget {
chat_id: self.0.id.into(),
message_id: Some(self.1),
chat_id: val.0.id,
message_id: Some(val.1),
}
}
}
@@ -165,7 +165,7 @@ pub fn create_multi_row_keyboard(rows: &[&[(&str, &str)]]) -> InlineKeyboardMark
}
// Extract callback data and answer callback query
pub async fn extract_callback_data<'c>(
pub async fn extract_callback_data(
bot: &Bot,
callback_query: CallbackQuery,
) -> HandlerResult<(String, User, MessageId)> {
@@ -184,7 +184,7 @@ pub async fn extract_callback_data<'c>(
// Answer the callback query to remove loading state
if let Err(e) = bot.answer_callback_query(callback_query.id.clone()).await {
log::warn!("Failed to answer callback query: {}", e);
log::warn!("Failed to answer callback query: {e}");
}
Ok((data, from, message_id))
@@ -202,7 +202,7 @@ pub fn pluralize<'a, N: One + PartialEq<N>>(
}
}
pub fn pluralize_with_count<'a, N: One + PartialEq<N> + Display + Copy>(
pub fn pluralize_with_count<N: One + PartialEq<N> + Display + Copy>(
count: N,
singular: &str,
plural: &str,