1pub mod api_client;
2pub mod at_home_server;
3pub mod auth_tokens;
4pub mod author;
5pub mod chapter;
6pub mod check_token_response;
7pub mod check_username_available;
8pub mod cover;
9pub mod custom_list;
10pub mod error;
11mod exports_types;
12pub mod forum_thread;
13pub mod is_following_response;
14pub mod legacy_id_mapping;
15pub mod login_response;
16pub mod manga;
17pub mod manga_aggregate;
18pub mod manga_links;
19pub mod manga_read_markers;
20pub mod manga_reading_status;
21pub mod manga_reading_statuses;
22pub mod manga_relation;
23pub mod oauth;
24pub mod ratings;
25pub mod refresh_token_response;
26pub mod report;
27pub mod scanlation_group;
28pub mod settings_template;
29pub mod statistics;
30pub mod tag;
31pub mod upload_required_approval;
32pub mod upload_session;
33pub mod upload_session_file;
34pub mod user;
35pub mod user_history;
36pub mod user_report;
37pub mod user_settings;
38
39pub use self::exports_types::*;
40use std::collections::HashMap;
41
42use mangadex_api_types as types;
43use serde::Deserialize;
44use uuid::Uuid;
45
46use types::{
47 Language, MangaDexDateTime, MangaRelation, RelationshipType, ResponseType, ResultType,
48};
49
50use crate::FromResponse;
51pub(crate) use crate::{ApiObject, ApiObjectNoRelationships};
52use types::error::RelationshipConversionError;
53
54#[derive(Debug, Deserialize, Clone)]
57#[allow(clippy::large_enum_variant)]
58#[serde(untagged)]
59#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
60#[cfg_attr(feature = "specta", derive(specta::Type))]
61pub enum RelatedAttributes {
62 Manga(MangaAttributes),
64 Chapter(ChapterAttributes),
66 CoverArt(CoverAttributes),
72 Author(AuthorAttributes),
74 ScanlationGroup(ScanlationGroupAttributes),
76 Tag(TagAttributes),
78 User(UserAttributes),
80 CustomList(CustomListAttributes),
82}
83
84impl TryFrom<Relationship> for ApiObjectNoRelationships<MangaAttributes> {
85 type Error = RelationshipConversionError;
86
87 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
88 if value.type_ != RelationshipType::Manga {
89 return Err(RelationshipConversionError::InvalidInputRelationshipType {
90 input: RelationshipType::Manga,
91 inner: value.type_,
92 });
93 }
94 if let Some(RelatedAttributes::Manga(attributes)) = value.attributes {
95 Ok(Self {
96 id: value.id,
97 type_: RelationshipType::Manga,
98 attributes,
99 })
100 } else {
101 Err(RelationshipConversionError::AttributesNotFound(
102 RelationshipType::Manga,
103 ))
104 }
105 }
106}
107
108impl TryFrom<Relationship> for ApiObjectNoRelationships<ChapterAttributes> {
109 type Error = RelationshipConversionError;
110
111 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
112 if value.type_ != RelationshipType::Chapter {
113 return Err(RelationshipConversionError::InvalidInputRelationshipType {
114 input: RelationshipType::Chapter,
115 inner: value.type_,
116 });
117 }
118 if let Some(RelatedAttributes::Chapter(attributes)) = value.attributes {
119 Ok(Self {
120 id: value.id,
121 type_: RelationshipType::Chapter,
122 attributes,
123 })
124 } else {
125 Err(RelationshipConversionError::AttributesNotFound(
126 RelationshipType::Chapter,
127 ))
128 }
129 }
130}
131
132impl TryFrom<Relationship> for ApiObjectNoRelationships<CoverAttributes> {
133 type Error = RelationshipConversionError;
134
135 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
136 if value.type_ != RelationshipType::CoverArt {
137 return Err(RelationshipConversionError::InvalidInputRelationshipType {
138 input: RelationshipType::CoverArt,
139 inner: value.type_,
140 });
141 }
142 if let Some(RelatedAttributes::CoverArt(attributes)) = value.attributes {
143 Ok(Self {
144 id: value.id,
145 type_: RelationshipType::CustomList,
146 attributes,
147 })
148 } else {
149 Err(RelationshipConversionError::AttributesNotFound(
150 RelationshipType::CoverArt,
151 ))
152 }
153 }
154}
155
156impl TryFrom<Relationship> for ApiObjectNoRelationships<AuthorAttributes> {
157 type Error = RelationshipConversionError;
158
159 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
160 if !(value.type_ == RelationshipType::Author || value.type_ == RelationshipType::Artist) {
161 return Err(RelationshipConversionError::InvalidInputRelationshipType {
162 input: RelationshipType::Author,
163 inner: value.type_,
164 });
165 }
166 if let Some(RelatedAttributes::Author(attributes)) = value.attributes {
167 Ok(Self {
168 id: value.id,
169 type_: RelationshipType::Author,
170 attributes,
171 })
172 } else {
173 Err(RelationshipConversionError::AttributesNotFound(
174 RelationshipType::Author,
175 ))
176 }
177 }
178}
179
180impl TryFrom<Relationship> for ApiObjectNoRelationships<ScanlationGroupAttributes> {
181 type Error = RelationshipConversionError;
182
183 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
184 if value.type_ != RelationshipType::ScanlationGroup {
185 return Err(RelationshipConversionError::InvalidInputRelationshipType {
186 input: RelationshipType::ScanlationGroup,
187 inner: value.type_,
188 });
189 }
190 if let Some(RelatedAttributes::ScanlationGroup(attributes)) = value.attributes {
191 Ok(Self {
192 id: value.id,
193 type_: RelationshipType::ScanlationGroup,
194 attributes,
195 })
196 } else {
197 Err(RelationshipConversionError::AttributesNotFound(
198 RelationshipType::ScanlationGroup,
199 ))
200 }
201 }
202}
203
204impl TryFrom<Relationship> for ApiObjectNoRelationships<TagAttributes> {
205 type Error = RelationshipConversionError;
206
207 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
208 if value.type_ != RelationshipType::Tag {
209 return Err(RelationshipConversionError::InvalidInputRelationshipType {
210 input: RelationshipType::Tag,
211 inner: value.type_,
212 });
213 }
214 if let Some(RelatedAttributes::Tag(attributes)) = value.attributes {
215 Ok(Self {
216 id: value.id,
217 type_: RelationshipType::Tag,
218 attributes,
219 })
220 } else {
221 Err(RelationshipConversionError::AttributesNotFound(
222 RelationshipType::Tag,
223 ))
224 }
225 }
226}
227
228impl TryFrom<Relationship> for ApiObjectNoRelationships<UserAttributes> {
229 type Error = RelationshipConversionError;
230
231 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
232 if !(value.type_ == RelationshipType::User
233 || value.type_ == RelationshipType::Member
234 || value.type_ == RelationshipType::Leader)
235 {
236 return Err(RelationshipConversionError::InvalidInputRelationshipType {
237 input: RelationshipType::User,
238 inner: value.type_,
239 });
240 }
241 if let Some(RelatedAttributes::User(attributes)) = value.attributes {
242 Ok(Self {
243 id: value.id,
244 type_: RelationshipType::User,
245 attributes,
246 })
247 } else {
248 Err(RelationshipConversionError::AttributesNotFound(
249 RelationshipType::User,
250 ))
251 }
252 }
253}
254
255impl TryFrom<Relationship> for ApiObjectNoRelationships<CustomListAttributes> {
256 type Error = RelationshipConversionError;
257
258 fn try_from(value: Relationship) -> Result<Self, Self::Error> {
259 if value.type_ != RelationshipType::CustomList {
260 return Err(RelationshipConversionError::InvalidInputRelationshipType {
261 input: RelationshipType::CustomList,
262 inner: value.type_,
263 });
264 }
265 if let Some(RelatedAttributes::CustomList(attributes)) = value.attributes {
266 Ok(Self {
267 id: value.id,
268 type_: RelationshipType::CustomList,
269 attributes,
270 })
271 } else {
272 Err(RelationshipConversionError::AttributesNotFound(
273 RelationshipType::CustomList,
274 ))
275 }
276 }
277}
278
279#[derive(Debug, Deserialize, Clone)]
280#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
281#[cfg_attr(feature = "specta", derive(specta::Type))]
282pub struct Relationship {
283 pub id: Uuid,
284 #[serde(rename = "type")]
285 pub type_: RelationshipType,
286 #[serde(skip_serializing_if = "Option::is_none")]
292 #[serde(default)]
293 pub related: Option<MangaRelation>,
294 #[serde(skip_serializing_if = "Option::is_none")]
298 #[serde(default)]
299 pub attributes: Option<RelatedAttributes>,
300}
301
302#[derive(Debug, Deserialize, Clone)]
303#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
304#[cfg_attr(feature = "specta", derive(specta::Type))]
305pub struct Results<T> {
306 #[serde(default)]
307 pub result: ResultType,
308 pub response: ResponseType,
309 pub data: Vec<T>,
310 pub limit: u32,
311 pub offset: u32,
312 pub total: u32,
313}
314
315impl<T> FromResponse for Results<T> {
316 type Response = Self;
317 fn from_response(res: Self::Response) -> Self {
318 res
319 }
320}
321
322pub type LocalizedString = HashMap<Language, String>;
323
324pub(crate) mod localizedstring_array_or_map {
330 use std::collections::HashMap;
331
332 use super::LocalizedString;
333 use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
334 #[cfg(feature = "serialize")]
335 use serde::ser::{Serialize, Serializer};
336
337 pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalizedString, D::Error>
338 where
339 D: Deserializer<'de>,
340 {
341 struct V;
342
343 impl<'de> Visitor<'de> for V {
344 type Value = LocalizedString;
345
346 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
347 formatter.write_str("array or object")
348 }
349
350 fn visit_seq<A>(self, mut _seq: A) -> Result<Self::Value, A::Error>
351 where
352 A: SeqAccess<'de>,
353 {
354 Ok(HashMap::new())
355 }
356
357 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
358 where
359 A: MapAccess<'de>,
360 {
361 let de = serde::de::value::MapAccessDeserializer::new(map);
362 let helper = LocalizedString::deserialize(de)?;
363 Ok(helper)
364 }
365 }
366
367 deserializer.deserialize_any(V)
368 }
369 #[cfg(feature = "serialize")]
370 pub fn serialize<S>(to_use: &LocalizedString, serializer: S) -> Result<S::Ok, S::Error>
371 where
372 S: Serializer,
373 {
374 to_use.serialize(serializer)
375 }
376}
377
378pub(crate) mod volume_aggregate_array_or_map {
384 use super::manga_aggregate::VolumeAggregate;
385 use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor};
386 #[cfg(feature = "serialize")]
387 use serde::ser::Serializer;
388 #[cfg(feature = "serialize")]
389 use serde::Serialize;
390 use std::collections::BTreeMap;
391 #[cfg(feature = "serialize")]
392 use std::collections::HashMap;
393
394 type VolumeAggregateCollection = Vec<VolumeAggregate>;
395
396 const PAD_WIDTH: usize = 5;
397
398 pub fn deserialize<'de, D>(deserializer: D) -> Result<VolumeAggregateCollection, D::Error>
399 where
400 D: Deserializer<'de>,
401 {
402 struct V;
403
404 impl<'de> Visitor<'de> for V {
405 type Value = VolumeAggregateCollection;
406
407 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
408 formatter.write_str("array or object")
409 }
410
411 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
412 where
413 A: SeqAccess<'de>,
414 {
415 let mut volumes = Vec::new();
416
417 while let Some(volume) = seq.next_element::<VolumeAggregate>()? {
418 volumes.push(volume);
419 }
420
421 Ok(volumes)
422 }
423
424 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
425 where
426 M: MapAccess<'de>,
427 {
428 let mut sorting_map = BTreeMap::new();
431
432 while let Some((volume_number, volume)) =
433 map.next_entry::<String, VolumeAggregate>()?
434 {
435 let volume_number = if volume_number.contains('.') {
436 match volume_number.parse::<f64>() {
437 Ok(_) => {
438 let (i, f) = volume_number.split_once('.').unwrap();
440 let i = i.parse::<i32>().unwrap();
441 format!("{i:0PAD_WIDTH$}.{f}")
444 }
445 Err(_) => volume_number,
446 }
447 } else {
448 match volume_number.parse::<i32>() {
449 Ok(n) => format!("{n:0PAD_WIDTH$}"),
450 Err(_) => volume_number,
451 }
452 };
453 sorting_map.insert(volume_number, volume);
454 }
455
456 Ok(sorting_map.values().cloned().collect())
457 }
458 }
459
460 deserializer.deserialize_any(V)
461 }
462 #[cfg(feature = "serialize")]
463 #[allow(dead_code)]
464 pub fn serialize<S>(
465 to_use: &VolumeAggregateCollection,
466 serializer: S,
467 ) -> Result<S::Ok, S::Error>
468 where
469 S: Serializer,
470 {
471 use super::manga_aggregate::VolumeAggregateSer;
472
473 let mut volumes: HashMap<String, VolumeAggregateSer> = HashMap::new();
474 for volume in to_use {
475 volumes.insert(volume.volume.clone(), Into::into(volume.clone()));
476 }
477 volumes.serialize(serializer)
478 }
479}
480
481pub(crate) mod chapter_aggregate_array_or_map {
487 use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor};
488 #[cfg(feature = "serialize")]
489 use serde::ser::Serializer;
490 #[cfg(feature = "serialize")]
491 use serde::Serialize;
492 use std::collections::BTreeMap;
493
494 use super::manga_aggregate::ChapterAggregate;
495
496 const PAD_WIDTH: usize = 5;
497
498 type ChapterAggregateCollection = Vec<ChapterAggregate>;
499
500 pub fn deserialize<'de, D>(deserializer: D) -> Result<ChapterAggregateCollection, D::Error>
501 where
502 D: Deserializer<'de>,
503 {
504 struct V;
505
506 impl<'de> Visitor<'de> for V {
507 type Value = ChapterAggregateCollection;
508
509 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
510 formatter.write_str("array or object")
511 }
512
513 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
514 where
515 A: SeqAccess<'de>,
516 {
517 let mut chapters = Vec::new();
518
519 while let Some(chapter) = seq.next_element::<ChapterAggregate>()? {
520 chapters.push(chapter);
521 }
522
523 Ok(chapters)
524 }
525
526 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
527 where
528 M: MapAccess<'de>,
529 {
530 let mut sorting_map = BTreeMap::new();
533
534 while let Some((chapter_number, chapter)) =
535 map.next_entry::<String, ChapterAggregate>()?
536 {
537 let chapter_number = if chapter_number.contains('.') {
538 match chapter_number.parse::<f64>() {
539 Ok(_) => {
540 let (i, f) = chapter_number.split_once('.').unwrap();
542 let i = i.parse::<i32>().unwrap();
543 format!("{i:0PAD_WIDTH$}.{f}")
546 }
547 Err(_) => chapter_number,
548 }
549 } else {
550 match chapter_number.parse::<i32>() {
551 Ok(n) => format!("{n:0PAD_WIDTH$}"),
552 Err(_) => chapter_number,
553 }
554 };
555 sorting_map.insert(chapter_number, chapter);
556 }
557
558 Ok(sorting_map.values().cloned().collect())
559 }
560 }
561
562 deserializer.deserialize_any(V)
563 }
564 #[cfg(feature = "serialize")]
565 pub fn serialize<S>(
566 to_use: &ChapterAggregateCollection,
567 serializer: S,
568 ) -> Result<S::Ok, S::Error>
569 where
570 S: Serializer,
571 {
572 use std::collections::HashMap;
573
574 let mut chapters: HashMap<String, ChapterAggregate> = HashMap::new();
575 for chapter in to_use {
576 chapters.insert(chapter.chapter.clone(), chapter.clone());
577 }
578 chapters.serialize(serializer)
579 }
580}
581
582pub(crate) mod manga_links_array_or_struct {
588 use crate::v5::MangaLinks;
589 use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
590 #[cfg(feature = "serialize")]
591 use serde::ser::Serializer;
592 #[cfg(feature = "serialize")]
593 use serde::Serialize;
594
595 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<MangaLinks>, D::Error>
597 where
598 D: Deserializer<'de>,
599 {
600 struct OptionMangaLinksVisitor;
601
602 impl<'de> Visitor<'de> for OptionMangaLinksVisitor {
603 type Value = Option<MangaLinks>;
604
605 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
606 formatter.write_str("some or none")
607 }
608
609 fn visit_none<E>(self) -> Result<Self::Value, E>
611 where
612 E: serde::de::Error,
613 {
614 Ok(None)
615 }
616
617 fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
619 where
620 D: Deserializer<'de>,
621 {
622 let manga_links = d.deserialize_any(MangaLinksVisitor)?;
623
624 let manga_links = if manga_links.has_no_links() {
625 None
626 } else {
627 Some(manga_links)
628 };
629
630 Ok(manga_links)
631 }
632
633 fn visit_unit<E>(self) -> Result<Self::Value, E>
635 where
636 E: serde::de::Error,
637 {
638 Ok(None)
639 }
640 }
641
642 struct MangaLinksVisitor;
643
644 impl<'de> Visitor<'de> for MangaLinksVisitor {
645 type Value = MangaLinks;
646
647 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
648 formatter.write_str("array or map")
649 }
650
651 fn visit_seq<A>(self, mut _seq: A) -> Result<Self::Value, A::Error>
653 where
654 A: SeqAccess<'de>,
655 {
656 Ok(Self::Value::default())
657 }
658
659 fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
661 where
662 M: MapAccess<'de>,
663 {
664 Deserialize::deserialize(serde::de::value::MapAccessDeserializer::new(map))
669 }
670 }
671
672 deserializer.deserialize_option(OptionMangaLinksVisitor)
673 }
674 #[cfg(feature = "serialize")]
675 pub fn serialize<S>(to_use: &Option<MangaLinks>, serializer: S) -> Result<S::Ok, S::Error>
676 where
677 S: Serializer,
678 {
679 match to_use {
680 None => serializer.serialize_none(),
681 Some(data) => data.serialize(serializer),
682 }
683 }
684}
685
686pub(crate) mod language_array_or_skip_null {
690 use mangadex_api_types::Language;
691 use serde::de::{Deserializer, SeqAccess, Visitor};
692 #[cfg(feature = "serialize")]
693 use serde::ser::Serializer;
694 #[cfg(feature = "serialize")]
695 use serde::Serialize;
696 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Language>, D::Error>
698 where
699 D: Deserializer<'de>,
700 {
701 struct V;
702
703 impl<'de> Visitor<'de> for V {
704 type Value = Vec<Language>;
705
706 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
707 formatter.write_str("a sequence of languages")
708 }
709
710 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
711 where
712 A: SeqAccess<'de>,
713 {
714 let mut languages = Vec::new();
715
716 while let Some(language) = seq.next_element::<Option<Language>>()? {
718 if let Some(language) = language {
720 languages.push(language);
721 }
722 }
723
724 Ok(languages)
725 }
726 }
727
728 deserializer.deserialize_seq(V)
729 }
730 #[cfg(feature = "serialize")]
731 pub fn serialize<S>(to_use: &Vec<Language>, serializer: S) -> Result<S::Ok, S::Error>
732 where
733 S: Serializer,
734 {
735 to_use.serialize(serializer)
736 }
737}
738
739pub fn mangadex_datetime_serialize<S>(
740 datetime: &MangaDexDateTime,
741 serializer: S,
742) -> Result<S::Ok, S::Error>
743where
744 S: serde::Serializer,
745{
746 serializer.serialize_str(datetime.to_string().as_str())
747}
748
749pub fn mangadex_datetime_serialize_option<S>(
750 datetime: &Option<MangaDexDateTime>,
751 serializer: S,
752) -> Result<S::Ok, S::Error>
753where
754 S: serde::Serializer,
755{
756 if let Some(d) = datetime {
757 serializer.serialize_str(d.to_string().as_str())
758 } else {
759 serializer.serialize_none()
760 }
761}
762
763#[cfg(test)]
764mod test {
765 use serde::{Deserialize, Serialize};
766
767 use mangadex_api_types::MangaDexDateTime;
768
769 #[derive(Serialize, Deserialize, Default)]
770 struct TestStruct {
771 #[serde(serialize_with = "crate::v5::mangadex_datetime_serialize")]
772 date: MangaDexDateTime,
773 }
774
775 #[tokio::test]
776 async fn mangadex_datetime_serialize_test() {
777 let test: TestStruct = Default::default();
778 println!("{}", serde_json::to_string_pretty(&test).unwrap());
779 }
780}