// 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, Clone)] #[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, Clone)] 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, Clone)] 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)] // TODO: Typo: Relationship pub struct RelationShip { pub id: Id, pub data_type: DataType, pub related: Option, pub attributes: Option, } #[derive(Deserialize, Debug, Clone)] pub struct TagAttributes { pub name: TagName, pub description: Description, pub group: String, pub version: u32, } #[derive(Deserialize, Debug, Clone)] pub struct TagName { pub en: String, } #[derive(Deserialize, Debug, Default, Clone)] 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, Clone)] pub struct Description { en: Option, ru: Option, } #[derive(Deserialize, Debug, Clone)] pub struct AltTitles { en: Option, ja: Option, ru: Option, } #[derive(Deserialize, Debug, Clone)] 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 TryInto for &str { type Error = (); fn try_into(self) -> Result { Ok(match self { "published" => State::Published, _ => return Err(()), }) } } impl TryInto for &str { type Error = (); fn try_into(self) -> Result { Ok(match self { "safe" => ContentRating::Safe, "suggestive" => ContentRating::Suggestive, "erotica" => ContentRating::Erotica, "pornographic" => ContentRating::Pornographic, _ => return Err(()), }) } } impl TryInto for &str { type Error = (); fn try_into(self) -> Result { Ok(match self { "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 TryInto for &str { type Error = (); fn try_into(self) -> Result { Ok(match self { "shounen" => PublicationDemographic::Shounen, "josei" => PublicationDemographic::Josei, "shoujo" => PublicationDemographic::Shoujo, "seinen" => PublicationDemographic::Seinen, _ => return Err(()), }) } } impl TryInto for &str { type Error = (); fn try_into(self) -> Result { Ok(match self { "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 TryInto for &str { type Error = (); fn try_into(self) -> Result { Ok(match self { "ongoing" => Status::Ongoing, "completed" => Status::Completed, "hiatus" => Status::Hiatus, "cancelled" => Status::Cancelled, _ => return Err(()), }) } } impl TryInto for &str { type Error = (); fn try_into(self) -> Result { match self { "ok" => Ok(ResponseResult::Ok), _ => Err(()), } } } impl TryInto for &str { type Error = (); fn try_into(self) -> Result { match self { "collection" => Ok(Response::Collection), "entity" => Ok(Response::Entity), _ => Err(()), } } } fn convert_response_to_result( search_response: SearchResponse, ) -> Result { let response = (search_response.response.as_str()) .try_into() .map_err(|_| ResponseConversionError::Result(search_response.response.clone()))?; let result: ResponseResult = (search_response.result.as_str()) .try_into() .map_err(|_| ResponseConversionError::Result({ search_response.result.clone() }))?; let data: Vec = search_response .data .iter() .map(|m| { Ok(Manga { id: Id(m.id.clone()), data_type: (m.type_name.as_str()).try_into().map_err(|_| { ResponseConversionError::AttributeError(AttributeConversionError::DataType( m.type_name.clone(), )) })?, attributes: convert_attributes(&m.attributes) .map_err(ResponseConversionError::AttributeError)?, relationships: m .relationships .iter() .map(|m| { Ok(RelationShip { id: Id(m.id.clone()), data_type: (m.type_name.as_str()).try_into().map_err(|_| { AttributeConversionError::DataType(m.type_name.clone()) })?, 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(), }) }) .collect::, AttributeConversionError>>() .map_err(ResponseConversionError::AttributeError)?, }) }) .collect::, ResponseConversionError>>()?; Ok(SearchResult { response, result, 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), } } } fn convert_attributes( attributes: &ContentAttributes, ) -> Result { Ok(MangaAttributes { title: attributes.title.clone(), alt_titles: attributes.alt_titles.clone(), description: attributes.description.clone(), is_locked: attributes.is_locked, links: attributes.links.clone(), original_language: (attributes.original_language.as_str()) .try_into() .map_err(|_| { AttributeConversionError::Language(attributes.original_language.clone()) })?, last_volume: match attributes.last_volume.clone() { 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.clone() { 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.clone() { Some(s) => Some( (s.as_str()) .try_into() .map_err(|_| AttributeConversionError::PublicationDemographic(s.clone()))?, ), None => None, }, status: (attributes.status.as_str()) .try_into() .map_err(|_| AttributeConversionError::Status(attributes.status.clone()))?, year: attributes.year, content_rating: attributes.content_rating.as_str().try_into().map_err(|_| { AttributeConversionError::ContentRating(attributes.content_rating.clone()) })?, tags: attributes .tags .clone() .iter() .map(|m| { Ok(Tag { data_type: (m.type_name.as_str()) .try_into() .map_err(|_| AttributeConversionError::DataType(m.type_name.clone()))?, id: Id(m.id.clone()), relationships: m .relationships .iter() .map(|m| { Ok(RelationShip { id: Id(m.id.clone()), data_type: (m.type_name.as_str()).try_into().map_err(|_| { AttributeConversionError::DataType(m.type_name.clone()) })?, // TODO: Do this attributes: None, related: m.related.clone(), }) }) .collect::, AttributeConversionError>>()?, attributes: TagAttributes { name: m.attributes.name.clone(), group: m.attributes.group.clone(), version: m.attributes.version, description: Description { en: m.attributes.description.en.clone(), ru: m.attributes.description.ru.clone(), }, }, }) }) .collect::, AttributeConversionError>>()?, state: (attributes.state.as_str()) .try_into() .map_err(|_| AttributeConversionError::State(attributes.state.clone()))?, 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.clone()) })?, version: attributes.version, available_translated_languages: attributes .available_translated_languages .iter() .map(|m| { Ok(match m { Some(s) => Some( (s.as_str()) .try_into() .map_err(|_| AttributeConversionError::Language(s.clone()))?, ), None => None, }) }) .collect::>, AttributeConversionError>>()?, 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) => { std::fs::write("out.json", json).unwrap(); eprintln!("ERROR: {:#?}", e); std::process::exit(1); } }; convert_some_test(id_query_response).unwrap() } fn convert_some_test(input: IdQueryResponse) -> Result { Ok(IdQueryResult { result: input.result.as_str().try_into().unwrap(), response: input.response.as_str().try_into().unwrap(), data: convert_data_to_manga(input.data).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) => { std::fs::write("out.json", json).unwrap(); eprintln!("ERROR: {:#?}", e); std::process::exit(1); } }; convert_chapter_feed(chapter_feed_response).unwrap() } pub fn deserializer(json: &str) -> SearchResult { let search_response: SearchResponse = match serde_json::from_str(json) { Ok(v) => v, Err(e) => { std::fs::write("out.json", json).unwrap(); eprintln!("ERROR: {:#?}", e); std::process::exit(1); } }; let search_result = convert_response_to_result(search_response); 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), } fn convert_chapter_feed( feed: ChapterFeedResponse, ) -> Result { Ok(ChapterFeed { result: (feed.result.as_str()) .try_into() .map_err(|_| ChapterFeedConversionError::Result(feed.result.clone()))?, response: (feed.response.as_str()) .try_into() .map_err(|_| ChapterFeedConversionError::Result(feed.response.clone()))?, data: feed .data .iter() .map(|m| { Ok(Chapter { data_type: (m.type_name.as_str()) .try_into() .map_err(|_| ChapterConversionError::DataType(m.type_name.clone()))?, id: Id(m.id.clone()), attributes: convert_chapter_attributes(&m.attributes) .map_err(ChapterConversionError::Attributes)?, relationships: m .relationships .iter() .map(|m| { Ok(ChapterRelationShip { data_type: (m.type_name.as_str()).try_into().map_err(|_| { ChapterRelationShipError::TypeData(m.type_name.clone()) })?, id: Id(m.id.clone()), }) }) .collect::, ChapterRelationShipError>>() .map_err(ChapterConversionError::RelationShip)?, }) }) .collect::, ChapterConversionError>>() .map_err(ChapterFeedConversionError::Chapter)?, 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), } fn convert_chapter_attributes( 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.clone()) })?, published_at: DateTime::parse_from_rfc3339(&attributes.publish_at).map_err(|_| { ChapterAttributeConversionError::CreatedAt(attributes.publish_at.clone()) })?, updated_at: DateTime::parse_from_rfc3339(&attributes.updated_at).map_err(|_| { ChapterAttributeConversionError::CreatedAt(attributes.updated_at.clone()) })?, external_url: attributes.external_url.clone(), title: attributes.title.clone(), pages: attributes.pages, translated_language: (attributes.translated_language.as_str()) .try_into() .map_err(|_| { ChapterAttributeConversionError::TranslatedLanguage( attributes.translated_language.clone(), ) })?, version: attributes.version, }) } fn convert_chapter_images(data: ChapterImagesContent) -> Result { Ok(ChapterImages { result: (data.result.as_str()) .try_into() .map_err(|_| ChapterImageError::Result(data.result.clone()))?, 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(convert_chapter_images(chapter_images).unwrap()) } fn convert_data_to_manga(m: ContentData) -> Result { Ok(Manga { id: Id(m.id.clone()), data_type: (m.type_name.as_str()).try_into().map_err(|_| { ResponseConversionError::AttributeError(AttributeConversionError::DataType( m.type_name.clone(), )) })?, attributes: convert_attributes(&m.attributes) .map_err(ResponseConversionError::AttributeError)?, relationships: m .relationships .iter() .map(|m| { Ok(RelationShip { id: Id(m.id.clone()), data_type: (m.type_name.as_str()) .try_into() .map_err(|_| AttributeConversionError::DataType(m.type_name.clone()))?, 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(), }) }) .collect::, AttributeConversionError>>() .map_err(ResponseConversionError::AttributeError)?, }) }