use std::sync::Arc;
use crate::error::Result;
use crate::model::{CanonicalMedia, MediaKind, SearchOptions};
use crate::provider::{
AniListProvider, ImdbProvider, JikanProvider, KitsuProvider, Provider, TvmazeProvider,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemoteSource {
AniList,
Jikan,
Kitsu,
Tvmaze,
Imdb,
}
impl RemoteSource {
pub fn as_str(self) -> &'static str {
match self {
Self::AniList => "anilist",
Self::Jikan => "jikan",
Self::Kitsu => "kitsu",
Self::Tvmaze => "tvmaze",
Self::Imdb => "imdb",
}
}
}
pub struct RemoteApi {
provider: Arc<dyn Provider>,
}
impl Default for RemoteApi {
fn default() -> Self {
Self::anilist()
}
}
impl RemoteApi {
pub fn anilist() -> Self {
Self::with_provider(AniListProvider::new())
}
pub fn jikan() -> Self {
Self::with_provider(JikanProvider::new())
}
pub fn kitsu() -> Self {
Self::with_provider(KitsuProvider::new())
}
pub fn tvmaze() -> Self {
Self::with_provider(TvmazeProvider::new())
}
pub fn imdb() -> Self {
Self::with_provider(ImdbProvider::new())
}
pub fn with_provider<P: Provider + 'static>(provider: P) -> Self {
Self {
provider: Arc::new(provider),
}
}
pub fn provider(&self) -> &dyn Provider {
self.provider.as_ref()
}
pub fn search(&self, query: &str, options: SearchOptions) -> Result<Vec<CanonicalMedia>> {
self.provider.search(query, options)
}
pub fn fetch_trending(&self, media_kind: MediaKind) -> Result<Vec<CanonicalMedia>> {
self.provider.fetch_trending(media_kind)
}
pub fn fetch_recommendations(
&self,
media_kind: MediaKind,
source_id: &str,
) -> Result<Vec<CanonicalMedia>> {
self.provider.fetch_recommendations(media_kind, source_id)
}
pub fn fetch_related(
&self,
media_kind: MediaKind,
source_id: &str,
) -> Result<Vec<CanonicalMedia>> {
self.provider.fetch_related(media_kind, source_id)
}
pub fn anime_metadata(&self) -> RemoteCollection {
RemoteCollection::new(
Arc::clone(&self.provider),
SearchOptions::default().with_media_kind(MediaKind::Anime),
)
}
pub fn manga_metadata(&self) -> RemoteCollection {
RemoteCollection::new(
Arc::clone(&self.provider),
SearchOptions::default().with_media_kind(MediaKind::Manga),
)
}
pub fn movie_metadata(&self) -> RemoteCollection {
RemoteCollection::new(
Arc::clone(&self.provider),
SearchOptions::default()
.with_media_kind(MediaKind::Anime)
.with_format("MOVIE"),
)
}
}
pub struct RemoteCollection {
provider: Arc<dyn Provider>,
options: SearchOptions,
}
impl RemoteCollection {
fn new(provider: Arc<dyn Provider>, options: SearchOptions) -> Self {
Self { provider, options }
}
pub fn options(&self) -> &SearchOptions {
&self.options
}
pub fn search(&self, query: &str) -> Result<Vec<CanonicalMedia>> {
self.provider.search(query, self.options.clone())
}
pub fn by_id(&self, source_id: &str) -> Result<Option<CanonicalMedia>> {
let kind = self.options.media_kind.unwrap_or(MediaKind::Anime);
let item = self.provider.get_by_id(kind, source_id)?;
Ok(item.filter(|m| {
self.options
.format
.as_ref()
.map(|fmt| m.format.as_ref().map(|v| v.eq_ignore_ascii_case(fmt)) == Some(true))
.unwrap_or(true)
}))
}
pub fn trending(&self) -> Result<Vec<CanonicalMedia>> {
let kind = self.options.media_kind.unwrap_or(MediaKind::Anime);
self.provider.fetch_trending(kind)
}
pub fn recommendations(&self, source_id: &str) -> Result<Vec<CanonicalMedia>> {
let kind = self.options.media_kind.unwrap_or(MediaKind::Anime);
self.provider.fetch_recommendations(kind, source_id)
}
pub fn related(&self, source_id: &str) -> Result<Vec<CanonicalMedia>> {
let kind = self.options.media_kind.unwrap_or(MediaKind::Anime);
self.provider.fetch_related(kind, source_id)
}
}
impl From<RemoteSource> for RemoteApi {
fn from(source: RemoteSource) -> Self {
match source {
RemoteSource::AniList => Self::anilist(),
RemoteSource::Jikan => Self::jikan(),
RemoteSource::Kitsu => Self::kitsu(),
RemoteSource::Tvmaze => Self::tvmaze(),
RemoteSource::Imdb => Self::imdb(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::SourceName;
#[test]
fn default_remote_api_uses_anilist() {
let api = RemoteApi::default();
assert_eq!(api.provider().source(), SourceName::AniList);
}
#[test]
fn movie_collection_defaults_to_anime_movie_format() {
let col = RemoteApi::jikan().movie_metadata();
assert_eq!(col.options().media_kind, Some(MediaKind::Anime));
assert_eq!(col.options().format.as_deref(), Some("MOVIE"));
}
#[test]
fn kitsu_provider_has_correct_source() {
let api = RemoteApi::kitsu();
assert_eq!(api.provider().source(), SourceName::Kitsu);
}
#[test]
fn custom_provider_via_with_provider() {
let api = RemoteApi::with_provider(TvmazeProvider::new());
assert_eq!(api.provider().source(), SourceName::Tvmaze);
}
}