diff --git a/.gitignore b/.gitignore index 718b7dc..31c8eda 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ src/main.rs Cargo.lock /target +/.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 05ac0dd..b1c89e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ categories = ["api-binding"] edition = "2021" [dependencies] -reqwest = "^0.11" +tokio = { version = "^1.40", features = ["macros", "rt-multi-thread"] } +serde = { version = "^1.0", features = ["derive"] } +reqwest = "^0.12" serde_json = "^1.0" -tokio = { version = "^1.17", features = ["macros", "rt-multi-thread"] } diff --git a/src/client.rs b/src/client.rs index a90048a..cea5b6c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::io::Read; use std::time::Duration; +use crate::models::{Anime, Character, Manga, Person}; use crate::Result; #[derive(Clone)] @@ -35,7 +36,7 @@ impl Client { pub async fn get_anime(&self, variables: serde_json::Value) -> Result { let data = self.request("anime", "get", variables).await.unwrap(); - let mut anime = crate::models::Anime::parse(&data["data"]["Media"]); + let mut anime = serde_json::from_str::(&data["data"]["Media"].to_string()).unwrap(); anime.is_full_loaded = true; Ok(anime) @@ -43,7 +44,7 @@ impl Client { pub async fn get_manga(&self, variables: serde_json::Value) -> Result { let data = self.request("manga", "get", variables).await.unwrap(); - let mut manga = crate::models::Manga::parse(&data["data"]["Media"]); + let mut manga = serde_json::from_str::(&data["data"]["Media"].to_string()).unwrap(); manga.is_full_loaded = true; Ok(manga) @@ -54,7 +55,8 @@ impl Client { variables: serde_json::Value, ) -> Result { let data = self.request("character", "get", variables).await.unwrap(); - let mut character = crate::models::Character::parse(&data["data"]["Character"]); + let mut character = + serde_json::from_str::(&data["data"]["Character"].to_string()).unwrap(); character.is_full_loaded = true; Ok(character) @@ -69,7 +71,8 @@ impl Client { .request("person", "get", serde_json::json!({ "id": id })) .await .unwrap(); - let mut person = crate::models::Person::parse(&data["data"]["Staff"]); + let mut person = + serde_json::from_str::(&data["data"]["Staff"].to_string()).unwrap(); person.is_full_loaded = true; Ok(person) @@ -109,11 +112,11 @@ impl Client { Ok(result) } - pub fn get_query(media_type: &str, action: &str) -> Result { + pub(crate) fn get_query(media_type: &str, action: &str) -> Result { let mut graphql_query = String::new(); let media_type = media_type.to_lowercase(); - let media_types = vec!["anime", "manga", "character", "user", "person", "studio"]; + let media_types = ["anime", "manga", "character", "user", "person", "studio"]; if !media_types.contains(&media_type.as_str()) { panic!("The media type '{}' does not exits", { media_type }); } diff --git a/src/lib.rs b/src/lib.rs index 4625875..899556d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,5 +12,5 @@ pub mod models; mod client; mod error; -pub use self::client::Client; -pub use self::error::{Error, Result}; +pub use client::Client; +pub use error::{Error, Result}; diff --git a/src/models/anime.rs b/src/models/anime.rs index 92fa3fd..944d880 100644 --- a/src/models/anime.rs +++ b/src/models/anime.rs @@ -1,26 +1,27 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::Deserialize; +use serde::Serialize; + use crate::models::Character; -use crate::models::Color; use crate::models::Cover; use crate::models::Date; use crate::models::Format; -use crate::models::Language; -use crate::models::Manga; -use crate::models::MediaType; +use crate::models::Link; use crate::models::Person; -use crate::models::Score; +use crate::models::Relation; use crate::models::Season; use crate::models::Source; use crate::models::Status; use crate::models::Studio; use crate::models::Tag; use crate::models::Title; -use crate::models::{Link, LinkType}; -use crate::models::{Relation, RelationType}; +use crate::Client; +use crate::Result; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Anime { pub id: i64, pub id_mal: Option, @@ -40,433 +41,43 @@ pub struct Anime { pub source: Option, pub hashtag: Option, pub updated_at: Option, + #[serde(rename = "coverImage")] pub cover: Cover, + #[serde(rename = "bannerImage")] pub banner: Option, pub genres: Option>, pub synonyms: Option>, - pub score: Score, + pub average_score: Option, + pub mean_score: Option, pub popularity: Option, pub is_locked: Option, pub trending: Option, pub favourites: Option, pub tags: Option>, + #[serde(skip)] pub relations: Option>, + #[serde(skip)] pub characters: Option>, + #[serde(skip)] pub staff: Option>, + #[serde(skip)] pub studios: Option>, pub is_favourite: Option, pub is_favourite_blocked: Option, pub is_adult: Option, - pub next_airing_episode: Option, + pub next_airing_episode: Option, pub external_links: Option>, pub streaming_episodes: Option>, + #[serde(rename = "siteUrl")] pub url: String, + #[serde(skip)] pub(crate) is_full_loaded: bool, } impl Anime { - pub(crate) fn parse(data: &serde_json::Value) -> Self { - let mut anime = Anime::default(); - - anime.id = data["id"].as_i64().unwrap(); - - if let Some(id_mal) = data["idMal"].as_i64() { - anime.id_mal = Some(id_mal); - } - - let title = data["title"].as_object().unwrap(); - anime.title = Title { - romaji: Some(title["romaji"].as_str().unwrap().to_string()), - english: title["english"].as_str().map(|title| title.to_string()), - native: title["native"].as_str().unwrap().to_string(), - user_preferred: Some(title["userPreferred"].as_str().unwrap().to_string()), - }; - - let format = data["format"].as_str().unwrap(); - anime.format = match format { - "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(), - }; - - let status = data["status"].as_str().unwrap(); - anime.status = match status { - "FINISHED" => Status::Finished, - "RELEASING" => Status::Releasing, - "CANCELLED" => Status::Cancelled, - "HIATUS" => Status::Hiatus, - _ => Status::default(), - }; - - anime.description = data["description"].as_str().unwrap().to_string(); - - if let Some(start_date) = data["startDate"].as_object() { - let mut date = Date::default(); - - if let Some(year) = start_date["year"].as_i64() { - date.year = Some(year); - } - if let Some(month) = start_date["month"].as_i64() { - date.month = Some(month); - } - if let Some(day) = start_date["day"].as_i64() { - date.day = Some(day); - } - - anime.start_date = Some(date); - } - - if let Some(end_date) = data["endDate"].as_object() { - let mut date = Date::default(); - - if let Some(year) = end_date["year"].as_i64() { - date.year = Some(year); - } - if let Some(month) = end_date["month"].as_i64() { - date.month = Some(month); - } - if let Some(day) = end_date["day"].as_i64() { - date.day = Some(day); - } - - anime.end_date = Some(date); - } - - if let Some(season) = data["season"].as_str() { - anime.season = match season { - "WINTER" => Some(Season::Winter), - "SPRING" => Some(Season::Spring), - "SUMMER" => Some(Season::Summer), - _ => Some(Season::Fall), - }; - } - - if let Some(season_year) = data["seasonYear"].as_i64() { - anime.season_year = Some(season_year); - } - - if let Some(season_int) = data["seasonInt"].as_i64() { - anime.season_int = Some(season_int); - } - - if let Some(episodes) = data["episodes"].as_i64() { - anime.episodes = Some(episodes); - } - - if let Some(duration) = data["duration"].as_i64() { - anime.duration = Some(duration); - } - - if let Some(country_of_origin) = data["countryOfOrigin"].as_str() { - anime.country_of_origin = Some(country_of_origin.to_string()); - } - - if let Some(is_licensed) = data["isLicensed"].as_bool() { - anime.is_licensed = Some(is_licensed); - } - - if let Some(source) = data["source"].as_str() { - anime.source = match source { - "MANGA" => Some(Source::Manga), - "LIGHT_NOVEL" => Some(Source::LightNovel), - "VISUAL_NOVEL" => Some(Source::VisualNovel), - "VIDEO_GAME" => Some(Source::VideoGame), - "OTHER" => Some(Source::Other), - "NOVEL" => Some(Source::Novel), - _ => Some(Source::default()), - }; - } - - if let Some(hashtag) = data["hashtag"].as_str() { - anime.hashtag = Some(hashtag.to_string()); - } - - if let Some(updated_at) = data["updatedAt"].as_i64() { - anime.updated_at = Some(updated_at); - } - - if let Some(cover_image) = data["coverImage"].as_object() { - let mut cover = Cover::default(); - - if let Some(extra_large) = cover_image["extraLarge"].as_str() { - cover.extra_large = Some(extra_large.to_string()); - } - - if let Some(large) = cover_image["large"].as_str() { - cover.large = Some(large.to_string()); - } - - if let Some(medium) = cover_image["medium"].as_str() { - cover.medium = Some(medium.to_string()); - } - - if let Some(color) = cover_image["color"].as_str() { - cover.color = Some(Color::Hex(color.to_string())); - } - - anime.cover = cover; - } - - if let Some(banner) = data["bannerImage"].as_str() { - anime.banner = Some(banner.to_string()); - } - - if let Some(genres_array) = data["genres"].as_array() { - let mut genres = Vec::with_capacity(genres_array.len()); - - for genre in genres_array { - genres.push(genre.as_str().unwrap().to_string()); - } - - anime.genres = Some(genres); - } - - if let Some(synonyms_array) = data["synonyms"].as_array() { - let mut synonyms = Vec::with_capacity(synonyms_array.len()); - - for synonym in synonyms_array { - synonyms.push(synonym.as_str().unwrap().to_string()); - } - - anime.synonyms = Some(synonyms); - } - - let mut score = Score::default(); - if let Some(average) = data["averageScore"].as_i64() { - score.average = average; - } - - if let Some(mean) = data["meanScore"].as_i64() { - score.mean = mean; - } - anime.score = score; - - if let Some(popularity) = data["popularity"].as_i64() { - anime.popularity = Some(popularity); - } - - if let Some(is_locked) = data["isLocked"].as_bool() { - anime.is_locked = Some(is_locked); - } - - if let Some(trending) = data["trendig"].as_i64() { - anime.trending = Some(trending); - } - - if let Some(favourites) = data["favourites"].as_i64() { - anime.favourites = Some(favourites); - } - - if let Some(tags_array) = data["tags"].as_array() { - let mut tags: Vec = Vec::with_capacity(tags_array.len()); - - for tag in tags_array { - tags.push(Tag { - id: tag["id"].as_i64().unwrap(), - name: tag["name"].as_str().unwrap().to_string(), - description: tag["description"].as_str().unwrap().to_string(), - category: tag["category"].as_str().unwrap().to_string(), - rank: tag["rank"].as_i64().unwrap(), - is_general_spoiler: tag["isGeneralSpoiler"].as_bool().unwrap(), - is_media_spoiler: tag["isMediaSpoiler"].as_bool().unwrap(), - is_adult: tag["isAdult"].as_bool().unwrap(), - user_id: tag["userId"].as_i64(), - }); - } - - anime.tags = Some(tags); - } - - if let Some(relations) = data["relations"].as_object() { - if let Some(edges) = relations["edges"].as_array() { - let mut relations: Vec = Vec::with_capacity(edges.len()); - - for edge in edges { - let node = edge.get("node").unwrap(); - let media_type = match node["type"].as_str().unwrap() { - "ANIME" => MediaType::Anime, - "MANGA" => MediaType::Manga, - _ => MediaType::default(), - }; - relations.push(Relation { - media_type, - anime: match media_type { - MediaType::Anime => Some(Anime::parse(node)), - _ => None, - }, - manga: match media_type { - MediaType::Manga => Some(Manga::parse(node)), - _ => None, - }, - id: edge["id"].as_i64().unwrap(), - relation_type: match edge["relationType"].as_str().unwrap() { - "ADAPTATION" => RelationType::Adaptation, - "PREQUEL" => RelationType::Prequel, - "SEQUEL" => RelationType::Sequel, - "PARENT" => RelationType::Parent, - "SIDE_STORY" => RelationType::SideStory, - "CHARACTER" => RelationType::Character, - "SUMMARY" => RelationType::Summary, - "ALTERNATIVE" => RelationType::Alternative, - "SPIN_OFF" => RelationType::SpinOff, - "OTHER" => RelationType::Other, - "COMPILATION" => RelationType::Compilation, - "CONTAINS" => RelationType::Contains, - _ => RelationType::Source, - }, - is_main_studio: edge["isMainStudio"].as_bool().unwrap(), - }); - } - - anime.relations = Some(relations); - } - } - - if let Some(characters) = data["characters"].as_object() { - if let Some(nodes) = characters["nodes"].as_array() { - let mut characters: Vec = Vec::with_capacity(nodes.len()); - - for node in nodes { - characters.push(Character::parse(node)); - } - - anime.characters = Some(characters); - } - } - - if let Some(persons) = data["staff"].as_object() { - if let Some(nodes) = persons["nodes"].as_array() { - let mut staff: Vec = Vec::with_capacity(nodes.len()); - - for node in nodes { - staff.push(Person::parse(node)); - } - - anime.staff = Some(staff); - } - } - - if let Some(studios) = data["studios"].as_object() { - if let Some(nodes) = studios["nodes"].as_array() { - let mut studios: Vec = Vec::with_capacity(nodes.len()); - - for node in nodes { - studios.push(Studio::parse(node, None)); - } - - anime.studios = Some(studios); - } - } - - if let Some(is_favourite) = data["isFavourite"].as_bool() { - anime.is_favourite = Some(is_favourite); - } - - if let Some(is_favourite_blocked) = data["isFavouriteBlocked"].as_bool() { - anime.is_favourite_blocked = Some(is_favourite_blocked); - } - - if let Some(is_adult) = data["isAdult"].as_bool() { - anime.is_adult = Some(is_adult); - } - - if let Some(next_airing_episode) = data["nextAiringEpisode"].as_object() { - anime.next_airing_episode = Some(AiringEpisode { - id: next_airing_episode["id"].as_i64().unwrap(), - at: next_airing_episode["airingAt"].as_i64().unwrap(), - time_until: next_airing_episode["timeUntilAiring"].as_i64().unwrap(), - episode: next_airing_episode["episode"].as_i64().unwrap(), - }); - } - - if let Some(external_links_array) = data["externalLinks"].as_array() { - let mut external_links: Vec = Vec::with_capacity(external_links_array.len()); - - for external_link in external_links_array { - external_links.push(Link { - id: external_link["id"].as_i64(), - url: external_link["url"].as_str().unwrap().to_string(), - site: external_link["site"].as_str().unwrap().to_string(), - site_id: external_link["siteId"].as_i64(), - link_type: match external_link["type"].as_str().unwrap() { - "STREAMING" => Some(LinkType::Streaming), - "SOCIAL" => Some(LinkType::Social), - _ => Some(LinkType::default()), - }, - language: match external_link["language"].as_str() { - Some(language) => match language.to_uppercase().as_str() { - "ENGLISH" => Some(Language::English), - "KOREAN" => Some(Language::Korean), - "ITALIAN" => Some(Language::Italian), - "SPANISH" => Some(Language::Spanish), - "PORTUGUESE" => Some(Language::Portuguese), - "FRENCH" => Some(Language::French), - "GERMAN" => Some(Language::German), - "HEBREW" => Some(Language::Hebrew), - "HUNGARIAN" => Some(Language::Hungarian), - "CHINESE" => Some(Language::Chinese), - "ARABIC" => Some(Language::Arabic), - "FILIPINO" => Some(Language::Filipino), - "CATALAN" => Some(Language::Catalan), - "FINNISH" => Some(Language::Finnish), - "TURKISH" => Some(Language::Turkish), - "DUTCH" => Some(Language::Dutch), - "SWEDISH" => Some(Language::Swedish), - "THAI" => Some(Language::Thai), - "TAGALOG" => Some(Language::Tagalog), - "MALAYSIAN" => Some(Language::Malaysian), - "INDONESIAN" => Some(Language::Indonesian), - "VIETNAMESE" => Some(Language::Vietnamese), - "NEPALI" => Some(Language::Nepali), - "HINDI" => Some(Language::Hindi), - "URDU" => Some(Language::Urdu), - _ => Some(Language::default()), - }, - None => None, - }, - color: external_link["color"] - .as_str() - .map(|hex| Color::Hex(hex.to_string())), - icon: external_link["icon"].as_str().map(|url| url.to_string()), - ..Default::default() - }) - } - - anime.external_links = Some(external_links); - } - - if let Some(streaming_episodes_array) = data["streamingEpisodes"].as_array() { - let mut streaming_episodes: Vec = - Vec::with_capacity(streaming_episodes_array.len()); - - for streaming_episode in streaming_episodes_array { - streaming_episodes.push(Link { - title: Some(streaming_episode["title"].as_str().unwrap().to_string()), - thumbnail: Some(streaming_episode["thumbnail"].as_str().unwrap().to_string()), - url: streaming_episode["url"].as_str().unwrap().to_string(), - site: streaming_episode["site"].as_str().unwrap().to_string(), - ..Default::default() - }) - } - - anime.streaming_episodes = Some(streaming_episodes); - } - - anime.url = data["siteUrl"].as_str().unwrap().to_string(); - - anime - } - - pub async fn load_full(self) -> crate::Result { + pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut anime = crate::Client::default() + let mut anime = Client::default() .get_anime(serde_json::json!({"id": self.id})) .await .unwrap(); @@ -478,10 +89,12 @@ impl Anime { } } -#[derive(Debug, Clone, PartialEq)] -pub struct AiringEpisode { +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct AiringSchedule { id: i64, + #[serde(rename = "airingAt")] at: i64, + #[serde(rename = "timeUntilAiring")] time_until: i64, episode: i64, } diff --git a/src/models/character.rs b/src/models/character.rs index 76de8c0..e375273 100644 --- a/src/models/character.rs +++ b/src/models/character.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::Deserialize; +use serde::Serialize; + use crate::models::Date; use crate::models::Gender; use crate::models::Image; @@ -9,7 +12,8 @@ use crate::models::Person; use crate::{Client, Result}; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Character { pub id: i64, pub name: Name, @@ -22,84 +26,19 @@ pub struct Character { pub blood_type: Option, pub is_favourite: Option, pub is_favourite_blocked: Option, + #[serde(rename = "siteUrl")] pub url: String, pub favourites: Option, pub voice_actors: Option>, pub mod_notes: Option, + #[serde(skip)] pub(crate) is_full_loaded: bool, } impl Character { - pub(crate) fn parse(data: &serde_json::Value) -> Self { - Self { - id: data["id"].as_i64().unwrap(), - name: data["name"] - .as_object() - .map(|object| Name { - first: object["first"].as_str().unwrap().to_string(), - middle: object["middle"].as_str().map(String::from), - last: object["last"].as_str().map(String::from), - full: object["full"].as_str().unwrap().to_string(), - native: object["native"].as_str().map(String::from), - alternative: object["alternative"] - .as_array() - .unwrap() - .into_iter() - .map(|item| item.as_str().unwrap().to_string()) - .collect::>(), - alternative_spoiler: object["alternativeSpoiler"] - .as_array() - .unwrap() - .into_iter() - .map(|item| item.as_str().unwrap().to_string()) - .collect::>(), - user_preferred: object["userPreferred"].as_str().map(String::from), - }) - .unwrap(), - role: data["role"] - .as_str() - .map(|role| match role.to_ascii_lowercase().as_str() { - "main" => Role::Main, - "supporting" => Role::Supporting, - _ => Role::default(), - }), - image: data["image"] - .as_object() - .map(|object| Image { - large: object["large"].as_str().unwrap().to_string(), - medium: object["medium"].as_str().unwrap().to_string(), - }) - .unwrap(), - description: data["description"].as_str().unwrap().to_string(), - gender: data["gender"].as_str().map(|gender| { - match gender.to_ascii_lowercase().as_str() { - "male" => Gender::Male, - "female" => Gender::Female, - "nonbinary" => Gender::NonBinary, - _ => Gender::Other(gender.to_string()), - } - }), - date_of_birth: data["dateOfBirth"].as_object().map(|object| { - Date { - year: object["year"].as_i64(), // TODO: Use u64 - month: object["month"].as_i64(), // Same as above - day: object["day"].as_i64(), // Same as above - } - }), - age: data["age"].as_str().map(String::from), - blood_type: data["bloodType"].as_str().map(String::from), - is_favourite: data["isFavourite"].as_bool(), - is_favourite_blocked: data["isFavouriteBlocked"].as_bool(), - url: data["siteUrl"].as_str().unwrap().to_string(), - favourites: data["favourites"].as_i64(), - mod_notes: data["modNotes"].as_str().map(String::from), - ..Default::default() - } - } - - pub async fn load_full(self, client: &Client) -> Result { + pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut character = client + let mut character = Client::default() .get_character(serde_json::json!({"id": self.id})) .await?; character.is_full_loaded = true; @@ -119,15 +58,10 @@ impl Character { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] pub enum Role { + #[default] Background, Main, Supporting, } - -impl Default for Role { - fn default() -> Self { - Role::Background - } -} diff --git a/src/models/color.rs b/src/models/color.rs index 0db5bcf..40a07ea 100644 --- a/src/models/color.rs +++ b/src/models/color.rs @@ -1,20 +1,19 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "UPPERCASE"))] pub enum Color { Blue, + #[default] Purple, Pink, Orange, Red, Green, Gray, + #[serde(untagged)] Hex(String), } - -impl Default for Color { - fn default() -> Self { - Color::Purple - } -} diff --git a/src/models/cover.rs b/src/models/cover.rs index 2203a52..4b76da0 100644 --- a/src/models/cover.rs +++ b/src/models/cover.rs @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::{Deserialize, Serialize}; + use crate::models::Color; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Cover { pub extra_large: Option, pub large: Option, diff --git a/src/models/date.rs b/src/models/date.rs index 88f3e75..2be169c 100644 --- a/src/models/date.rs +++ b/src/models/date.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Default, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] pub struct Date { pub year: Option, pub month: Option, diff --git a/src/models/format.rs b/src/models/format.rs index d07087a..243b6a8 100644 --- a/src/models/format.rs +++ b/src/models/format.rs @@ -1,8 +1,12 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum Format { + #[default] Tv, TvShort, Movie, @@ -14,9 +18,3 @@ pub enum Format { Novel, OneShot, } - -impl Default for Format { - fn default() -> Self { - Format::Tv - } -} diff --git a/src/models/gender.rs b/src/models/gender.rs index 3cb1cd8..2332252 100644 --- a/src/models/gender.rs +++ b/src/models/gender.rs @@ -1,10 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "PascalCase"))] pub enum Gender { Male, Female, + #[serde(rename = "Non-binary")] NonBinary, Other(String), } diff --git a/src/models/image.rs b/src/models/image.rs index 7ca1e01..75ca41c 100644 --- a/src/models/image.rs +++ b/src/models/image.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Default, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "lowercase"))] pub struct Image { pub large: String, pub medium: String, diff --git a/src/models/language.rs b/src/models/language.rs index e145fd4..9f648cb 100644 --- a/src/models/language.rs +++ b/src/models/language.rs @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT -// // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "PascalCase"))] pub enum Language { + #[default] Japanese, English, Korean, @@ -31,9 +34,3 @@ pub enum Language { Hindi, Urdu, } - -impl Default for Language { - fn default() -> Self { - Language::Japanese - } -} diff --git a/src/models/link.rs b/src/models/link.rs index b6efdda..790d076 100644 --- a/src/models/link.rs +++ b/src/models/link.rs @@ -1,32 +1,34 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::Deserialize; +use serde::Serialize; + use crate::models::Color; use crate::models::Language; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Link { pub id: Option, pub title: Option, pub thumbnail: Option, - pub url: String, - pub site: String, + pub url: Option, + pub site: Option, pub site_id: Option, pub link_type: Option, pub language: Option, pub color: Option, pub icon: Option, + pub notes: Option, + pub is_disabled: Option, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "UPPERCASE"))] pub enum Type { + #[default] Info, Streaming, Social, } - -impl Default for Type { - fn default() -> Self { - Type::Info - } -} diff --git a/src/models/manga.rs b/src/models/manga.rs index 71d960b..ec7ea83 100644 --- a/src/models/manga.rs +++ b/src/models/manga.rs @@ -1,25 +1,26 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -use crate::models::Anime; +use serde::Deserialize; +use serde::Serialize; + use crate::models::Character; -use crate::models::Color; use crate::models::Cover; use crate::models::Date; use crate::models::Format; -use crate::models::Language; -use crate::models::MediaType; +use crate::models::Link; use crate::models::Person; -use crate::models::Score; +use crate::models::Relation; use crate::models::Source; use crate::models::Status; use crate::models::Studio; use crate::models::Tag; use crate::models::Title; -use crate::models::{Link, LinkType}; -use crate::models::{Relation, RelationType}; +use crate::Client; +use crate::Result; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Manga { pub id: i64, pub id_mal: Option, @@ -36,388 +37,41 @@ pub struct Manga { pub source: Option, pub hashtag: Option, pub updated_at: Option, + #[serde(rename = "coverImage")] pub cover: Cover, + #[serde(rename = "bannerImage")] pub banner: Option, pub genres: Option>, pub synonyms: Option>, - pub score: Score, + pub average_score: Option, + pub mean_score: Option, pub popularity: Option, pub is_locked: Option, pub trending: Option, pub favourites: Option, pub tags: Option>, + #[serde(skip)] pub relations: Option>, + #[serde(skip)] pub characters: Option>, + #[serde(skip)] pub staff: Option>, + #[serde(skip)] pub studios: Option>, pub is_favourite: Option, pub is_favourite_blocked: Option, pub is_adult: Option, pub external_links: Option>, + #[serde(rename = "siteUrl")] pub url: String, + #[serde(skip)] pub(crate) is_full_loaded: bool, } impl Manga { - pub(crate) fn parse(data: &serde_json::Value) -> Self { - let mut manga = Manga::default(); - - manga.id = data["id"].as_i64().unwrap(); - - if let Some(id_mal) = data["idMal"].as_i64() { - manga.id_mal = Some(id_mal); - } - - let title = data["title"].as_object().unwrap(); - manga.title = Title { - romaji: Some(title["romaji"].as_str().unwrap().to_string()), - english: title["english"].as_str().map(|title| title.to_string()), - native: title["native"].as_str().unwrap().to_string(), - user_preferred: Some(title["userPreferred"].as_str().unwrap().to_string()), - }; - - let format = data["format"].as_str().unwrap(); - manga.format = match format { - "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(), - }; - - let status = data["status"].as_str().unwrap(); - manga.status = match status { - "FINISHED" => Status::Finished, - "RELEASING" => Status::Releasing, - "CANCELLED" => Status::Cancelled, - "HIATUS" => Status::Hiatus, - _ => Status::default(), - }; - - manga.description = data["description"].as_str().unwrap().to_string(); - - if let Some(start_date) = data["startDate"].as_object() { - let mut date = Date::default(); - - if let Some(year) = start_date["year"].as_i64() { - date.year = Some(year); - } - if let Some(month) = start_date["month"].as_i64() { - date.month = Some(month); - } - if let Some(day) = start_date["day"].as_i64() { - date.day = Some(day); - } - - manga.start_date = Some(date); - } - - if let Some(end_date) = data["endDate"].as_object() { - let mut date = Date::default(); - - if let Some(year) = end_date["year"].as_i64() { - date.year = Some(year); - } - if let Some(month) = end_date["month"].as_i64() { - date.month = Some(month); - } - if let Some(day) = end_date["day"].as_i64() { - date.day = Some(day); - } - - manga.end_date = Some(date); - } - - if let Some(chapters) = data["chapters"].as_i64() { - manga.chapters = Some(chapters); - } - - if let Some(volumes) = data["volumes"].as_i64() { - manga.volumes = Some(volumes); - } - - if let Some(country_of_origin) = data["countryOfOrigin"].as_str() { - manga.country_of_origin = Some(country_of_origin.to_string()); - } - - if let Some(is_licensed) = data["isLicensed"].as_bool() { - manga.is_licensed = Some(is_licensed); - } - - if let Some(source) = data["source"].as_str() { - manga.source = match source { - "MANGA" => Some(Source::Manga), - "LIGHT_NOVEL" => Some(Source::LightNovel), - "VISUAL_NOVEL" => Some(Source::VisualNovel), - "VIDEO_GAME" => Some(Source::VideoGame), - "OTHER" => Some(Source::Other), - "NOVEL" => Some(Source::Novel), - _ => Some(Source::default()), - }; - } - - if let Some(hashtag) = data["hashtag"].as_str() { - manga.hashtag = Some(hashtag.to_string()); - } - - if let Some(updated_at) = data["updatedAt"].as_i64() { - manga.updated_at = Some(updated_at); - } - - if let Some(cover_image) = data["coverImage"].as_object() { - let mut cover = Cover::default(); - - if let Some(extra_large) = cover_image["extraLarge"].as_str() { - cover.extra_large = Some(extra_large.to_string()); - } - - if let Some(large) = cover_image["large"].as_str() { - cover.large = Some(large.to_string()); - } - - if let Some(medium) = cover_image["medium"].as_str() { - cover.medium = Some(medium.to_string()); - } - - if let Some(color) = cover_image["color"].as_str() { - cover.color = Some(Color::Hex(color.to_string())); - } - - manga.cover = cover; - } - - if let Some(banner) = data["bannerImage"].as_str() { - manga.banner = Some(banner.to_string()); - } - - if let Some(genres_array) = data["genres"].as_array() { - let mut genres = Vec::with_capacity(genres_array.len()); - - for genre in genres_array { - genres.push(genre.as_str().unwrap().to_string()); - } - - manga.genres = Some(genres); - } - - if let Some(synonyms_array) = data["synonyms"].as_array() { - let mut synonyms = Vec::with_capacity(synonyms_array.len()); - - for synonym in synonyms_array { - synonyms.push(synonym.as_str().unwrap().to_string()); - } - - manga.synonyms = Some(synonyms); - } - - let mut score = Score::default(); - if let Some(average) = data["averageScore"].as_i64() { - score.average = average; - } - - if let Some(mean) = data["meanScore"].as_i64() { - score.mean = mean; - } - manga.score = score; - - if let Some(popularity) = data["popularity"].as_i64() { - manga.popularity = Some(popularity); - } - - if let Some(is_locked) = data["isLocked"].as_bool() { - manga.is_locked = Some(is_locked); - } - - if let Some(trending) = data["trendig"].as_i64() { - manga.trending = Some(trending); - } - - if let Some(favourites) = data["favourites"].as_i64() { - manga.favourites = Some(favourites); - } - - if let Some(tags_array) = data["tags"].as_array() { - let mut tags: Vec = Vec::with_capacity(tags_array.len()); - - for tag in tags_array { - tags.push(Tag { - id: tag["id"].as_i64().unwrap(), - name: tag["name"].as_str().unwrap().to_string(), - description: tag["description"].as_str().unwrap().to_string(), - category: tag["category"].as_str().unwrap().to_string(), - rank: tag["rank"].as_i64().unwrap(), - is_general_spoiler: tag["isGeneralSpoiler"].as_bool().unwrap(), - is_media_spoiler: tag["isMediaSpoiler"].as_bool().unwrap(), - is_adult: tag["isAdult"].as_bool().unwrap(), - user_id: tag["userId"].as_i64(), - }); - } - - manga.tags = Some(tags); - } - - if let Some(relations) = data["relations"].as_object() { - if let Some(edges) = relations["edges"].as_array() { - let mut relations: Vec = Vec::with_capacity(edges.len()); - - for edge in edges { - let node = edge.get("node").unwrap(); - let media_type = match node["type"].as_str().unwrap() { - "ANIME" => MediaType::Anime, - "MANGA" => MediaType::Manga, - _ => MediaType::default(), - }; - relations.push(Relation { - media_type, - anime: match media_type { - MediaType::Anime => Some(Anime::parse(node)), - _ => None, - }, - manga: match media_type { - MediaType::Manga => Some(Manga::parse(node)), - _ => None, - }, - id: edge["id"].as_i64().unwrap(), - relation_type: match edge["relationType"].as_str().unwrap() { - "ADAPTATION" => RelationType::Adaptation, - "PREQUEL" => RelationType::Prequel, - "SEQUEL" => RelationType::Sequel, - "PARENT" => RelationType::Parent, - "SIDE_STORY" => RelationType::SideStory, - "CHARACTER" => RelationType::Character, - "SUMMARY" => RelationType::Summary, - "ALTERNATIVE" => RelationType::Alternative, - "SPIN_OFF" => RelationType::SpinOff, - "OTHER" => RelationType::Other, - "COMPILATION" => RelationType::Compilation, - "CONTAINS" => RelationType::Contains, - _ => RelationType::Source, - }, - is_main_studio: edge["isMainStudio"].as_bool().unwrap(), - }); - } - - manga.relations = Some(relations); - } - } - - if let Some(characters) = data["characters"].as_object() { - if let Some(nodes) = characters["nodes"].as_array() { - let mut characters: Vec = Vec::with_capacity(nodes.len()); - - for node in nodes { - characters.push(Character::parse(node)); - } - - manga.characters = Some(characters); - } - } - - if let Some(persons) = data["staff"].as_object() { - if let Some(nodes) = persons["nodes"].as_array() { - let mut staff: Vec = Vec::with_capacity(nodes.len()); - - for node in nodes { - staff.push(Person::parse(node)); - } - - manga.staff = Some(staff); - } - } - - if let Some(studios) = data["studios"].as_object() { - if let Some(nodes) = studios["nodes"].as_array() { - let mut studios: Vec = Vec::with_capacity(nodes.len()); - - for node in nodes { - studios.push(Studio::parse(node, None)); - } - - manga.studios = Some(studios); - } - } - - if let Some(is_favourite) = data["isFavourite"].as_bool() { - manga.is_favourite = Some(is_favourite); - } - - if let Some(is_favourite_blocked) = data["isFavouriteBlocked"].as_bool() { - manga.is_favourite_blocked = Some(is_favourite_blocked); - } - - if let Some(is_adult) = data["isAdult"].as_bool() { - manga.is_adult = Some(is_adult); - } - - if let Some(external_links_array) = data["externalLinks"].as_array() { - let mut external_links: Vec = Vec::with_capacity(external_links_array.len()); - - for external_link in external_links_array { - external_links.push(Link { - id: external_link["id"].as_i64(), - url: external_link["url"].as_str().unwrap().to_string(), - site: external_link["site"].as_str().unwrap().to_string(), - site_id: external_link["siteId"].as_i64(), - link_type: match external_link["type"].as_str().unwrap() { - "STREAMING" => Some(LinkType::Streaming), - "SOCIAL" => Some(LinkType::Social), - _ => Some(LinkType::default()), - }, - language: match external_link["language"].as_str() { - Some(language) => match language.to_uppercase().as_str() { - "ENGLISH" => Some(Language::English), - "KOREAN" => Some(Language::Korean), - "ITALIAN" => Some(Language::Italian), - "SPANISH" => Some(Language::Spanish), - "PORTUGUESE" => Some(Language::Portuguese), - "FRENCH" => Some(Language::French), - "GERMAN" => Some(Language::German), - "HEBREW" => Some(Language::Hebrew), - "HUNGARIAN" => Some(Language::Hungarian), - "CHINESE" => Some(Language::Chinese), - "ARABIC" => Some(Language::Arabic), - "FILIPINO" => Some(Language::Filipino), - "CATALAN" => Some(Language::Catalan), - "FINNISH" => Some(Language::Finnish), - "TURKISH" => Some(Language::Turkish), - "DUTCH" => Some(Language::Dutch), - "SWEDISH" => Some(Language::Swedish), - "THAI" => Some(Language::Thai), - "TAGALOG" => Some(Language::Tagalog), - "MALAYSIAN" => Some(Language::Malaysian), - "INDONESIAN" => Some(Language::Indonesian), - "VIETNAMESE" => Some(Language::Vietnamese), - "NEPALI" => Some(Language::Nepali), - "HINDI" => Some(Language::Hindi), - "URDU" => Some(Language::Urdu), - _ => Some(Language::default()), - }, - None => None, - }, - color: external_link["color"] - .as_str() - .map(|hex| Color::Hex(hex.to_string())), - icon: external_link["icon"].as_str().map(|url| url.to_string()), - ..Default::default() - }) - } - - manga.external_links = Some(external_links); - } - - manga.url = data["siteUrl"].as_str().unwrap().to_string(); - - manga - } - - pub async fn load_full(self) -> crate::Result { + pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut manga = crate::Client::default() + let mut manga = Client::default() .get_manga(serde_json::json!({"id": self.id})) .await .unwrap(); diff --git a/src/models/mod.rs b/src/models/mod.rs index 0dca473..69afc24 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -16,7 +16,6 @@ mod name; mod notification; mod person; mod relation; -mod score; mod season; mod source; mod status; @@ -40,8 +39,8 @@ pub use name::Name; pub use notification::{Notification, NotificationOption, Type as NotificationType}; pub use person::Person; pub use relation::{Relation, Type as RelationType}; -pub use score::{Format as ScoreFormat, Score}; pub use season::Season; +use serde::{Deserialize, Serialize}; pub use source::Source; pub use status::Status; pub use studio::Studio; @@ -49,15 +48,10 @@ pub use tag::Tag; pub use title::Title; pub use user::User; -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] pub enum MediaType { Anime, Manga, + #[default] Unknown, } - -impl Default for MediaType { - fn default() -> Self { - MediaType::Unknown - } -} diff --git a/src/models/name.rs b/src/models/name.rs index 8441750..4a24a29 100644 --- a/src/models/name.rs +++ b/src/models/name.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Default, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Name { pub first: String, pub middle: Option, @@ -9,6 +12,6 @@ pub struct Name { pub full: String, pub native: Option, pub alternative: Vec, - pub alternative_spoiler: Vec, + pub alternative_spoiler: Option>, pub user_preferred: Option, } diff --git a/src/models/notification.rs b/src/models/notification.rs index 3cdd601..39c062e 100644 --- a/src/models/notification.rs +++ b/src/models/notification.rs @@ -1,17 +1,22 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Default, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] pub struct Notification {} -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct NotificationOption { notification_type: Type, enabled: bool, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum Type { + #[default] ActivityMessage, ActivityReply, Following, @@ -27,9 +32,3 @@ pub enum Type { MediaMerge, MediaDeletion, } - -impl Default for Type { - fn default() -> Self { - Type::ActivityMessage - } -} diff --git a/src/models/person.rs b/src/models/person.rs index 0df3f26..9ccb7eb 100644 --- a/src/models/person.rs +++ b/src/models/person.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::Deserialize; +use serde::Serialize; + use crate::models::Character; use crate::models::Date; use crate::models::Gender; @@ -8,12 +11,15 @@ use crate::models::Image; use crate::models::Language; use crate::models::Name; +use crate::Client; use crate::Result; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Person { pub id: i64, pub name: Name, + #[serde(rename = "languageV2")] pub language: Language, pub image: Option, pub description: Option, @@ -22,187 +28,25 @@ pub struct Person { pub date_of_birth: Option, pub date_of_death: Option, pub age: Option, - pub years_active: Option<(u64, u64)>, + // pub years_active: Option<(u64, u64)>, pub home_town: Option, pub blood_type: Option, pub is_favourite: Option, pub is_favourite_blocked: Option, + #[serde(rename = "siteUrl")] pub url: String, + #[serde(skip)] pub characters: Option>, pub favourites: i64, pub mod_notes: Option, + #[serde(skip)] pub(crate) is_full_loaded: bool, } impl Person { - pub(crate) fn parse(data: &serde_json::Value) -> Self { - let mut person = Person::default(); - - person.id = data["id"].as_i64().unwrap(); - - if let Some(name_object) = data["name"].as_object() { - let mut name = Name::default(); - - name.first = name_object["first"].as_str().unwrap().to_string(); - - if let Some(middle) = name_object["middle"].as_str() { - name.middle = Some(middle.to_string()); - } - - if let Some(last) = name_object["last"].as_str() { - name.last = Some(last.to_string()); - } - - name.full = name_object["full"].as_str().unwrap().to_string(); - - if let Some(native) = name_object["native"].as_str() { - name.native = Some(native.to_string()); - } - - if let Some(alternative_array) = name_object["alternative"].as_array() { - let mut alternative = Vec::with_capacity(alternative_array.len()); - - for alternative_name in alternative_array { - alternative.push(alternative_name.as_str().unwrap().to_string()); - } - - name.alternative = alternative; - } - - if let Some(user_preferred) = name_object["userPreferred"].as_str() { - name.user_preferred = Some(user_preferred.to_string()); - } - - person.name = name; - } - - if let Some(language) = data["languageV2"].as_str() { - person.language = match language.to_uppercase().as_str() { - "ENGLISH" => Language::English, - "KOREAN" => Language::Korean, - "ITALIAN" => Language::Italian, - "SPANISH" => Language::Spanish, - "PORTUGUESE" => Language::Portuguese, - "FRENCH" => Language::French, - "GERMAN" => Language::German, - "HEBREW" => Language::Hebrew, - "HUNGARIAN" => Language::Hungarian, - "CHINESE" => Language::Chinese, - "ARABIC" => Language::Arabic, - "FILIPINO" => Language::Filipino, - "CATALAN" => Language::Catalan, - "FINNISH" => Language::Finnish, - "TURKISH" => Language::Turkish, - "DUTCH" => Language::Dutch, - "SWEDISH" => Language::Swedish, - "THAI" => Language::Thai, - "TAGALOG" => Language::Tagalog, - "MALAYSIAN" => Language::Malaysian, - "INDONESIAN" => Language::Indonesian, - "VIETNAMESE" => Language::Vietnamese, - "NEPALI" => Language::Nepali, - "HINDI" => Language::Hindi, - "URDU" => Language::Urdu, - _ => Language::default(), - }; - } - - if let Some(image_object) = data["image"].as_object() { - person.image = Some(Image { - large: image_object["large"].as_str().unwrap().to_string(), - medium: image_object["medium"].as_str().unwrap().to_string(), - }); - } - - if let Some(date_of_birth) = data["dateOfBirth"].as_object() { - let mut date = Date::default(); - - if let Some(year) = date_of_birth["year"].as_i64() { - date.year = Some(year); - } - if let Some(month) = date_of_birth["month"].as_i64() { - date.month = Some(month); - } - if let Some(day) = date_of_birth["day"].as_i64() { - date.day = Some(day); - } - - person.date_of_birth = Some(date); - } - - if let Some(date_of_death) = data["dateOfDeath"].as_object() { - let mut date = Date::default(); - - if let Some(year) = date_of_death["year"].as_i64() { - date.year = Some(year); - } - if let Some(month) = date_of_death["month"].as_i64() { - date.month = Some(month); - } - if let Some(day) = date_of_death["day"].as_i64() { - date.day = Some(day); - } - - person.date_of_death = Some(date); - } - - if let Some(age) = data["age"].as_i64() { - person.age = Some(age); - } - - if let Some(years_active) = data["yearsActive"].as_array() { - person.years_active = match years_active.len() { - 2 => Some(( - years_active[0].as_u64().unwrap(), - years_active[1].as_u64().unwrap(), - )), - 1 => Some((years_active[0].as_u64().unwrap(), 0)), - _ => None, - }; - } - - if let Some(home_town) = data["homeTown"].as_str() { - person.home_town = Some(home_town.to_string()); - } - - if let Some(blood_type) = data["bloodType"].as_str() { - person.blood_type = Some(blood_type.to_string()); - } - - if let Some(is_favourite) = data["isFavourite"].as_bool() { - person.is_favourite = Some(is_favourite); - } - - if let Some(is_favourite_blocked) = data["isFavouriteBlocked"].as_bool() { - person.is_favourite_blocked = Some(is_favourite_blocked); - } - - person.url = data["siteUrl"].as_str().unwrap().to_string(); - - if let Some(characters) = data["characters"].as_object() { - if let Some(nodes) = characters["nodes"].as_array() { - let mut characters: Vec = Vec::with_capacity(nodes.len()); - - for node in nodes { - characters.push(Character::parse(node)); - } - - person.characters = Some(characters); - } - } - - person.favourites = data["favourites"].as_i64().unwrap(); - - if let Some(mod_notes) = data["modNotes"].as_str() { - person.mod_notes = Some(mod_notes.to_string()); - } - - person - } - - pub async fn load_full(self) -> crate::Result { + pub async fn load_full(self) -> Result { if !self.is_full_loaded { - let mut person = crate::Client::default().get_person(self.id).await.unwrap(); + let mut person = Client::default().get_person(self.id).await.unwrap(); person.is_full_loaded = true; Ok(person) } else { diff --git a/src/models/relation.rs b/src/models/relation.rs index 2f67c2e..2f646be 100644 --- a/src/models/relation.rs +++ b/src/models/relation.rs @@ -1,12 +1,16 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::Deserialize; +use serde::Serialize; + use crate::models::Anime; use crate::models::Manga; use crate::models::MediaType; // TODO: Use generic type -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Relation { pub media_type: MediaType, pub anime: Option, @@ -16,7 +20,8 @@ pub struct Relation { pub is_main_studio: bool, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "UPPERCASE"))] pub enum Type { Adaptation, Prequel, diff --git a/src/models/score.rs b/src/models/score.rs deleted file mode 100644 index ec46789..0000000 --- a/src/models/score.rs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2022 Andriel Ferreira - -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Score { - pub average: i64, - pub mean: i64, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Format { - Point100, - Point10Decimal, - Point10, - Point5, - Point3, -} - -impl Default for Format { - fn default() -> Self { - Format::Point10Decimal - } -} diff --git a/src/models/season.rs b/src/models/season.rs index c0a120d..36c5ce6 100644 --- a/src/models/season.rs +++ b/src/models/season.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT↴↴ // Copyright (c) 2022 Andriel Ferreira ↴↴ -#[derive(Debug, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "UPPERCASE"))] pub enum Season { Winter, Spring, diff --git a/src/models/source.rs b/src/models/source.rs index fde72d2..46a1484 100644 --- a/src/models/source.rs +++ b/src/models/source.rs @@ -1,8 +1,12 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum Source { + #[default] Original, Manga, LightNovel, @@ -19,9 +23,3 @@ pub enum Source { MultimediaProject, PictureBook, } - -impl Default for Source { - fn default() -> Self { - Source::Original - } -} diff --git a/src/models/status.rs b/src/models/status.rs index dd86f53..36b0e04 100644 --- a/src/models/status.rs +++ b/src/models/status.rs @@ -1,10 +1,14 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum Status { Finished, Releasing, + #[default] NotYetReleased, Cancelled, Hiatus, @@ -15,9 +19,3 @@ pub enum Status { Paused, Repeating, } - -impl Default for Status { - fn default() -> Self { - Status::NotYetReleased - } -} diff --git a/src/models/studio.rs b/src/models/studio.rs index 00cb818..a342cae 100644 --- a/src/models/studio.rs +++ b/src/models/studio.rs @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::{Deserialize, Serialize}; + use crate::Result; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Studio { pub id: i64, pub name: String, @@ -14,27 +17,6 @@ pub struct Studio { } impl Studio { - pub(crate) fn parse(data: &serde_json::Value, studio: Option) -> Self { - let mut studio = match studio { - Some(studio) => studio, - None => Studio::default(), - }; - - studio.id = data["id"].as_i64().unwrap(); - studio.name = data["name"].as_str().unwrap().to_string(); - studio.is_animation_studio = data["isAnimationStudio"].as_bool().unwrap(); - - studio.url = data["siteUrl"].as_str().unwrap().to_string(); - - if let Some(is_favourite) = data["isFavourite"].as_bool() { - studio.is_favourite = Some(is_favourite); - } - - studio.favourites = data["favourites"].as_i64().unwrap(); - - studio - } - pub async fn get_medias() -> Result { todo!() } diff --git a/src/models/tag.rs b/src/models/tag.rs index 2d2af56..61d706d 100644 --- a/src/models/tag.rs +++ b/src/models/tag.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Default, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Tag { pub id: i64, pub name: String, diff --git a/src/models/title.rs b/src/models/title.rs index 9256da5..87a03a7 100644 --- a/src/models/title.rs +++ b/src/models/title.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira -#[derive(Debug, Default, Clone, PartialEq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "lowercase"))] pub struct Title { pub romaji: Option, pub english: Option, diff --git a/src/models/user.rs b/src/models/user.rs index c4bc036..c4b4d85 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2022 Andriel Ferreira +use serde::Deserialize; +use serde::Serialize; + use crate::models::Anime; use crate::models::Character; use crate::models::Color; @@ -11,9 +14,9 @@ use crate::models::NotificationOption; use crate::models::Person; use crate::models::Status; use crate::models::Studio; -use crate::models::{Score, ScoreFormat}; -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct User { id: i32, name: String, @@ -29,18 +32,8 @@ pub struct User { statistics: UserStatisticTypes, } -impl User { - pub(crate) fn parse(data: &serde_json::Value, user: Option) -> Self { - let mut user = match user { - Some(user) => user, - None => User::default(), - }; - - user - } -} - -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] struct Options { title_language: UserTitleLanguage, display_adult_content: bool, @@ -54,8 +47,10 @@ struct Options { disabled_list_activity: Vec, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum UserTitleLanguage { + #[default] Romaji, English, Native, @@ -64,40 +59,32 @@ pub enum UserTitleLanguage { NativeStylised, } -impl Default for UserTitleLanguage { - fn default() -> Self { - UserTitleLanguage::Romaji - } -} - -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))] pub enum UserStaffNameLanguage { RomajiWestern, + #[default] Romaji, Native, } -impl Default for UserStaffNameLanguage { - fn default() -> Self { - UserStaffNameLanguage::Romaji - } -} - -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct ListActivityOption { status: Status, disabled: bool, } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct MediaListOptions { - score_format: ScoreFormat, row_order: String, anime_list: MediaListTypeOptions, manga_list: MediaListTypeOptions, } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct MediaListTypeOptions { section_order: Vec, split_completed_section_by_format: bool, @@ -106,7 +93,8 @@ pub struct MediaListTypeOptions { advanced_scoring_enabled: bool, } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct Favourites { anime: Vec, manga: Vec, @@ -115,16 +103,17 @@ pub struct Favourites { studios: Vec, } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct UserStatisticTypes { anime: UserStatistics, manga: UserStatistics, } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct UserStatistics { count: i32, - score: Score, standard_deviation: f32, minutes_watched: Option, episodes_watched: Option, @@ -134,20 +123,20 @@ pub struct UserStatistics { statuses: Vec, } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct UserFormatStatistic { count: i32, - score: Score, minutes_watched: Option, chapters_read: Option, media_ids: Vec, format: Format, } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all(deserialize = "camelCase"))] pub struct UserStatusStatistic { count: i32, - score: Score, minutes_watched: Option, chapters_read: Option, media_ids: Vec,