diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..557ec9b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,215 @@ +#![allow(unused)] +use serde::Deserialize; +use std::fmt; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ResponseConversionError { + #[error("failed to convert Attribute")] + Attribute(#[from] AttributeConversionError), + #[error("failed to convert Response, got {0}")] + Response(String), + #[error("failed to convert Result, got {0}")] + Result(String), + #[error("failed to convert ContentType, got {0}")] + ContentType(String), +} + +#[derive(Error, Debug)] +pub enum AttributeConversionError { + #[error("failed to convert attribute Language, got {0}")] + Language(String), + #[error("failed to convert attribute Locale, got {0}")] + Locale(String), + #[error("failed to convert attribute LastVolume, got {0}")] + LastVolume(String), + #[error("failed to convert attribute LastChapter, got {0}")] + LastChapter(String), + #[error("failed to convert attribute CreatedAtDateTime, got {0}")] + CreatedAtDateTime(String), + #[error("failed to convert attribute UpdatedAtDateTime, got {0}")] + UpdatedAtDateTime(String), + #[error("failed to convert attribute State, got {0}")] + State(String), + #[error("failed to convert attribute ContentRating, got {0}")] + ContentRating(String), + #[error("failed to convert attribute Status, got {0}")] + Status(String), + #[error("failed to convert attribute PublicationDemographic, got {0}")] + PublicationDemographic(String), + #[error("failed to convert attribute DataType, got {0}")] + DataType(String), +} + +#[derive(Debug, Error)] +pub enum ChapterImageError { + #[error("chapter image result {0}")] + Result(String), +} + +#[derive(Debug, Error)] +pub enum IdQueryResponseError { + #[error("failed to convert id query response Result")] + Result, + #[error("failed to convert id query response Response")] + Response, + #[error("failed to convert id query response Data")] + Data(#[from] ResponseConversionError), +} + +#[derive(Debug, Error)] +pub enum JsonError { + #[error("failed to parse json, msg: {0}")] + Message(String), + #[error("failed to parse json, unexpected end of file")] + Eof, + #[error("failed to parse json, syntax error")] + Syntax, + #[error("failed to parse json, expected boolean")] + ExpectedBoolean, + #[error("failed to parse json, expected integer")] + ExpectedInteger, + #[error("failed to parse json, expected string")] + ExpectedString, + #[error("failed to parse json, expected null")] + ExpectedNull, + #[error("failed to parse json, expected array")] + ExpectedArray, + #[error("failed to parse json, expected array comma")] + ExpectedArrayComma, + #[error("failed to parse json, expected array end")] + ExpectedArrayEnd, + #[error("failed to parse json, expected map")] + ExpectedMap, + #[error("failed to parse json, expected map colon")] + ExpectedMapColon, + #[error("failed to parse json, expected map comma")] + ExpectedMapComma, + #[error("failed to parse json, expected map end")] + ExpectedMapEnd, + #[error("failed to parse json, expected enum")] + ExpectedEnum, + #[error("failed to parse json, expected trailing characters")] + TrailingCharacters, +} + +#[derive(Debug, Error)] +pub enum ChapterFeedError { + #[error("failed to parse json")] + Serde(#[from] JsonError), + #[error("failed to convert chapter feed")] + Conversion(#[from] ChapterFeedConversionError), +} + +#[derive(Debug, Error)] +pub enum ChapterFeedConversionError { + #[error("failed to convert chapter feed result, got {0}")] + Result(String), + #[error("failed to convert chapter feed response, got {0}")] + Response(String), + #[error("failed to convert chapter feed chapter")] + Chapter(#[from] ChapterConversionError), +} + +#[derive(Debug, Error)] +pub enum ChapterConversionError { + #[error("failed to convert chapter DataType, got {0}")] + DataType(String), + #[error("failed to convert chapter Id, got {0}")] + Id(String), + #[error("failed to convert chapter Relationship")] + Relationship(#[from] ChapterRelationshipError), + #[error("failed to convert chapter Attributes")] + Attributes(#[from] ChapterAttributeConversionError), +} + +#[derive(Debug, Error)] +pub enum ChapterRelationshipError { + #[error("failed to convert chapter relationship TypeData, got {0}")] + TypeData(String), + #[error("failed to convert chapter relationship Id, got {0}")] + Id(String), +} + +#[derive(Error, Debug)] +pub enum ChapterAttributeConversionError { + #[error("unable to convert chapter attribute Volume, got {0}")] + Volume(String), + #[error("unable to convert chapter attribute Chapter, got {0}")] + Chapter(String), + #[error("unable to convert chapter attribute CreatedAt, got {0}")] + CreatedAt(String), + #[error("unable to convert chapter attribute UpdatedAt, got {0}")] + UpdatedAt(String), + #[error("unable to convert chapter attribute PublishedAt, got {0}")] + PublishedAt(String), + #[error("unable to convert chapter attribute TranslatedLanguage, got {0}")] + TranslatedLanguage(String), +} + +#[derive(Debug, Error)] +pub enum ChapterImagesError { + #[error("failed to deserialize chapter images")] + Image(#[from] ChapterImageError), + #[error("failed to deserialize chapter images")] + Content(#[from] ChapterImagesContentError), +} + +#[derive(Debug, Error)] +pub enum SearchResultError { + #[error("failed to deserialize json")] + Serde(#[from] JsonError), + #[error("failed to convert response to SearchResult")] + SearchResult(#[from] ResponseConversionError), +} + +#[derive(Debug, Error)] +pub enum IdQueryResultError { + #[error("failed to deserialize json")] + Serde(#[from] JsonError), + #[error("failed to convert to IdQueryResult")] + IdQueryResult(#[from] IdQueryResponseError), +} + +#[derive(Debug, Error, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiError { + pub id: String, + pub status: u32, + pub title: String, + pub detail: String, + pub context: Option, +} + +#[derive(Debug, Error, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ChapterImagesContentError { + pub result: String, + pub errors: Vec, +} + +impl fmt::Display for ApiError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + format!( + "Api Error:\nid: {}\nstatus: {}\ntitle: {}\ndetail: {}\ncontext{:?}", + self.id, self.status, self.title, self.detail, self.context + ) + .fmt(f) + } +} + +impl fmt::Display for ChapterImagesContentError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + format!( + "chapter images content error:\nresult: {}\nerrors: \n{:#?}", + self.result, self.errors + ) + .fmt(f) + } +} + +impl serde::de::Error for JsonError { + fn custom(msg: T) -> Self { + JsonError::Message(msg.to_string()) + } +} diff --git a/src/main.rs b/src/main.rs index d5b969d..bc9c344 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; use response_deserializer::{ChapterImages, SearchResult}; +use error::{ChapterImageError, ChapterImagesError}; use std::fs::File; use std::io::Write; use std::path::Path; @@ -12,6 +13,8 @@ mod response_deserializer; mod select; mod test; mod util; +mod error; +mod client; use response_deserializer::{Chapter, Id}; use select::Entry; @@ -34,7 +37,7 @@ async fn main() { let client = &client; let filters = [ // ("publicationDemographic[]", "seinen"), - //("status[]", "completed"), + // ("status[]", "completed"), // ("contentRating[]", "suggestive"), ]; let limit = config.result_limit; @@ -179,17 +182,25 @@ async fn main() { let result = response_deserializer::deserialize_chapter_images(&json); match result { Ok(v) => break v, - Err(e) => { - if e.result != "error" { - panic!("brotha, api gone wrong (wild)"); - } - for error in e.errors { - if error.status == 429 { - println!("you sent too many requests"); + Err(e) => match e { + ChapterImagesError::Image(i) => match i { + ChapterImageError::Result(s) => { + eprintln!("chapter image error: {s}"); + std::process::exit(1); + } + }, + ChapterImagesError::Content(e) => { + if e.result != "error" { + panic!("brotha, api gone wrong (wild)"); + } + for error in e.errors { + if error.status == 429 { + eprintln!("you sent too many requests"); + } + std::thread::sleep(std::time::Duration::from_millis(20000)); } - std::thread::sleep(std::time::Duration::from_millis(20000)); } - } + }, } }; println!( @@ -350,7 +361,7 @@ async fn get_chapters(client: &Client, id: &Id) -> Result, reqwest_ let params = [("limit", limit.as_str()), ("translatedLanguage[]", "en")]; let url = format!("{BASE}/manga/{id}/feed"); let json = client.get(url).query(¶ms).send().await?.text().await?; - let mut result = response_deserializer::deserialize_chapter_feed(&json); + let mut result = response_deserializer::deserialize_chapter_feed(&json).unwrap(); let mut total_chapters_received = result.limit; while total_chapters_received < result.total { @@ -362,7 +373,7 @@ async fn get_chapters(client: &Client, id: &Id) -> Result, reqwest_ ]; let url = format!("{BASE}/manga/{id}/feed"); let json = client.get(url).query(¶ms).send().await?.text().await?; - let mut new_result = response_deserializer::deserialize_chapter_feed(&json); + let mut new_result = response_deserializer::deserialize_chapter_feed(&json).unwrap(); result.data.append(&mut new_result.data); total_chapters_received += result.limit; } @@ -391,7 +402,7 @@ async fn search( .text() .await .unwrap(); - response_deserializer::deserializer(&json) + response_deserializer::deserializer(&json).unwrap() } async fn select_manga_from_search( @@ -473,5 +484,5 @@ async fn id_query_get_info(client: &Client, id: &Id) -> response_deserializer::I .text() .await .unwrap(); - response_deserializer::deserialize_id_query(&json) + response_deserializer::deserialize_id_query(&json).unwrap() } diff --git a/src/response_deserializer.rs b/src/response_deserializer.rs index eccf81d..b3a08db 100644 --- a/src/response_deserializer.rs +++ b/src/response_deserializer.rs @@ -1,8 +1,9 @@ -// TODO: Remove this #![allow(unused)] +// TODO: Remove this +use crate::error::*; use chrono::{DateTime, FixedOffset}; use serde::Deserialize; -use std::fmt::Display; +use std::fmt; #[derive(Debug, Clone)] pub struct Id(pub String); @@ -94,7 +95,6 @@ pub enum Language { Greenlandic, Guarani, Gujarati, - Gwic, Haitian, Hausa, Hebrew, @@ -168,7 +168,6 @@ pub enum Language { NorwegianNynorsk, Nuosu, Nyanja, - Nynors, Occidental, Occitan, Ojibwa, @@ -210,7 +209,6 @@ pub enum Language { Slovenian, Somali, Sotho, - SouthNdebele, Spanish, Sundanese, Swahili, @@ -322,23 +320,6 @@ struct ChapterImagesContent { chapter: ChapterImageDataContent, } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ApiError { - pub id: String, - pub status: u32, - pub title: String, - pub detail: String, - pub context: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChapterImagesContentError { - pub result: String, - pub errors: Vec, -} - #[derive(Debug, Deserialize)] struct ChapterImageDataContent { hash: String, @@ -612,19 +593,6 @@ pub struct Titles { pub en: String, } -#[derive(Debug)] -enum ResponseConversionError { - AttributeError(AttributeConversionError), - Response(String), - Result(String), - ContentType(String), -} - -#[derive(Debug)] -enum ChapterImageError { - Result(String), -} - impl TryFrom<&str> for State { type Error = (); @@ -635,6 +603,7 @@ impl TryFrom<&str> for State { }) } } + impl TryFrom<&str> for ContentRating { type Error = (); @@ -648,6 +617,7 @@ impl TryFrom<&str> for ContentRating { }) } } + impl TryFrom<&str> for Language { type Error = (); @@ -873,6 +843,7 @@ impl TryFrom<&str> for Language { "zh-ro" => Language::RomanizedChinese, "zh" => Language::SimplifiedChinese, "zh-hk" => Language::TraditionalChinese, + "ko-ro" => Language::RomanizedKorean, "pt" => Language::Portuguese, "es" => Language::CastilianSpanish, "es-la" => Language::LatinAmericanSpanish, @@ -883,6 +854,7 @@ impl TryFrom<&str> for Language { }) } } + impl TryFrom<&str> for PublicationDemographic { type Error = (); @@ -896,6 +868,7 @@ impl TryFrom<&str> for PublicationDemographic { }) } } + impl TryFrom<&str> for DataType { type Error = (); @@ -915,6 +888,7 @@ impl TryFrom<&str> for DataType { }) } } + impl TryFrom<&str> for Status { type Error = (); @@ -928,6 +902,7 @@ impl TryFrom<&str> for Status { }) } } + impl TryFrom<&str> for ResponseResult { type Error = (); @@ -938,6 +913,7 @@ impl TryFrom<&str> for ResponseResult { } } } + impl TryFrom<&str> for Response { type Error = (); @@ -981,28 +957,13 @@ impl TryFrom for SearchResult { } } -#[derive(Debug)] -enum AttributeConversionError { - Language(String), - Locale(String), - LastVolume(String), - LastChapter(String), - CreatedAtDateTime(String), - UpdatedAtDateTime(String), - State(String), - ContentRating(String), - Status(String), - PublicationDemographic(String), - DataType(String), -} - -impl Display for Id { +impl fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } -impl Display for Status { +impl fmt::Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Ongoing => "ongoing".fmt(f), @@ -1013,7 +974,7 @@ impl Display for Status { } } -impl Display for ContentRating { +impl fmt::Display for ContentRating { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Safe => "safe".fmt(f), @@ -1167,79 +1128,55 @@ impl TryFrom for MangaAttributes { } } -pub fn deserialize_id_query(json: &str) -> IdQueryResult { - let id_query_response: IdQueryResponse = match serde_json::from_str(json) { - Ok(v) => v, - Err(e) => { - eprintln!("ERROR: {e:#?}"); - std::fs::write("out.json", json).unwrap(); - std::process::exit(1); - } - }; - id_query_response.try_into().unwrap() +pub fn deserialize_id_query(json: &str) -> Result { + let id_query_response: IdQueryResponse = serde_json::from_str(json) + .map_err(|e| IdQueryResultError::Serde(JsonError::Message(e.to_string())))?; + id_query_response + .try_into() + .map_err(IdQueryResultError::IdQueryResult) } impl TryFrom for IdQueryResult { - type Error = AttributeConversionError; + type Error = IdQueryResponseError; fn try_from(response: IdQueryResponse) -> Result { Ok(IdQueryResult { - result: response.result.as_str().try_into().unwrap(), - response: response.response.as_str().try_into().unwrap(), - data: response.data.try_into().unwrap(), + result: response + .result + .as_str() + .try_into() + .map_err(|_| IdQueryResponseError::Result)?, + response: response + .response + .as_str() + .try_into() + .map_err(|_| IdQueryResponseError::Response)?, + data: response + .data + .try_into() + .map_err(IdQueryResponseError::Data)?, }) } } -pub fn deserialize_chapter_feed(json: &str) -> ChapterFeed { +pub fn deserialize_chapter_feed(json: &str) -> Result { let chapter_feed_response: ChapterFeedResponse = match serde_json::from_str(json) { Ok(v) => v, Err(e) => { - eprintln!("ERROR: {e:#?}"); - std::fs::write("chapter_feed.json", json).unwrap(); - std::process::exit(1); + // TODO: Actually do error handling here + return Err(ChapterFeedError::Serde(JsonError::Message(e.to_string()))); } }; - chapter_feed_response.try_into().unwrap() + chapter_feed_response + .try_into() + .map_err(|e| ChapterFeedError::Conversion(e)) } -pub fn deserializer(json: &str) -> SearchResult { - let search_response: SearchResponse = match serde_json::from_str(json) { - Ok(v) => v, - Err(e) => { - eprintln!("ERROR: {e:#?}"); - std::fs::write("search_result.json", json).unwrap(); - std::process::exit(1); - } - }; - let search_result = search_response.try_into(); - match search_result { - Ok(v) => v, - Err(e) => { - eprintln!("ERROR: Failed to convert search response: {e:#?}"); - std::process::exit(1); - } - } -} - -#[derive(Debug)] -enum ChapterFeedConversionError { - Result(String), - Response(String), - Chapter(ChapterConversionError), -} - -#[derive(Debug)] -enum ChapterConversionError { - DataType(String), - Id(String), - Relationship(ChapterRelationshipError), - Attributes(ChapterAttributeConversionError), -} - -#[derive(Debug)] -enum ChapterRelationshipError { - TypeData(String), - Id(String), +pub fn deserializer(json: &str) -> Result { + let search_response: SearchResponse = serde_json::from_str(json) + .map_err(|e| SearchResultError::Serde(JsonError::Message(e.to_string())))?; + search_response + .try_into() + .map_err(SearchResultError::SearchResult) } impl TryFrom for ChapterFeed { @@ -1308,16 +1245,6 @@ impl TryFrom for ChapterFeed { } } -#[derive(Debug)] -enum ChapterAttributeConversionError { - Volume(String), - Chapter(String), - CreatedAt(String), - UpdatedAt(String), - PublishedAt(String), - TranslatedLanguage(String), -} - impl TryFrom for ChapterAttributes { type Error = ChapterAttributeConversionError; fn try_from(attributes: ChapterAttributesContent) -> Result { @@ -1373,21 +1300,21 @@ impl TryFrom for ChapterImages { } } -pub fn deserialize_chapter_images(json: &str) -> Result { +pub fn deserialize_chapter_images(json: &str) -> Result { let chapter_images: ChapterImagesContent = match serde_json::from_str(json) { Ok(v) => v, Err(e) => { match serde_json::from_str::(json) { - Ok(v) => return Err(v), - Err(e) => { + Ok(v) => return Err(ChapterImagesError::Content(v)), + Err(f) => { // If you can't parse the error then there is no point in continuing. - eprintln!("ERROR: {e:#?}"); + eprintln!("ERROR: {e:#?}, with {f:#?}"); std::process::exit(1); } } } }; - Ok(chapter_images.try_into().unwrap()) + chapter_images.try_into().map_err(ChapterImagesError::Image) } impl TryFrom for Manga { @@ -1396,14 +1323,12 @@ impl TryFrom for Manga { Ok(Manga { id: Id(m.id), data_type: (m.type_name.as_str()).try_into().map_err(|_| { - ResponseConversionError::AttributeError(AttributeConversionError::DataType( - m.type_name, - )) + ResponseConversionError::Attribute(AttributeConversionError::DataType(m.type_name)) })?, attributes: m .attributes .try_into() - .map_err(ResponseConversionError::AttributeError)?, + .map_err(ResponseConversionError::Attribute)?, relationships: { let mut relationships = Vec::with_capacity(m.relationships.len()); for m in m.relationships { @@ -1458,7 +1383,7 @@ impl TryFrom for Manga { }) } })() - .map_err(ResponseConversionError::AttributeError)?, + .map_err(ResponseConversionError::Attribute)?, ); } relationships