Skip to content

Commit

Permalink
feat: add competition ranking to leaderboard (#439)
Browse files Browse the repository at this point in the history
Add competition ranking (1224) to the Leaderboard API.
  • Loading branch information
virratanasangpunth authored Feb 12, 2025
1 parent 023984a commit df4bd46
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 35 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ doc = false


[dependencies]
momento-protos = { version = "0.120.0" }
momento-protos = { version = "0.122.1" }
log = "0.4"
hyper = { version = "0.14" }
h2 = { version = "0.3" }
Expand Down
17 changes: 15 additions & 2 deletions src/leaderboard/leaderboard_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use crate::leaderboard::messages::data::delete::{DeleteRequest, DeleteResponse};
use crate::leaderboard::messages::data::fetch::FetchResponse;
use crate::leaderboard::messages::data::fetch_by_rank::{FetchByRankRequest, RankRange};
use crate::leaderboard::messages::data::fetch_by_score::{FetchByScoreRequest, ScoreRange};
use crate::leaderboard::messages::data::get_rank::{GetRankRequest, GetRankResponse};
use crate::leaderboard::messages::data::get_competition_rank::GetCompetitionRankRequest;
use crate::leaderboard::messages::data::get_rank::GetRankRequest;
use crate::leaderboard::messages::data::length::{LengthRequest, LengthResponse};
use crate::leaderboard::messages::data::remove_elements::{
RemoveElementsRequest, RemoveElementsResponse,
Expand Down Expand Up @@ -80,7 +81,7 @@ impl Leaderboard {
pub async fn get_rank(
&self,
ids: impl IntoIterator<Item = u32>,
) -> MomentoResult<GetRankResponse> {
) -> MomentoResult<FetchResponse> {
let request = GetRankRequest::new(ids);
request.send(self).await
}
Expand All @@ -103,6 +104,18 @@ impl Leaderboard {
request.send(self).await
}

/// Get rank of elements, using competition ranking, from a leaderboard using their element ids.
///
/// Defaults to DESCENDING order rank, meaning rank 0 is the element with
/// the highest score.
pub async fn get_competition_rank(
&self,
ids: impl IntoIterator<Item = u32>,
) -> MomentoResult<FetchResponse> {
let request = GetCompetitionRankRequest::new(ids);
request.send(self).await
}

/* helper fns */
pub(crate) fn new(
data_clients: Vec<
Expand Down
1 change: 0 additions & 1 deletion src/leaderboard/messages/data/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ impl LeaderboardRequest for DeleteRequest {
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::DeleteLeaderboardRequest {
cache_name: cache_name.to_string(),
leaderboard: leaderboard.leaderboard_name().to_string(),
},
)?;
Expand Down
1 change: 0 additions & 1 deletion src/leaderboard/messages/data/fetch_by_rank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ impl LeaderboardRequest for FetchByRankRequest {
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::GetByRankRequest {
cache_name: cache_name.to_string(),
leaderboard: leaderboard.leaderboard_name().to_string(),
rank_range: Some(self.rank_range.into()),
order: self.order.into_proto() as i32,
Expand Down
1 change: 0 additions & 1 deletion src/leaderboard/messages/data/fetch_by_score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ impl LeaderboardRequest for FetchByScoreRequest {
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::GetByScoreRequest {
cache_name: cache_name.to_string(),
leaderboard: leaderboard.leaderboard_name().to_string(),
score_range: Some(self.score_range.into()),
offset: self.offset.unwrap_or(0),
Expand Down
59 changes: 59 additions & 0 deletions src/leaderboard/messages/data/get_competition_rank.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use super::fetch::FetchResponse;
use super::Order;
use crate::leaderboard::LeaderboardRequest;
use crate::utils::prep_leaderboard_request_with_timeout;
use crate::{Leaderboard, MomentoResult};

/// A request to get ranked elements, sorted by competition ranking, by providing a list of element IDs.
pub struct GetCompetitionRankRequest {
ids: Vec<u32>,
order: Order,
}

impl GetCompetitionRankRequest {
/// Constructs a new `GetCompetitionRankRequest`.
///
/// Defaults to DESCENDING order, meaning that rank 0
/// is the element with the highest score.
pub fn new(ids: impl IntoIterator<Item = u32>) -> Self {
Self {
ids: ids.into_iter().collect(),
order: Order::Descending,
}
}

/// Sets the order ranking.
///
/// Defaults to DESCENDING order.
pub fn order(mut self, order: Order) -> Self {
self.order = order;
self
}
}

impl LeaderboardRequest for GetCompetitionRankRequest {
type Response = FetchResponse;

async fn send(self, leaderboard: &Leaderboard) -> MomentoResult<Self::Response> {
let cache_name = leaderboard.cache_name();
let request = prep_leaderboard_request_with_timeout(
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::GetCompetitionRankRequest {
leaderboard: leaderboard.leaderboard_name().to_string(),
ids: self.ids,
order: Some(self.order.into_proto() as i32),
},
)?;

let response = leaderboard
.next_data_client()
.get_competition_rank(request)
.await?
.into_inner();

Ok(FetchResponse::new(
response.elements.iter().map(|v| v.into()).collect(),
))
}
}
29 changes: 6 additions & 23 deletions src/leaderboard/messages/data/get_rank.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{Order, RankedElement};
use super::fetch::FetchResponse;
use super::Order;
use crate::leaderboard::LeaderboardRequest;
use crate::utils::prep_leaderboard_request_with_timeout;
use crate::{Leaderboard, MomentoResult};
Expand Down Expand Up @@ -31,15 +32,14 @@ impl GetRankRequest {
}

impl LeaderboardRequest for GetRankRequest {
type Response = GetRankResponse;
type Response = FetchResponse;

async fn send(self, leaderboard: &Leaderboard) -> MomentoResult<Self::Response> {
let cache_name = leaderboard.cache_name();
let request = prep_leaderboard_request_with_timeout(
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::GetRankRequest {
cache_name: cache_name.to_string(),
leaderboard: leaderboard.leaderboard_name().to_string(),
ids: self.ids,
order: self.order.into_proto() as i32,
Expand All @@ -52,25 +52,8 @@ impl LeaderboardRequest for GetRankRequest {
.await?
.into_inner();

Ok(Self::Response {
elements: response.elements.iter().map(|v| v.into()).collect(),
})
}
}

/// The response type for a successful `GetRankRequest`
pub struct GetRankResponse {
elements: Vec<RankedElement>,
}

impl GetRankResponse {
/// Returns the ranked elements in the response.
pub fn elements(&self) -> &[RankedElement] {
&self.elements
}

/// Consumes the response and returns the ranked elements.
pub fn into_elements(self) -> Vec<RankedElement> {
self.elements
Ok(FetchResponse::new(
response.elements.iter().map(|v| v.into()).collect(),
))
}
}
1 change: 0 additions & 1 deletion src/leaderboard/messages/data/length.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ impl LeaderboardRequest for LengthRequest {
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::GetLeaderboardLengthRequest {
cache_name: cache_name.to_string(),
leaderboard: leaderboard.leaderboard_name().to_string(),
},
)?;
Expand Down
3 changes: 3 additions & 0 deletions src/leaderboard/messages/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub mod fetch_by_rank;
/// Contains the request and response types for requesting elements from a
/// leaderboard by score.
pub mod fetch_by_score;
/// Contains the request and response types for requesting elements, sorted
/// by competition ranking, from a leaderboard using their element ids.
pub mod get_competition_rank;
/// Contains the request and response types for requesting elements from a
/// leaderboard using their element ids.
pub mod get_rank;
Expand Down
1 change: 0 additions & 1 deletion src/leaderboard/messages/data/remove_elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ impl LeaderboardRequest for RemoveElementsRequest {
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::RemoveElementsRequest {
cache_name: cache_name.to_string(),
leaderboard: leaderboard.leaderboard_name().to_string(),
ids: self.ids,
},
Expand Down
1 change: 0 additions & 1 deletion src/leaderboard/messages/data/upsert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ where
cache_name,
leaderboard.client_timeout(),
momento_protos::leaderboard::UpsertElementsRequest {
cache_name: cache_name.to_string(),
leaderboard: leaderboard.leaderboard_name().to_string(),
elements: self
.elements
Expand Down
2 changes: 1 addition & 1 deletion src/leaderboard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use messages::LeaderboardRequest;
pub use messages::data::delete::{DeleteRequest, DeleteResponse};
pub use messages::data::fetch_by_rank::{FetchByRankRequest, RankRange};
pub use messages::data::fetch_by_score::{FetchByScoreRequest, ScoreRange};
pub use messages::data::get_rank::{GetRankRequest, GetRankResponse};
pub use messages::data::get_rank::GetRankRequest;
pub use messages::data::length::{LengthRequest, LengthResponse};
pub use messages::data::remove_elements::{RemoveElementsRequest, RemoveElementsResponse};
pub use messages::data::upsert::{Element, UpsertRequest, UpsertResponse};
Expand Down
91 changes: 91 additions & 0 deletions tests/leaderboard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,94 @@ mod fetch_by_score {
Ok(())
}
}

mod get_competition_rank {
use momento::leaderboard::{
messages::data::get_competition_rank::GetCompetitionRankRequest, Element, RankedElement,
};

use super::*;

fn test_competition_leaderboard() -> Vec<Element> {
vec![
Element { id: 0, score: 20.0 },
Element { id: 1, score: 10.0 },
Element { id: 2, score: 10.0 },
Element { id: 3, score: 5.0 },
]
}

#[tokio::test]
async fn get_competition_rank_of_elements() -> MomentoResult<()> {
let leaderboard = unique_leaderboard();
leaderboard.upsert(test_competition_leaderboard()).await?;

let response = leaderboard.get_competition_rank([0, 1, 2, 3, 4]).await?;

assert_eq!(
vec![
RankedElement {
id: 0,
score: 20.0,
rank: 0
},
RankedElement {
id: 1,
score: 10.0,
rank: 1
},
RankedElement {
id: 2,
score: 10.0,
rank: 1
},
RankedElement {
id: 3,
score: 5.0,
rank: 3
},
],
response.elements(),
"Expected the leaderboard to be sorted in 0113 order"
);
Ok(())
}

#[tokio::test]
async fn get_rank_of_elements_asc() -> MomentoResult<()> {
let leaderboard = unique_leaderboard();
leaderboard.upsert(test_competition_leaderboard()).await?;

let response = leaderboard
.send_request(GetCompetitionRankRequest::new([0, 1, 2, 3, 4]).order(Order::Ascending))
.await?;

assert_eq!(
vec![
RankedElement {
id: 0,
score: 20.0,
rank: 3
},
RankedElement {
id: 1,
score: 10.0,
rank: 1
},
RankedElement {
id: 2,
score: 10.0,
rank: 1
},
RankedElement {
id: 3,
score: 5.0,
rank: 0
},
],
response.elements(),
"Expected the leaderboard to be sorted in 3110 order"
);
Ok(())
}
}

0 comments on commit df4bd46

Please sign in to comment.