Skip to main content

provenant/output_schema/
file_info.rs

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}