use serde::de::{self, Visitor}; use serde::{Deserialize, Deserializer}; use std::collections::BTreeMap; use std::fmt; use std::marker::PhantomData; use std::num::ParseIntError; #[derive(Deserialize, Debug)] pub struct APIError { pub error: String, } #[derive(Deserialize, Debug)] pub struct GalleryTitleInfo { pub english: Option, pub japanese: Option, pub pretty: Option, } #[derive(Deserialize, Debug)] pub struct GalleryImageInfo { pub t: String, pub w: i32, pub h: i32, } #[derive(Deserialize, Debug)] pub struct GalleryImagesInfo { pub pages: Vec, pub cover: GalleryImageInfo, pub thumbnail: GalleryImageInfo, } #[derive(Deserialize, Debug)] pub struct GalleryTagInfo { pub id: i32, pub r#type: String, pub name: String, pub url: String, pub count: i32, } #[derive(Deserialize, Debug)] pub struct GalleryInfoSuccess { #[serde(deserialize_with = "convert_to_i32")] pub id: i32, pub media_id: String, pub title: GalleryTitleInfo, pub images: GalleryImagesInfo, pub scanlator: String, pub upload_date: i32, pub tags: Vec, pub num_pages: i32, pub num_favorites: i32, } #[derive(Deserialize, Debug)] #[serde(untagged)] pub enum GalleryInfo { Info(GalleryInfoSuccess), Error(APIError), } #[derive(Deserialize, Debug)] pub struct RelatedGalleriesSuccess { pub result: Vec, } #[derive(Deserialize, Debug)] #[serde(untagged)] pub enum RelatedGalleries { Galleries(RelatedGalleriesSuccess), Error(APIError), } #[derive(Debug)] pub struct MiniGalleryInfo { pub id: i32, pub title: String, } impl fmt::Display for GalleryInfoSuccess { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { let mut text = format!("Sauce: {}\nTitle: ", self.id); let japanese_title = self.title.japanese.as_ref(); let english_title = self.title.english.as_ref(); if english_title.filter(|i| !i.is_empty()).is_some() { text.push_str(english_title.unwrap()); if japanese_title.filter(|i| !i.is_empty()).is_some() { text.push_str(&format!("\nJapanese Title: {}", japanese_title.unwrap())); } } else { text.push_str(japanese_title.unwrap()); } let mut tag_hashmap = BTreeMap::new(); for tag_info in &self.tags { let tag_key = tag_info.r#type.as_str(); let tag_value = tag_info.name.as_str(); let tag_vec = tag_hashmap.entry(tag_key).or_insert(Vec::new()); tag_vec.push(tag_value); } for (tag_key, tag_value) in tag_hashmap { let tag_key = match tag_key { "tag" => "Tags", "artist" => "Artists", "parody" => "Parodies", "character" => "Characters", "group" => "Groups", "language" => "Languages", "category" => "Categories", _ => tag_key, }; text.push_str(&format!("\n{}: {}", tag_key, tag_value.join(", "))); } text.push_str(&format!( "\nPages: {}\nFavorites: {}", self.num_pages, self.num_favorites )); formatter.write_str(&text) } } #[derive(Debug)] pub enum Error { Reqwest(reqwest::Error), SerdeJSON(serde_json::Error), QuickXML(quick_xml::Error), ParseInt(ParseIntError), Unknown(&'static str), } impl From for Error { #[inline] fn from(error: reqwest::Error) -> Error { Error::Reqwest(error) } } impl From for Error { #[inline] fn from(error: serde_json::Error) -> Error { Error::SerdeJSON(error) } } impl From for Error { #[inline] fn from(error: quick_xml::Error) -> Error { Error::QuickXML(error) } } impl From for Error { #[inline] fn from(error: ParseIntError) -> Error { Error::ParseInt(error) } } impl fmt::Display for Error { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { let str = match self { Error::Reqwest(err) => format!("reqwest error: {}", err), Error::SerdeJSON(err) => format!("serde_json error: {}", err), Error::QuickXML(err) => format!("quick_xml error: {}", err), Error::ParseInt(err) => format!("parse int error: {}", err), Error::Unknown(err) => err.to_string(), }; formatter.write_str(&str) } } fn convert_to_i32<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { struct ConvertToI32(PhantomData T>); impl<'de> Visitor<'de> for ConvertToI32 { type Value = i32; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("an integer between -2^31 and 2^31") } fn visit_i8(self, value: i8) -> Result where E: de::Error, { Ok(i32::from(value)) } fn visit_i16(self, value: i16) -> Result where E: de::Error, { Ok(i32::from(value)) } fn visit_i32(self, value: i32) -> Result where E: de::Error, { Ok(value) } fn visit_i64(self, value: i64) -> Result where E: de::Error, { use std::i32; if value >= i64::from(i32::MIN) && value <= i64::from(i32::MAX) { Ok(value as i32) } else { Err(E::custom(format!("i32 out of range: {}", value))) } } fn visit_u8(self, value: u8) -> Result where E: de::Error, { Ok(i32::from(value)) } fn visit_u16(self, value: u16) -> Result where E: de::Error, { Ok(i32::from(value)) } fn visit_u32(self, value: u32) -> Result where E: de::Error, { use std::{i32, u32}; if value <= i32::MAX as u32 { Ok(value as i32) } else { Err(E::custom(format!("i32 out of range: {}", value))) } } fn visit_u64(self, value: u64) -> Result where E: de::Error, { use std::{i32, u64}; if value <= i32::MAX as u64 { Ok(value as i32) } else { Err(E::custom(format!("i32 out of range: {}", value))) } } fn visit_str(self, value: &str) -> Result where E: de::Error, { // https://brokenco.de/2020/08/03/serde-deserialize-with-string.html value.parse::().map_err(serde::de::Error::custom) } } deserializer.deserialize_any(ConvertToI32(PhantomData)) }