modio/types/
files.rs

1use std::fmt;
2
3use serde::de::{Deserialize, Deserializer, IgnoredAny, MapAccess, Visitor};
4use serde_derive::Deserialize;
5use url::Url;
6
7use crate::types::{DeserializeField, MissingField, TargetPlatform};
8
9use super::id::{FileId, ModId};
10use super::{utils, Timestamp};
11
12/// See the [Modfile Object](https://docs.mod.io/restapiref/#modfile-object) docs for more information.
13#[derive(Debug)]
14#[non_exhaustive]
15pub struct File {
16    pub id: FileId,
17    pub mod_id: ModId,
18    pub date_added: Timestamp,
19    pub virus_scan: VirusScan,
20    pub filesize: u64,
21    pub filesize_uncompressed: u64,
22    pub filehash: FileHash,
23    pub filename: String,
24    pub version: Option<String>,
25    pub changelog: Option<String>,
26    pub metadata_blob: Option<String>,
27    pub download: Download,
28    pub platforms: Vec<Platform>,
29}
30
31impl<'de> Deserialize<'de> for File {
32    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
33        #[derive(Deserialize)]
34        #[serde(field_identifier, rename_all = "snake_case")]
35        enum Field {
36            Id,
37            ModId,
38            DateAdded,
39            DateScanned,
40            VirusStatus,
41            VirusPositive,
42            Filesize,
43            FilesizeUncompressed,
44            Filehash,
45            Filename,
46            Version,
47            Changelog,
48            MetadataBlob,
49            Download,
50            Platforms,
51            #[allow(dead_code)]
52            Other(String),
53        }
54
55        struct FileVisitor;
56
57        impl<'de> Visitor<'de> for FileVisitor {
58            type Value = File;
59
60            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61                formatter.write_str("struct File")
62            }
63
64            fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
65                let mut id = None;
66                let mut mod_id = None;
67                let mut date_added = None;
68                let mut date_scanned = None;
69                let mut virus_status = None;
70                let mut virus_result = None;
71                let mut filesize = None;
72                let mut filesize_uncompressed = None;
73                let mut filehash = None;
74                let mut filename = None;
75                let mut version = None;
76                let mut changelog = None;
77                let mut metadata_blob = None;
78                let mut download = None;
79                let mut platforms = None;
80
81                while let Some(key) = map.next_key()? {
82                    match key {
83                        Field::Id => {
84                            id.deserialize_value("id", &mut map)?;
85                        }
86                        Field::ModId => {
87                            mod_id.deserialize_value("mod_id", &mut map)?;
88                        }
89                        Field::DateAdded => {
90                            date_added.deserialize_value("date_added", &mut map)?;
91                        }
92                        Field::DateScanned => {
93                            date_scanned.deserialize_value("date_scanned", &mut map)?;
94                        }
95                        Field::VirusStatus => {
96                            virus_status.deserialize_value("virus_status", &mut map)?;
97                        }
98                        Field::VirusPositive => {
99                            virus_result.deserialize_value("virus_positive", &mut map)?;
100                        }
101                        Field::Filesize => {
102                            filesize.deserialize_value("filesize", &mut map)?;
103                        }
104                        Field::FilesizeUncompressed => {
105                            filesize_uncompressed
106                                .deserialize_value("filesize_uncompressed", &mut map)?;
107                        }
108                        Field::Filehash => {
109                            filehash.deserialize_value("filehash", &mut map)?;
110                        }
111                        Field::Filename => {
112                            filename.deserialize_value("filename", &mut map)?;
113                        }
114                        Field::Version => {
115                            version.deserialize_value("version", &mut map)?;
116                        }
117                        Field::Changelog => {
118                            changelog.deserialize_value("changelog", &mut map)?;
119                        }
120                        Field::MetadataBlob => {
121                            metadata_blob.deserialize_value("metadata_blob", &mut map)?;
122                        }
123                        Field::Download => {
124                            download.deserialize_value("download", &mut map)?;
125                        }
126                        Field::Platforms => {
127                            platforms.deserialize_value("platforms", &mut map)?;
128                        }
129                        Field::Other(_) => {
130                            map.next_value::<IgnoredAny>()?;
131                        }
132                    }
133                }
134
135                let id = id.missing_field("id")?;
136                let mod_id = mod_id.missing_field("mod_id")?;
137                let date_added = date_added.missing_field("date_added")?;
138                let date_scanned = date_scanned.missing_field("date_scanned")?;
139                let virus_status = virus_status.missing_field("virus_status")?;
140                let virus_result = virus_result.missing_field("virus_positive")?;
141                let filesize = filesize.missing_field("filesize")?;
142                let filesize_uncompressed =
143                    filesize_uncompressed.missing_field("filesize_uncompressed")?;
144                let filehash = filehash.missing_field("filehash")?;
145                let filename = filename.missing_field("filename")?;
146                let version = version.missing_field("version")?;
147                let changelog = changelog.missing_field("changelog")?;
148                let metadata_blob = metadata_blob.missing_field("metadata_blob")?;
149                let download = download.missing_field("download")?;
150                let platforms = platforms.missing_field("platforms")?;
151
152                Ok(File {
153                    id,
154                    mod_id,
155                    date_added,
156                    virus_scan: VirusScan {
157                        date_scanned,
158                        status: virus_status,
159                        result: virus_result,
160                    },
161                    filesize,
162                    filesize_uncompressed,
163                    filehash,
164                    filename,
165                    version,
166                    changelog,
167                    metadata_blob,
168                    download,
169                    platforms,
170                })
171            }
172        }
173
174        deserializer.deserialize_map(FileVisitor)
175    }
176}
177
178/// See the [Modfile Object](https://docs.mod.io/restapiref/#modfile-object) docs for more information.
179#[derive(Debug)]
180#[non_exhaustive]
181pub struct VirusScan {
182    pub date_scanned: Timestamp,
183    pub status: VirusStatus,
184    pub result: VirusResult,
185}
186
187newtype_enum! {
188    /// See the [Modfile Object](https://docs.mod.io/restapiref/#modfile-object) docs for more information.
189    pub struct VirusStatus: u8 {
190        const NOT_SCANNED       = 0;
191        const SCAN_COMPLETED    = 1;
192        const IN_PROGRESS       = 2;
193        const TOO_LARGE_TO_SCAN = 3;
194        const FILE_NOT_FOUND    = 4;
195        const ERROR_SCANNING    = 5;
196    }
197
198    /// See the [Modfile Object](https://docs.mod.io/restapiref/#modfile-object) docs for more information.
199    pub struct VirusResult: u8 {
200        const NO_THREATS_DETECTED = 0;
201        const MALICIOUS           = 1;
202        const POTENTIALLY_HARMFUL = 2;
203    }
204}
205
206/// See the [Filehash Object](https://docs.mod.io/restapiref/#filehash-object) docs for more information.
207#[derive(Debug, Deserialize)]
208#[non_exhaustive]
209pub struct FileHash {
210    pub md5: String,
211}
212
213/// See the [Download Object](https://docs.mod.io/restapiref/#download-object) docs for more information.
214#[derive(Deserialize)]
215#[non_exhaustive]
216pub struct Download {
217    #[serde(with = "utils::url")]
218    pub binary_url: Url,
219    pub date_expires: Timestamp,
220}
221
222impl fmt::Debug for Download {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        f.debug_struct("Download")
225            .field("binary_url", &self.binary_url.as_str())
226            .field("date_expires", &self.date_expires)
227            .finish()
228    }
229}
230
231/// See the [Modfile Platform Object](https://docs.mod.io/restapiref/#modfile-platform-object) docs for more
232/// information.
233#[derive(Debug, Deserialize)]
234#[non_exhaustive]
235pub struct Platform {
236    #[serde(rename = "platform")]
237    pub target: TargetPlatform,
238    pub status: PlatformStatus,
239}
240
241newtype_enum! {
242    /// See the [Modfile Platform Object](https://docs.mod.io/restapiref/#modfile-platform-object) docs for
243    /// more information.
244    pub struct PlatformStatus: u8 {
245        const PENDING  = 0;
246        const APPROVED = 1;
247        const DENIED   = 2;
248    }
249}