Files
manga-cli/src/response_deserializer.rs

1463 lines
43 KiB
Rust

// 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<Manga>,
}
#[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<String>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChapterImagesContentError {
pub result: String,
pub errors: Vec<ApiError>,
}
#[derive(Debug, Deserialize)]
struct ChapterImageDataContent {
hash: String,
data: Vec<String>,
}
pub struct ChapterImageData {
pub hash: String,
pub data: Vec<String>,
}
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<Chapter>,
pub limit: u32,
pub offset: u32,
pub total: u32,
}
#[derive(Deserialize, Debug)]
struct ChapterFeedResponse {
result: String,
response: String,
data: Vec<ChapterContent>,
limit: u32,
offset: u32,
total: u32,
}
#[derive(Debug)]
pub struct MangaAttributes {
pub title: Titles,
pub alt_titles: Vec<AltTitles>,
pub description: Description,
pub is_locked: bool,
pub links: Option<Links>,
pub original_language: Language,
pub last_volume: Option<u32>,
pub last_chapter: Option<u32>,
pub publication_demographic: Option<PublicationDemographic>,
pub status: Status,
pub year: Option<u32>,
pub content_rating: ContentRating,
pub tags: Vec<Tag>,
pub state: State,
pub chapter_numbers_reset_on_new_volume: bool,
pub created_at: DateTime<FixedOffset>,
pub updated_at: DateTime<FixedOffset>,
pub version: u32,
pub available_translated_languages: Vec<Option<Language>>,
pub latest_uploaded_chapter: Option<Id>,
}
#[derive(Deserialize, Debug)]
struct ChapterContent {
id: String,
#[serde(rename = "type")]
type_name: String,
attributes: ChapterAttributesContent,
relationships: Vec<ContentRelationShip>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ChapterAttributesContent {
volume: Option<String>,
chapter: Option<String>,
title: String,
translated_language: String,
external_url: Option<String>,
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<ChapterRelationShip>,
}
#[derive(Debug)]
pub struct ChapterAttributes {
pub volume: Option<u32>,
pub chapter: Option<f32>,
pub title: String,
pub translated_language: Language,
pub external_url: Option<String>,
pub published_at: DateTime<FixedOffset>,
pub created_at: DateTime<FixedOffset>,
pub updated_at: DateTime<FixedOffset>,
pub pages: u32,
pub version: u32,
}
#[derive(Debug)]
pub struct Manga {
pub id: Id,
pub data_type: DataType,
pub attributes: MangaAttributes,
pub relationships: Vec<RelationShip>,
}
#[derive(Deserialize, Debug)]
struct SearchResponse {
result: String,
response: String,
data: Vec<ContentData>,
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<ContentRelationShip>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ContentAttributes {
title: Titles,
alt_titles: Vec<AltTitles>,
description: Description,
is_locked: bool,
links: Option<Links>,
original_language: String,
last_volume: Option<String>,
last_chapter: Option<String>,
publication_demographic: Option<String>,
status: String,
year: Option<u32>,
content_rating: String,
tags: Vec<ContentTag>,
state: String,
chapter_numbers_reset_on_new_volume: bool,
created_at: String,
updated_at: String,
version: u32,
available_translated_languages: Vec<Option<String>>,
latest_uploaded_chapter: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct ContentCoverAttributes {
description: String,
volume: Option<String>,
file_name: String,
locale: String,
created_at: String,
updated_at: String,
version: u32,
}
#[derive(Debug)]
pub struct CoverAttributes {
pub description: String,
pub volume: Option<u32>,
pub file_name: Id,
pub locale: Language,
pub created_at: DateTime<FixedOffset>,
pub updated_at: DateTime<FixedOffset>,
pub version: u32,
}
#[derive(Deserialize, Debug, Clone)]
pub struct ContentTag {
id: String,
#[serde(rename = "type")]
type_name: String,
attributes: TagAttributes,
relationships: Vec<ContentRelationShip>,
}
#[derive(Debug)]
pub struct Tag {
pub id: Id,
pub data_type: DataType,
pub attributes: TagAttributes,
pub relationships: Vec<RelationShip>,
}
#[derive(Deserialize, Debug, Clone)]
struct ContentRelationShip {
id: String,
#[serde(rename = "type")]
type_name: String,
related: Option<String>,
attributes: Option<ContentCoverAttributes>,
}
#[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<String>,
pub attributes: Option<CoverAttributes>,
}
#[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<String>,
ap: Option<String>,
bw: Option<String>,
kt: Option<String>,
mu: Option<String>,
amz: Option<String>,
cdj: Option<String>,
ebj: Option<String>,
mal: Option<String>,
engtl: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct Description {
en: Option<String>,
ru: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
pub struct AltTitles {
en: Option<String>,
ja: Option<String>,
ru: Option<String>,
}
#[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<State> for &str {
type Error = ();
fn try_into(self) -> Result<State, ()> {
Ok(match self {
"published" => State::Published,
_ => return Err(()),
})
}
}
impl TryInto<ContentRating> for &str {
type Error = ();
fn try_into(self) -> Result<ContentRating, ()> {
Ok(match self {
"safe" => ContentRating::Safe,
"suggestive" => ContentRating::Suggestive,
"erotica" => ContentRating::Erotica,
"pornographic" => ContentRating::Pornographic,
_ => return Err(()),
})
}
}
impl TryInto<Language> for &str {
type Error = ();
fn try_into(self) -> Result<Language, ()> {
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<PublicationDemographic> for &str {
type Error = ();
fn try_into(self) -> Result<PublicationDemographic, ()> {
Ok(match self {
"shounen" => PublicationDemographic::Shounen,
"josei" => PublicationDemographic::Josei,
"shoujo" => PublicationDemographic::Shoujo,
"seinen" => PublicationDemographic::Seinen,
_ => return Err(()),
})
}
}
impl TryInto<DataType> for &str {
type Error = ();
fn try_into(self) -> Result<DataType, ()> {
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<Status> for &str {
type Error = ();
fn try_into(self) -> Result<Status, ()> {
Ok(match self {
"ongoing" => Status::Ongoing,
"completed" => Status::Completed,
"hiatus" => Status::Hiatus,
"cancelled" => Status::Cancelled,
_ => return Err(()),
})
}
}
impl TryInto<ResponseResult> for &str {
type Error = ();
fn try_into(self) -> Result<ResponseResult, ()> {
match self {
"ok" => Ok(ResponseResult::Ok),
_ => Err(()),
}
}
}
impl TryInto<Response> for &str {
type Error = ();
fn try_into(self) -> Result<Response, ()> {
match self {
"collection" => Ok(Response::Collection),
"entity" => Ok(Response::Entity),
_ => Err(()),
}
}
}
fn convert_response_to_result(
search_response: SearchResponse,
) -> Result<SearchResult, ResponseConversionError> {
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<Manga> = 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::<Result<Vec<RelationShip>, AttributeConversionError>>()
.map_err(ResponseConversionError::AttributeError)?,
})
})
.collect::<Result<Vec<Manga>, 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<MangaAttributes, AttributeConversionError> {
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::<Result<Vec<RelationShip>, 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::<Result<Vec<Tag>, 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::<Result<Vec<Option<Language>>, 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<IdQueryResult, AttributeConversionError> {
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<ChapterFeed, ChapterFeedConversionError> {
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::<Result<Vec<ChapterRelationShip>, ChapterRelationShipError>>()
.map_err(ChapterConversionError::RelationShip)?,
})
})
.collect::<Result<Vec<Chapter>, 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<ChapterAttributes, ChapterAttributeConversionError> {
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<ChapterImages, ChapterImageError> {
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<ChapterImages, ChapterImagesContentError> {
let chapter_images: ChapterImagesContent = match serde_json::from_str(json) {
Ok(v) => v,
Err(e) => {
match serde_json::from_str::<ChapterImagesContentError>(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<Manga, ResponseConversionError> {
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::<Result<Vec<RelationShip>, AttributeConversionError>>()
.map_err(ResponseConversionError::AttributeError)?,
})
}