use serde::Deserialize;
use std::fmt;
use super::snowflake;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum SortBy {
#[default]
MonthlyVotes,
TotalVotes,
ServerCount,
}
impl SortBy {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::MonthlyVotes => "monthly_votes_rank",
Self::TotalVotes => "total_votes_rank",
Self::ServerCount => "server_count_rank",
}
}
}
impl fmt::Display for SortBy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum SortOrder {
#[default]
Ascending,
Descending,
}
impl SortOrder {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Ascending => "asc",
Self::Descending => "desc",
}
}
}
impl fmt::Display for SortOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct RankedBot {
#[serde(deserialize_with = "snowflake::as_string::deserialize")]
pub id: u64,
pub name: String,
pub monthly_votes: i64,
pub monthly_votes_rank: i64,
pub server_count: Option<i64>,
pub server_count_rank: Option<i64>,
pub total_votes: i64,
pub total_votes_rank: i64,
pub review_count: Option<i64>,
pub monthly_votes_rank_change: Option<i64>,
pub server_count_rank_change: Option<i64>,
pub total_votes_rank_change: Option<i64>,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct RankingsResponse {
#[serde(rename = "totalBotCount")]
pub total_bot_count: i64,
pub data: Vec<RankedBot>,
}
#[derive(Debug, Clone, Default)]
pub struct RankingsQuery {
pub limit: Option<u16>,
pub offset: Option<u32>,
pub sort_by: SortBy,
pub sort_order: SortOrder,
}
impl RankingsQuery {
#[must_use]
pub const fn limit(mut self, limit: u16) -> Self {
self.limit = Some(limit);
self
}
#[must_use]
pub const fn offset(mut self, offset: u32) -> Self {
self.offset = Some(offset);
self
}
#[must_use]
pub const fn sort_by(mut self, sort_by: SortBy) -> Self {
self.sort_by = sort_by;
self
}
#[must_use]
pub const fn sort_order(mut self, sort_order: SortOrder) -> Self {
self.sort_order = sort_order;
self
}
pub fn validate(&self) -> Result<(), crate::Error> {
if let Some(limit) = self.limit.filter(|&l| l == 0 || l > 500) {
return Err(crate::Error::InvalidInput(format!(
"Rankings limit must be between 1 and 500, got {limit}"
)));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sort_by_as_str() {
assert_eq!(SortBy::MonthlyVotes.as_str(), "monthly_votes_rank");
assert_eq!(SortBy::TotalVotes.as_str(), "total_votes_rank");
assert_eq!(SortBy::ServerCount.as_str(), "server_count_rank");
}
#[test]
fn test_sort_order_as_str() {
assert_eq!(SortOrder::Ascending.as_str(), "asc");
assert_eq!(SortOrder::Descending.as_str(), "desc");
}
#[test]
fn test_ranked_bot_deserialization() {
let json = r#"{
"id": "432610292342587392",
"name": "Mudae",
"monthly_votes": 2312207,
"monthly_votes_rank": 1,
"server_count": 3371839,
"server_count_rank": 13,
"total_votes": 205265098,
"total_votes_rank": 1,
"review_count": 1050,
"monthly_votes_rank_change": 1,
"server_count_rank_change": 2,
"total_votes_rank_change": 3
}"#;
let bot: RankedBot = serde_json::from_str(json).unwrap();
assert_eq!(bot.id, 432_610_292_342_587_392);
assert_eq!(bot.name, "Mudae");
assert_eq!(bot.monthly_votes_rank, 1);
assert_eq!(bot.server_count, Some(3_371_839));
assert_eq!(bot.monthly_votes_rank_change, Some(1));
}
#[test]
fn test_rankings_response_deserialization() {
let json = r#"{
"totalBotCount": 43990,
"data": [
{
"id": "432610292342587392",
"name": "Mudae",
"monthly_votes": 2312207,
"monthly_votes_rank": 1,
"total_votes": 205265098,
"total_votes_rank": 1
}
]
}"#;
let response: RankingsResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.total_bot_count, 43990);
assert_eq!(response.data.len(), 1);
assert_eq!(response.data[0].name, "Mudae");
}
#[test]
fn test_rankings_query_builder() {
let query = RankingsQuery::default()
.limit(100)
.offset(50)
.sort_by(SortBy::TotalVotes)
.sort_order(SortOrder::Descending);
assert_eq!(query.limit, Some(100));
assert_eq!(query.offset, Some(50));
assert_eq!(query.sort_by, SortBy::TotalVotes);
assert_eq!(query.sort_order, SortOrder::Descending);
}
#[test]
fn test_rankings_query_validation() {
let valid_query = RankingsQuery::default().limit(100);
assert!(valid_query.validate().is_ok());
let invalid_query_zero = RankingsQuery::default().limit(0);
assert!(invalid_query_zero.validate().is_err());
let invalid_query_too_large = RankingsQuery::default().limit(501);
assert!(invalid_query_too_large.validate().is_err());
}
#[test]
fn test_defaults() {
assert_eq!(SortBy::default(), SortBy::MonthlyVotes);
assert_eq!(SortOrder::default(), SortOrder::Ascending);
}
}