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#[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#[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 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 pub struct VirusResult: u8 {
200 const NO_THREATS_DETECTED = 0;
201 const MALICIOUS = 1;
202 const POTENTIALLY_HARMFUL = 2;
203 }
204}
205
206#[derive(Debug, Deserialize)]
208#[non_exhaustive]
209pub struct FileHash {
210 pub md5: String,
211}
212
213#[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#[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 pub struct PlatformStatus: u8 {
245 const PENDING = 0;
246 const APPROVED = 1;
247 const DENIED = 2;
248 }
249}