pub mod api_client;
pub mod at_home_server;
pub mod auth_tokens;
pub mod author;
pub mod chapter;
pub mod check_token_response;
pub mod check_username_available;
pub mod cover;
pub mod custom_list;
pub mod error;
mod exports_types;
pub mod forum_thread;
pub mod is_following_response;
pub mod legacy_id_mapping;
pub mod login_response;
pub mod manga;
pub mod manga_aggregate;
pub mod manga_links;
pub mod manga_read_markers;
pub mod manga_reading_status;
pub mod manga_reading_statuses;
pub mod manga_relation;
pub mod oauth;
pub mod ratings;
pub mod refresh_token_response;
pub mod report;
pub mod scanlation_group;
pub mod settings_template;
pub mod statistics;
pub mod tag;
pub mod upload_required_approval;
pub mod upload_session;
pub mod upload_session_file;
pub mod user;
pub mod user_history;
pub mod user_report;
pub mod user_settings;
pub use self::exports_types::*;
use std::collections::HashMap;
use mangadex_api_types as types;
use serde::Deserialize;
use uuid::Uuid;
use types::{
Language, MangaDexDateTime, MangaRelation, RelationshipType, ResponseType, ResultType,
};
use crate::FromResponse;
pub(crate) use crate::{ApiObject, ApiObjectNoRelationships};
use types::error::RelationshipConversionError;
#[derive(Debug, Deserialize, Clone)]
#[allow(clippy::large_enum_variant)]
#[serde(untagged)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub enum RelatedAttributes {
Manga(MangaAttributes),
Chapter(ChapterAttributes),
CoverArt(CoverAttributes),
Author(AuthorAttributes),
ScanlationGroup(ScanlationGroupAttributes),
Tag(TagAttributes),
User(UserAttributes),
CustomList(CustomListAttributes),
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<MangaAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if value.type_ != RelationshipType::Manga {
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::Manga,
inner: value.type_,
});
}
if let Some(RelatedAttributes::Manga(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::Manga,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::Manga,
))
}
}
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<ChapterAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if value.type_ != RelationshipType::Chapter {
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::Chapter,
inner: value.type_,
});
}
if let Some(RelatedAttributes::Chapter(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::Chapter,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::Chapter,
))
}
}
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<CoverAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if value.type_ != RelationshipType::CoverArt {
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::CoverArt,
inner: value.type_,
});
}
if let Some(RelatedAttributes::CoverArt(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::CustomList,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::CoverArt,
))
}
}
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<AuthorAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if !(value.type_ == RelationshipType::Author || value.type_ == RelationshipType::Artist) {
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::Author,
inner: value.type_,
});
}
if let Some(RelatedAttributes::Author(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::Author,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::Author,
))
}
}
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<ScanlationGroupAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if value.type_ != RelationshipType::ScanlationGroup {
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::ScanlationGroup,
inner: value.type_,
});
}
if let Some(RelatedAttributes::ScanlationGroup(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::ScanlationGroup,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::ScanlationGroup,
))
}
}
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<TagAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if value.type_ != RelationshipType::Tag {
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::Tag,
inner: value.type_,
});
}
if let Some(RelatedAttributes::Tag(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::Tag,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::Tag,
))
}
}
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<UserAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if !(value.type_ == RelationshipType::User
|| value.type_ == RelationshipType::Member
|| value.type_ == RelationshipType::Leader)
{
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::User,
inner: value.type_,
});
}
if let Some(RelatedAttributes::User(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::User,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::User,
))
}
}
}
impl TryFrom<Relationship> for ApiObjectNoRelationships<CustomListAttributes> {
type Error = RelationshipConversionError;
fn try_from(value: Relationship) -> Result<Self, Self::Error> {
if value.type_ != RelationshipType::CustomList {
return Err(RelationshipConversionError::InvalidInputRelationshipType {
input: RelationshipType::CustomList,
inner: value.type_,
});
}
if let Some(RelatedAttributes::CustomList(attributes)) = value.attributes {
Ok(Self {
id: value.id,
type_: RelationshipType::CustomList,
attributes,
})
} else {
Err(RelationshipConversionError::AttributesNotFound(
RelationshipType::CustomList,
))
}
}
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct Relationship {
pub id: Uuid,
#[serde(rename = "type")]
pub type_: RelationshipType,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub related: Option<MangaRelation>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub attributes: Option<RelatedAttributes>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "specta", derive(specta::Type))]
pub struct Results<T> {
#[serde(default)]
pub result: ResultType,
pub response: ResponseType,
pub data: Vec<T>,
pub limit: u32,
pub offset: u32,
pub total: u32,
}
impl<T> FromResponse for Results<T> {
type Response = Self;
fn from_response(res: Self::Response) -> Self {
res
}
}
pub type LocalizedString = HashMap<Language, String>;
pub(crate) mod localizedstring_array_or_map {
use std::collections::HashMap;
use super::LocalizedString;
use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
#[cfg(feature = "serialize")]
use serde::ser::{Serialize, Serializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalizedString, D::Error>
where
D: Deserializer<'de>,
{
struct V;
impl<'de> Visitor<'de> for V {
type Value = LocalizedString;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("array or object")
}
fn visit_seq<A>(self, mut _seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
Ok(HashMap::new())
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let de = serde::de::value::MapAccessDeserializer::new(map);
let helper = LocalizedString::deserialize(de)?;
Ok(helper)
}
}
deserializer.deserialize_any(V)
}
#[cfg(feature = "serialize")]
pub fn serialize<S>(to_use: &LocalizedString, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
to_use.serialize(serializer)
}
}
pub(crate) mod volume_aggregate_array_or_map {
use super::manga_aggregate::VolumeAggregate;
use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor};
#[cfg(feature = "serialize")]
use serde::ser::Serializer;
#[cfg(feature = "serialize")]
use serde::Serialize;
use std::collections::BTreeMap;
#[cfg(feature = "serialize")]
use std::collections::HashMap;
type VolumeAggregateCollection = Vec<VolumeAggregate>;
const PAD_WIDTH: usize = 5;
pub fn deserialize<'de, D>(deserializer: D) -> Result<VolumeAggregateCollection, D::Error>
where
D: Deserializer<'de>,
{
struct V;
impl<'de> Visitor<'de> for V {
type Value = VolumeAggregateCollection;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("array or object")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut volumes = Vec::new();
while let Some(volume) = seq.next_element::<VolumeAggregate>()? {
volumes.push(volume);
}
Ok(volumes)
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut sorting_map = BTreeMap::new();
while let Some((volume_number, volume)) =
map.next_entry::<String, VolumeAggregate>()?
{
let volume_number = if volume_number.contains('.') {
match volume_number.parse::<f64>() {
Ok(_) => {
let (i, f) = volume_number.split_once('.').unwrap();
let i = i.parse::<i32>().unwrap();
format!("{i:0PAD_WIDTH$}.{f}")
}
Err(_) => volume_number,
}
} else {
match volume_number.parse::<i32>() {
Ok(n) => format!("{n:0PAD_WIDTH$}"),
Err(_) => volume_number,
}
};
sorting_map.insert(volume_number, volume);
}
Ok(sorting_map.values().cloned().collect())
}
}
deserializer.deserialize_any(V)
}
#[cfg(feature = "serialize")]
#[allow(dead_code)]
pub fn serialize<S>(
to_use: &VolumeAggregateCollection,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use super::manga_aggregate::VolumeAggregateSer;
let mut volumes: HashMap<String, VolumeAggregateSer> = HashMap::new();
for volume in to_use {
volumes.insert(volume.volume.clone(), Into::into(volume.clone()));
}
volumes.serialize(serializer)
}
}
pub(crate) mod chapter_aggregate_array_or_map {
use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor};
#[cfg(feature = "serialize")]
use serde::ser::Serializer;
#[cfg(feature = "serialize")]
use serde::Serialize;
use std::collections::BTreeMap;
use super::manga_aggregate::ChapterAggregate;
const PAD_WIDTH: usize = 5;
type ChapterAggregateCollection = Vec<ChapterAggregate>;
pub fn deserialize<'de, D>(deserializer: D) -> Result<ChapterAggregateCollection, D::Error>
where
D: Deserializer<'de>,
{
struct V;
impl<'de> Visitor<'de> for V {
type Value = ChapterAggregateCollection;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("array or object")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut chapters = Vec::new();
while let Some(chapter) = seq.next_element::<ChapterAggregate>()? {
chapters.push(chapter);
}
Ok(chapters)
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut sorting_map = BTreeMap::new();
while let Some((chapter_number, chapter)) =
map.next_entry::<String, ChapterAggregate>()?
{
let chapter_number = if chapter_number.contains('.') {
match chapter_number.parse::<f64>() {
Ok(_) => {
let (i, f) = chapter_number.split_once('.').unwrap();
let i = i.parse::<i32>().unwrap();
format!("{i:0PAD_WIDTH$}.{f}")
}
Err(_) => chapter_number,
}
} else {
match chapter_number.parse::<i32>() {
Ok(n) => format!("{n:0PAD_WIDTH$}"),
Err(_) => chapter_number,
}
};
sorting_map.insert(chapter_number, chapter);
}
Ok(sorting_map.values().cloned().collect())
}
}
deserializer.deserialize_any(V)
}
#[cfg(feature = "serialize")]
pub fn serialize<S>(
to_use: &ChapterAggregateCollection,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use std::collections::HashMap;
let mut chapters: HashMap<String, ChapterAggregate> = HashMap::new();
for chapter in to_use {
chapters.insert(chapter.chapter.clone(), chapter.clone());
}
chapters.serialize(serializer)
}
}
pub(crate) mod manga_links_array_or_struct {
use crate::v5::MangaLinks;
use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
#[cfg(feature = "serialize")]
use serde::ser::Serializer;
#[cfg(feature = "serialize")]
use serde::Serialize;
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<MangaLinks>, D::Error>
where
D: Deserializer<'de>,
{
struct OptionMangaLinksVisitor;
impl<'de> Visitor<'de> for OptionMangaLinksVisitor {
type Value = Option<MangaLinks>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("some or none")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
let manga_links = d.deserialize_any(MangaLinksVisitor)?;
let manga_links = if manga_links.has_no_links() {
None
} else {
Some(manga_links)
};
Ok(manga_links)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
}
struct MangaLinksVisitor;
impl<'de> Visitor<'de> for MangaLinksVisitor {
type Value = MangaLinks;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("array or map")
}
fn visit_seq<A>(self, mut _seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
Ok(Self::Value::default())
}
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
Deserialize::deserialize(serde::de::value::MapAccessDeserializer::new(map))
}
}
deserializer.deserialize_option(OptionMangaLinksVisitor)
}
#[cfg(feature = "serialize")]
pub fn serialize<S>(to_use: &Option<MangaLinks>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match to_use {
None => serializer.serialize_none(),
Some(data) => data.serialize(serializer),
}
}
}
pub(crate) mod language_array_or_skip_null {
use mangadex_api_types::Language;
use serde::de::{Deserializer, SeqAccess, Visitor};
#[cfg(feature = "serialize")]
use serde::ser::Serializer;
#[cfg(feature = "serialize")]
use serde::Serialize;
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Language>, D::Error>
where
D: Deserializer<'de>,
{
struct V;
impl<'de> Visitor<'de> for V {
type Value = Vec<Language>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a sequence of languages")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut languages = Vec::new();
while let Some(language) = seq.next_element::<Option<Language>>()? {
if let Some(language) = language {
languages.push(language);
}
}
Ok(languages)
}
}
deserializer.deserialize_seq(V)
}
#[cfg(feature = "serialize")]
pub fn serialize<S>(to_use: &Vec<Language>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
to_use.serialize(serializer)
}
}
pub fn mangadex_datetime_serialize<S>(
datetime: &MangaDexDateTime,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(datetime.to_string().as_str())
}
pub fn mangadex_datetime_serialize_option<S>(
datetime: &Option<MangaDexDateTime>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if let Some(d) = datetime {
serializer.serialize_str(d.to_string().as_str())
} else {
serializer.serialize_none()
}
}
#[cfg(test)]
mod test {
use serde::{Deserialize, Serialize};
use mangadex_api_types::MangaDexDateTime;
#[derive(Serialize, Deserialize, Default)]
struct TestStruct {
#[serde(serialize_with = "crate::v5::mangadex_datetime_serialize")]
date: MangaDexDateTime,
}
#[tokio::test]
async fn mangadex_datetime_serialize_test() {
let test: TestStruct = Default::default();
println!("{}", serde_json::to_string_pretty(&test).unwrap());
}
}