diff --git a/Cargo.toml b/Cargo.toml index 5948e32..27acd0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ edition = "2021" [dependencies] tokio = { version = "1", features = ["macros", "rt-multi-thread"] } serde = { version = "1", features = ["derive"] } +chrono = "*" reqwest = "*" thiserror = "2" serde_json = "1" diff --git a/src/client.rs b/src/client.rs index 30676ea..809e807 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,14 +1,19 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022-2025 Andriel Ferreira -use std::time::Duration; - use serde::Deserialize; +use std::time::Duration; -use crate::models::{Anime, Character, Manga, Person, User}; -use crate::Result; +use crate::{ + models::{Anime, Character, Image, Manga, MediaType, Person, Title, User}, + Result, +}; -#[derive(Clone)] +/// Represents a client for interacting with an API. +/// +/// The `Client` struct contains the necessary configuration for making +/// requests to an API, including the API token and the timeout duration. +#[derive(Clone, Debug, PartialEq)] pub struct Client { /// The API token to use for requests. api_token: Option, @@ -17,18 +22,63 @@ pub struct Client { } impl Client { - /// Set the API token. - pub fn api_token(mut self, token: &str) -> Self { - self.api_token = Some(token.to_string()); - self + /// Creates a new client instance with the specified timeout duration. + /// + /// This method initializes a new `Client` instance with the provided + /// timeout duration. + /// + /// # Arguments + /// + /// * `timeout` - The timeout duration for requests, in seconds. + pub fn with_timeout(timeout: u64) -> Self { + Self { + api_token: None, + timeout, + } } - /// Set the timeout for the requests (in seconds). + /// Creates a new client instance with the specified API token. + /// + /// This method initializes a new `Client` instance with the provided + /// API token and a default timeout duration of 20 seconds. + /// + /// # Arguments + /// + /// * `token` - A string slice that holds the API token. + pub fn with_api_token(token: &str) -> Self { + Self { + api_token: Some(token.to_string()), + timeout: 20, + } + } + + /// Sets the timeout duration for the client. + /// + /// This method allows you to set the timeout duration for the client + /// in seconds. The timeout duration determines how long the client + /// will wait for a response before timing out. + /// + /// # Arguments + /// + /// * `seconds` - The timeout duration in seconds. pub fn timeout(mut self, seconds: u64) -> Self { self.timeout = seconds; self } + /// Sets the API token for the client. + /// + /// This method allows you to set the API token for the client, which + /// will be used for authenticating API requests. + /// + /// # Arguments + /// + /// * `token` - A string slice that holds the API token. + pub fn api_token(mut self, token: &str) -> Self { + self.api_token = Some(token.to_string()); + self + } + /// Get an anime by its ID or MAL ID. /// /// # Arguments @@ -49,7 +99,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_anime(&self, id: i64) -> Result { + pub async fn get_anime(&self, id: i64) -> Result { let data = self .request( MediaType::Anime, @@ -61,6 +111,7 @@ impl Client { match serde_json::from_str::(&data["data"]["Media"].to_string()) { Ok(mut anime) => { + anime.client = self.clone(); anime.is_full_loaded = true; Ok(anime) @@ -89,7 +140,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_manga(&self, id: i64) -> Result { + pub async fn get_manga(&self, id: i64) -> Result { let data = self .request( MediaType::Manga, @@ -101,6 +152,7 @@ impl Client { match serde_json::from_str::(&data["data"]["Media"].to_string()) { Ok(mut manga) => { + manga.client = self.clone(); manga.is_full_loaded = true; Ok(manga) @@ -128,7 +180,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_character(&self, id: i64) -> Result { + pub async fn get_character(&self, id: i64) -> Result { let data = self .request( MediaType::Character, @@ -140,6 +192,7 @@ impl Client { match serde_json::from_str::(&data["data"]["Character"].to_string()) { Ok(mut character) => { + character.client = self.clone(); character.is_full_loaded = true; Ok(character) @@ -167,7 +220,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_char(&self, id: i64) -> Result { + pub async fn get_char(&self, id: i64) -> Result { self.get_character(id).await } @@ -190,7 +243,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user(&self, id: i64) -> Result { + pub async fn get_user(&self, id: i32) -> Result { let data = self .request( MediaType::User, @@ -225,7 +278,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_user_by_name(&self, name: N) -> Result { + pub async fn get_user_by_name(&self, name: N) -> Result { let name = name.to_string(); let data = self @@ -238,7 +291,12 @@ impl Client { .unwrap(); match serde_json::from_str::(&data["data"]["User"].to_string()) { - Ok(user) => Ok(user), + Ok(mut user) => { + user.client = self.clone(); + user.is_full_loaded = true; + + Ok(user) + } Err(e) => Err(crate::Error::ApiError(e.to_string())), } } @@ -262,7 +320,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn get_person(&self, id: i64) -> Result { + pub async fn get_person(&self, id: i64) -> Result { let data = self .request( MediaType::Person, @@ -274,6 +332,7 @@ impl Client { match serde_json::from_str::(&data["data"]["Staff"].to_string()) { Ok(mut person) => { + person.client = self.clone(); person.is_full_loaded = true; Ok(person) @@ -303,12 +362,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn search_anime( - &self, - title: &str, - page: u16, - limit: u16, - ) -> Option> { + pub async fn search_anime(&self, title: &str, page: u16, limit: u16) -> Option> { let result = self .request( MediaType::Anime, @@ -322,10 +376,12 @@ impl Client { let mut animes = Vec::new(); for media in medias.iter() { - animes.push(crate::models::Anime { + animes.push(Anime { id: media["id"].as_i64().unwrap(), - title: crate::models::Title::deserialize(&media["title"]).unwrap(), + title: Title::deserialize(&media["title"]).unwrap(), url: media["siteUrl"].as_str().unwrap().to_string(), + + client: self.clone(), ..Default::default() }); } @@ -357,12 +413,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn search_manga( - &self, - title: &str, - page: u16, - limit: u16, - ) -> Option> { + pub async fn search_manga(&self, title: &str, page: u16, limit: u16) -> Option> { let result = self .request( MediaType::Manga, @@ -376,10 +427,12 @@ impl Client { let mut mangas = Vec::new(); for media in medias.iter() { - mangas.push(crate::models::Manga { + mangas.push(Manga { id: media["id"].as_i64().unwrap(), - title: crate::models::Title::deserialize(&media["title"]).unwrap(), + title: Title::deserialize(&media["title"]).unwrap(), url: media["siteUrl"].as_str().unwrap().to_string(), + + client: self.clone(), ..Default::default() }); } @@ -411,12 +464,7 @@ impl Client { /// # Ok(()) /// # } /// ``` - pub async fn search_user( - &self, - name: &str, - page: u16, - limit: u16, - ) -> Option> { + pub async fn search_user(&self, name: &str, page: u16, limit: u16) -> Option> { let result = self .request( MediaType::User, @@ -430,10 +478,12 @@ impl Client { let mut vec = Vec::new(); for user in users.iter() { - vec.push(crate::models::User { + vec.push(User { id: user["id"].as_i64().unwrap() as i32, name: user["name"].as_str().unwrap().to_string(), - avatar: crate::models::Image::deserialize(&user["avatar"]).ok(), + avatar: Image::deserialize(&user["avatar"]).ok(), + + client: self.clone(), ..Default::default() }); } @@ -475,7 +525,7 @@ impl Client { } let response = body.send().await?.text().await?; - let result: serde_json::Value = serde_json::from_str(&response).unwrap(); + let result = serde_json::from_str::(&response).unwrap(); Ok(result) } @@ -485,6 +535,7 @@ impl Client { /// # Arguments /// /// * `media_type` - The type of media to get the query for. + /// * `action` - The action to perform. /// /// # Errors /// @@ -534,26 +585,18 @@ impl Default for Client { } } -/// The action to perform. +/// Represents an action that can be performed by the client. +/// +/// The `Action` enum defines various actions that the client can perform, +/// such as getting media by ID or searching for media. +/// +/// # Variants +/// +/// * `Get` - Represents the action of getting media by ID. +/// * `Search` - Represents the action of searching for media. enum Action { /// Get media by ID. Get, /// Search for media. Search, } - -/// The type of media to request. -enum MediaType { - /// An anime. - Anime, - /// A manga. - Manga, - /// A character. - Character, - /// An user. - User, - /// A person. - Person, - /// A studio. - Studio, -} diff --git a/src/models/anime.rs b/src/models/anime.rs index 443fe1b..7f3ca7d 100644 --- a/src/models/anime.rs +++ b/src/models/anime.rs @@ -9,7 +9,56 @@ use super::{ }; use crate::{Client, Result}; -/// An anime. +/// Represents an anime with various attributes. +/// +/// The `Anime` struct contains detailed information about an anime, +/// including its ID, title, format, status, description, dates, season, +/// episodes, duration, country of origin, licensing status, source, +/// hashtags, images, genres, synonyms, scores, popularity, tags, +/// relations, characters, staff, studios, and other metadata. +/// +/// # Fields +/// +/// * `id` - The ID of the anime. +/// * `id_mal` - The ID of the anime on MAL (MyAnimeList). +/// * `title` - The title of the anime. +/// * `format` - The format of the anime (e.g., TV, movie). +/// * `status` - The status of the anime (e.g., airing, completed). +/// * `description` - The description of the anime. +/// * `start_date` - The start date of the anime. +/// * `end_date` - The end date of the anime. +/// * `season` - The season of the anime. +/// * `season_year` - The year of the season of the anime. +/// * `season_int` - The integer representation of the season of the anime. +/// * `episodes` - The number of episodes of the anime. +/// * `duration` - The duration of the episodes of the anime. +/// * `country_of_origin` - The country of origin of the anime. +/// * `is_licensed` - Whether the anime is licensed or not. +/// * `source` - The source of the anime (e.g., manga, light novel). +/// * `hashtag` - The hashtag of the anime. +/// * `updated_at` - The updated date of the anime. +/// * `cover` - The cover image of the anime. +/// * `banner` - The banner image of the anime. +/// * `genres` - The genres of the anime. +/// * `synonyms` - The synonyms of the anime. +/// * `average_score` - The average score of the anime. +/// * `mean_score` - The mean score of the anime. +/// * `popularity` - The popularity of the anime. +/// * `is_locked` - Whether the anime is locked or not. +/// * `trending` - The trending of the anime. +/// * `favourites` - The number of favourites of the anime. +/// * `tags` - The tags of the anime. +/// * `relations` - The relations of the anime. +/// * `characters` - The characters of the anime. +/// * `staff` - The staff of the anime. +/// * `studios` - The studios of the anime. +/// * `is_favourite` - Whether the anime is favourite or not. +/// * `is_favourite_blocked` - Whether the anime is favourite blocked or not. +/// * `is_adult` - Whether the anime is adult or not. +/// * `next_airing_episode` - The next airing episode of the anime. +/// * `external_links` - The external links of the anime. +/// * `streaming_episodes` - The streaming episodes of the anime. +/// * `url` - The site URL of the anime. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Anime { @@ -100,28 +149,46 @@ pub struct Anime { /// The site URL of the anime. #[serde(rename = "siteUrl")] pub url: String, + + /// The client used to fetch additional data. #[serde(skip)] + pub(crate) client: Client, + /// Whether the person's data is fully loaded. + #[serde(default)] pub(crate) is_full_loaded: bool, } impl Anime { - /// Load fully the anime. + /// Loads the full details of the anime. /// /// # Errors /// - /// Returns an error if the anime is already full loaded. + /// Returns an error if the anime details cannot be loaded. + /// + /// # Panics + /// + /// Panics if the anime is already fully loaded. pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut anime = Client::default().get_anime(self.id).await.unwrap(); - anime.is_full_loaded = true; - - Ok(anime) + self.client.get_anime(self.id).await } else { panic!("This anime is already full loaded!") } } } +/// Represents the airing schedule of an anime. +/// +/// The `AiringSchedule` struct contains information about the airing +/// schedule of an anime, including the ID, airing date, time until +/// airing, and the episode number. +/// +/// # Fields +/// +/// * `id` - The ID of the airing schedule. +/// * `at` - The airing date, represented as a Unix timestamp. +/// * `time_until` - The time until the airing, represented in seconds. +/// * `episode` - The episode number that is airing. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct AiringSchedule { /// The ID of the airing schedule. diff --git a/src/models/character.rs b/src/models/character.rs index fe923ff..6f76962 100644 --- a/src/models/character.rs +++ b/src/models/character.rs @@ -12,54 +12,90 @@ use crate::models::Person; use crate::{Client, Result}; +/// Represents a character. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Character { + /// The ID of the character. pub id: i64, + /// The name of the character. pub name: Name, - pub role: Option, + /// The role of the character in the story. + pub role: Option, + /// The image of the character. pub image: Image, + /// The description of the character. pub description: String, + /// The gender of the character. pub gender: Option, + /// The date of birth of the character. pub date_of_birth: Option, + /// The age of the character. pub age: Option, + /// The blood type of the character. pub blood_type: Option, + /// Whether the character is a favorite. pub is_favourite: Option, + /// Whether the character is blocked from being a favorite. pub is_favourite_blocked: Option, + /// The URL of the character's site. #[serde(rename = "siteUrl")] pub url: String, + /// The number of favorites the character has. pub favourites: Option, + /// The voice actors of the character. pub voice_actors: Option>, + /// The moderator notes for the character. pub mod_notes: Option, + + /// The client used to fetch additional data. #[serde(skip)] + pub(crate) client: Client, + /// Whether the person's data is fully loaded. + #[serde(default)] pub(crate) is_full_loaded: bool, } impl Character { + /// Loads the full details of the character. + /// + /// # Errors + /// + /// Returns an error if the character details cannot be loaded. + /// + /// # Panics + /// + /// Panics if the character is already fully loaded. pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut character = Client::default().get_character(self.id).await?; - character.is_full_loaded = true; - - Ok(character) + self.client.get_character(self.id).await } else { panic!("This character is already full loaded") } } + /// Retrieves the media associated with the chcharacterr. + /// + /// # Errors + /// + /// Returns an error if the media cannot be retrieved. + /// + /// # Type Parameters + /// + /// * `T` - The type of the media. pub async fn get_medias(&self) -> Result { unimplemented!() } - - pub fn is_full_loaded(&self) -> bool { - self.is_full_loaded - } } -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] -pub enum Role { +/// Represents the role of a character in a story. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] +pub enum CharacterRole { + /// A background character. #[default] Background, + /// A main character. Main, + /// A supporting character. Supporting, } diff --git a/src/models/color.rs b/src/models/color.rs index b4f2f98..29413f1 100644 --- a/src/models/color.rs +++ b/src/models/color.rs @@ -3,17 +3,87 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +/// Represents a color with various predefined options and a custom hex value. +/// +/// The `Color` enum defines a list of supported colors, each with an +/// associated variant. Additionally, it supports custom colors defined +/// by a hex string. +/// +/// # Variants +/// +/// * `Blue` - The blue color. +/// * `Purple` - The purple color. This is the default color. +/// * `Pink` - The pink color. +/// * `Orange` - The orange color. +/// * `Red` - The red color. +/// * `Green` - The green color. +/// * `Gray` - The gray color. +/// * `Hex(String)` - A custom color defined by a hex string. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "UPPERCASE"))] pub enum Color { + /// The blue color. Blue, + /// The purple color. #[default] Purple, + /// The pink color. Pink, + /// The orange color. Orange, + /// The red color. Red, + /// The green color. Green, + /// The gray color. Gray, + /// Others colors as hex. #[serde(untagged)] Hex(String), } + +impl Color { + /// Returns the hex value of the color. + pub fn hex(&self) -> Option<&str> { + match self { + Color::Hex(hex) => Some(hex), + _ => None, + } + } +} + +impl From<&str> for Color { + fn from(value: &str) -> Self { + match value.trim().to_uppercase().as_str() { + "BLUE" => Color::Blue, + "PURPLE" => Color::Purple, + "PINK" => Color::Pink, + "ORANGE" => Color::Orange, + "RED" => Color::Red, + "GREEN" => Color::Green, + "GRAY" => Color::Gray, + _ => Color::Hex(value.to_string()), + } + } +} + +impl From for Color { + fn from(value: String) -> Self { + Color::from(value.as_str()) + } +} + +impl std::fmt::Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Color::Blue => write!(f, "Blue"), + Color::Purple => write!(f, "Purple"), + Color::Pink => write!(f, "Pink"), + Color::Orange => write!(f, "Orange"), + Color::Red => write!(f, "Red"), + Color::Green => write!(f, "Green"), + Color::Gray => write!(f, "Gray"), + Color::Hex(hex) => write!(f, "{}", hex), + } + } +} diff --git a/src/models/cover.rs b/src/models/cover.rs index 9ef325f..d5d2217 100644 --- a/src/models/cover.rs +++ b/src/models/cover.rs @@ -5,11 +5,26 @@ use serde::{Deserialize, Serialize}; use crate::models::Color; +/// Represents the cover images of various sizes and the color of the cover. +/// +/// The `Cover` struct contains URLs for the cover images in different sizes +/// (extra large, large, and medium) and an optional color. +/// +/// # Fields +/// +/// * `extra_large` - The URL of the cover image in extra large size. +/// * `large` - The URL of the cover image in large size. +/// * `medium` - The URL of the cover image in medium size. +/// * `color` - The color of the cover image. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Cover { + /// The URL of the cover image in extra large size. pub extra_large: Option, + /// The URL of the cover image in large size. pub large: Option, + /// The URL of the cover image in medium size. pub medium: Option, + /// The color of the cover image. pub color: Option, } diff --git a/src/models/date.rs b/src/models/date.rs index 5d707e9..9e08638 100644 --- a/src/models/date.rs +++ b/src/models/date.rs @@ -1,12 +1,140 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022-2025 Andriel Ferreira +use chrono::{Datelike, NaiveDate}; use serde::{Deserialize, Serialize}; +/// A date. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Date { - pub year: Option, - pub month: Option, - pub day: Option, + /// The year of the date. + pub year: Option, + /// The month of the date. + pub month: Option, + /// The day of the date. + pub day: Option, +} + +impl Date { + /// Creates a new date. + pub fn new(year: Option, month: Option, day: Option) -> Self { + Self { year, month, day } + } + + /// Returns the year of the date. + pub fn year(&self) -> Option { + self.year + } + + /// Returns the month of the date. + pub fn month(&self) -> Option { + self.month + } + + /// Returns the day of the date. + pub fn day(&self) -> Option { + self.day + } + + /// Formats the date according to the given format string. + /// + /// The format string can contain the following placeholders: + /// - `{year}`, `{yyyy}`, `{yy}`, `{y}`, `{YEAR}`, `{YYYY}`, `{YY}`, `{Y}`: Year + /// - `{month}`, `{mon}`, `{mm}`, `{m}`, `{MONTH}`, `{MON}`, `{MM}`, `{M}`: Month + /// - `{day}`, `{dd}`, `{d}`, `{DAY}`, `{DD}`, `{D}`: Day + /// + /// # Arguments + /// + /// * `format` - A string slice that holds the format pattern. + /// + /// # Example + /// + /// ``` + /// let date = Date { year: Some(2023), month: Some(10), day: Some(5) }; + /// let formatted = date.format("{yyyy}-{mm}-{dd}"); + /// assert_eq!(formatted, "2023-10-05"); + /// ``` + pub fn format(&self, format: &str) -> String { + let mut formatted = format.to_string(); + + if let Some(year) = self.year { + formatted = formatted.replace("{year}", &year.to_string()); + formatted = formatted.replace("{yyyy}", &year.to_string()); + formatted = formatted.replace("{yy}", &format!("{:02}", year % 100)); + formatted = formatted.replace("{y}", &year.to_string()); + formatted = formatted.replace("{YEAR}", &year.to_string()); + formatted = formatted.replace("{YYYY}", &year.to_string()); + formatted = formatted.replace("{YY}", &format!("{:02}", year % 100)); + formatted = formatted.replace("{Y}", &year.to_string()); + } + + if let Some(month) = self.month { + formatted = formatted.replace("{month}", &format!("{:02}", month)); + formatted = formatted.replace("{mon}", &format!("{:02}", month)); + formatted = formatted.replace("{mm}", &format!("{:02}", month)); + formatted = formatted.replace("{m}", &month.to_string()); + formatted = formatted.replace("{MONTH}", &format!("{:02}", month)); + formatted = formatted.replace("{MON}", &format!("{:02}", month)); + formatted = formatted.replace("{MM}", &format!("{:02}", month)); + formatted = formatted.replace("{M}", &month.to_string()); + } + + if let Some(day) = self.day { + formatted = formatted.replace("{day}", &format!("{:02}", day)); + formatted = formatted.replace("{dd}", &format!("{:02}", day)); + formatted = formatted.replace("{d}", &day.to_string()); + formatted = formatted.replace("{DAY}", &format!("{:02}", day)); + formatted = formatted.replace("{DD}", &format!("{:02}", day)); + formatted = formatted.replace("{D}", &day.to_string()); + } + + formatted + } + + /// Returns the date as a `NaiveDate`. + pub fn as_date(&self) -> NaiveDate { + NaiveDate::from_ymd_opt( + self.year.unwrap_or(0), + self.month.unwrap_or(0), + self.day.unwrap_or(0), + ) + .unwrap() + } + + /// Returns the date as a string. + pub fn as_string(&self) -> String { + let year = self.year.map_or(String::new(), |y| y.to_string()); + let month = self.month.map_or(String::new(), |m| format!("{:02}", m)); + let day = self.day.map_or(String::new(), |d| format!("{:02}", d)); + + format!("{}-{}-{}", year, month, day) + } + + /// Returns whether the date is valid. + pub fn is_valid(&self) -> bool { + self.year.is_some() && self.month.is_some() && self.day.is_some() + } +} + +impl From for Date { + fn from(date: NaiveDate) -> Self { + Self { + year: Some(date.year()), + month: Some(date.month()), + day: Some(date.day()), + } + } +} + +impl From for NaiveDate { + fn from(date: Date) -> Self { + date.as_date() + } +} + +impl std::fmt::Display for Date { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_string()) + } } diff --git a/src/models/format.rs b/src/models/format.rs index 50cb239..9e4b079 100644 --- a/src/models/format.rs +++ b/src/models/format.rs @@ -3,18 +3,94 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +/// Represents the format of a media item. +/// +/// The `Format` enum defines various formats that a media item can have, +/// such as TV shows, movies, specials, OVAs, ONAs, music, manga, novels, +/// and one-shots. +/// +/// # Variants +/// +/// * `Tv` - Represents a TV show. +/// * `TvShort` - Represents a short TV show. +/// * `Movie` - Represents a movie. +/// * `Special` - Represents a special. +/// * `Ova` - Represents an original video animation. +/// * `Ona` - Represents an original net animation. +/// * `Music` - Represents a music video. +/// * `Manga` - Represents a manga. +/// * `Novel` - Represents a novel. +/// * `OneShot` - Represents a one-shot. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum Format { + /// Represents a TV show. #[default] Tv, + /// Represents a short TV show. TvShort, + /// Represents a movie. Movie, + /// Represents a special. Special, + /// Represents an original video animation. Ova, + /// Represents an original net animation. Ona, + /// Represents a music video. Music, + /// Represents a manga. Manga, + /// Represents a novel. Novel, + /// Represents a one-shot. OneShot, } + +impl Format { + /// Returns the name of the format. + pub fn name(&self) -> &str { + match self { + Format::Tv => "TV", + Format::TvShort => "TV Short", + Format::Movie => "Movie", + Format::Special => "Special", + Format::Ova => "OVA", + Format::Ona => "ONA", + Format::Music => "Music", + Format::Manga => "Manga", + Format::Novel => "Novel", + Format::OneShot => "One-Shot", + } + } +} + +impl From<&str> for Format { + fn from(value: &str) -> Self { + match value.trim().to_uppercase().as_str() { + "TV" => Format::Tv, + "TV_SHORT" => Format::TvShort, + "MOVIE" => Format::Movie, + "SPECIAL" => Format::Special, + "OVA" => Format::Ova, + "ONA" => Format::Ona, + "MUSIC" => Format::Music, + "MANGA" => Format::Manga, + "NOVEL" => Format::Novel, + "ONE_SHOT" => Format::OneShot, + _ => Format::default(), + } + } +} + +impl From for Format { + fn from(value: String) -> Self { + Format::from(value.as_str()) + } +} + +impl std::fmt::Display for Format { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } +} diff --git a/src/models/gender.rs b/src/models/gender.rs index 223b5d6..4001681 100644 --- a/src/models/gender.rs +++ b/src/models/gender.rs @@ -3,13 +3,28 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +/// Represents the gender of a person. +/// +/// The `Gender` enum defines various gender identities, including male, +/// female, non-binary, and other custom genders. +/// +/// # Variants +/// +/// * `Male` - Represents the male gender. +/// * `Female` - Represents the female gender. +/// * `NonBinary` - Represents the non-binary gender. +/// * `Other` - Represents a custom gender specified by a string. +#[derive(Debug, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "PascalCase"))] pub enum Gender { + /// Represents the male gender. Male, + /// Represents the female gender. Female, + /// Represents the non-binary gender. #[serde(rename = "Non-binary")] NonBinary, + /// Represents a custom gender specified by a string. Other(String), } diff --git a/src/models/image.rs b/src/models/image.rs index 93aa342..b4d5c02 100644 --- a/src/models/image.rs +++ b/src/models/image.rs @@ -3,9 +3,12 @@ use serde::{Deserialize, Serialize}; +/// Represents an image with different sizes. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "lowercase"))] pub struct Image { + /// URL of the large version of the image. pub large: String, + /// URL of the medium version of the image. pub medium: String, } diff --git a/src/models/language.rs b/src/models/language.rs index 05b4ae3..587ab5e 100644 --- a/src/models/language.rs +++ b/src/models/language.rs @@ -3,34 +3,239 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +/// Represents a language with various options. +/// +/// The `Language` enum defines a list of supported languages, each with +/// an associated variant. The default language is Japanese. +/// +/// # Variants +/// +/// * `Japanese` - The Japanese language. +/// * `English` - The English language. +/// * `Korean` - The Korean language. +/// * `Italian` - The Italian language. +/// * `Spanish` - The Spanish language. +/// * `Portuguese` - The Portuguese language. +/// * `French` - The French language. +/// * `German` - The German language. +/// * `Hebrew` - The Hebrew language. +/// * `Hungarian` - The Hungarian language. +/// * `Chinese` - The Chinese language. +/// * `Arabic` - The Arabic language. +/// * `Filipino` - The Filipino language. +/// * `Catalan` - The Catalan language. +/// * `Finnish` - The Finnish language. +/// * `Turkish` - The Turkish language. +/// * `Dutch` - The Dutch language. +/// * `Swedish` - The Swedish language. +/// * `Thai` - The Thai language. +/// * `Tagalog` - The Tagalog language. +/// * `Malaysian` - The Malaysian language. +/// * `Indonesian` - The Indonesian language. +/// * `Vietnamese` - The Vietnamese language. +/// * `Nepali` - The Nepali language. +/// * `Hindi` - The Hindi language. +/// * `Urdu` - The Urdu language. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "PascalCase"))] pub enum Language { + /// The Japanese language. #[default] Japanese, + /// The English language. English, + /// The Korean language. Korean, + /// The Italian language. Italian, + /// The Spanish language. Spanish, + /// The Portuguese language. Portuguese, + /// The French language. French, + /// The German language. German, + /// The Hebrew language. Hebrew, + /// The Hungarian language. Hungarian, + /// The Chinese language. Chinese, + /// The Arabic language. Arabic, + /// The Filipino language. Filipino, + /// The Catalan language. Catalan, + /// The Finnish language. Finnish, + /// The Turkish language. Turkish, + /// The Dutch language. Dutch, + /// The Swedish language. Swedish, + /// The Thai language. Thai, + /// The Tagalog language. Tagalog, + /// The Malaysian language. Malaysian, + /// The Indonesian language. Indonesian, + /// The Vietnamese language. Vietnamese, + /// The Nepali language. Nepali, + /// The Hindi language. Hindi, + /// The Urdu language. Urdu, } + +impl Language { + /// Returns the ISO 639-1 code of the language. + pub fn code(&self) -> &str { + match self { + Language::Japanese => "ja", + Language::English => "en", + Language::Korean => "ko", + Language::Italian => "it", + Language::Spanish => "es", + Language::Portuguese => "pt", + Language::French => "fr", + Language::German => "de", + Language::Hebrew => "he", + Language::Hungarian => "hu", + Language::Chinese => "zh", + Language::Arabic => "ar", + Language::Filipino => "fil", + Language::Catalan => "ca", + Language::Finnish => "fi", + Language::Turkish => "tr", + Language::Dutch => "nl", + Language::Swedish => "sv", + Language::Thai => "th", + Language::Tagalog => "tl", + Language::Malaysian => "ms", + Language::Indonesian => "id", + Language::Vietnamese => "vi", + Language::Nepali => "ne", + Language::Hindi => "hi", + Language::Urdu => "ur", + } + } + + /// Returns the ISO 639-1 code of the language. + /// + /// Alias of `code`. + pub fn iso(&self) -> &str { + self.code() + } + + /// Returns the name of the language in the native language. + pub fn native(&self) -> &str { + match self { + Language::Japanese => "日本語", + Language::English => "English", + Language::Korean => "한국어", + Language::Italian => "Italiano", + Language::Spanish => "Español", + Language::Portuguese => "Português", + Language::French => "Français", + Language::German => "Deutsch", + Language::Hebrew => "עברית", + Language::Hungarian => "Magyar", + Language::Chinese => "中文", + Language::Arabic => "العربية", + Language::Filipino => "Filipino", + Language::Catalan => "Català", + Language::Finnish => "Suomi", + Language::Turkish => "Türkçe", + Language::Dutch => "Nederlands", + Language::Swedish => "Svenska", + Language::Thai => "ไทย", + Language::Tagalog => "Tagalog", + Language::Malaysian => "Bahasa Melayu", + Language::Indonesian => "Bahasa Indonesia", + Language::Vietnamese => "Tiếng Việt", + Language::Nepali => "नेपाली", + Language::Hindi => "हिंदी", + Language::Urdu => "اردو", + } + } +} + +impl From<&str> for Language { + fn from(value: &str) -> Self { + match value.trim().to_uppercase().as_str() { + "JA" | "JP" | "JAPANESE" => Language::Japanese, + "EN" | "UK" | "ENGLISH" => Language::English, + "KO" | "KOREAN" => Language::Korean, + "IT" | "ITALIAN" => Language::Italian, + "ES" | "SPANISH" => Language::Spanish, + "PT" | "PORTUGUESE" => Language::Portuguese, + "FR" | "FRENCH" => Language::French, + "DE" | "GERMAN" => Language::German, + "HE" | "HEBREW" => Language::Hebrew, + "HU" | "HUNGARIAN" => Language::Hungarian, + "ZH" | "CHINESE" => Language::Chinese, + "AR" | "ARABIC" => Language::Arabic, + "FIL" | "PHILIPPINE" => Language::Filipino, + "CA" | "CATALAN" => Language::Catalan, + "FI" | "FINNISH" => Language::Finnish, + "TR" | "TURKISH" => Language::Turkish, + "NL" | "DUTCH" => Language::Dutch, + "SV" | "SWEDISH" => Language::Swedish, + "TH" | "THAI" => Language::Thai, + "TL" | "TAGALOG" => Language::Tagalog, + "MS" | "MALAYSIAN" => Language::Malaysian, + "ID" | "INDONESIAN" => Language::Indonesian, + "VI" | "VIETNAMESE" => Language::Vietnamese, + "NE" | "NEPALI" => Language::Nepali, + "HI" | "HINDI" => Language::Hindi, + "UR" | "URDU" => Language::Urdu, + _ => Language::default(), + } + } +} + +impl From for Language { + fn from(value: String) -> Self { + Language::from(value.as_str()) + } +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Language::Japanese => write!(f, "Japanese"), + Language::English => write!(f, "English"), + Language::Korean => write!(f, "Korean"), + Language::Italian => write!(f, "Italian"), + Language::Spanish => write!(f, "Spanish"), + Language::Portuguese => write!(f, "Portuguese"), + Language::French => write!(f, "French"), + Language::German => write!(f, "German"), + Language::Hebrew => write!(f, "Hebrew"), + Language::Hungarian => write!(f, "Hungarian"), + Language::Chinese => write!(f, "Chinese"), + Language::Arabic => write!(f, "Arabic"), + Language::Filipino => write!(f, "Filipino"), + Language::Catalan => write!(f, "Catalan"), + Language::Finnish => write!(f, "Finnish"), + Language::Turkish => write!(f, "Turkish"), + Language::Dutch => write!(f, "Dutch"), + Language::Swedish => write!(f, "Swedish"), + Language::Thai => write!(f, "Thai"), + Language::Tagalog => write!(f, "Tagalog"), + Language::Malaysian => write!(f, "Malaysian"), + Language::Indonesian => write!(f, "Indonesian"), + Language::Vietnamese => write!(f, "Vietnamese"), + Language::Nepali => write!(f, "Nepali"), + Language::Hindi => write!(f, "Hindi"), + Language::Urdu => write!(f, "Urdu"), + } + } +} diff --git a/src/models/link.rs b/src/models/link.rs index 04f1198..e358df6 100644 --- a/src/models/link.rs +++ b/src/models/link.rs @@ -1,34 +1,59 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022-2025 Andriel Ferreira -use serde::Deserialize; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -use crate::models::Color; -use crate::models::Language; +use super::{Color, Language}; +/// Represents a link. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Link { + /// The ID of the link. pub id: Option, + /// The title of the link. pub title: Option, + /// The thumbnail of the link. pub thumbnail: Option, + /// The URL of the link. pub url: Option, + /// The site of the link. pub site: Option, + /// The ID of the site of the link. pub site_id: Option, - pub link_type: Option, + /// The type of the link. + pub link_type: Option, + /// The language of the link. pub language: Option, + /// The color of the link. pub color: Option, + /// The icon of the link. pub icon: Option, + /// The notes of the link. pub notes: Option, + /// Whether the link is disabled or not. pub is_disabled: Option, } -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +/// The type of link. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "UPPERCASE"))] -pub enum Type { +pub enum LinkType { + /// The info link type. #[default] Info, + /// The streaming link type. Streaming, + /// The social link type. Social, } + +impl std::fmt::Display for LinkType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LinkType::Info => write!(f, "Info"), + LinkType::Streaming => write!(f, "Streaming"), + LinkType::Social => write!(f, "Social"), + } + } +} diff --git a/src/models/manga.rs b/src/models/manga.rs index 6c75acb..7f3a8c1 100644 --- a/src/models/manga.rs +++ b/src/models/manga.rs @@ -8,7 +8,51 @@ use super::{ }; use crate::{Client, Result}; -/// A manga. +/// Represents a manga with various attributes. +/// +/// The `Manga` struct contains detailed information about a manga, +/// including its ID, title, format, status, description, dates, +/// chapters, volumes, country of origin, licensing status, source, +/// hashtags, images, genres, synonyms, scores, popularity, tags, +/// relations, characters, staff, studios, and other metadata. +/// +/// # Fields +/// +/// * `id` - The ID of the manga. +/// * `id_mal` - The ID of the manga on MAL (MyAnimeList). +/// * `title` - The title of the manga. +/// * `format` - The format of the manga (e.g., manga, novel). +/// * `status` - The status of the manga (e.g., publishing, completed). +/// * `description` - The description of the manga. +/// * `start_date` - The start date of the manga. +/// * `end_date` - The end date of the manga. +/// * `chapters` - The number of chapters of the manga. +/// * `volumes` - The number of volumes of the manga. +/// * `country_of_origin` - The country of origin of the manga. +/// * `is_licensed` - Whether the manga is licensed or not. +/// * `source` - The source of the manga (e.g., original, adaptation). +/// * `hashtag` - The hashtag of the manga. +/// * `updated_at` - The updated date of the manga. +/// * `cover` - The cover image of the manga. +/// * `banner` - The banner image of the manga. +/// * `genres` - The genres of the manga. +/// * `synonyms` - The synonyms of the manga. +/// * `average_score` - The average score of the manga. +/// * `mean_score` - The mean score of the manga. +/// * `popularity` - The popularity of the manga. +/// * `is_locked` - Whether the manga is locked or not. +/// * `trending` - The trending of the manga. +/// * `favourites` - The number of favourites of the manga. +/// * `tags` - The tags of the manga. +/// * `relations` - The relations of the manga. +/// * `characters` - The characters of the manga. +/// * `staff` - The staff of the manga. +/// * `studios` - The studios of the manga. +/// * `is_favourite` - Whether the manga is favourite or not. +/// * `is_favourite_blocked` - Whether the manga is favourite blocked or not. +/// * `is_adult` - Whether the manga is adult or not. +/// * `external_links` - The external links of the manga. +/// * `url` - The site URL of the manga. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Manga { @@ -89,22 +133,28 @@ pub struct Manga { /// The site URL of the manga. #[serde(rename = "siteUrl")] pub url: String, + + /// The client used to fetch additional data. #[serde(skip)] + pub(crate) client: Client, + /// Whether the person's data is fully loaded. + #[serde(default)] pub(crate) is_full_loaded: bool, } impl Manga { - /// Load fully the manga. + /// Loads the full details of the manga. /// /// # Errors /// - /// Returns an error if the manga is already full loaded. + /// Returns an error if the manga details cannot be loaded. + /// + /// # Panics + /// + /// Panics if the manga is already fully loaded. pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut manga = Client::default().get_manga(self.id).await.unwrap(); - manga.is_full_loaded = true; - - Ok(manga) + self.client.get_manga(self.id).await } else { panic!("This manga is already full loaded") } diff --git a/src/models/mod.rs b/src/models/mod.rs index d3c90ba..39c999e 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -25,7 +25,7 @@ mod title; mod user; pub use anime::Anime; -pub use character::{Character, Role as CharacterRole}; +pub use character::{Character, CharacterRole}; pub use color::Color; pub use cover::Cover; pub use date::Date; @@ -33,14 +33,13 @@ pub use format::Format; pub use gender::Gender; pub use image::Image; pub use language::Language; -pub use link::{Link, Type as LinkType}; +pub use link::{Link, LinkType}; pub use manga::Manga; pub use name::Name; -pub use notification::{Notification, NotificationOption, Type as NotificationType}; +pub use notification::{Notification, NotificationOption, NotificationType}; pub use person::Person; -pub use relation::{Relation, Type as RelationType}; +pub use relation::{Relation, RelationType}; pub use season::Season; -use serde::{Deserialize, Serialize}; pub use source::Source; pub use status::Status; pub use studio::Studio; @@ -48,10 +47,37 @@ pub use tag::Tag; pub use title::Title; pub use user::User; -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +use serde::{Deserialize, Serialize}; + +/// Represents different types of media. +/// +/// The `MediaType` enum defines various types of media, such as anime, +/// manga, character, user, person, studio, and an unknown type. +/// +/// # Variants +/// +/// * `Anime` - Represents an anime. +/// * `Manga` - Represents a manga. +/// * `Character` - Represents a character. +/// * `User` - Represents a user. +/// * `Person` - Represents a person. +/// * `Studio` - Represents a studio. +/// * `Unknown` - Represents an unknown type of media. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] pub enum MediaType { + /// An anime. Anime, + /// A manga. Manga, + /// A character. + Character, + /// An user. + User, + /// A person. + Person, + /// A studio. + Studio, + /// Unknown type. #[default] Unknown, } diff --git a/src/models/name.rs b/src/models/name.rs index 2c5cf23..5bf1d34 100644 --- a/src/models/name.rs +++ b/src/models/name.rs @@ -3,15 +3,51 @@ use serde::{Deserialize, Serialize}; +/// Represents a name. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Name { + /// The first name. pub first: String, + /// The middle name, if any. pub middle: Option, + /// The last name, if any. pub last: Option, + /// The full name. pub full: String, + /// The native name, if any. pub native: Option, + /// Alternative names. pub alternative: Vec, + /// Alternative names that may contain spoilers. pub alternative_spoiler: Option>, + /// The name preferred by the user, if any. pub user_preferred: Option, } + +impl Name { + /// Returns the full name. + pub fn full(&self) -> String { + self.full.clone() + } + + /// Returns the native name, if any. + pub fn native(&self) -> Option { + self.native.clone() + } + + /// Returns the alternative names. + pub fn alternative(&self) -> Vec { + self.alternative.clone() + } + + /// Returns the alternative names that may contain spoilers. + pub fn spoiler(&self) -> Option> { + self.alternative_spoiler.clone() + } + + /// Returns the name preferred by the user, if any. + pub fn user_preferred(&self) -> Option { + self.user_preferred.clone() + } +} diff --git a/src/models/notification.rs b/src/models/notification.rs index de487c2..f92e7ef 100644 --- a/src/models/notification.rs +++ b/src/models/notification.rs @@ -6,29 +6,68 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] pub struct Notification {} +/// Represents the options for a notification. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct NotificationOption { - notification_type: Type, + /// The type of the notification. + notification_type: NotificationType, + /// Whether the notification is enabled. enabled: bool, } -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +/// Represents the type of a notification. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] -pub enum Type { +pub enum NotificationType { + /// Notification for an activity message. #[default] ActivityMessage, + /// Notification for an activity reply. ActivityReply, + /// Notification for a new follower. Following, + /// Notification for an activity mention. ActivityMention, + /// Notification for a thread comment mention. ThreadCommentMention, + /// Notification for an airing. Airing, + /// Notification for an activity like. ActivityLike, + /// Notification for an activity reply like. ActivityReplyLike, + /// Notification for a thread like. ThreadLike, + /// Notification for being subscribed to an activity reply. ActivityReplySubscribed, + /// Notification for a related media addition. RelatedMediaAddition, + /// Notification for a media data change. MediaDataChange, + /// Notification for a media merge. MediaMerge, + /// Notification for a media deletion. MediaDeletion, } + +impl std::fmt::Display for NotificationType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NotificationType::ActivityMessage => write!(f, "Activity Message"), + NotificationType::ActivityReply => write!(f, "Activity Reply"), + NotificationType::Following => write!(f, "Following"), + NotificationType::ActivityMention => write!(f, "Activity Mention"), + NotificationType::ThreadCommentMention => write!(f, "Thread Comment Mention"), + NotificationType::Airing => write!(f, "Airing"), + NotificationType::ActivityLike => write!(f, "Activity Like"), + NotificationType::ActivityReplyLike => write!(f, "Activity Reply Like"), + NotificationType::ThreadLike => write!(f, "Thread Like"), + NotificationType::ActivityReplySubscribed => write!(f, "Activity Reply Subscribed"), + NotificationType::RelatedMediaAddition => write!(f, "Related Media Addition"), + NotificationType::MediaDataChange => write!(f, "Media Data Change"), + NotificationType::MediaMerge => write!(f, "Media Merge"), + NotificationType::MediaDeletion => write!(f, "Media Deletion"), + } + } +} diff --git a/src/models/person.rs b/src/models/person.rs index ffb467a..02b5798 100644 --- a/src/models/person.rs +++ b/src/models/person.rs @@ -1,63 +1,109 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022-2025 Andriel Ferreira -use serde::Deserialize; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -use crate::models::Character; -use crate::models::Date; -use crate::models::Gender; -use crate::models::Image; -use crate::models::Language; -use crate::models::Name; - -use crate::Client; -use crate::Result; +use super::{Character, Date, Gender, Image, Language, Name}; +use crate::{Client, Result}; +/// Represents a person. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Person { + /// The ID of the person. pub id: i64, + /// The name of the person. pub name: Name, + /// The language of the person. #[serde(rename = "languageV2")] pub language: Language, + /// The image of the person, if any. pub image: Option, + /// The description of the person, if any. pub description: Option, + /// The primary occupations of the person, if any. pub primary_occupations: Option>, + /// The gender of the person. pub gender: Gender, + /// The date of birth of the person, if any. pub date_of_birth: Option, + /// The date of death of the person, if any. pub date_of_death: Option, + /// The age of the person, if any. pub age: Option, + // The years the person was active, if any. // pub years_active: Option<(u64, u64)>, + /// The hometown of the person, if any. pub home_town: Option, + /// The blood type of the person, if any. pub blood_type: Option, + /// Whether the person is a favorite, if any. pub is_favourite: Option, + /// Whether the person is blocked from being a favorite, if any. pub is_favourite_blocked: Option, + /// The URL of the person's site. #[serde(rename = "siteUrl")] pub url: String, + /// The characters associated with the person, if any. #[serde(skip)] pub characters: Option>, + /// The number of favorites the person has. pub favourites: i64, + /// The moderator notes for the person, if any. pub mod_notes: Option, + + /// The client used to fetch additional data. #[serde(skip)] + pub(crate) client: Client, + /// Whether the person's data is fully loaded. + #[serde(default)] pub(crate) is_full_loaded: bool, } impl Person { + /// Loads the full details of the person. + /// + /// # Errors + /// + /// Returns an error if the person details cannot be loaded. + /// + /// # Panics + /// + /// Panics if the person is already fully loaded. pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut person = Client::default().get_person(self.id).await.unwrap(); - person.is_full_loaded = true; - Ok(person) + self.client.get_person(self.id).await } else { panic!("This person is already full loaded") } } + /// Retrieves the media associated with the person. + /// + /// # Errors + /// + /// Returns an error if the media cannot be retrieved. + /// + /// # Type Parameters + /// + /// * `T` - The type of the media. pub async fn get_medias() -> Result { unimplemented!() } + /// Retrieves the media associated with a character. + /// + /// # Arguments + /// + /// * `_character_id` - The ID of the character whose media is to be retrieved. + /// + /// # Errors + /// + /// Returns an error if the media cannot be retrieved. + /// + /// # Type Parameters + /// + /// * `T` - The type of the media. pub async fn get_character_medias(_character_id: i64) -> Result { unimplemented!() } diff --git a/src/models/relation.rs b/src/models/relation.rs index cd01e7b..0e4ab7e 100644 --- a/src/models/relation.rs +++ b/src/models/relation.rs @@ -1,39 +1,91 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022-2025 Andriel Ferreira -use serde::Deserialize; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -use crate::models::Anime; -use crate::models::Manga; -use crate::models::MediaType; +use super::{Anime, Manga, MediaType}; +/// Represents a relation between different media types. +/// +/// The `Relation` struct contains information about the relationship +/// between different media types, such as anime and manga, including +/// the media type, related anime or manga, relation ID, relation type, +/// and whether it is the main studio. +/// +/// # Fields +/// +/// * `media_type` - The type of media (e.g., anime, manga). +/// * `anime` - An optional related anime. +/// * `manga` - An optional related manga. +/// * `id` - The ID of the relation. +/// * `relation_type` - The type of relation (e.g., adaptation, sequel). +/// * `is_main_studio` - Whether the relation is the main studio. // TODO: Use generic type #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Relation { + /// The type of media (e.g., anime, manga). pub media_type: MediaType, + /// An optional related anime. pub anime: Option, + /// An optional related manga. pub manga: Option, + /// The ID of the relation. pub id: i64, - pub relation_type: Type, + /// The type of relation (e.g., adaptation, sequel). + pub relation_type: RelationType, + /// Whether the relation is the main studio. pub is_main_studio: bool, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +/// Represents the type of relation between different media. +/// +/// The `RelationType` enum defines various types of relationships that +/// can exist between different media, such as adaptations, sequels, +/// prequels, and more. +/// +/// # Variants +/// +/// * `Adaptation` - The media is an adaptation of another work. +/// * `Prequel` - The media is a prequel to another work. +/// * `Sequel` - The media is a sequel to another work. +/// * `Parent` - The media is a parent story to another work. +/// * `SideStory` - The media is a side story to another work. +/// * `Character` - The media shares characters with another work. +/// * `Summary` - The media is a summary of another work. +/// * `Alternative` - The media is an alternative version of another work. +/// * `SpinOff` - The media is a spin-off of another work. +/// * `Other` - The media has some other type of relation to another work. +/// * `Source` - The media is the source material for another work. +/// * `Compilation` - The media is a compilation of another work. +/// * `Contains` - The media contains another work. +#[derive(Debug, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "UPPERCASE"))] -pub enum Type { +pub enum RelationType { + /// The media is an adaptation of another work. Adaptation, + /// The media is a prequel to another work. Prequel, + /// The media is a sequel to another work. Sequel, + /// The media is a parent story to another work. Parent, + /// The media is a side story to another work. SideStory, + /// The media shares characters with another work. Character, + /// The media is a summary of another work. Summary, + /// The media is an alternative version of another work. Alternative, + /// The media is a spin-off of another work. SpinOff, + /// The media has some other type of relation to another work. Other, + /// The media is the source material for another work. Source, + /// The media is a compilation of another work. Compilation, + /// The media contains another work. Contains, } diff --git a/src/models/season.rs b/src/models/season.rs index c4cb140..fd7a04c 100644 --- a/src/models/season.rs +++ b/src/models/season.rs @@ -3,11 +3,71 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +/// Represents the four seasons of the year. +/// +/// The `Season` enum defines the four seasons: Winter, Spring, Summer, +/// and Fall. This can be used to categorize or filter data based on +/// the season. +/// +/// # Variants +/// +/// * `Winter` - Represents the winter season. +/// * `Spring` - Represents the spring season. +/// * `Summer` - Represents the summer season. +/// * `Fall` - Represents the fall season. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "UPPERCASE"))] pub enum Season { + /// Represents the winter season. + #[default] Winter, + /// Represents the spring season. Spring, + /// Represents the summer season. Summer, + /// Represents the fall season. Fall, } + +impl Season { + /// Returns the name of the season. + /// + /// # Example + /// + /// ``` + /// let season = Season::Winter; + /// assert_eq!(season.name(), "Winter"); + /// ``` + pub fn name(&self) -> &str { + match self { + Season::Winter => "Winter", + Season::Spring => "Spring", + Season::Summer => "Summer", + Season::Fall => "Fall", + } + } +} + +impl From<&str> for Season { + fn from(value: &str) -> Self { + match value.trim().to_uppercase().as_str() { + "WINTER" => Season::Winter, + "SPRING" => Season::Spring, + "SUMMER" => Season::Summer, + "FALL" => Season::Fall, + _ => Season::default(), + } + } +} + +impl From for Season { + fn from(value: String) -> Self { + Season::from(value.as_str()) + } +} + +impl std::fmt::Display for Season { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } +} diff --git a/src/models/source.rs b/src/models/source.rs index 6a8d727..9e24740 100644 --- a/src/models/source.rs +++ b/src/models/source.rs @@ -3,23 +3,90 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +/// Represents the source of a media. +#[derive(Debug, Default, Clone, Eq, Hash, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum Source { + /// The original source. #[default] Original, + /// Manga source. Manga, + /// Light novel source. LightNovel, + /// Visual novel source. VisualNovel, + /// Video game source. VideoGame, + /// Other source. Other, + /// Novel source. Novel, + /// Doujinshi source. Doujinshi, + /// Anime source. Anime, + /// Web novel source. WebNovel, + /// Live action source. LiveAction, + /// Game source. Game, + /// Comic source. Comic, + /// Multimedia project source. MultimediaProject, + /// Picture book source. PictureBook, } + +impl From<&str> for Source { + fn from(source: &str) -> Self { + match source.to_ascii_uppercase().as_str() { + "ORIGINAL" => Source::Original, + "MANGA" => Source::Manga, + "LIGHT_NOVEL" => Source::LightNovel, + "VISUAL_NOVEL" => Source::VisualNovel, + "VIDEO_GAME" => Source::VideoGame, + "OTHER" => Source::Other, + "NOVEL" => Source::Novel, + "DOUJINSHI" => Source::Doujinshi, + "ANIME" => Source::Anime, + "WEB_NOVEL" => Source::WebNovel, + "LIVE_ACTION" => Source::LiveAction, + "GAME" => Source::Game, + "COMIC" => Source::Comic, + "MULTIMEDIA_PROJECT" => Source::MultimediaProject, + "PICTURE_BOOK" => Source::PictureBook, + _ => Source::Other, + } + } +} + +impl From for Source { + fn from(source: String) -> Self { + Source::from(source.as_str()) + } +} + +impl std::fmt::Display for Source { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Source::Original => write!(f, "Original"), + Source::Manga => write!(f, "Manga"), + Source::LightNovel => write!(f, "Light Novel"), + Source::VisualNovel => write!(f, "Visual Novel"), + Source::VideoGame => write!(f, "Video Game"), + Source::Other => write!(f, "Other"), + Source::Novel => write!(f, "Novel"), + Source::Doujinshi => write!(f, "Doujinshi"), + Source::Anime => write!(f, "Anime"), + Source::WebNovel => write!(f, "Web Novel"), + Source::LiveAction => write!(f, "Live Action"), + Source::Game => write!(f, "Game"), + Source::Comic => write!(f, "Comic"), + Source::MultimediaProject => write!(f, "Multimedia Project"), + Source::PictureBook => write!(f, "Picture Book"), + } + } +} diff --git a/src/models/studio.rs b/src/models/studio.rs index 3c00d84..b52a41b 100644 --- a/src/models/studio.rs +++ b/src/models/studio.rs @@ -5,19 +5,53 @@ use serde::{Deserialize, Serialize}; use crate::Result; +/// Represents a studio with various attributes. +/// +/// The `Studio` struct contains detailed information about a studio, +/// including its ID, name, whether it is an animation studio, URL, +/// whether it is a favorite, and the number of favorites. +/// +/// # Fields +/// +/// * `id` - The ID of the studio. +/// * `name` - The name of the studio. +/// * `is_animation_studio` - Whether the studio is an animation studio. +/// * `url` - The URL of the studio. +/// * `is_favourite` - An optional boolean indicating if the studio is a favorite. +/// * `favourites` - The number of favorites the studio has. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Studio { + /// The ID of the studio. pub id: i64, + /// The name of the studio. pub name: String, + /// Whether the studio is an animation studio. pub is_animation_studio: bool, + /// The URL of the studio. pub url: String, + /// Whether the studio is a favorite. pub is_favourite: Option, + /// The number of favorites the studio has. pub favourites: i64, } impl Studio { + /// Retrieves media associated with the studio. + /// + /// This function fetches media related to the studio and returns a + /// result containing the media data of type `T`. + /// + /// # Type Parameters + /// + /// * `T` - The type of the media data to be returned. + /// + /// # Example + /// + /// ``` + /// let animes = studio.get_medias::().await?; + /// ``` pub async fn get_medias() -> Result { - todo!() + unimplemented!() } } diff --git a/src/models/tag.rs b/src/models/tag.rs index bb0834a..f455b2c 100644 --- a/src/models/tag.rs +++ b/src/models/tag.rs @@ -3,16 +3,26 @@ use serde::{Deserialize, Serialize}; +/// Represents a tag in the system. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Tag { + /// The ID of the tag. pub id: i64, + /// The name of the tag. pub name: String, + /// The description of the tag. pub description: String, + /// The category of the tag. pub category: String, + /// The rank of the tag. pub rank: i64, + /// Whether the tag is a general spoiler. pub is_general_spoiler: bool, + /// Whether the tag is a media spoiler. pub is_media_spoiler: bool, + /// Whether the tag is adult content. pub is_adult: bool, + /// The user ID associated with the tag. pub user_id: Option, } diff --git a/src/models/title.rs b/src/models/title.rs index 83af7a6..4da9a2d 100644 --- a/src/models/title.rs +++ b/src/models/title.rs @@ -3,11 +3,50 @@ use serde::{Deserialize, Serialize}; +/// Represents a title with various language options. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "lowercase"))] pub struct Title { + /// The title in Romaji (Latin script). pub romaji: Option, + /// The title in English. pub english: Option, + /// The title in the native language. pub native: String, + /// The title preferred by the user. pub user_preferred: Option, } + +impl Title { + /// Returns the title in Romaji (Latin script). + pub fn romaji(&self) -> &str { + self.romaji.as_deref().unwrap_or(&self.native) + } + + /// Returns the title in English. + pub fn english(&self) -> &str { + self.english.as_deref().unwrap_or(&self.native) + } + + /// Returns the title in the native language. + pub fn native(&self) -> &str { + &self.native + } + + /// Returns the title preferred by the user. + pub fn user_preferred(&self) -> &str { + self.user_preferred.as_deref().unwrap_or(&self.native) + } +} + +impl From for String { + fn from(title: Title) -> Self { + title.native().to_string() + } +} + +impl std::fmt::Display for Title { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.native()) + } +} diff --git a/src/models/user.rs b/src/models/user.rs index 34446a9..2daa079 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -6,8 +6,36 @@ use serde::{Deserialize, Serialize}; use super::{ Anime, Character, Color, Format, Image, Manga, NotificationOption, Person, Status, Studio, }; +use crate::{Client, Result}; -/// A user. +/// Represents a user with various attributes. +/// +/// The `User` struct contains detailed information about a user, +/// including their ID, name, about section, avatar, banner, donator +/// status, favourites, follow status, media list options, site URL, +/// statistics, notification count, and timestamps for creation and +/// updates. +/// +/// # Fields +/// +/// * `id` - The ID of the user. +/// * `name` - The name of the user. +/// * `about` - An optional about section for the user. +/// * `avatar` - An optional avatar image for the user. +/// * `banner` - An optional banner image for the user. +/// * `donator_badge` - The donator badge of the user. +/// * `donator_tier` - The donator tier of the user. +/// * `favourites` - The user's favourites. +/// * `is_blocked` - An optional boolean indicating if the user is blocked. +/// * `is_follower` - An optional boolean indicating if the user is a follower. +/// * `is_following` - An optional boolean indicating if the user is following someone. +/// * `media_list_options` - Optional media list options for the user. +/// * `options` - Optional additional options for the user. +/// * `url` - The site URL of the user. +/// * `statistics` - The user's statistics. +/// * `unread_notification_count` - An optional count of unread notifications. +/// * `created_at` - The timestamp when the user was created. +/// * `updated_at` - The timestamp when the user was last updated. #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all(deserialize = "camelCase"))] pub struct User { @@ -49,6 +77,32 @@ pub struct User { pub created_at: i64, /// The updated date of the user. pub updated_at: i64, + + /// The client used to fetch additional data. + #[serde(skip)] + pub(crate) client: Client, + /// Whether the person's data is fully loaded. + #[serde(default)] + pub(crate) is_full_loaded: bool, +} + +impl User { + /// Loads the full details of the user. + /// + /// # Errors + /// + /// Returns an error if the user details cannot be loaded. + /// + /// # Panics + /// + /// Panics if the user is already fully loaded. + pub async fn load_full(self) -> Result<Self> { + if !self.is_full_loaded { + self.client.get_user(self.id).await + } else { + panic!("This user is already full loaded") + } + } } /// The options of a user.