use crate::Language;
#[cfg(feature = "chrono")]
use chrono::{serde::ts_seconds, DateTime, Utc};
use serde::Deserialize;
use serde_repr::Deserialize_repr;
#[derive(
Debug, Deserialize_repr, Clone, Copy, PartialEq, PartialOrd, Eq, Ord,
)]
#[repr(u8)]
pub enum LongStrip {
No = 0,
Yes = 1,
}
impl From<bool> for LongStrip {
fn from(b: bool) -> Self {
if b { LongStrip::Yes } else { LongStrip::No }
}
}
impl From<LongStrip> for bool {
fn from(b: LongStrip) -> Self {
match b {
LongStrip::No => false,
LongStrip::Yes => true,
}
}
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct Data {
pub server: String,
pub hash: String,
pub page_array: Vec<String>,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct ChapterBase {
pub id: u32,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds", default = "Utc::now")]
pub timestamp: DateTime<Utc>,
#[cfg(not(feature = "chrono"))]
pub timestamp: u64,
pub manga_id: u32,
pub volume: String,
pub chapter: String,
pub title: String,
#[serde(rename(deserialize = "lang_code"))]
pub lang: Language,
pub comments: Option<u32>,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct GroupIDs {
pub group_id: u32,
pub group_id_2: u32,
pub group_id_3: u32,
}
impl From<GroupIDs> for [Option<std::num::NonZeroU32>; 3] {
fn from(v: GroupIDs) -> Self {
use std::num::NonZeroU32;
let GroupIDs {
group_id,
group_id_2,
group_id_3,
} = v;
[
NonZeroU32::new(group_id),
NonZeroU32::new(group_id_2),
NonZeroU32::new(group_id_3),
]
}
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct Delayed {
#[serde(default)]
pub group_website: String,
#[serde(flatten)]
pub info: ChapterBase,
#[serde(flatten)]
pub groups: GroupIDs,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct Unavailable(ChapterBase);
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct External {
#[serde(flatten)]
pub info: ChapterBase,
#[serde(flatten)]
pub groups: GroupIDs,
pub external: String,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct OK {
#[serde(flatten)]
pub pages: Data,
#[serde(flatten)]
pub info: ChapterBase,
#[serde(flatten)]
pub groups: GroupIDs,
pub long_strip: LongStrip,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
#[serde(tag = "status")]
pub enum Chapter {
#[serde(rename(deserialize = "deleted"))]
Deleted { id: u32 },
#[serde(rename(deserialize = "delayed"))]
Delayed(Box<Delayed>),
#[serde(rename(deserialize = "unavailable"))]
Unavailable(Box<Unavailable>),
#[serde(rename(deserialize = "OK"))]
Ok(Box<OK>),
#[serde(rename(deserialize = "external"))]
External(Box<External>),
#[serde(rename(deserialize = "error"))]
Error(Error),
}
impl Chapter {
pub fn is_ok(&self) -> bool {
match self {
Chapter::Ok(_) => true,
_ => false,
}
}
pub fn is_deleted(&self) -> bool {
match self {
Chapter::Deleted { .. } => true,
_ => false,
}
}
pub fn is_delayed(&self) -> bool {
match self {
Chapter::Delayed(_) => true,
_ => false,
}
}
pub fn is_error(&self) -> bool {
match self {
Chapter::Error { .. } => true,
_ => false,
}
}
pub fn is_unavailable(&self) -> bool {
match self {
Chapter::Unavailable(_) => true,
_ => false,
}
}
pub fn is_external(&self) -> bool {
match self {
Chapter::External(_) => true,
_ => false,
}
}
pub fn ok(self) -> Option<OK> {
match self {
Chapter::Ok(ok) => Some(*ok),
_ => None,
}
}
pub fn delayed(self) -> Option<Delayed> {
match self {
Chapter::Delayed(delayed) => Some(*delayed),
_ => None,
}
}
pub fn err(self) -> Option<Error> {
match self {
Chapter::Error(err) => Some(err),
_ => None,
}
}
pub fn external(self) -> Option<External> {
match self {
Chapter::External(ext) => Some(*ext),
_ => None,
}
}
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
pub struct Error {
pub id: u32,
pub message: String,
}
use std::fmt;
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{} on id {}", self.message, self.id)
}
}
impl std::error::Error for Error {}
#[cfg(test)]
pub mod tests {
use super::Chapter as API;
use crate::test_data::*;
use serde_json::from_str;
#[test]
fn it_decodes_a_deleted_chapter() -> R {
let test_deleted: API = from_str(TEST_CHAPTER_DELETED)?;
assert!(test_deleted.is_deleted());
eprintln!("{:?}", test_deleted);
Ok(())
}
#[test]
fn it_decodes_an_available_chapter() -> R {
let test_avilable: API = from_str(TEST_CHAPTER_OK)?;
assert!(test_avilable.is_ok());
eprintln!("{:?}", test_avilable);
let _ = test_avilable.ok().unwrap();
Ok(())
}
#[test]
fn it_decodes_another_available_chapter() -> R {
let test_avilable: API = from_str(TEST_CHAPTER_OK_2)?;
assert!(test_avilable.is_ok());
eprintln!("{:?}", test_avilable);
let _ = test_avilable.ok().unwrap();
Ok(())
}
#[test]
fn it_decodes_an_unavailable_chapter() -> R {
let test_unavilable: API = from_str(TEST_CHAPTER_UNAVAILABLE)?;
assert!(test_unavilable.is_unavailable());
eprintln!("{:?}", test_unavilable);
Ok(())
}
#[test]
fn it_decodes_a_delayed_chapter() -> R {
let test_delayed: API = from_str(TEST_CHAPTER_DELAYED)?;
assert!(test_delayed.is_delayed());
eprintln!("{:?}", test_delayed);
let _ = test_delayed.delayed().unwrap();
Ok(())
}
#[test]
fn it_decodes_an_external_chapter() -> R {
let test_extern: API = from_str(TEST_CHAPTER_EXTERNAL)?;
assert!(test_extern.is_external());
eprintln!("{:?}", test_extern);
let _ = test_extern.external().unwrap();
Ok(())
}
#[test]
fn it_decodes_a_non_existing_chapter() -> R {
let test_non_existing: API = from_str(TEST_CHAPTER_ERROR)?;
assert!(test_non_existing.is_error());
eprintln!("{:?}", test_non_existing);
let _ = test_non_existing.err().unwrap();
Ok(())
}
#[test]
fn it_does_not_decode_a_non_chapter() -> Result<(), API> {
let test_non_chapter: Result<API, serde_json::Error> =
from_str("{\"status\":\"None\"}");
if test_non_chapter.is_err() {
let err = test_non_chapter.expect_err("Error not found");
assert!(err.is_data());
eprintln!("{:?}", err);
eprintln!("{}", err);
Ok(())
} else {
Err(test_non_chapter.unwrap())
}
}
}