mangadex_api_schema_rust/
v5.rs

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// TODO: Find a way to reduce the boilerplate for this.
54// `struct-variant` (https://docs.rs/struct-variant) is a potential candidate for this.
55#[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 resource.
63    Manga(MangaAttributes),
64    /// Chapter resource.
65    Chapter(ChapterAttributes),
66    /// A Cover Art for a manga.
67    ///
68    /// On manga resources, only one cover art resource relation is returned,
69    /// marking the primary cover if there are more than one. By default, this will be the latest
70    /// volume's cover art. To see all the covers for a given manga, use the cover search endpoint.
71    CoverArt(CoverAttributes),
72    /// Author resource.
73    Author(AuthorAttributes),
74    /// ScanlationGroup resource.
75    ScanlationGroup(ScanlationGroupAttributes),
76    /// Tag resource.
77    Tag(TagAttributes),
78    /// User resource.
79    User(UserAttributes),
80    /// CustomList resource.
81    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    /// Related Manga type.
288    ///
289    /// <https://api.mangadex.org/docs/static-data/#manga-related-enum>
290    ///
291    /// This is only present for a Manga entity and a Manga relationship.
292    #[serde(skip_serializing_if = "Option::is_none")]
293    #[serde(default)]
294    pub related: Option<MangaRelation>,
295    /// Contains object attributes for the type.
296    ///
297    /// Present if [Reference Expansion](https://api.mangadex.org/docs/reference-expansion/) is applied.
298    #[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
332/// Originally a Deserializer helper to handle JSON array or object types.
333///
334/// MangaDex currently returns an empty array when the localized string field isn't present.
335///
336/// The Serializer was added in 0.2.0 for pratical and necessities reason
337pub(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
386/// Originally a Deserializer helper to handle JSON array or object types.
387///
388/// MangaDex sometimes returns an array instead of a JSON object for the volume aggregate field.
389///
390/// The Serializer was added in 0.2.0 for pratical and necessities reason
391pub(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                // Temporary collection to sort the results because serde doesn't seem to iterate
437                // through the map in the order they appear.
438                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                                //
447                                let (i, f) = volume_number.split_once('.').unwrap();
448                                let i = i.parse::<i32>().unwrap();
449                                // Pad the whole number part so that it is sorted correctly with the
450                                // other keys.
451                                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
489/// Originally a Deserializer helper to handle JSON array or object types.
490///
491/// MangaDex sometimes returns an array instead of a JSON object for the chapter aggregate field.
492///
493/// The Serializer was added in 0.2.0 for pratical and necessities reason
494pub(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                // Temporary collection to sort the results because serde doesn't seem to iterate
539                // through the map in the order they appear.
540                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                                //
549                                let (i, f) = chapter_number.split_once('.').unwrap();
550                                let i = i.parse::<i32>().unwrap();
551                                // Pad the whole number part so that it is sorted correctly with the
552                                // other keys.
553                                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
590/// Originally a Deserializer helper to handle JSON array or object types.
591///
592/// MangaDex sometimes returns an array instead of a JSON object for the `links` field for `MangaAttributes`.
593///
594/// The Serializer was added in 0.2.0 for pratical and necessities reason
595pub(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    /// Deserialize a `MangaLinks` from a JSON value or none.
604    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            /// Deserialize a `MangaLinks` from none.
618            fn visit_none<E>(self) -> Result<Self::Value, E>
619            where
620                E: serde::de::Error,
621            {
622                Ok(None)
623            }
624
625            /// Deserialize a `MangaLinks` from a JSON value.
626            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            /// Deserialize a `MangaLinks` from none (`null`).
642            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            /// Deserialize a `MangaLinks` from a sequence (array).
660            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            /// Deserialize a `MangaLinks` from a map (JSON object).
668            fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
669            where
670                M: MapAccess<'de>,
671            {
672                // `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
673                // into a `Deserializer`, allowing it to be used as the input to T's
674                // `Deserialize` implementation. T then deserializes itself using
675                // the entries from the map visitor.
676                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
694/// Originally a Deserializer for an array of languages, discarding elements that are `null`.
695///
696/// The Serializer was added in 0.2.0 for pratical and necessities reason
697pub(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    /// Deserialize a `Vec<Language>` from an array of JSON values.
705    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                // Skip over invalid or `null` languages.
725                while let Some(language) = seq.next_element::<Option<Language>>()? {
726                    // `language` will be `None` if an element value is `null` from JSON.
727                    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}