use serde::{Deserialize, Serialize};
use crate::{DynastyReaderRoute, DYNASTY_READER_BASE};
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DirectoryConfig {
pub kind: DirectoryKind,
pub page_number: u64,
}
impl DynastyReaderRoute for DirectoryConfig {
fn request_builder(
&self,
client: &reqwest::Client,
url: reqwest::Url,
) -> reqwest::RequestBuilder {
client.get(url).query(&[("page", self.page_number)])
}
fn request_url(&self) -> reqwest::Url {
DYNASTY_READER_BASE
.join(&format!("{}.json", self.kind))
.unwrap()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum DirectoryKind {
Anthology,
Doujin,
Issue,
Series,
Author,
Scanlator,
#[serde(alias = "General")]
Tag,
Pairing,
}
impl std::fmt::Display for DirectoryKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = {
use DirectoryKind::*;
match &self {
Anthology => "anthologies",
Doujin => "doujins",
Issue => "issues",
Series => "series",
Author => "authors",
Scanlator => "scanlators",
Tag => "tags",
Pairing => "pairings",
}
};
write!(f, "{s}")
}
}
impl std::str::FromStr for DirectoryKind {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use DirectoryKind::*;
match s {
"anthologies" => Ok(Anthology),
"authors" => Ok(Author),
"doujins" => Ok(Doujin),
"tags" => Ok(Tag),
"issues" => Ok(Issue),
"pairings" => Ok(Pairing),
"scanlators" => Ok(Scanlator),
"series" => Ok(Series),
_ => Err(anyhow::anyhow!("`{}` is not a valid directory", s)),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct UntaggedDirectory {
items: Vec<UntaggedDirectoryItem>,
page_number: u64,
max_page_number: u64,
}
impl<'de> Deserialize<'de> for UntaggedDirectory {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::collections::HashMap;
#[derive(Deserialize)]
struct UntaggedDirectoryWrapper {
tags: Vec<HashMap<String, Vec<UntaggedDirectoryItem>>>,
current_page: u64,
total_pages: u64,
}
let wrapper: UntaggedDirectoryWrapper = Deserialize::deserialize(deserializer)?;
let UntaggedDirectoryWrapper {
tags: items,
current_page: page_number,
total_pages: max_page_number,
} = wrapper;
let items = items
.into_iter()
.flatten()
.flat_map(|(_, items)| items)
.collect();
Ok(UntaggedDirectory {
items,
page_number,
max_page_number,
})
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Directory {
pub items: Vec<DirectoryItem>,
pub page_number: u64,
pub max_page_number: u64,
}
impl UntaggedDirectory {
pub(crate) fn into_tagged(self, kind: DirectoryKind) -> Directory {
let items = self
.items
.into_iter()
.map(|item| item.into_tagged(kind))
.collect();
Directory {
items,
page_number: self.page_number,
max_page_number: self.max_page_number,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct UntaggedDirectoryItem {
name: String,
permalink: String,
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct DirectoryItem {
pub name: String,
pub permalink: String,
pub kind: DirectoryKind,
}
impl UntaggedDirectoryItem {
pub(crate) fn into_tagged(self, kind: DirectoryKind) -> DirectoryItem {
DirectoryItem {
kind,
name: self.name,
permalink: self.permalink,
}
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use crate::test_utils::tryhard_configs;
use super::*;
fn create_config(kind: DirectoryKind) -> DirectoryConfig {
DirectoryConfig {
kind,
page_number: 1,
}
}
#[tokio::test]
#[ignore = "requires internet"]
async fn response_structure() -> Result<()> {
let configs = {
use DirectoryKind::*;
[
Anthology, Doujin, Issue, Series, Author, Scanlator, Tag, Pairing,
]
.map(create_config)
};
tryhard_configs(configs, |client, config| client.directory(config)).await?;
Ok(())
}
}