use std::fmt; use std::marker::PhantomData; use std::collections::BTreeMap; use serde::de::{self, Visitor}; use serde::{Deserialize, Deserializer}; #[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)] pub struct GalleryInfoError { pub error: String } #[derive(Deserialize, Debug)] #[serde(untagged)] pub enum GalleryInfo { Info(GalleryInfoSuccess), Error(GalleryInfoError) } #[derive(Deserialize, Debug)] pub struct SearchInfo { pub result: Vec, pub num_pages: i32, pub per_page: i32 } 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) } } 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)) }