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;
10mod exports_types;
11pub mod forum_thread;
12pub mod is_following_response;
13pub mod legacy_id_mapping;
14pub mod login_response;
15pub mod manga;
16pub mod manga_aggregate;
17pub mod manga_links;
18pub mod manga_read_markers;
19pub mod manga_reading_status;
20pub mod manga_reading_statuses;
21pub mod manga_recommendation;
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::error::RelationshipConversionError;
51pub(crate) use crate::{ApiObject, ApiObjectNoRelationships};
52
53#[derive(Debug, Deserialize, Clone)]
56#[allow(clippy::large_enum_variant)]
57#[serde(untagged)]
58#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
59#[cfg_attr(feature = "specta", derive(specta::Type))]
60#[non_exhaustive]
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::CoverArt,
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, Default)]
280#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
281#[cfg_attr(feature = "specta", derive(specta::Type))]
282#[non_exhaustive]
283pub struct Relationship {
284 pub id: Uuid,
285 #[serde(rename = "type")]
286 pub type_: RelationshipType,
287 #[serde(skip_serializing_if = "Option::is_none")]
293 #[serde(default)]
294 pub related: Option<MangaRelation>,
295 #[serde(skip_serializing_if = "Option::is_none")]
299 #[serde(default)]
300 pub attributes: Option<RelatedAttributes>,
301}
302
303#[derive(Debug, Deserialize, Clone)]
304#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
305#[cfg_attr(feature = "specta", derive(specta::Type))]
306#[non_exhaustive]
307pub struct Results<T> {
308 #[serde(default)]
309 pub result: ResultType,
310 pub response: ResponseType,
311 pub data: Vec<T>,
312 pub limit: u32,
313 pub offset: u32,
314 pub total: u32,
315}
316
317impl<T> Default for Results<T> {
318 fn default() -> Self {
319 Self {
320 result: ResultType::Ok,
321 response: ResponseType::Collection,
322 data: Vec::default(),
323 limit: 0,
324 offset: 0,
325 total: 0,
326 }
327 }
328}
329
330pub type LocalizedString = HashMap<Language, String>;
331
332pub(crate) mod localizedstring_array_or_map {
338 use std::collections::HashMap;
339
340 use super::LocalizedString;
341 use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
342 #[cfg(feature = "serialize")]
343 use serde::ser::{Serialize, Serializer};
344
345 pub fn deserialize<'de, D>(deserializer: D) -> Result<LocalizedString, D::Error>
346 where
347 D: Deserializer<'de>,
348 {
349 struct V;
350
351 impl<'de> Visitor<'de> for V {
352 type Value = LocalizedString;
353
354 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
355 formatter.write_str("array or object")
356 }
357
358 fn visit_seq<A>(self, mut _seq: A) -> Result<Self::Value, A::Error>
359 where
360 A: SeqAccess<'de>,
361 {
362 Ok(HashMap::new())
363 }
364
365 fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
366 where
367 A: MapAccess<'de>,
368 {
369 let de = serde::de::value::MapAccessDeserializer::new(map);
370 let helper = LocalizedString::deserialize(de)?;
371 Ok(helper)
372 }
373 }
374
375 deserializer.deserialize_any(V)
376 }
377 #[cfg(feature = "serialize")]
378 pub fn serialize<S>(to_use: &LocalizedString, serializer: S) -> Result<S::Ok, S::Error>
379 where
380 S: Serializer,
381 {
382 to_use.serialize(serializer)
383 }
384}
385
386pub(crate) mod volume_aggregate_array_or_map {
392 use super::manga_aggregate::VolumeAggregate;
393 #[cfg(feature = "serialize")]
394 use serde::Serialize;
395 use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor};
396 #[cfg(feature = "serialize")]
397 use serde::ser::Serializer;
398 use std::collections::BTreeMap;
399 #[cfg(feature = "serialize")]
400 use std::collections::HashMap;
401
402 type VolumeAggregateCollection = Vec<VolumeAggregate>;
403
404 const PAD_WIDTH: usize = 5;
405
406 pub fn deserialize<'de, D>(deserializer: D) -> Result<VolumeAggregateCollection, D::Error>
407 where
408 D: Deserializer<'de>,
409 {
410 struct V;
411
412 impl<'de> Visitor<'de> for V {
413 type Value = VolumeAggregateCollection;
414
415 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
416 formatter.write_str("array or object")
417 }
418
419 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
420 where
421 A: SeqAccess<'de>,
422 {
423 let mut volumes = Vec::new();
424
425 while let Some(volume) = seq.next_element::<VolumeAggregate>()? {
426 volumes.push(volume);
427 }
428
429 Ok(volumes)
430 }
431
432 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
433 where
434 M: MapAccess<'de>,
435 {
436 let mut sorting_map = BTreeMap::new();
439
440 while let Some((volume_number, volume)) =
441 map.next_entry::<String, VolumeAggregate>()?
442 {
443 let volume_number = if volume_number.contains('.') {
444 match volume_number.parse::<f64>() {
445 Ok(_) => {
446 let (i, f) = volume_number.split_once('.').unwrap();
448 let i = i.parse::<i32>().unwrap();
449 format!("{i:0PAD_WIDTH$}.{f}")
452 }
453 Err(_) => volume_number,
454 }
455 } else {
456 match volume_number.parse::<i32>() {
457 Ok(n) => format!("{n:0PAD_WIDTH$}"),
458 Err(_) => volume_number,
459 }
460 };
461 sorting_map.insert(volume_number, volume);
462 }
463
464 Ok(sorting_map.values().cloned().collect())
465 }
466 }
467
468 deserializer.deserialize_any(V)
469 }
470 #[cfg(feature = "serialize")]
471 #[allow(dead_code)]
472 pub fn serialize<S>(
473 to_use: &VolumeAggregateCollection,
474 serializer: S,
475 ) -> Result<S::Ok, S::Error>
476 where
477 S: Serializer,
478 {
479 use super::manga_aggregate::VolumeAggregateSer;
480
481 let mut volumes: HashMap<String, VolumeAggregateSer> = HashMap::new();
482 for volume in to_use {
483 volumes.insert(volume.volume.clone(), Into::into(volume.clone()));
484 }
485 volumes.serialize(serializer)
486 }
487}
488
489pub(crate) mod chapter_aggregate_array_or_map {
495 #[cfg(feature = "serialize")]
496 use serde::Serialize;
497 use serde::de::{Deserializer, MapAccess, SeqAccess, Visitor};
498 #[cfg(feature = "serialize")]
499 use serde::ser::Serializer;
500 use std::collections::BTreeMap;
501
502 use super::manga_aggregate::ChapterAggregate;
503
504 const PAD_WIDTH: usize = 5;
505
506 type ChapterAggregateCollection = Vec<ChapterAggregate>;
507
508 pub fn deserialize<'de, D>(deserializer: D) -> Result<ChapterAggregateCollection, D::Error>
509 where
510 D: Deserializer<'de>,
511 {
512 struct V;
513
514 impl<'de> Visitor<'de> for V {
515 type Value = ChapterAggregateCollection;
516
517 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
518 formatter.write_str("array or object")
519 }
520
521 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
522 where
523 A: SeqAccess<'de>,
524 {
525 let mut chapters = Vec::new();
526
527 while let Some(chapter) = seq.next_element::<ChapterAggregate>()? {
528 chapters.push(chapter);
529 }
530
531 Ok(chapters)
532 }
533
534 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
535 where
536 M: MapAccess<'de>,
537 {
538 let mut sorting_map = BTreeMap::new();
541
542 while let Some((chapter_number, chapter)) =
543 map.next_entry::<String, ChapterAggregate>()?
544 {
545 let chapter_number = if chapter_number.contains('.') {
546 match chapter_number.parse::<f64>() {
547 Ok(_) => {
548 let (i, f) = chapter_number.split_once('.').unwrap();
550 let i = i.parse::<i32>().unwrap();
551 format!("{i:0PAD_WIDTH$}.{f}")
554 }
555 Err(_) => chapter_number,
556 }
557 } else {
558 match chapter_number.parse::<i32>() {
559 Ok(n) => format!("{n:0PAD_WIDTH$}"),
560 Err(_) => chapter_number,
561 }
562 };
563 sorting_map.insert(chapter_number, chapter);
564 }
565
566 Ok(sorting_map.values().cloned().collect())
567 }
568 }
569
570 deserializer.deserialize_any(V)
571 }
572 #[cfg(feature = "serialize")]
573 pub fn serialize<S>(
574 to_use: &ChapterAggregateCollection,
575 serializer: S,
576 ) -> Result<S::Ok, S::Error>
577 where
578 S: Serializer,
579 {
580 use std::collections::HashMap;
581
582 let mut chapters: HashMap<String, ChapterAggregate> = HashMap::new();
583 for chapter in to_use {
584 chapters.insert(chapter.chapter.clone(), chapter.clone());
585 }
586 chapters.serialize(serializer)
587 }
588}
589
590pub(crate) mod manga_links_array_or_struct {
596 use crate::v5::MangaLinks;
597 #[cfg(feature = "serialize")]
598 use serde::Serialize;
599 use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
600 #[cfg(feature = "serialize")]
601 use serde::ser::Serializer;
602
603 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<MangaLinks>, D::Error>
605 where
606 D: Deserializer<'de>,
607 {
608 struct OptionMangaLinksVisitor;
609
610 impl<'de> Visitor<'de> for OptionMangaLinksVisitor {
611 type Value = Option<MangaLinks>;
612
613 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
614 formatter.write_str("some or none")
615 }
616
617 fn visit_none<E>(self) -> Result<Self::Value, E>
619 where
620 E: serde::de::Error,
621 {
622 Ok(None)
623 }
624
625 fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
627 where
628 D: Deserializer<'de>,
629 {
630 let manga_links = d.deserialize_any(MangaLinksVisitor)?;
631
632 let manga_links = if manga_links.has_no_links() {
633 None
634 } else {
635 Some(manga_links)
636 };
637
638 Ok(manga_links)
639 }
640
641 fn visit_unit<E>(self) -> Result<Self::Value, E>
643 where
644 E: serde::de::Error,
645 {
646 Ok(None)
647 }
648 }
649
650 struct MangaLinksVisitor;
651
652 impl<'de> Visitor<'de> for MangaLinksVisitor {
653 type Value = MangaLinks;
654
655 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
656 formatter.write_str("array or map")
657 }
658
659 fn visit_seq<A>(self, mut _seq: A) -> Result<Self::Value, A::Error>
661 where
662 A: SeqAccess<'de>,
663 {
664 Ok(Self::Value::default())
665 }
666
667 fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
669 where
670 M: MapAccess<'de>,
671 {
672 Deserialize::deserialize(serde::de::value::MapAccessDeserializer::new(map))
677 }
678 }
679
680 deserializer.deserialize_option(OptionMangaLinksVisitor)
681 }
682 #[cfg(feature = "serialize")]
683 pub fn serialize<S>(to_use: &Option<MangaLinks>, serializer: S) -> Result<S::Ok, S::Error>
684 where
685 S: Serializer,
686 {
687 match to_use {
688 None => serializer.serialize_none(),
689 Some(data) => data.serialize(serializer),
690 }
691 }
692}
693
694pub(crate) mod language_array_or_skip_null {
698 use mangadex_api_types::Language;
699 #[cfg(feature = "serialize")]
700 use serde::Serialize;
701 use serde::de::{Deserializer, SeqAccess, Visitor};
702 #[cfg(feature = "serialize")]
703 use serde::ser::Serializer;
704 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Language>, D::Error>
706 where
707 D: Deserializer<'de>,
708 {
709 struct V;
710
711 impl<'de> Visitor<'de> for V {
712 type Value = Vec<Language>;
713
714 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
715 formatter.write_str("a sequence of languages")
716 }
717
718 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
719 where
720 A: SeqAccess<'de>,
721 {
722 let mut languages = Vec::new();
723
724 while let Some(language) = seq.next_element::<Option<Language>>()? {
726 if let Some(language) = language {
728 languages.push(language);
729 }
730 }
731
732 Ok(languages)
733 }
734 }
735
736 deserializer.deserialize_seq(V)
737 }
738 #[cfg(feature = "serialize")]
739 pub fn serialize<S>(to_use: &Vec<Language>, serializer: S) -> Result<S::Ok, S::Error>
740 where
741 S: Serializer,
742 {
743 to_use.serialize(serializer)
744 }
745}
746
747pub fn mangadex_datetime_serialize<S>(
748 datetime: &MangaDexDateTime,
749 serializer: S,
750) -> Result<S::Ok, S::Error>
751where
752 S: serde::Serializer,
753{
754 serializer.serialize_str(datetime.to_string().as_str())
755}
756
757pub fn mangadex_datetime_serialize_option<S>(
758 datetime: &Option<MangaDexDateTime>,
759 serializer: S,
760) -> Result<S::Ok, S::Error>
761where
762 S: serde::Serializer,
763{
764 if let Some(d) = datetime {
765 serializer.serialize_str(d.to_string().as_str())
766 } else {
767 serializer.serialize_none()
768 }
769}
770
771#[cfg(test)]
772mod test {
773 use serde::{Deserialize, Serialize};
774
775 use mangadex_api_types::MangaDexDateTime;
776
777 #[derive(Serialize, Deserialize, Default)]
778 struct TestStruct {
779 #[serde(serialize_with = "crate::v5::mangadex_datetime_serialize")]
780 date: MangaDexDateTime,
781 }
782
783 #[tokio::test]
784 async fn mangadex_datetime_serialize_test() {
785 let test: TestStruct = Default::default();
786 println!("{}", serde_json::to_string_pretty(&test).unwrap());
787 }
788}