use serde::Deserialize;
use crate::request::SimklRequest;
pub mod fields {
pub const SIMKL: &str = "simkl";
pub const EXT: &str = "ext";
pub const RANK: &str = "rank";
pub const STATUS: &str = "status";
pub const YEAR: &str = "year";
pub const REACTIONS: &str = "reactions";
pub const HAS_TRAILER: &str = "has_trailer";
pub const DROPRATE: &str = "droprate";
}
#[derive(Debug, Clone, Deserialize)]
pub struct RatingScore {
pub rating: Option<f32>,
pub votes: Option<u64>,
pub droprate: Option<String>,
pub rank: Option<u64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Reactions {
pub total: Option<u32>,
pub reviews: Option<u32>,
pub comments: Option<u32>,
pub positive: Option<u32>,
pub negative: Option<u32>,
pub neutral: Option<u32>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RankInfo {
pub r#type: Option<String>,
pub value: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RatingsResponse {
pub id: Option<u64>,
pub link: Option<String>,
pub simkl: Option<RatingScore>,
pub release_year: Option<String>,
pub reactions: Option<Reactions>,
pub rank: Option<RankInfo>,
pub droprate: Option<String>,
#[serde(rename = "IMDB")]
pub imdb: Option<RatingScore>,
pub has_trailer: Option<bool>,
}
#[derive(Debug, Clone, Default)]
pub struct RatingsById {
pub simkl: Option<u64>,
pub hulu: Option<u64>,
pub netflix: Option<u64>,
pub mal: Option<u64>,
pub tvdb: Option<u64>,
pub tmdb: Option<u64>,
pub imdb: Option<String>,
pub anidb: Option<u64>,
pub crunchyroll: Option<u64>,
pub anilist: Option<u64>,
pub kitsu: Option<u64>,
pub livechart: Option<u64>,
pub anisearch: Option<u64>,
pub animeplanet: Option<u64>,
pub traktslug: Option<String>,
pub letterboxd: Option<String>,
pub r#type: Option<String>,
pub fields: Option<String>,
pub client_id: Option<String>,
}
impl RatingsById {
pub fn new(simkl_id: u64) -> Self {
Self {
simkl: Some(simkl_id),
..Default::default()
}
}
pub fn with_simkl(mut self, id: u64) -> Self {
self.simkl = Some(id);
self
}
pub fn with_hulu(mut self, id: u64) -> Self {
self.hulu = Some(id);
self
}
pub fn with_netflix(mut self, id: u64) -> Self {
self.netflix = Some(id);
self
}
pub fn with_mal(mut self, id: u64) -> Self {
self.mal = Some(id);
self
}
pub fn with_tvdb(mut self, id: u64) -> Self {
self.tvdb = Some(id);
self
}
pub fn with_tmdb(mut self, id: u64) -> Self {
self.tmdb = Some(id);
self
}
pub fn with_imdb(mut self, id: impl Into<String>) -> Self {
self.imdb = Some(id.into());
self
}
pub fn with_anidb(mut self, id: u64) -> Self {
self.anidb = Some(id);
self
}
pub fn with_crunchyroll(mut self, id: u64) -> Self {
self.crunchyroll = Some(id);
self
}
pub fn with_anilist(mut self, id: u64) -> Self {
self.anilist = Some(id);
self
}
pub fn with_kitsu(mut self, id: u64) -> Self {
self.kitsu = Some(id);
self
}
pub fn with_livechart(mut self, id: u64) -> Self {
self.livechart = Some(id);
self
}
pub fn with_anisearch(mut self, id: u64) -> Self {
self.anisearch = Some(id);
self
}
pub fn with_animeplanet(mut self, id: u64) -> Self {
self.animeplanet = Some(id);
self
}
pub fn with_traktslug(mut self, slug: impl Into<String>) -> Self {
self.traktslug = Some(slug.into());
self
}
pub fn with_letterboxd(mut self, slug: impl Into<String>) -> Self {
self.letterboxd = Some(slug.into());
self
}
pub fn with_type(mut self, media_type: impl Into<String>) -> Self {
self.r#type = Some(media_type.into());
self
}
pub fn with_fields(mut self, fields: impl Into<String>) -> Self {
self.fields = Some(fields.into());
self
}
pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
self.client_id = Some(client_id.into());
self
}
}
impl SimklRequest for RatingsById {
fn endpoint(&self) -> String {
"/ratings".to_string()
}
fn query_params(&self) -> Vec<(String, String)> {
let mut params: Vec<(String, String)> = Vec::new();
if let Some(id) = self.simkl {
params.push(("simkl".to_string(), id.to_string()));
}
if let Some(id) = self.hulu {
params.push(("hulu".to_string(), id.to_string()));
}
if let Some(id) = self.netflix {
params.push(("netflix".to_string(), id.to_string()));
}
if let Some(id) = self.mal {
params.push(("mal".to_string(), id.to_string()));
}
if let Some(id) = self.tvdb {
params.push(("tvdb".to_string(), id.to_string()));
}
if let Some(id) = self.tmdb {
params.push(("tmdb".to_string(), id.to_string()));
}
if let Some(ref id) = self.imdb {
params.push(("imdb".to_string(), id.clone()));
}
if let Some(id) = self.anidb {
params.push(("anidb".to_string(), id.to_string()));
}
if let Some(id) = self.crunchyroll {
params.push(("crunchyroll".to_string(), id.to_string()));
}
if let Some(id) = self.anilist {
params.push(("anilist".to_string(), id.to_string()));
}
if let Some(id) = self.kitsu {
params.push(("kitsu".to_string(), id.to_string()));
}
if let Some(id) = self.livechart {
params.push(("livechart".to_string(), id.to_string()));
}
if let Some(id) = self.anisearch {
params.push(("anisearch".to_string(), id.to_string()));
}
if let Some(id) = self.animeplanet {
params.push(("animeplanet".to_string(), id.to_string()));
}
if let Some(ref slug) = self.traktslug {
params.push(("traktslug".to_string(), slug.clone()));
}
if let Some(ref slug) = self.letterboxd {
params.push(("letterboxd".to_string(), slug.clone()));
}
if let Some(ref t) = self.r#type {
params.push(("type".to_string(), t.clone()));
}
if let Some(ref f) = self.fields {
params.push(("fields".to_string(), f.clone()));
}
if let Some(ref cid) = self.client_id {
params.push(("client_id".to_string(), cid.clone()));
}
params
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct WatchlistRatingsItem {
pub id: Option<u64>,
pub r#type: Option<String>,
pub link: Option<String>,
pub release_status: Option<String>,
pub release_year: Option<u32>,
pub rank: Option<u64>,
pub simkl: Option<RatingScore>,
pub imdb: Option<RatingScore>,
pub mal: Option<RatingScore>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum WatchlistType {
#[default]
All,
Tv,
Anime,
Movies,
}
impl WatchlistType {
pub fn as_str(&self) -> &'static str {
match self {
WatchlistType::All => "",
WatchlistType::Tv => "tv",
WatchlistType::Anime => "anime",
WatchlistType::Movies => "movies",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RatingsByWatchlist {
pub watchlist_type: WatchlistType,
pub user_watchlist: Option<String>,
pub fields: Option<String>,
pub client_id: Option<String>,
}
impl RatingsByWatchlist {
pub fn with_watchlist_type(mut self, t: WatchlistType) -> Self {
self.watchlist_type = t;
self
}
pub fn with_user_watchlist(mut self, statuses: impl Into<String>) -> Self {
self.user_watchlist = Some(statuses.into());
self
}
pub fn with_fields(mut self, fields: impl Into<String>) -> Self {
self.fields = Some(fields.into());
self
}
pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
self.client_id = Some(client_id.into());
self
}
}
impl SimklRequest for RatingsByWatchlist {
fn endpoint(&self) -> String {
let type_seg = self.watchlist_type.as_str();
if type_seg.is_empty() {
"/ratings".to_string()
} else {
format!("/ratings/{}", type_seg)
}
}
fn query_params(&self) -> Vec<(String, String)> {
let mut params: Vec<(String, String)> = Vec::new();
if let Some(ref wl) = self.user_watchlist {
params.push(("user_watchlist".to_string(), wl.clone()));
}
if let Some(ref f) = self.fields {
params.push(("fields".to_string(), f.clone()));
}
if let Some(ref cid) = self.client_id {
params.push(("client_id".to_string(), cid.clone()));
}
params
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::request::SimklRequest;
#[test]
fn test_ratings_by_id_minimal() {
let url = RatingsById::new(10280)
.with_client_id("testkey")
.build_url();
assert_eq!(
url,
"https://api.simkl.com/ratings?simkl=10280&client_id=testkey"
);
}
#[test]
fn test_ratings_by_id_with_fields() {
let url = RatingsById::new(10280)
.with_fields("simkl,rank,reactions")
.with_client_id("testkey")
.build_url();
assert_eq!(
url,
"https://api.simkl.com/ratings?simkl=10280&fields=simkl%2Crank%2Creactions&client_id=testkey"
);
}
#[test]
fn test_ratings_by_id_with_imdb() {
let url = RatingsById::default()
.with_imdb("tt0944947")
.with_client_id("testkey")
.build_url();
assert_eq!(
url,
"https://api.simkl.com/ratings?imdb=tt0944947&client_id=testkey"
);
}
#[test]
fn test_ratings_by_watchlist_all_types() {
let url = RatingsByWatchlist::default()
.with_user_watchlist("watching,plantowatch")
.with_client_id("testkey")
.build_url();
assert_eq!(
url,
"https://api.simkl.com/ratings?user_watchlist=watching%2Cplantowatch&client_id=testkey"
);
}
#[test]
fn test_ratings_by_watchlist_movies() {
let url = RatingsByWatchlist::default()
.with_watchlist_type(WatchlistType::Movies)
.with_user_watchlist("watching,plantowatch")
.with_fields("simkl,rank")
.with_client_id("testkey")
.build_url();
assert_eq!(
url,
"https://api.simkl.com/ratings/movies?user_watchlist=watching%2Cplantowatch&fields=simkl%2Crank&client_id=testkey"
);
}
#[test]
fn test_ratings_by_watchlist_tv() {
let url = RatingsByWatchlist::default()
.with_watchlist_type(WatchlistType::Tv)
.with_user_watchlist("completed")
.with_client_id("testkey")
.build_url();
assert!(url.starts_with("https://api.simkl.com/ratings/tv?"));
assert!(url.contains("user_watchlist=completed"));
}
#[test]
fn test_ratings_by_watchlist_anime() {
let url = RatingsByWatchlist::default()
.with_watchlist_type(WatchlistType::Anime)
.with_user_watchlist("watching")
.with_client_id("testkey")
.build_url();
assert!(url.starts_with("https://api.simkl.com/ratings/anime?"));
}
#[test]
fn test_field_constants() {
assert_eq!(fields::SIMKL, "simkl");
assert_eq!(fields::EXT, "ext");
assert_eq!(fields::RANK, "rank");
assert_eq!(fields::STATUS, "status");
assert_eq!(fields::YEAR, "year");
assert_eq!(fields::REACTIONS, "reactions");
assert_eq!(fields::HAS_TRAILER, "has_trailer");
assert_eq!(fields::DROPRATE, "droprate");
}
}