// TODO: Remove this #![allow(unused)] use chrono::{DateTime, FixedOffset}; use serde::Deserialize; use std::fmt::Display; #[derive(Debug, Clone)] pub struct Id(pub String); #[derive(Debug)] pub enum ResponseResult { Ok, } // https://api.mangadex.org/docs/3-enumerations/ // https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes // https://www.loc.gov/standards/iso639-2/php/English_list.php // This is a fucking mess, I hate this. I could have just used a library, but I would have to adapt // it to the special cases which mangadex require. // The two-letter codes are not unique, and the code esentially just choose the first one in the // list. Why have identifiers if they are not unique? Who knows what they were smoking when making // this. Also Why is there Bouth SouthNdebele and Ndebele, South? Who knows? Why is there Bokmål // and Norwegian Bokmål, many questions to ask, but few answers to get. Best part of this is that // the updated ISO pdf is behind a paywall. https://www.iso.org/standard/74575.html // If the code works 98% of the time and is 95% correct I guess that is "good enough". #[derive(Debug)] pub enum Language { Abkhazian, Afar, Afrikaans, Akan, Albanian, Amharic, Arabic, Aragonese, Armenian, Assamese, Avaric, Avestan, Aymara, Azerbaijani, Bambara, Bashkir, Basque, Belarusian, Bengali, Bislama, Bosnian, BrazilianPortugese, Breton, Bulgarian, Burmese, Castilian, CastilianSpanish, Catalan, Central, Chamorro, Chechen, Chewa, Chichewa, SimplifiedChinese, TraditionalChinese, Chuang, ChurchSlavic, ChurchSlavonic, Chuvash, Cornish, Corsican, Cree, Croatian, Czech, Danish, Dhivehi, Divehi, Dutch, Dzongkha, English, Esperanto, Estonian, Ewe, Faroese, Fijian, Finnish, Flemish, French, Fulah, Gaelic, Galician, Ganda, Georgian, German, Gikuyu, Greek, Greenlandic, Guarani, Gujarati, Gwic, Haitian, Hausa, Hebrew, Herero, Hindi, Hiri, Hungarian, Icelandic, Ido, Igbo, Indonesian, Interlingua, Interlingue, Inuktitut, Inupiaq, Irish, Italian, Japanese, Javanese, Kalaallisut, Kannada, Kanuri, Kashmiri, Kazakh, Kikuyu, Kinyarwanda, Kirghiz, Komi, Kongo, Korean, Kuanyama, Kurdish, Kwanyama, Kyrgyz, Lao, Latin, LatinAmericanSpanish, Latvian, Letzeburgesch, Limburgan, Limburger, Limburgish, Lingala, Lithuanian, LubaKatanga, Luxembourgish, Macedonian, Malagasy, Malay, Malayalam, Maldivian, Maltese, Manx, Maori, Marathi, Marshallese, MiMoldavian, Moldovan, Mongolian, NNauru, Navaho, Navajo, NdebeleNorth, NdebeleSouth, Ndonga, Nepali, North, Northern, Norwegian, NorwegianBokmål, NorwegianNynorsk, Nuosu, Nyanja, Nynors, Occidental, Occitan, Ojibwa, OldBulgarian, OldChurchSlavonic, OldSlavonic, Oriya, Oromo, Ossetian, Ossetic, Pali, Panjabi, Pashto, Persian, Polish, Portuguese, ProvençPunjabi, Pushto, Quechua, Romanian, RomanizedJapanese, RomanizedKorean, RomanizedChinese, Romansh, Rundi, Russian, Samoan, Sango, Sanskrit, Sardinian, Scottish, Serbian, Shona, Sichuan, Sindhi, Sinhala, Sinhalese, Slovak, Slovenian, Somali, Sotho, SouthNdebele, Spanish, Sundanese, Swahili, Swati, Swedish, Tagalog, Tahitian, Tajik, Tamil, Tatar, Telugu, Thai, Tibetan, Tigrinya, Tonga, Tsonga, Tswana, Turkish, Turkmen, Twi, Uighur, Ukrainian, Urdu, Uyghur, Uzbek, Valencian, Venda, Vietnamese, Volapük, Walloon, Welsh, Western, Wolof, Xhosa, Yiddish, Yoruba, Zhuang, Zulu, } #[derive(Debug)] pub enum Status { Completed, Ongoing, Hiatus, Cancelled, } #[derive(Debug)] pub enum ContentRating { Safe, Suggestive, Erotica, Pornographic, } #[derive(Debug)] pub enum PublicationDemographic { Shounen, Shoujo, Seinen, Josei, } #[derive(Debug)] pub enum State { Published, } #[derive(Debug)] pub enum Response { Collection, Entity, } #[derive(Debug)] pub enum DataType { Manga, Chapter, CoverArt, Author, Artist, ScanlationGroup, Tag, User, CustomList, Creator, } #[derive(Debug)] pub struct SearchResult { pub result: ResponseResult, pub response: Response, pub data: Vec, } #[derive(Debug)] pub struct IdQueryResult { pub result: ResponseResult, pub response: Response, pub data: Manga, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct ChapterImagesContent { result: String, base_url: String, 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, data: Vec, } pub struct ChapterImageData { pub hash: String, pub data: Vec, } pub struct ChapterImages { pub result: ResponseResult, pub base_url: String, pub chapter: ChapterImageData, } pub struct ChapterFeed { pub result: ResponseResult, pub response: Response, pub data: Vec, pub limit: u32, pub offset: u32, pub total: u32, } #[derive(Deserialize, Debug)] struct ChapterFeedResponse { result: String, response: String, data: Vec, limit: u32, offset: u32, total: u32, } #[derive(Debug)] pub struct MangaAttributes { pub title: Titles, pub alt_titles: Vec, pub description: Description, pub is_locked: bool, pub links: Option, pub original_language: Language, pub last_volume: Option, pub last_chapter: Option, pub publication_demographic: Option, pub status: Status, pub year: Option, pub content_rating: ContentRating, pub tags: Vec, pub state: State, pub chapter_numbers_reset_on_new_volume: bool, pub created_at: DateTime, pub updated_at: DateTime, pub version: u32, pub available_translated_languages: Vec>, pub latest_uploaded_chapter: Option, } #[derive(Deserialize, Debug)] struct ChapterContent { id: String, #[serde(rename = "type")] type_name: String, attributes: ChapterAttributesContent, relationships: Vec, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct ChapterAttributesContent { volume: Option, chapter: Option, title: String, translated_language: String, external_url: Option, publish_at: String, readable_at: String, created_at: String, updated_at: String, pages: u32, version: u32, } pub struct Chapter { pub id: Id, pub data_type: DataType, pub attributes: ChapterAttributes, pub relationships: Vec, } #[derive(Debug)] pub struct ChapterAttributes { pub volume: Option, pub chapter: Option, pub title: String, pub translated_language: Language, pub external_url: Option, pub published_at: DateTime, pub created_at: DateTime, pub updated_at: DateTime, pub pages: u32, pub version: u32, } #[derive(Debug)] pub struct Manga { pub id: Id, pub data_type: DataType, pub attributes: MangaAttributes, pub relationships: Vec, } #[derive(Deserialize, Debug)] struct SearchResponse { result: String, response: String, data: Vec, limit: u32, offset: u32, total: u32, } #[derive(Deserialize, Debug)] struct IdQueryResponse { result: String, response: String, data: ContentData, } #[derive(Deserialize, Debug)] struct ContentData { id: String, #[serde(rename = "type")] type_name: String, attributes: ContentAttributes, relationships: Vec, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct ContentAttributes { title: Titles, alt_titles: Vec, description: Description, is_locked: bool, links: Option, original_language: String, last_volume: Option, last_chapter: Option, publication_demographic: Option, status: String, year: Option, content_rating: String, tags: Vec, state: String, chapter_numbers_reset_on_new_volume: bool, created_at: String, updated_at: String, version: u32, available_translated_languages: Vec>, latest_uploaded_chapter: Option, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct ContentCoverAttributes { description: String, volume: Option, file_name: String, locale: String, created_at: String, updated_at: String, version: u32, } #[derive(Debug)] pub struct CoverAttributes { pub description: String, pub volume: Option, pub file_name: Id, pub locale: Language, pub created_at: DateTime, pub updated_at: DateTime, pub version: u32, } #[derive(Deserialize, Debug)] pub struct ContentTag { id: String, #[serde(rename = "type")] type_name: String, attributes: TagAttributes, relationships: Vec, } #[derive(Debug)] pub struct Tag { pub id: Id, pub data_type: DataType, pub attributes: TagAttributes, pub relationships: Vec, } #[derive(Deserialize, Debug)] struct ContentRelationship { id: String, #[serde(rename = "type")] type_name: String, related: Option, attributes: Option, } #[derive(Debug)] pub struct ChapterRelationship { id: Id, data_type: DataType, } #[derive(Debug)] pub struct Relationship { pub id: Id, pub data_type: DataType, pub related: Option, pub attributes: Option, } #[derive(Deserialize, Debug)] pub struct TagAttributes { pub name: TagName, pub description: Description, pub group: String, pub version: u32, } #[derive(Deserialize, Debug)] pub struct TagName { pub en: String, } #[derive(Deserialize, Debug, Default)] pub struct Links { al: Option, ap: Option, bw: Option, kt: Option, mu: Option, amz: Option, cdj: Option, ebj: Option, mal: Option, engtl: Option, } #[derive(Deserialize, Debug)] pub struct Description { en: Option, ru: Option, } #[derive(Deserialize, Debug)] pub struct AltTitles { en: Option, ja: Option, ru: Option, } #[derive(Deserialize, Debug)] 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 = (); fn try_from(s: &str) -> Result { Ok(match s { "published" => State::Published, _ => return Err(()), }) } } impl TryFrom<&str> for ContentRating { type Error = (); fn try_from(s: &str) -> Result { Ok(match s { "safe" => ContentRating::Safe, "suggestive" => ContentRating::Suggestive, "erotica" => ContentRating::Erotica, "pornographic" => ContentRating::Pornographic, _ => return Err(()), }) } } impl TryFrom<&str> for Language { type Error = (); fn try_from(s: &str) -> Result { Ok(match s { "ab" => Language::Abkhazian, "aa" => Language::Afar, "af" => Language::Afrikaans, "ak" => Language::Akan, "sq" => Language::Albanian, "am" => Language::Amharic, "ar" => Language::Arabic, "an" => Language::Aragonese, "hy" => Language::Armenian, "as" => Language::Assamese, "av" => Language::Avaric, "ae" => Language::Avestan, "ay" => Language::Aymara, "az" => Language::Azerbaijani, "bm" => Language::Bambara, "ba" => Language::Bashkir, "eu" => Language::Basque, "be" => Language::Belarusian, "bn" => Language::Bengali, "bi" => Language::Bislama, "nb" => Language::NorwegianBokmål, "bs" => Language::Bosnian, "br" => Language::Breton, "bg" => Language::Bulgarian, "my" => Language::Burmese, "es" => Language::Castilian, "ca" => Language::Catalan, "km" => Language::Central, "ch" => Language::Chamorro, "ce" => Language::Chechen, "ny" => Language::Chewa, "ny" => Language::Chichewa, "zh" => Language::SimplifiedChinese, "za" => Language::Chuang, "cu" => Language::ChurchSlavic, "cu" => Language::ChurchSlavonic, "cv" => Language::Chuvash, "kw" => Language::Cornish, "co" => Language::Corsican, "cr" => Language::Cree, "hr" => Language::Croatian, "cs" => Language::Czech, "da" => Language::Danish, "dv" => Language::Dhivehi, "dv" => Language::Divehi, "nl" => Language::Dutch, "dz" => Language::Dzongkha, "en" => Language::English, "eo" => Language::Esperanto, "et" => Language::Estonian, "ee" => Language::Ewe, "fo" => Language::Faroese, "fj" => Language::Fijian, "fi" => Language::Finnish, "nl" => Language::Flemish, "fr" => Language::French, "ff" => Language::Fulah, "gd" => Language::Gaelic, "gl" => Language::Galician, "lg" => Language::Ganda, "ka" => Language::Georgian, "de" => Language::German, "ki" => Language::Gikuyu, "el" => Language::Greek, "kl" => Language::Greenlandic, "gn" => Language::Guarani, "gu" => Language::Gujarati, "ht" => Language::Haitian, "ha" => Language::Hausa, "he" => Language::Hebrew, "hz" => Language::Herero, "hi" => Language::Hindi, "ho" => Language::Hiri, "hu" => Language::Hungarian, "is" => Language::Icelandic, "io" => Language::Ido, "ig" => Language::Igbo, "id" => Language::Indonesian, "ia" => Language::Interlingua, "ie" => Language::Interlingue, "iu" => Language::Inuktitut, "ik" => Language::Inupiaq, "ga" => Language::Irish, "it" => Language::Italian, "ja" => Language::Japanese, "jv" => Language::Javanese, "kl" => Language::Kalaallisut, "kn" => Language::Kannada, "kr" => Language::Kanuri, "ks" => Language::Kashmiri, "kk" => Language::Kazakh, "ki" => Language::Kikuyu, "rw" => Language::Kinyarwanda, "ky" => Language::Kirghiz, "kv" => Language::Komi, "kg" => Language::Kongo, "ko" => Language::Korean, "kj" => Language::Kuanyama, "ku" => Language::Kurdish, "kj" => Language::Kwanyama, "ky" => Language::Kyrgyz, "lo" => Language::Lao, "la" => Language::Latin, "lv" => Language::Latvian, "lb" => Language::Letzeburgesch, "li" => Language::Limburgan, "li" => Language::Limburger, "li" => Language::Limburgish, "ln" => Language::Lingala, "lt" => Language::Lithuanian, "lu" => Language::LubaKatanga, "lb" => Language::Luxembourgish, "mk" => Language::Macedonian, "mg" => Language::Malagasy, "ms" => Language::Malay, "ml" => Language::Malayalam, "dv" => Language::Maldivian, "mt" => Language::Maltese, "gv" => Language::Manx, "mi" => Language::Maori, "mr" => Language::Marathi, "mh" => Language::Marshallese, "ro" => Language::MiMoldavian, "ro" => Language::Moldovan, "mn" => Language::Mongolian, "na" => Language::NNauru, "nv" => Language::Navaho, "nv" => Language::Navajo, "nd" => Language::NdebeleNorth, "nr" => Language::NdebeleSouth, "ng" => Language::Ndonga, "ne" => Language::Nepali, "nd" => Language::North, "se" => Language::Northern, "no" => Language::Norwegian, "nb" => Language::NorwegianBokmål, "nn" => Language::NorwegianNynorsk, "ii" => Language::Nuosu, "ny" => Language::Nyanja, "nn" => Language::NorwegianNynorsk, "ie" => Language::Occidental, "oc" => Language::Occitan, "oj" => Language::Ojibwa, "cu" => Language::OldBulgarian, "cu" => Language::OldChurchSlavonic, "cu" => Language::OldSlavonic, "or" => Language::Oriya, "om" => Language::Oromo, "os" => Language::Ossetian, "os" => Language::Ossetic, "pi" => Language::Pali, "pa" => Language::Panjabi, "ps" => Language::Pashto, "fa" => Language::Persian, "pl" => Language::Polish, "pt" => Language::Portuguese, "pa" => Language::ProvençPunjabi, "ps" => Language::Pushto, "qu" => Language::Quechua, "ro" => Language::Romanian, "rm" => Language::Romansh, "rn" => Language::Rundi, "ru" => Language::Russian, "sm" => Language::Samoan, "sg" => Language::Sango, "sa" => Language::Sanskrit, "sc" => Language::Sardinian, "gd" => Language::Scottish, "sr" => Language::Serbian, "sn" => Language::Shona, "ii" => Language::Sichuan, "sd" => Language::Sindhi, "si" => Language::Sinhala, "si" => Language::Sinhalese, "sk" => Language::Slovak, "sl" => Language::Slovenian, "so" => Language::Somali, "st" => Language::Sotho, "es" => Language::Spanish, "su" => Language::Sundanese, "sw" => Language::Swahili, "ss" => Language::Swati, "sv" => Language::Swedish, "tl" => Language::Tagalog, "ty" => Language::Tahitian, "tg" => Language::Tajik, "ta" => Language::Tamil, "tt" => Language::Tatar, "te" => Language::Telugu, "th" => Language::Thai, "bo" => Language::Tibetan, "ti" => Language::Tigrinya, "to" => Language::Tonga, "ts" => Language::Tsonga, "tn" => Language::Tswana, "tr" => Language::Turkish, "tk" => Language::Turkmen, "tw" => Language::Twi, "ug" => Language::Uighur, "uk" => Language::Ukrainian, "ur" => Language::Urdu, "ug" => Language::Uyghur, "uz" => Language::Uzbek, "ca" => Language::Valencian, "ve" => Language::Venda, "vi" => Language::Vietnamese, "vo" => Language::Volapük, "wa" => Language::Walloon, "cy" => Language::Welsh, "fy" => Language::Western, "wo" => Language::Wolof, "xh" => Language::Xhosa, "yi" => Language::Yiddish, "yo" => Language::Yoruba, "za" => Language::Zhuang, "zu" => Language::Zulu, "zh-ro" => Language::RomanizedChinese, "zh" => Language::SimplifiedChinese, "zh-hk" => Language::TraditionalChinese, "pt" => Language::Portuguese, "es" => Language::CastilianSpanish, "es-la" => Language::LatinAmericanSpanish, "ja-ro" => Language::RomanizedJapanese, "pt-br" => Language::BrazilianPortugese, _ => return Err(()), }) } } impl TryFrom<&str> for PublicationDemographic { type Error = (); fn try_from(s: &str) -> Result { Ok(match s { "shounen" => PublicationDemographic::Shounen, "josei" => PublicationDemographic::Josei, "shoujo" => PublicationDemographic::Shoujo, "seinen" => PublicationDemographic::Seinen, _ => return Err(()), }) } } impl TryFrom<&str> for DataType { type Error = (); fn try_from(s: &str) -> Result { Ok(match s { "manga" => DataType::Manga, "chapter" => DataType::Chapter, "cover_art" => DataType::CoverArt, "author" => DataType::Author, "artist" => DataType::Artist, "scanlation_group" => DataType::ScanlationGroup, "tag" => DataType::Tag, "user" => DataType::User, "custom_list" => DataType::CustomList, "creator" => DataType::Creator, _ => return Err(()), }) } } impl TryFrom<&str> for Status { type Error = (); fn try_from(s: &str) -> Result { Ok(match s { "ongoing" => Status::Ongoing, "completed" => Status::Completed, "hiatus" => Status::Hiatus, "cancelled" => Status::Cancelled, _ => return Err(()), }) } } impl TryFrom<&str> for ResponseResult { type Error = (); fn try_from(s: &str) -> Result { match s { "ok" => Ok(ResponseResult::Ok), _ => Err(()), } } } impl TryFrom<&str> for Response { type Error = (); fn try_from(s: &str) -> Result { match s { "collection" => Ok(Response::Collection), "entity" => Ok(Response::Entity), _ => Err(()), } } } impl TryFrom for SearchResult { type Error = ResponseConversionError; fn try_from(search_response: SearchResponse) -> Result { let response = (search_response.response.as_str()) .try_into() .map_err(|_| ResponseConversionError::Result(search_response.response))?; let result: ResponseResult = (search_response.result.as_str()) .try_into() .map_err(|_| ResponseConversionError::Result(search_response.result))?; let mut data: Result, ResponseConversionError> = Ok(Vec::with_capacity(search_response.data.len())); for m in search_response.data { if let Ok(ref mut d) = data { match m.try_into() { Ok(v) => d.push(v), Err(e) => { data = Err(e); break; } } } } Ok(SearchResult { response, result, data: data?, }) } } #[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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Ongoing => "ongoing".fmt(f), Self::Completed => "completed".fmt(f), Self::Cancelled => "cancelled".fmt(f), Self::Hiatus => "hiatus".fmt(f), } } } impl Display for ContentRating { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Safe => "safe".fmt(f), Self::Suggestive => "suggestive".fmt(f), Self::Erotica => "erotica".fmt(f), Self::Pornographic => "pornographic".fmt(f), } } } impl TryFrom for MangaAttributes { type Error = AttributeConversionError; fn try_from(attributes: ContentAttributes) -> Result { Ok(MangaAttributes { title: attributes.title, alt_titles: attributes.alt_titles, description: attributes.description, is_locked: attributes.is_locked, links: attributes.links, original_language: (attributes.original_language.as_str()) .try_into() .map_err(|_| AttributeConversionError::Language(attributes.original_language))?, last_volume: match attributes.last_volume { Some(s) => match s.parse() { Ok(v) => Some(v), Err(_) => { if s.is_empty() { None } else { return Err(AttributeConversionError::LastVolume(s)); } } }, None => None, }, last_chapter: match attributes.last_chapter { Some(n) => match n.parse() { Ok(v) => Some(v), Err(_) => { if n.is_empty() { None } else { return Err(AttributeConversionError::LastVolume(n)); } } }, None => None, }, publication_demographic: match attributes.publication_demographic { Some(s) => Some( (s.as_str()) .try_into() .map_err(|_| AttributeConversionError::PublicationDemographic(s))?, ), None => None, }, status: (attributes.status.as_str()) .try_into() .map_err(|_| AttributeConversionError::Status(attributes.status))?, year: attributes.year, content_rating: attributes .content_rating .as_str() .try_into() .map_err(|_| AttributeConversionError::ContentRating(attributes.content_rating))?, tags: { let mut tags = Vec::with_capacity(attributes.tags.len()); for m in attributes.tags { tags.push(({ || { Ok::(Tag { data_type: (m.type_name.as_str()) .try_into() .map_err(|_| AttributeConversionError::DataType(m.type_name))?, id: Id(m.id), relationships: { let mut relationships = Vec::with_capacity(m.relationships.len()); for m in m.relationships { relationships.push(({ || { Ok::( Relationship { id: Id(m.id), data_type: (m.type_name.as_str()) .try_into() .map_err(|_| { AttributeConversionError::DataType( m.type_name, ) })?, // TODO: Do this attributes: None, related: m.related, }, ) } })( )?); } relationships }, attributes: TagAttributes { name: m.attributes.name, group: m.attributes.group, version: m.attributes.version, description: Description { en: m.attributes.description.en, ru: m.attributes.description.ru, }, }, }) } })()?); } tags }, state: (attributes.state.as_str()) .try_into() .map_err(|_| AttributeConversionError::State(attributes.state))?, chapter_numbers_reset_on_new_volume: attributes.chapter_numbers_reset_on_new_volume, created_at: DateTime::parse_from_rfc3339(&attributes.created_at).map_err(|_| { AttributeConversionError::CreatedAtDateTime(attributes.created_at.clone()) })?, updated_at: DateTime::parse_from_rfc3339(&attributes.updated_at) .map_err(|_| AttributeConversionError::UpdatedAtDateTime(attributes.created_at))?, version: attributes.version, available_translated_languages: { let mut av = Vec::with_capacity(attributes.available_translated_languages.len()); for m in attributes.available_translated_languages { av.push(({ || { Ok::, AttributeConversionError>(match m { Some(s) => Some( (s.as_str()) .try_into() .map_err(|_| AttributeConversionError::Language(s))?, ), None => None, }) } })()?); } av }, latest_uploaded_chapter: attributes .latest_uploaded_chapter .as_ref() .map(|m| Id(m.clone())), }) } } 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() } impl TryFrom for IdQueryResult { type Error = AttributeConversionError; 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(), }) } } pub fn deserialize_chapter_feed(json: &str) -> ChapterFeed { 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); } }; chapter_feed_response.try_into().unwrap() } 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), } impl TryFrom for ChapterFeed { type Error = ChapterFeedConversionError; fn try_from(feed: ChapterFeedResponse) -> Result { // Now this is a bit of an abomination. It uses closures such that you can use the ? syntax // sugar to return an error. Also uses for loops instead of iterators since they do not take // ownership. I think I should have just kept the iterators. let mut data: Vec = Vec::with_capacity(feed.data.len()); for m in feed.data { let chapter: Chapter = ({ || { Ok::(Chapter { data_type: (m.type_name.as_str()) .try_into() .map_err(|_| ChapterConversionError::DataType(m.type_name))?, id: Id(m.id), attributes: m .attributes .try_into() .map_err(ChapterConversionError::Attributes)?, relationships: { let mut relationships = Vec::with_capacity(m.relationships.len()); for r in m.relationships { relationships.push( ({ || { Ok({ ChapterRelationship { data_type: (r.type_name.as_str()) .try_into() .map_err(|_| { ChapterRelationshipError::TypeData( r.type_name, ) })?, id: Id(r.id), } }) } })() .map_err(ChapterConversionError::Relationship)?, ); } relationships }, }) } })() .map_err(ChapterFeedConversionError::Chapter)?; data.push(chapter); } Ok(ChapterFeed { result: (feed.result.as_str()) .try_into() .map_err(|_| ChapterFeedConversionError::Result(feed.result))?, response: (feed.response.as_str()) .try_into() .map_err(|_| ChapterFeedConversionError::Result(feed.response))?, data, limit: feed.limit, offset: feed.offset, total: feed.total, }) } } #[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 { Ok(ChapterAttributes { volume: match &attributes.volume { Some(v) => match v.parse() { Ok(n) => Some(n), Err(_) => return Err(ChapterAttributeConversionError::Volume(v.to_owned())), }, None => None, }, chapter: match &attributes.chapter { Some(v) => match v.parse() { Ok(v) => Some(v), Err(_) => return Err(ChapterAttributeConversionError::Chapter(v.to_owned())), }, None => None, }, created_at: DateTime::parse_from_rfc3339(&attributes.created_at) .map_err(|_| ChapterAttributeConversionError::CreatedAt(attributes.created_at))?, published_at: DateTime::parse_from_rfc3339(&attributes.publish_at) .map_err(|_| ChapterAttributeConversionError::CreatedAt(attributes.publish_at))?, updated_at: DateTime::parse_from_rfc3339(&attributes.updated_at) .map_err(|_| ChapterAttributeConversionError::CreatedAt(attributes.updated_at))?, external_url: attributes.external_url, title: attributes.title, pages: attributes.pages, translated_language: (attributes.translated_language.as_str()) .try_into() .map_err(|_| { ChapterAttributeConversionError::TranslatedLanguage( attributes.translated_language, ) })?, version: attributes.version, }) } } impl TryFrom for ChapterImages { type Error = ChapterImageError; fn try_from(data: ChapterImagesContent) -> Result { Ok(ChapterImages { result: (data.result.as_str()) .try_into() .map_err(|_| ChapterImageError::Result(data.result))?, base_url: data.base_url, chapter: ChapterImageData { hash: data.chapter.hash, data: data.chapter.data, }, }) } } 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) => { // If you can't parse the error then there is no point in continuing. eprintln!("ERROR: {e:#?}"); std::process::exit(1); } } } }; Ok(chapter_images.try_into().unwrap()) } impl TryFrom for Manga { type Error = ResponseConversionError; fn try_from(m: ContentData) -> Result { Ok(Manga { id: Id(m.id), data_type: (m.type_name.as_str()).try_into().map_err(|_| { ResponseConversionError::AttributeError(AttributeConversionError::DataType( m.type_name, )) })?, attributes: m .attributes .try_into() .map_err(ResponseConversionError::AttributeError)?, relationships: { let mut relationships = Vec::with_capacity(m.relationships.len()); for m in m.relationships { relationships.push( ({ || { Ok::(Relationship { id: Id(m.id), data_type: m.type_name.as_str().try_into().map_err(|_| { AttributeConversionError::DataType(m.type_name) })?, attributes: { if let Some(attributes) = m.attributes { Some(CoverAttributes { created_at: DateTime::parse_from_rfc3339( &attributes.created_at, ) .map_err(|_| { AttributeConversionError::CreatedAtDateTime( attributes.created_at.clone(), ) })?, updated_at: DateTime::parse_from_rfc3339( &attributes.created_at, ) .map_err(|_| { AttributeConversionError::CreatedAtDateTime( attributes.created_at.clone(), ) })?, // TODO: Something should probably be done here description: String::new(), file_name: Id(attributes.file_name.clone()), locale: (attributes.locale.as_str()) .try_into() .map_err(|_| { AttributeConversionError::Locale( attributes.locale.clone(), ) })?, version: attributes.version, volume: match &attributes.volume { Some(v) => v.parse().ok(), None => None, }, }) } else { None } }, related: m.related.clone(), }) } })() .map_err(ResponseConversionError::AttributeError)?, ); } relationships }, }) } }