1use serde::{Deserialize, Serialize, Serializer};
2use serde_json::Map;
3
4use super::author::OutputAuthor;
5use super::copyright::OutputCopyright;
6use super::email::OutputEmail;
7use super::holder::OutputHolder;
8use super::license_detection::OutputLicenseDetection;
9use super::license_match::OutputMatch;
10use super::license_policy_entry::OutputLicensePolicyEntry;
11use super::package_data::OutputPackageData;
12use super::serde_helpers::insert_json;
13use super::tallies::OutputTallies;
14use super::url::OutputURL;
15
16#[derive(Debug, Clone, Deserialize)]
17pub struct OutputFileInfo {
18 pub name: String,
19 pub base_name: String,
20 pub extension: String,
21 pub path: String,
22 #[serde(rename = "type")]
23 pub file_type: crate::models::FileType,
24 pub mime_type: Option<String>,
25 pub file_type_label: Option<String>,
26 pub size: u64,
27 pub date: Option<String>,
28 pub sha1: Option<String>,
29 pub md5: Option<String>,
30 pub sha256: Option<String>,
31 pub sha1_git: Option<String>,
32 pub programming_language: Option<String>,
33 #[serde(default)]
34 pub package_data: Vec<OutputPackageData>,
35 #[serde(rename = "detected_license_expression_spdx")]
36 pub license_expression: Option<String>,
37 #[serde(default)]
38 pub license_detections: Vec<OutputLicenseDetection>,
39 #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub license_clues: Vec<OutputMatch>,
41 pub percentage_of_license_text: Option<f64>,
42 #[serde(default)]
43 pub copyrights: Vec<OutputCopyright>,
44 #[serde(default)]
45 pub holders: Vec<OutputHolder>,
46 #[serde(default)]
47 pub authors: Vec<OutputAuthor>,
48 #[serde(default, skip_serializing_if = "Vec::is_empty")]
49 pub emails: Vec<OutputEmail>,
50 #[serde(default)]
51 pub urls: Vec<OutputURL>,
52 #[serde(default)]
53 pub for_packages: Vec<String>,
54 #[serde(default)]
55 pub scan_errors: Vec<String>,
56 pub license_policy: Option<Vec<OutputLicensePolicyEntry>>,
57 pub is_generated: Option<bool>,
58 pub is_binary: Option<bool>,
59 pub is_text: Option<bool>,
60 pub is_archive: Option<bool>,
61 pub is_media: Option<bool>,
62 pub is_source: Option<bool>,
63 pub is_script: Option<bool>,
64 pub files_count: Option<usize>,
65 pub dirs_count: Option<usize>,
66 pub size_count: Option<u64>,
67 pub source_count: Option<usize>,
68 #[serde(default, skip_serializing_if = "is_false")]
69 pub is_legal: bool,
70 #[serde(default, skip_serializing_if = "is_false")]
71 pub is_manifest: bool,
72 #[serde(default, skip_serializing_if = "is_false")]
73 pub is_readme: bool,
74 #[serde(default, skip_serializing_if = "is_false")]
75 pub is_top_level: bool,
76 #[serde(default, skip_serializing_if = "is_false")]
77 pub is_key_file: bool,
78 #[serde(default, skip_serializing_if = "is_false")]
79 pub is_community: bool,
80 #[serde(default, skip_serializing_if = "Vec::is_empty")]
81 pub facets: Vec<String>,
82 pub tallies: Option<OutputTallies>,
83}
84
85impl OutputFileInfo {
86 pub(crate) fn should_serialize_info_surface(&self) -> bool {
87 self.date.is_some()
88 || self.sha1.is_some()
89 || self.md5.is_some()
90 || self.sha256.is_some()
91 || self.sha1_git.is_some()
92 || self.mime_type.is_some()
93 || self.file_type_label.is_some()
94 || self.programming_language.is_some()
95 || self.is_binary.is_some()
96 || self.is_text.is_some()
97 || self.is_archive.is_some()
98 || self.is_media.is_some()
99 || self.is_source.is_some()
100 || self.is_script.is_some()
101 || self.files_count.is_some()
102 || self.dirs_count.is_some()
103 || self.size_count.is_some()
104 }
105
106 pub(crate) fn should_serialize_license_surface(&self) -> bool {
107 self.license_expression.is_some()
108 || !self.license_detections.is_empty()
109 || !self.license_clues.is_empty()
110 || self.percentage_of_license_text.is_some()
111 }
112}
113
114impl Serialize for OutputFileInfo {
115 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
116 where
117 S: Serializer,
118 {
119 let mut map = Map::new();
120 insert_json(&mut map, "path", &self.path)?;
121 insert_json(&mut map, "type", &self.file_type)?;
122 insert_json(&mut map, "name", &self.name)?;
123 insert_json(&mut map, "base_name", &self.base_name)?;
124 insert_json(&mut map, "extension", &self.extension)?;
125 insert_json(&mut map, "size", self.size)?;
126
127 if self.should_serialize_info_surface() {
128 insert_json(&mut map, "date", &self.date)?;
129 insert_json(&mut map, "sha1", self.sha1.as_ref())?;
130 insert_json(&mut map, "md5", self.md5.as_ref())?;
131 insert_json(&mut map, "sha256", self.sha256.as_ref())?;
132 insert_json(&mut map, "sha1_git", self.sha1_git.as_ref())?;
133 insert_json(&mut map, "mime_type", &self.mime_type)?;
134 insert_json(&mut map, "file_type", &self.file_type_label)?;
135 insert_json(&mut map, "programming_language", &self.programming_language)?;
136 insert_json(&mut map, "is_binary", self.is_binary)?;
137 insert_json(&mut map, "is_text", self.is_text)?;
138 insert_json(&mut map, "is_archive", self.is_archive)?;
139 insert_json(&mut map, "is_media", self.is_media)?;
140 insert_json(&mut map, "is_source", self.is_source)?;
141 insert_json(&mut map, "is_script", self.is_script)?;
142 insert_json(&mut map, "files_count", self.files_count)?;
143 insert_json(&mut map, "dirs_count", self.dirs_count)?;
144 insert_json(&mut map, "size_count", self.size_count)?;
145 }
146
147 insert_json(&mut map, "package_data", &self.package_data)?;
148 insert_json(
149 &mut map,
150 "detected_license_expression_spdx",
151 &self.license_expression,
152 )?;
153 insert_json(&mut map, "license_detections", &self.license_detections)?;
154 if self.should_serialize_license_surface() {
155 insert_json(&mut map, "license_clues", &self.license_clues)?;
156 }
157 if self.percentage_of_license_text.is_some() {
158 insert_json(
159 &mut map,
160 "percentage_of_license_text",
161 self.percentage_of_license_text,
162 )?;
163 }
164 insert_json(&mut map, "copyrights", &self.copyrights)?;
165 insert_json(&mut map, "holders", &self.holders)?;
166 insert_json(&mut map, "authors", &self.authors)?;
167 if !self.emails.is_empty() {
168 insert_json(&mut map, "emails", &self.emails)?;
169 }
170 insert_json(&mut map, "urls", &self.urls)?;
171 insert_json(&mut map, "for_packages", &self.for_packages)?;
172 insert_json(&mut map, "scan_errors", &self.scan_errors)?;
173 if self.license_policy.is_some() {
174 insert_json(&mut map, "license_policy", &self.license_policy)?;
175 }
176 if self.is_generated.is_some() {
177 insert_json(&mut map, "is_generated", self.is_generated)?;
178 }
179 if self.source_count.is_some() {
180 insert_json(&mut map, "source_count", self.source_count)?;
181 }
182 if self.is_legal {
183 insert_json(&mut map, "is_legal", self.is_legal)?;
184 }
185 if self.is_manifest {
186 insert_json(&mut map, "is_manifest", self.is_manifest)?;
187 }
188 if self.is_readme {
189 insert_json(&mut map, "is_readme", self.is_readme)?;
190 }
191 if self.is_top_level {
192 insert_json(&mut map, "is_top_level", self.is_top_level)?;
193 }
194 if self.is_key_file {
195 insert_json(&mut map, "is_key_file", self.is_key_file)?;
196 }
197 if self.is_community {
198 insert_json(&mut map, "is_community", self.is_community)?;
199 }
200 if !self.facets.is_empty() {
201 insert_json(&mut map, "facets", &self.facets)?;
202 }
203 if self.tallies.is_some() {
204 insert_json(&mut map, "tallies", &self.tallies)?;
205 }
206
207 map.serialize(serializer)
208 }
209}
210
211impl From<&crate::models::FileInfo> for OutputFileInfo {
212 fn from(value: &crate::models::FileInfo) -> Self {
213 Self {
214 name: value.name.clone(),
215 base_name: value.base_name.clone(),
216 extension: value.extension.clone(),
217 path: value.path.clone(),
218 file_type: value.file_type.clone(),
219 mime_type: value.mime_type.clone(),
220 file_type_label: value.file_type_label.clone(),
221 size: value.size,
222 date: value.date.clone(),
223 sha1: value.sha1.as_ref().map(|d| d.as_hex()),
224 md5: value.md5.as_ref().map(|d| d.as_hex()),
225 sha256: value.sha256.as_ref().map(|d| d.as_hex()),
226 sha1_git: value.sha1_git.as_ref().map(|d| d.as_hex()),
227 programming_language: value.programming_language.clone(),
228 package_data: value
229 .package_data
230 .iter()
231 .map(OutputPackageData::from)
232 .collect(),
233 license_expression: value.license_expression.clone(),
234 license_detections: value
235 .license_detections
236 .iter()
237 .map(OutputLicenseDetection::from)
238 .collect(),
239 license_clues: value.license_clues.iter().map(OutputMatch::from).collect(),
240 percentage_of_license_text: value.percentage_of_license_text,
241 copyrights: value.copyrights.iter().map(OutputCopyright::from).collect(),
242 holders: value.holders.iter().map(OutputHolder::from).collect(),
243 authors: value.authors.iter().map(OutputAuthor::from).collect(),
244 emails: value.emails.iter().map(OutputEmail::from).collect(),
245 urls: value.urls.iter().map(OutputURL::from).collect(),
246 for_packages: value
247 .for_packages
248 .iter()
249 .map(|uid| uid.to_string())
250 .collect(),
251 scan_errors: value.scan_errors.clone(),
252 license_policy: value
253 .license_policy
254 .as_ref()
255 .map(|v| v.iter().map(OutputLicensePolicyEntry::from).collect()),
256 is_generated: value.is_generated,
257 is_binary: value.is_binary,
258 is_text: value.is_text,
259 is_archive: value.is_archive,
260 is_media: value.is_media,
261 is_source: value.is_source,
262 is_script: value.is_script,
263 files_count: value.files_count,
264 dirs_count: value.dirs_count,
265 size_count: value.size_count,
266 source_count: value.source_count,
267 is_legal: value.is_legal,
268 is_manifest: value.is_manifest,
269 is_readme: value.is_readme,
270 is_top_level: value.is_top_level,
271 is_key_file: value.is_key_file,
272 is_community: value.is_community,
273 facets: value.facets.clone(),
274 tallies: value.tallies.as_ref().map(OutputTallies::from),
275 }
276 }
277}
278
279impl TryFrom<&OutputFileInfo> for crate::models::FileInfo {
280 type Error = String;
281 fn try_from(value: &OutputFileInfo) -> Result<Self, Self::Error> {
282 let mut package_data = Vec::with_capacity(value.package_data.len());
283 for p in &value.package_data {
284 package_data.push(crate::models::PackageData::try_from(p)?);
285 }
286 let mut license_detections = Vec::with_capacity(value.license_detections.len());
287 for d in &value.license_detections {
288 license_detections.push(crate::models::LicenseDetection::try_from(d)?);
289 }
290 let mut license_clues = Vec::with_capacity(value.license_clues.len());
291 for m in &value.license_clues {
292 license_clues.push(crate::models::Match::try_from(m)?);
293 }
294 let mut copyrights = Vec::with_capacity(value.copyrights.len());
295 for c in &value.copyrights {
296 copyrights.push(crate::models::Copyright::try_from(c)?);
297 }
298 let mut holders = Vec::with_capacity(value.holders.len());
299 for h in &value.holders {
300 holders.push(crate::models::Holder::try_from(h)?);
301 }
302 let mut authors = Vec::with_capacity(value.authors.len());
303 for a in &value.authors {
304 authors.push(crate::models::Author::try_from(a)?);
305 }
306 let mut emails = Vec::with_capacity(value.emails.len());
307 for e in &value.emails {
308 emails.push(crate::models::OutputEmail::try_from(e)?);
309 }
310 let mut urls = Vec::with_capacity(value.urls.len());
311 for u in &value.urls {
312 urls.push(crate::models::OutputURL::try_from(u)?);
313 }
314 let license_policy = value
315 .license_policy
316 .as_ref()
317 .map(|v| {
318 v.iter()
319 .map(crate::models::LicensePolicyEntry::try_from)
320 .collect::<Result<Vec<_>, _>>()
321 })
322 .transpose()?;
323 Ok(Self {
324 name: value.name.clone(),
325 base_name: value.base_name.clone(),
326 extension: value.extension.clone(),
327 path: value.path.clone(),
328 file_type: value.file_type.clone(),
329 mime_type: value.mime_type.clone(),
330 file_type_label: value.file_type_label.clone(),
331 size: value.size,
332 date: value.date.clone(),
333 sha1: value
334 .sha1
335 .as_ref()
336 .map(|s| crate::models::Sha1Digest::from_hex(s))
337 .transpose()
338 .map_err(|e| format!("invalid sha1: {}", e))?,
339 md5: value
340 .md5
341 .as_ref()
342 .map(|s| crate::models::Md5Digest::from_hex(s))
343 .transpose()
344 .map_err(|e| format!("invalid md5: {}", e))?,
345 sha256: value
346 .sha256
347 .as_ref()
348 .map(|s| crate::models::Sha256Digest::from_hex(s))
349 .transpose()
350 .map_err(|e| format!("invalid sha256: {}", e))?,
351 sha1_git: value
352 .sha1_git
353 .as_ref()
354 .map(|s| crate::models::GitSha1::from_hex(s))
355 .transpose()
356 .map_err(|e| format!("invalid sha1_git: {}", e))?,
357 programming_language: value.programming_language.clone(),
358 package_data,
359 license_expression: value.license_expression.clone(),
360 license_detections,
361 license_clues,
362 percentage_of_license_text: value.percentage_of_license_text,
363 copyrights,
364 holders,
365 authors,
366 emails,
367 urls,
368 for_packages: value
369 .for_packages
370 .iter()
371 .map(|s| crate::models::PackageUid::from_raw(s.clone()))
372 .collect(),
373 scan_errors: value.scan_errors.clone(),
374 license_policy,
375 is_generated: value.is_generated,
376 is_binary: value.is_binary,
377 is_text: value.is_text,
378 is_archive: value.is_archive,
379 is_media: value.is_media,
380 is_source: value.is_source,
381 is_script: value.is_script,
382 files_count: value.files_count,
383 dirs_count: value.dirs_count,
384 size_count: value.size_count,
385 source_count: value.source_count,
386 is_legal: value.is_legal,
387 is_manifest: value.is_manifest,
388 is_readme: value.is_readme,
389 is_top_level: value.is_top_level,
390 is_key_file: value.is_key_file,
391 is_community: value.is_community,
392 facets: value.facets.clone(),
393 tallies: value
394 .tallies
395 .as_ref()
396 .map(crate::models::Tallies::try_from)
397 .transpose()?,
398 })
399 }
400}