imdb-async 0.12.2

Opinionated and unopinionated async wrappers to efficiently retrieve and parse IMDB's dataset
Documentation
use std::cmp::Ordering;
use std::convert::TryFrom;

use arrayvec::ArrayVec;
use compact_str::CompactString;

use crate::genre::Genre;
use crate::EpisodeLink;
use crate::Error;
use crate::Title;
use crate::TitleType;

#[derive(Eq, PartialEq)]
/// Represents a TV series from IMDB.
pub struct Show {
	pub imdb_id: u32,
	pub title: CompactString,
	pub is_adult: bool,
	pub start_year: u16,
	pub end_year: Option<u16>,
	pub runtime_minutes: Option<u16>,
	pub genres: ArrayVec<Genre, 3>,
	pub episodes: Vec<Episode>
}

impl std::fmt::Debug for Show /* {{{ */ {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("Show")
			.field("imdb_id", &self.imdb_id)
			.field("title", &self.title)
			.field("is_adult", &self.is_adult)
			.field("start_year", &self.start_year)
			.field("end_year", &self.end_year)
			.field("runtime_minutes", &self.runtime_minutes)
			.field("genres", &self.genres)
			.field("episodes", &self.episodes.len())
			.finish()
	}
} // }}}

impl Show {
	pub(crate) fn from_wrapped_title(input: Result<Title, Error>) -> Result<Self, Error> /* {{{ */ {
		Self::try_from(input?)
	} // }}}
}

impl TryFrom<Title> for Show /* {{{ */ {
	type Error = Error;
	#[inline]
	fn try_from(input: Title) -> Result<Self, Error> {
		match input.title_type {
			TitleType::TVSeries => Ok(Self{
				imdb_id: input.imdb_id,
				title: input.primary_title,
				is_adult: input.is_adult,
				start_year: match input.start_year {
					Some(v) => v,
					None => return Err(Error::YearMissing)
				},
				end_year: input.end_year,
				runtime_minutes: input.runtime_minutes,
				genres: input.genres,
				episodes: Vec::new()
			}),
			_ => Err(Error::WrongMediaType(input.title_type.into(), "Show"))
		}
	}
} // }}}

#[derive(Clone, Debug, Eq, PartialEq)]
/// Represents an episode of a TV series from IMDB.  Pared down from [Title] and EpisodeLink based on fields that make sense for an individual episode.
pub struct Episode {
	pub season: u16,
	pub episode: u16,
	pub imdb_id: u32,
	pub title: CompactString,
	pub year: Option<u16>,
	pub runtime_minutes: Option<u16>
}

impl PartialOrd for Episode /* {{{ */ {
	#[inline]
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
} // }}}

impl Ord for Episode /* {{{ */ {
	#[inline]
	fn cmp(&self, other: &Self) -> Ordering {
		match self.season.cmp(&other.season) {
			Ordering::Equal => self.episode.cmp(&other.episode),
			v => v
		}
	}
} // }}}

impl Episode {
	#[cfg(test)]
	pub(crate) fn new(season: u16, episode: u16, imdb_id: u32, title: &str, year: Option<u16>, runtime_minutes: Option<u16>) -> Self /* {{{ */ {
		Self{
			season,
			episode,
			imdb_id,
			title: CompactString::new(title),
			year,
			runtime_minutes
		}
	} // }}}

	#[inline]
	pub(crate) fn from_title_and_link(title: Title, link: EpisodeLink) -> Result<Self, Error> /* {{{ */ {
		match title.title_type {
			TitleType::Episode => Ok(Self{
				season: match link.season {
					Some(v) => v,
					None => return Err(Error::SeasonMissing)
				},
				episode: match link.episode {
					Some(v) => v,
					None => return Err(Error::EpisodeMissing)
				},
				imdb_id: title.imdb_id,
				title: title.primary_title,
				year: title.start_year,
				runtime_minutes: title.runtime_minutes
			}),
			_ => Err(Error::WrongMediaType(title.title_type.into(), "Episode"))
		}
	} // }}}
}

#[inline]
pub(crate) fn title_matches_show_name_and_year(title: &Result<Title, Error>, name: &str, year: u16) -> bool /* {{{ */ {
	if let Ok(title) = title {
		if(title.title_type != TitleType::TVSeries) {
			return false;
		}
		if let Some(title_year) = title.start_year {
			if(title_year != year) {
				return false;
			}
		} else {
			return false;
		}
		if(title.primary_title != name && title.original_title != name) {
			return false;
		}
		return true;
	}
	false
} // }}}