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