1use std::ffi::OsStr;
3use std::path::Path;
4
5use mime::{APPLICATION_OCTET_STREAM, IMAGE_STAR};
6use url::Url;
7
8use crate::comments::Comments;
9use crate::file_source::FileSource;
10use crate::files::{FileRef, Files};
11use crate::metadata::Metadata;
12use crate::prelude::*;
13use crate::teams::Members;
14use crate::types::id::{FileId, GameId, ModId};
15
16pub use crate::types::mods::{
17 CommunityOptions, Dependency, Event, EventType, Image, MaturityOption, Media, Mod, Platform,
18 Popularity, Ratings, Statistics, Tag, Visibility,
19};
20pub use crate::types::Logo;
21pub use crate::types::Status;
22
23#[derive(Clone)]
25pub struct Mods {
26 modio: Modio,
27 game: GameId,
28}
29
30impl Mods {
31 pub(crate) fn new(modio: Modio, game: GameId) -> Self {
32 Self { modio, game }
33 }
34
35 pub fn search(&self, filter: Filter) -> Query<Mod> {
39 let route = Route::GetMods { game_id: self.game };
40 Query::new(self.modio.clone(), route, filter)
41 }
42
43 pub fn get(&self, id: ModId) -> ModRef {
45 ModRef::new(self.modio.clone(), self.game, id)
46 }
47
48 #[allow(clippy::should_implement_trait)]
50 pub async fn add(self, options: AddModOptions) -> Result<Mod> {
51 let route = Route::AddMod { game_id: self.game };
52 self.modio
53 .request(route)
54 .multipart(Form::from(options))
55 .send()
56 .await
57 }
58
59 pub fn statistics(self, filter: Filter) -> Query<Statistics> {
63 let route = Route::GetModsStats { game_id: self.game };
64 Query::new(self.modio, route, filter)
65 }
66
67 pub fn events(self, filter: Filter) -> Query<Event> {
72 let route = Route::GetModsEvents { game_id: self.game };
73 Query::new(self.modio, route, filter)
74 }
75}
76
77#[derive(Clone)]
79pub struct ModRef {
80 modio: Modio,
81 game: GameId,
82 id: ModId,
83}
84
85impl ModRef {
86 pub(crate) fn new(modio: Modio, game: GameId, id: ModId) -> Self {
87 Self { modio, game, id }
88 }
89
90 pub async fn get(self) -> Result<Mod> {
92 let route = Route::GetMod {
93 game_id: self.game,
94 mod_id: self.id,
95 };
96 self.modio.request(route).send().await
97 }
98
99 pub fn files(&self) -> Files {
101 Files::new(self.modio.clone(), self.game, self.id)
102 }
103
104 pub fn file(&self, id: FileId) -> FileRef {
106 FileRef::new(self.modio.clone(), self.game, self.id, id)
107 }
108
109 pub fn metadata(&self) -> Metadata {
111 Metadata::new(self.modio.clone(), self.game, self.id)
112 }
113
114 pub fn tags(&self) -> Tags {
116 Tags::new(self.modio.clone(), self.game, self.id)
117 }
118
119 pub fn comments(&self) -> Comments {
121 Comments::new(self.modio.clone(), self.game, self.id)
122 }
123
124 pub fn dependencies(&self) -> Dependencies {
126 Dependencies::new(self.modio.clone(), self.game, self.id)
127 }
128
129 pub async fn statistics(self) -> Result<Statistics> {
131 let route = Route::GetModStats {
132 game_id: self.game,
133 mod_id: self.id,
134 };
135 self.modio.request(route).send().await
136 }
137
138 pub fn events(self, filter: Filter) -> Query<Event> {
142 let route = Route::GetModEvents {
143 game_id: self.game,
144 mod_id: self.id,
145 };
146 Query::new(self.modio, route, filter)
147 }
148
149 pub fn members(&self) -> Members {
151 Members::new(self.modio.clone(), self.game, self.id)
152 }
153
154 pub async fn edit(self, options: EditModOptions) -> Result<Editing<Mod>> {
156 let route = Route::EditMod {
157 game_id: self.game,
158 mod_id: self.id,
159 };
160 self.modio.request(route).form(&options).send().await
161 }
162
163 pub async fn delete(self) -> Result<()> {
165 let route = Route::DeleteMod {
166 game_id: self.game,
167 mod_id: self.id,
168 };
169 self.modio.request(route).send().await
170 }
171
172 pub async fn add_media(self, options: AddMediaOptions) -> Result<()> {
174 let route = Route::AddModMedia {
175 game_id: self.game,
176 mod_id: self.id,
177 };
178 self.modio
179 .request(route)
180 .multipart(Form::from(options))
181 .send::<Message>()
182 .await?;
183
184 Ok(())
185 }
186
187 pub async fn delete_media(self, options: DeleteMediaOptions) -> Result<Deletion> {
189 let route = Route::DeleteModMedia {
190 game_id: self.game,
191 mod_id: self.id,
192 };
193 self.modio.request(route).form(&options).send().await
194 }
195
196 pub async fn reorder_media(self, options: ReorderMediaOptions) -> Result<()> {
198 let route = Route::ReorderModMedia {
199 game_id: self.game,
200 mod_id: self.id,
201 };
202 self.modio.request(route).form(&options).send().await
203 }
204
205 pub async fn rate(self, rating: Rating) -> Result<()> {
207 let route = Route::RateMod {
208 game_id: self.game,
209 mod_id: self.id,
210 };
211 self.modio
212 .request(route)
213 .form(&rating)
214 .send::<Message>()
215 .await
216 .map(|_| ())
217 .or_else(|err| match (err.status(), err.error_ref()) {
218 (Some(StatusCode::BAD_REQUEST), Some(15028 | 15043)) => Ok(()),
219 _ => Err(err),
220 })
221 }
222
223 pub async fn subscribe(self) -> Result<()> {
225 let route = Route::SubscribeToMod {
226 game_id: self.game,
227 mod_id: self.id,
228 };
229 self.modio
230 .request(route)
231 .send::<Mod>()
232 .await
233 .map(|_| ())
234 .or_else(|err| match (err.status(), err.error_ref()) {
235 (Some(StatusCode::BAD_REQUEST), Some(15004)) => Ok(()),
236 _ => Err(err),
237 })
238 }
239
240 pub async fn unsubscribe(self) -> Result<()> {
242 let route = Route::UnsubscribeFromMod {
243 game_id: self.game,
244 mod_id: self.id,
245 };
246 self.modio.request(route).send().await.or_else(|err| {
247 match (err.status(), err.error_ref()) {
248 (Some(StatusCode::BAD_REQUEST), Some(15005)) => Ok(()),
249 _ => Err(err),
250 }
251 })
252 }
253}
254
255#[derive(Clone)]
257pub struct Dependencies {
258 modio: Modio,
259 game_id: GameId,
260 mod_id: ModId,
261}
262
263impl Dependencies {
264 fn new(modio: Modio, game_id: GameId, mod_id: ModId) -> Self {
265 Self {
266 modio,
267 game_id,
268 mod_id,
269 }
270 }
271
272 pub async fn list(self) -> Result<Vec<Dependency>> {
274 let route = Route::GetModDependencies {
275 game_id: self.game_id,
276 mod_id: self.mod_id,
277 };
278 Query::new(self.modio, route, Filter::default())
279 .collect()
280 .await
281 }
282
283 #[allow(clippy::iter_not_returning_iterator)]
285 pub async fn iter(self) -> Result<impl Stream<Item = Result<Dependency>>> {
286 let route = Route::GetModDependencies {
287 game_id: self.game_id,
288 mod_id: self.mod_id,
289 };
290 let filter = Filter::default();
291 Query::new(self.modio, route, filter).iter().await
292 }
293
294 #[allow(clippy::should_implement_trait)]
296 pub async fn add(self, options: EditDependenciesOptions) -> Result<()> {
297 let route = Route::AddModDependencies {
298 game_id: self.game_id,
299 mod_id: self.mod_id,
300 };
301 self.modio
302 .request(route)
303 .form(&options)
304 .send::<Message>()
305 .await?;
306 Ok(())
307 }
308
309 pub async fn delete(self, options: EditDependenciesOptions) -> Result<Deletion> {
311 let route = Route::DeleteModDependencies {
312 game_id: self.game_id,
313 mod_id: self.mod_id,
314 };
315 self.modio.request(route).form(&options).send().await
316 }
317}
318
319#[derive(Clone)]
321pub struct Tags {
322 modio: Modio,
323 game_id: GameId,
324 mod_id: ModId,
325}
326
327impl Tags {
328 fn new(modio: Modio, game_id: GameId, mod_id: ModId) -> Self {
329 Self {
330 modio,
331 game_id,
332 mod_id,
333 }
334 }
335
336 pub async fn list(self) -> Result<Vec<Tag>> {
338 let route = Route::GetModTags {
339 game_id: self.game_id,
340 mod_id: self.mod_id,
341 };
342 Query::new(self.modio, route, Filter::default())
343 .collect()
344 .await
345 }
346
347 #[allow(clippy::iter_not_returning_iterator)]
349 pub async fn iter(self) -> Result<impl Stream<Item = Result<Tag>>> {
350 let route = Route::GetModTags {
351 game_id: self.game_id,
352 mod_id: self.mod_id,
353 };
354 let filter = Filter::default();
355 Query::new(self.modio, route, filter).iter().await
356 }
357
358 #[allow(clippy::should_implement_trait)]
360 pub async fn add(self, options: EditTagsOptions) -> Result<()> {
361 let route = Route::AddModTags {
362 game_id: self.game_id,
363 mod_id: self.mod_id,
364 };
365 self.modio
366 .request(route)
367 .form(&options)
368 .send::<Message>()
369 .await?;
370 Ok(())
371 }
372
373 pub async fn delete(self, options: EditTagsOptions) -> Result<Deletion> {
375 let route = Route::DeleteModTags {
376 game_id: self.game_id,
377 mod_id: self.mod_id,
378 };
379 self.modio.request(route).form(&options).send().await
380 }
381}
382
383#[rustfmt::skip]
431pub mod filters {
432 #[doc(inline)]
433 pub use crate::filter::prelude::Fulltext;
434 #[doc(inline)]
435 pub use crate::filter::prelude::Id;
436 #[doc(inline)]
437 pub use crate::filter::prelude::Name;
438 #[doc(inline)]
439 pub use crate::filter::prelude::NameId;
440 #[doc(inline)]
441 pub use crate::filter::prelude::Status;
442 #[doc(inline)]
443 pub use crate::filter::prelude::DateAdded;
444 #[doc(inline)]
445 pub use crate::filter::prelude::DateUpdated;
446 #[doc(inline)]
447 pub use crate::filter::prelude::DateLive;
448 #[doc(inline)]
449 pub use crate::filter::prelude::SubmittedBy;
450
451 filter!(GameId, GAME_ID, "game_id", Eq, NotEq, In, Cmp, OrderBy);
452 filter!(Visible, VISIBLE, "visible", Eq);
453 filter!(MaturityOption, MATURITY_OPTION, "maturity_option", Eq, Cmp, Bit);
454 filter!(Summary, SUMMARY, "summary", Like);
455 filter!(Description, DESCRIPTION, "description", Like);
456 filter!(Homepage, HOMEPAGE, "homepage_url", Eq, NotEq, Like, In);
457 filter!(Modfile, MODFILE, "modfile", Eq, NotEq, In, Cmp);
458 filter!(MetadataBlob, METADATA_BLOB, "metadata_blob", Eq, NotEq, Like);
459 filter!(MetadataKVP, METADATA_KVP, "metadata_kvp", Eq, NotEq, Like);
460 filter!(Tags, TAGS, "tags", Eq, NotEq, Like, In);
461
462 filter!(Downloads, DOWNLOADS, "downloads", OrderBy);
463 filter!(Popular, POPULAR, "popular", OrderBy);
464 filter!(Ratings, RATINGS, "ratings", OrderBy);
465 filter!(Subscribers, SUBSCRIBERS, "subscribers", OrderBy);
466
467 pub mod events {
494 #[doc(inline)]
495 pub use crate::filter::prelude::Id;
496 #[doc(inline)]
497 pub use crate::filter::prelude::ModId;
498 #[doc(inline)]
499 pub use crate::filter::prelude::DateAdded;
500
501 filter!(UserId, USER_ID, "user_id", Eq, NotEq, In, Cmp, OrderBy);
502 filter!(EventType, EVENT_TYPE, "event_type", Eq, NotEq, In, OrderBy);
503 }
504
505 pub mod stats {
532 #[doc(inline)]
533 pub use crate::filter::prelude::ModId;
534
535 filter!(Popularity, POPULARITY, "popularity_rank_position", Eq, NotEq, In, Cmp, OrderBy);
536 filter!(Downloads, DOWNLOADS, "downloads_total", Eq, NotEq, In, Cmp, OrderBy);
537 filter!(Subscribers, SUBSCRIBERS, "subscribers_total", Eq, NotEq, In, Cmp, OrderBy);
538 filter!(RatingsPositive, RATINGS_POSITIVE, "ratings_positive", Eq, NotEq, In, Cmp, OrderBy);
539 filter!(RatingsNegative, RATINGS_NEGATIVE, "ratings_negative", Eq, NotEq, In, Cmp, OrderBy);
540 }
541}
542
543#[derive(Clone, Copy)]
544pub enum Rating {
545 Positive,
546 Negative,
547 None,
548}
549
550#[doc(hidden)]
551impl serde::ser::Serialize for Rating {
552 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
553 where
554 S: serde::ser::Serializer,
555 {
556 use serde::ser::SerializeMap;
557
558 let mut map = serializer.serialize_map(Some(1))?;
559 match self {
560 Rating::Negative => map.serialize_entry("rating", "-1")?,
561 Rating::Positive => map.serialize_entry("rating", "1")?,
562 Rating::None => map.serialize_entry("rating", "0")?,
563 }
564 map.end()
565 }
566}
567
568pub struct AddModOptions {
569 visible: Option<Visibility>,
570 logo: FileSource,
571 name: String,
572 name_id: Option<String>,
573 summary: String,
574 description: Option<String>,
575 homepage_url: Option<Url>,
576 stock: Option<u32>,
577 maturity_option: Option<MaturityOption>,
578 metadata_blob: Option<String>,
579 tags: Option<Vec<String>>,
580}
581
582impl AddModOptions {
583 pub fn new<T, P>(name: T, logo: P, summary: T) -> AddModOptions
584 where
585 T: Into<String>,
586 P: AsRef<Path>,
587 {
588 let filename = logo
589 .as_ref()
590 .file_name()
591 .and_then(OsStr::to_str)
592 .map_or_else(String::new, ToString::to_string);
593
594 let logo = FileSource::new_from_file(logo, filename, IMAGE_STAR);
595
596 AddModOptions {
597 name: name.into(),
598 logo,
599 summary: summary.into(),
600 visible: None,
601 name_id: None,
602 description: None,
603 homepage_url: None,
604 stock: None,
605 maturity_option: None,
606 metadata_blob: None,
607 tags: None,
608 }
609 }
610
611 #[must_use]
612 pub fn visible(self, v: bool) -> Self {
613 Self {
614 visible: if v {
615 Some(Visibility::PUBLIC)
616 } else {
617 Some(Visibility::HIDDEN)
618 },
619 ..self
620 }
621 }
622
623 option!(name_id);
624 option!(description);
625 option!(homepage_url: Url);
626 option!(stock: u32);
627 option!(maturity_option: MaturityOption);
628 option!(metadata_blob);
629
630 #[must_use]
631 pub fn tags(self, tags: &[String]) -> Self {
632 Self {
633 tags: Some(tags.to_vec()),
634 ..self
635 }
636 }
637}
638
639#[doc(hidden)]
640impl From<AddModOptions> for Form {
641 fn from(opts: AddModOptions) -> Form {
642 let mut form = Form::new();
643
644 form = form.text("name", opts.name).text("summary", opts.summary);
645
646 if let Some(visible) = opts.visible {
647 form = form.text("visible", visible.to_string());
648 }
649 if let Some(name_id) = opts.name_id {
650 form = form.text("name_id", name_id);
651 }
652 if let Some(desc) = opts.description {
653 form = form.text("description", desc);
654 }
655 if let Some(url) = opts.homepage_url {
656 form = form.text("homepage_url", url.to_string());
657 }
658 if let Some(stock) = opts.stock {
659 form = form.text("stock", stock.to_string());
660 }
661 if let Some(maturity_option) = opts.maturity_option {
662 form = form.text("maturity_option", maturity_option.to_string());
663 }
664 if let Some(metadata_blob) = opts.metadata_blob {
665 form = form.text("metadata_blob", metadata_blob);
666 }
667 if let Some(tags) = opts.tags {
668 for tag in tags {
669 form = form.text("tags[]", tag);
670 }
671 }
672 form.part("logo", opts.logo.into())
673 }
674}
675
676#[derive(Debug, Default)]
677pub struct EditModOptions {
678 params: std::collections::BTreeMap<&'static str, String>,
679}
680
681impl EditModOptions {
682 option!(status: Status >> "status");
683
684 #[must_use]
685 pub fn visible(self, v: bool) -> Self {
686 let value = if v {
687 Visibility::PUBLIC
688 } else {
689 Visibility::HIDDEN
690 };
691 let mut params = self.params;
692 params.insert("visible", value.to_string());
693 Self { params }
694 }
695
696 option!(visibility: Visibility >> "visible");
697 option!(name >> "name");
698 option!(name_id >> "name_id");
699 option!(summary >> "summary");
700 option!(description >> "description");
701 option!(homepage_url: Url >> "homepage_url");
702 option!(stock >> "stock");
703 option!(maturity_option: MaturityOption >> "maturity_option");
704 option!(metadata_blob >> "metadata_blob");
705}
706
707impl_serialize_params!(EditModOptions >> params);
708
709pub struct EditDependenciesOptions {
710 dependencies: Vec<ModId>,
711}
712
713impl EditDependenciesOptions {
714 pub fn new(dependencies: &[ModId]) -> Self {
715 Self {
716 dependencies: dependencies.to_vec(),
717 }
718 }
719
720 pub fn one(dependency: ModId) -> Self {
721 Self {
722 dependencies: vec![dependency],
723 }
724 }
725}
726
727#[doc(hidden)]
728impl serde::ser::Serialize for EditDependenciesOptions {
729 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
730 where
731 S: serde::ser::Serializer,
732 {
733 use serde::ser::SerializeMap;
734
735 let mut map = serializer.serialize_map(Some(self.dependencies.len()))?;
736 for d in &self.dependencies {
737 map.serialize_entry("dependencies[]", d)?;
738 }
739 map.end()
740 }
741}
742
743pub struct EditTagsOptions {
744 tags: Vec<String>,
745}
746
747impl EditTagsOptions {
748 pub fn new(tags: &[String]) -> Self {
749 Self {
750 tags: tags.to_vec(),
751 }
752 }
753}
754
755#[doc(hidden)]
756impl serde::ser::Serialize for EditTagsOptions {
757 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
758 where
759 S: serde::ser::Serializer,
760 {
761 use serde::ser::SerializeMap;
762
763 let mut map = serializer.serialize_map(Some(self.tags.len()))?;
764 for t in &self.tags {
765 map.serialize_entry("tags[]", t)?;
766 }
767 map.end()
768 }
769}
770
771#[derive(Default)]
772pub struct AddMediaOptions {
773 logo: Option<FileSource>,
774 images_zip: Option<FileSource>,
775 images: Option<Vec<FileSource>>,
776 youtube: Option<Vec<String>>,
777 sketchfab: Option<Vec<String>>,
778}
779
780impl AddMediaOptions {
781 #[must_use]
782 pub fn logo<P: AsRef<Path>>(self, logo: P) -> Self {
783 let logo = logo.as_ref();
784 let filename = logo
785 .file_name()
786 .and_then(OsStr::to_str)
787 .map_or_else(String::new, ToString::to_string);
788
789 Self {
790 logo: Some(FileSource::new_from_file(logo, filename, IMAGE_STAR)),
791 ..self
792 }
793 }
794
795 #[must_use]
796 pub fn images_zip<P: AsRef<Path>>(self, images: P) -> Self {
797 Self {
798 images_zip: Some(FileSource::new_from_file(
799 images,
800 "images.zip".into(),
801 APPLICATION_OCTET_STREAM,
802 )),
803 ..self
804 }
805 }
806
807 #[must_use]
808 pub fn images<P: AsRef<Path>>(self, images: &[P]) -> Self {
809 Self {
810 images: Some(
811 images
812 .iter()
813 .map(|p| {
814 let file = p.as_ref();
815 let filename = file
816 .file_name()
817 .and_then(OsStr::to_str)
818 .map_or_else(String::new, ToString::to_string);
819
820 FileSource::new_from_file(file, filename, IMAGE_STAR)
821 })
822 .collect(),
823 ),
824 ..self
825 }
826 }
827
828 #[must_use]
829 pub fn youtube(self, urls: &[String]) -> Self {
830 Self {
831 youtube: Some(urls.to_vec()),
832 ..self
833 }
834 }
835
836 #[must_use]
837 pub fn sketchfab(self, urls: &[String]) -> Self {
838 Self {
839 sketchfab: Some(urls.to_vec()),
840 ..self
841 }
842 }
843}
844
845#[doc(hidden)]
846impl From<AddMediaOptions> for Form {
847 fn from(opts: AddMediaOptions) -> Form {
848 let mut form = Form::new();
849 if let Some(logo) = opts.logo {
850 form = form.part("logo", logo.into());
851 }
852 if let Some(zip) = opts.images_zip {
853 form = form.part("images", zip.into());
854 }
855 if let Some(images) = opts.images {
856 for (i, image) in images.into_iter().enumerate() {
857 form = form.part(format!("image{i}"), image.into());
858 }
859 }
860 if let Some(youtube) = opts.youtube {
861 for url in youtube {
862 form = form.text("youtube[]", url);
863 }
864 }
865 if let Some(sketchfab) = opts.sketchfab {
866 for url in sketchfab {
867 form = form.text("sketchfab[]", url);
868 }
869 }
870 form
871 }
872}
873
874#[derive(Default)]
875pub struct DeleteMediaOptions {
876 images: Option<Vec<String>>,
877 youtube: Option<Vec<String>>,
878 sketchfab: Option<Vec<String>>,
879}
880
881impl DeleteMediaOptions {
882 #[must_use]
883 pub fn images(self, images: &[String]) -> Self {
884 Self {
885 images: Some(images.to_vec()),
886 ..self
887 }
888 }
889
890 #[must_use]
891 pub fn youtube(self, urls: &[String]) -> Self {
892 Self {
893 youtube: Some(urls.to_vec()),
894 ..self
895 }
896 }
897
898 #[must_use]
899 pub fn sketchfab(self, urls: &[String]) -> Self {
900 Self {
901 sketchfab: Some(urls.to_vec()),
902 ..self
903 }
904 }
905}
906
907#[doc(hidden)]
908impl serde::ser::Serialize for DeleteMediaOptions {
909 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
910 where
911 S: serde::ser::Serializer,
912 {
913 use serde::ser::SerializeMap;
914
915 let len = self.images.as_ref().map(Vec::len).unwrap_or_default()
916 + self.youtube.as_ref().map(Vec::len).unwrap_or_default()
917 + self.sketchfab.as_ref().map(Vec::len).unwrap_or_default();
918 let mut map = serializer.serialize_map(Some(len))?;
919 if let Some(ref images) = self.images {
920 for e in images {
921 map.serialize_entry("images[]", e)?;
922 }
923 }
924 if let Some(ref urls) = self.youtube {
925 for e in urls {
926 map.serialize_entry("youtube[]", e)?;
927 }
928 }
929 if let Some(ref urls) = self.sketchfab {
930 for e in urls {
931 map.serialize_entry("sketchfab[]", e)?;
932 }
933 }
934 map.end()
935 }
936}
937
938#[derive(Default)]
939pub struct ReorderMediaOptions {
940 images: Option<Vec<String>>,
941 youtube: Option<Vec<String>>,
942 sketchfab: Option<Vec<String>>,
943}
944
945impl ReorderMediaOptions {
946 #[must_use]
947 pub fn images(self, images: &[String]) -> Self {
948 Self {
949 images: Some(images.to_vec()),
950 ..self
951 }
952 }
953
954 #[must_use]
955 pub fn youtube(self, urls: &[String]) -> Self {
956 Self {
957 youtube: Some(urls.to_vec()),
958 ..self
959 }
960 }
961
962 #[must_use]
963 pub fn sketchfab(self, urls: &[String]) -> Self {
964 Self {
965 sketchfab: Some(urls.to_vec()),
966 ..self
967 }
968 }
969}
970
971#[doc(hidden)]
972impl serde::ser::Serialize for ReorderMediaOptions {
973 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
974 where
975 S: serde::ser::Serializer,
976 {
977 use serde::ser::SerializeMap;
978
979 let len = self.images.as_ref().map(Vec::len).unwrap_or_default()
980 + self.youtube.as_ref().map(Vec::len).unwrap_or_default()
981 + self.sketchfab.as_ref().map(Vec::len).unwrap_or_default();
982 let mut map = serializer.serialize_map(Some(len))?;
983 if let Some(ref images) = self.images {
984 for e in images {
985 map.serialize_entry("images[]", e)?;
986 }
987 }
988 if let Some(ref urls) = self.youtube {
989 for e in urls {
990 map.serialize_entry("youtube[]", e)?;
991 }
992 }
993 if let Some(ref urls) = self.sketchfab {
994 for e in urls {
995 map.serialize_entry("sketchfab[]", e)?;
996 }
997 }
998 map.end()
999 }
1000}